reviewer-1.2.x-dev/src/Reviewer/Task/TaskBase.php

src/Reviewer/Task/TaskBase.php
<?php

declare(strict_types=1);

namespace Drupal\reviewer\Reviewer\Task;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\reviewer\Attribute\Task as TaskAttribute;
use Drupal\reviewer\Exception\AttributeMissingException;
use Drupal\reviewer\Reviewer\Action;
use Drupal\reviewer\Reviewer\Checklist\ChecklistInterface;
use Drupal\reviewer\Reviewer\Result\CollectionResultInterface;
use Drupal\reviewer\Reviewer\Result\IndividualResultInterface;
use Drupal\reviewer\Reviewer\Result\ResultFactoryInterface;
use Drupal\reviewer\Reviewer\Status\Status;
use Drupal\reviewer\Reviewer\Status\StatusEvaluatorInterface;
use Drupal\reviewer\Reviewer\Status\StatusFactoryInterface;

/**
 * Provides a base class for tasks that all tasks should extend.
 *
 * Tasks are the basic unit of work in reviewer, responsible for checking for
 * correct and incorrect configuration values.
 *
 * Create tasks in the \Drupal\my_module\Plugin\reviewer\Task namespace. Task
 * classes must have the \Drupal\reviewer\Attribute\Task attribute.
 *
 * Tasks implementing \Drupal\reviewer\Reviewer\Task\FixableInterface can have
 * failures automatically fixed by reviewer.
 *
 * This class provides methods required by reviewer to run reviews, as well as
 * some convenience methods for creating results from tasks and loading
 * configuration defined in the reviews. To jump start creating tasks, check out
 * the base tasks provided by the reviewer test kit module, which allow you to
 * create checks and fixes through class properties alone:
 *
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\ConfigTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\Form\FieldWidgetSettingsTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\Form\FieldWidgetsTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\Form\FormFieldGroupSettingsTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\Form\FormFieldsDisabledTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\View\FieldFormatterSettingsTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\View\FieldFormattersTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\View\ViewFieldGroupSettingsTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\View\ViewFieldsDisabledTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\EntityTypeTaskBase
 * @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\DisplayTaskBase
 *
 * Here is an example of a simple task which checks and fixes some
 * configuration:
 *
 * @code
 * #[Task('example')]
 * final class Example extends TaskBase implements FixableInterface {
 *
 *   public function check(): ResultInterface {
 *     $config = $this->configFactory->get('reviewer.example');
 *
 *     return $this->createCheckResult(
 *       $config->get('item') === 'correct',
 *       'Config reviewer.example.item has the correct value.',
 *       'Config reviewer.example.item has an incorrect value.',
 *     );
 *   }
 *
 *   public function fix(): ResultInterface {
 *     $config = $this->configFactory->getEditable('reviewer.example');
 *
 *     if ($config->get('item') !== 'correct') {
 *       $config->set('item', 'example')->save();
 *     }
 *
 *     return $this->createFixResult(
 *       // Run the check again to verify changes.
 *       $this->check()->getStatus(),
 *       'Fixed reviewer.example configuration.',
 *       'Unable to fix reviewer.example configuration.',
 *     );
 *   }
 *
 * }
 * @endcode
 *
 * It is possible for tasks to declare module dependencies so that errors are
 * not reported unnecessarily when running reviews. Tasks that do not meet their
 * module dependency return the \Drupal\reviewer\Reviewer\Status\Status::NotRun
 * status. This allows reviews to still run and not create errors on
 * environments which may not have the module installed, or for the re-use of
 * reviews across projects without having to define new checklists and reviews.
 *
 * Here is an example attribute of a task which depends on the workflows module:
 *
 * @code
 * #[Task(
 *   id: 'example',
 *   module_dependencies: ['workflows'],
 * )]
 * final class Example extends TaskBase implements FixableInterface {}
 * @endcode
 *
 * @see \Drupal\reviewer\Attribute\Task
 * @see \Drupal\reviewer\Reviewer\Result\ResultInterface
 * @see \Drupal\reviewer\Reviewer\Result\CollectionResultInterface
 */
abstract class TaskBase implements TaskInterface {

  private TaskAttribute $taskAttribute;

  private ChecklistInterface $checklist;

  protected CollectionResultInterface $results;

  // phpcs:ignore Drupal.Commenting.FunctionComment.Missing
  public function __construct(
    protected readonly EntityDisplayRepositoryInterface $entityDisplayRepository,
    protected readonly EntityFieldManagerInterface $entityFieldManager,
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly ConfigFactoryInterface $configFactory,
    private readonly ModuleHandlerInterface $moduleHandler,
    private readonly ResultFactoryInterface $resultFactory,
    protected readonly StatusEvaluatorInterface $statusEvaluator,
    protected readonly StatusFactoryInterface $statusFactory,
  ) {}

  /**
   * Get the config entity loaded by the review this task is in.
   */
  protected function getConfigEntity(): ConfigEntityInterface|null {
    return $this->checklist->getConfigEntity();
  }

  /**
   * Create an individual result.
   *
   * It is recommended to use the more specific createCheckResult() or
   * createFixResult() over this method in most cases.
   */
  protected function createResult(
    Status $status,
    string $message,
    string $variant = '',
  ): IndividualResultInterface {
    return $this->resultFactory->createResult(
      $this->resultId($variant),
      $status,
      $message,
      $this instanceof FixableInterface,
    );
  }

  /**
   * Create an individual result for a task.
   */
  protected function createCheckResult(
    bool $is_pass,
    string $pass_message,
    string $failure_message,
    string $variant = '',
  ): IndividualResultInterface {
    $message = $is_pass ? $pass_message : $failure_message;

    return $this->resultFactory->createResult(
      $this->resultId($variant),
      $this->statusFactory->createFromBool($is_pass),
      $message,
      $this instanceof FixableInterface,
    );
  }

  /**
   * Create an individual result for a fix.
   */
  protected function createFixResult(
    Status $status,
    string $success_message,
    string $failure_message,
  ): IndividualResultInterface {
    $status = $this->statusEvaluator->isPass($status) ? Status::Fixed : $status;
    $message = $this->statusEvaluator->isFixed($status) ? $success_message : $failure_message;

    return $this->resultFactory->createResult(
      $this->resultId(),
      $status,
      $message,
      $this instanceof FixableInterface,
    );
  }

  /**
   * Create a result collection.
   *
   * @param \Drupal\reviewer\Reviewer\Result\ResultInterface[] $results
   *
   * @return \Drupal\reviewer\Reviewer\Result\CollectionResultInterface
   */
  protected function createCollection(array $results = []): CollectionResultInterface {
    return $this->resultFactory->createCollection($this->resultId(), $results);
  }

  /**
   * {@inheritdoc}
   */
  public function run(Action $action): TaskInterface {
    $unmet_dependencies = $this->checkDependencies();
    if ($unmet_dependencies) {
      $not_run = $this->createResult(
        Status::NotRun,
        sprintf('The following modules required by this task are not enabled: %s.', implode(', ', $unmet_dependencies)),
      );
      $this->results = $this->createCollection([$not_run]);
      return $this;
    }

    try {
      $result = $this->check();

      if (
        $action === Action::Fix
        && $this instanceof FixableInterface
        && $this->statusEvaluator->isFailure($result->getStatus())
        && !$this->statusEvaluator->isIgnored($result->getStatus())
      ) {
        $result = $this->fix();
      }
    }
    catch (\Exception $e) {
      $result = $this->createResult(
        Status::Error,
        $e->getMessage(),
      );
    }

    if ($result instanceof CollectionResultInterface) {
      $this->results = $result;
    }
    else {
      $this->results = $this->createCollection([$result]);
    }

    return $this;
  }

  /**
   * Check that all module dependencies are satisfied and return all errors.
   *
   * @return string[]
   */
  private function checkDependencies(): array {
    $unmet_dependencies = [];
    foreach ($this->getModuleDependencies() as $module) {
      if (!$this->moduleHandler->moduleExists($module)) {
        $unmet_dependencies[] = $module;
      }
    }
    return $unmet_dependencies;
  }

  /**
   * Get the task attribute for this task.
   *
   * @throws \Drupal\reviewer\Exception\AttributeMissingException
   *   Thrown when the task attribute is missing.
   */
  private function getTaskAttribute(): TaskAttribute {
    if (!isset($this->taskAttribute)) {
      $attributes = (new \ReflectionClass($this))->getAttributes(TaskAttribute::class);
      if (!$attributes) {
        throw new AttributeMissingException(TaskAttribute::class, $this::class);
      }
      $this->taskAttribute = reset($attributes)->newInstance();
    }
    return $this->taskAttribute;
  }

  /**
   * {@inheritdoc}
   */
  public function getAttributeId(): string {
    return $this->getTaskAttribute()->getId();
  }

  /**
   * {@inheritdoc}
   */
  public function getModuleDependencies(): array {
    return $this->getTaskAttribute()->getModuleDependencies();
  }

  /**
   * {@inheritdoc}
   */
  public function getId(): string {
    return "{$this->checklist->getId()}.{$this->getAttributeId()}";
  }

  /**
   * {@inheritdoc}
   */
  public function resultId(string $variant = ''): string {
    $id = trim("{$this->checklist->resultId()}.{$this->getId()}.$variant", '.');
    $id_parts = explode('.', $id);
    $id_unique_parts = array_unique($id_parts);
    return implode('.', $id_unique_parts);
  }

  /**
   * {@inheritdoc}
   */
  public function setChecklist(ChecklistInterface $checklist): TaskInterface {
    $this->checklist = $checklist;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getStatus(): Status {
    return $this->getResults()->getStatus();
  }

  /**
   * {@inheritdoc}
   */
  public function getResults(): CollectionResultInterface {
    return $this->results;
  }

  /**
   * {@inheritdoc}
   */
  public function getIgnored(): array {
    return $this->checklist->getIgnored();
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc