devel_wizard-2.x-dev/src/Plugin/DevelWizard/Spell/EntityTypeGroupSpell.php

src/Plugin/DevelWizard/Spell/EntityTypeGroupSpell.php
<?php

declare(strict_types=1);

namespace Drupal\devel_wizard\Plugin\DevelWizard\Spell;

use Drupal\Component\Plugin\ConfigurableInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\devel_wizard\Attribute\DevelWizardSpell;
use Drupal\devel_wizard\Spell\SpellManagerInterface;
use Drupal\devel_wizard\Spell\SpellTraitPackageManager;
use Drupal\devel_wizard\Utils;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\String\UnicodeString;

#[DevelWizardSpell(
  id: 'devel_wizard_entity_type_group',
  category: new TranslatableMarkup('Code'),
  label: new TranslatableMarkup('Group support for an entity type'),
  description: new TranslatableMarkup('Generates PHP and YML files.'),
  tags: [
    'code' => new TranslatableMarkup('Code'),
    'group' => new TranslatableMarkup('Group'),
    'config_entity' => new TranslatableMarkup('Config entity'),
    'content_entity' => new TranslatableMarkup('Content entity'),
  ],
)]
class EntityTypeGroupSpell extends SpellBase implements
  PluginFormInterface,
  ConfigurableInterface,
  ContainerFactoryPluginInterface {

  use SpellTraitPackageManager;

  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('messenger'),
      $container->get('logger.channel.devel_wizard_spell'),
      $container->get('string_translation'),
      $container->get('devel_wizard.utils'),
      $container->get('config.factory'),
      $container->get('entity_type.manager'),
      $container->get('plugin.manager.devel_wizard.spell'),
      $container->get('module_installer'),
      $container->get('extension.list.module'),
      $container->get('twig'),
      new Filesystem(),
    );
  }

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    MessengerInterface $messenger,
    LoggerInterface $logger,
    TranslationInterface $stringTranslation,
    protected Utils $utils,
    protected ConfigFactoryInterface $configFactory,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected SpellManagerInterface $spellManager,
    protected ModuleInstallerInterface $moduleInstaller,
    protected ModuleExtensionList $moduleList,
    protected TwigEnvironment $twig,
    protected Filesystem $fs,
  ) {
    parent::__construct(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $messenger,
      $logger,
      $stringTranslation,
      $utils,
      $configFactory,
    );
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function defaultConfiguration() {
    $moduleSpell = $this->spellManager->createInstance('devel_wizard_module');

    $default = [
      'contentEntityType' => [
        'id' => '',
      ],
      'module' => $moduleSpell->defaultConfiguration(),
    ];

    $default['module']['type'] = 'sub_module';

    return $default;
  }

  /**
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  protected function populateCalculatedConfigurationValues(): static {
    parent::populateCalculatedConfigurationValues();

    if (empty($this->configuration['contentEntityType']['id'])) {
      throw new \InvalidArgumentException('content entity type id is required');
    }

    $contentEntityType = $this
      ->entityTypeManager
      ->getDefinition($this->configuration['contentEntityType']['id']);
    $this->configuration['contentEntityType'] += $this
      ->utils
      ->getEntityTypeInfo($contentEntityType);

    if ($this->configuration['module']['type'] === 'sub_module') {
      if (empty($this->configuration['module']['sub_module_of'])) {
        $this->configuration['module']['sub_module_of'] = $contentEntityType->getProvider();
      }

      if (empty($this->configuration['module']['machine_name'])) {
        $this->configuration['module']['machine_name'] = $this->configuration['module']['sub_module_of'] . '_group';
      }
    }

    if (empty($this->configuration['module']['machine_name'])) {
      $this->configuration['module']['machine_name'] = $contentEntityType->getProvider() . '_group';
    }

    $moduleSpell = $this->spellManager->createInstance(
      'devel_wizard_module',
      $this->configuration['module'],
    );
    $moduleSpell->prepare();
    $this->configuration['module'] = $moduleSpell->getConfiguration();
    // @todo Use the "project:module" format.
    $this->configuration['module']['info.yml']['dependencies'][] = $contentEntityType->getProvider();
    $this->configuration['module']['info.yml']['dependencies'][] = 'group:group';

    $configEntityTypeId = $contentEntityType->getBundleEntityType();
    if ($configEntityTypeId) {
      $configEntityType = $this->entityTypeManager->getDefinition($configEntityTypeId);
      $this->configuration['configEntityType']['id'] = $configEntityTypeId;
      $this->configuration['configEntityType']['definition'] = $configEntityType;
      $this->configuration['configEntityType'] += $this->utils->getEntityTypeInfo($configEntityType);
    }

    $this->configuration['groupRelationPlugin'] = [
      'id' => $this->configuration['contentEntityType']['id'],
      'deriverClass' => $this->configuration['contentEntityType']['idUpperCamel'] . 'Deriver',
      'deriverClassFqn' => "Drupal\\{$this->configuration['module']['machineName']}\\Plugin\\Group\\Relation\\{$this->configuration['contentEntityType']['idUpperCamel']}Deriver",
    ];
    $this->configuration['groupRelationPlugin']['idUpperCamel'] = (new UnicodeString('a_' . $this->configuration['groupRelationPlugin']['id']))
      ->camel()
      ->trimPrefix('a')
      ->toString();

    $this->configuration['groupPermissionProvider'] = [
      'class' => $this->configuration['groupRelationPlugin']['idUpperCamel'] . 'PermissionProvider',
      'classNamespace' => "Drupal\\{$this->configuration['module']['machineName']}\\Plugin\\Group\\RelationHandler",
    ];
    $this->configuration['groupPermissionProvider']['filePath'] = "src/Plugin/Group/RelationHandler/{$this->configuration['groupPermissionProvider']['class']}.php";
    $this->configuration['groupPermissionProvider']['classFqn'] = "{$this->configuration['groupPermissionProvider']['classNamespace']}\\{$this->configuration['groupPermissionProvider']['class']}";

    return $this;
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $conf = $this->getConfiguration();

    $form['contentEntityType'] = [
      '#tree' => TRUE,
      '#type' => 'details',
      '#open' => TRUE,
      '#title' => $this->t('Content entity'),
      'id' => [
        '#type' => 'machine_name',
        '#required' => FALSE,
        '#title' => $this->t('Content entity type machine-name'),
        '#description' => $this->t('Content entity type to add Group support for.'),
        '#default_value' => $conf['contentEntityType']['id'],
        '#maxlength' => $this->identifierMaxLength,
        '#size' => $this->identifierInputSize,
        '#machine_name' => [
          'exists' => [Utils::class, 'alwaysFalse'],
          'standalone' => TRUE,
        ],
        '#autocomplete_route_name' => 'devel_wizard.autocomplete.entity_type_id',
        '#autocomplete_route_parameters' => [
          'types' => 'content',
        ],
      ],
    ];

    $moduleSpell = $this->spellManager->createInstance('devel_wizard_module', $conf['module']);
    if ($moduleSpell instanceof PluginFormInterface) {
      $parents = $form['#parents'];
      $parents[] = 'module';
      $form['module'] = [
        '#parents' => $parents,
        '#type' => 'details',
        '#tree' => TRUE,
        '#open' => TRUE,
        '#title' => $this->t('Module'),
        '#description' => $this->t('The module to put the new codes into.'),
      ];
      $form['module'] = $moduleSpell->buildConfigurationForm($form['module'], $form_state);
      $form['module']['machine_name']['#required'] = FALSE;
    }

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // @todo Implement validateConfigurationForm() method.
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue($form['#parents'], []);

    /** @var \Drupal\devel_wizard\Plugin\DevelWizard\Spell\ModuleSpell $moduleSpell */
    $moduleSpell = $this
      ->spellManager
      ->createInstance('devel_wizard_module');
    $moduleSpell->submitConfigurationForm($form['module'], $form_state);
    $values['module'] = $moduleSpell->getConfiguration();

    $this->setConfiguration($values);
  }

  /**
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Twig\Error\SyntaxError
   * @throws \Twig\Error\RuntimeError
   * @throws \Twig\Error\LoaderError
   */
  protected function doIt(): static {
    $conf = $this->getConfiguration();

    if (!isset($this->batchContext['sandbox']['current_step'])) {
      $isModuleExists = $this->moduleList->exists($conf['module']['machine_name']);

      $this->batchContext['sandbox']['current_step'] = $isModuleExists ?
        'module_install'
        : 'module_create';

      $this->batchContext['sandbox']['sub_spells'] = [
        'module_create' => [
          'finished' => $isModuleExists ? 1.0 : 0,
        ],
      ];

      if ($isModuleExists) {
        $this->batchContext['sandbox']['module_create_spell_configuration']['dst_dir'] = $this
          ->moduleList
          ->getPath($conf['module']['machine_name']);
      }
    }

    switch ($this->batchContext['sandbox']['current_step']) {
      case 'module_create':
        $moduleSpell = $this->spellManager->createInstance(
          'devel_wizard_module',
          $conf['module'],
        );
        $moduleSpell->prepare();
        $moduleSpell->abracadabra($this->batchContext['sandbox']['sub_spells']['module_create']);
        $this->batchContext['sandbox']['module_create_spell_configuration'] = $moduleSpell->getConfiguration();

        if ($this->batchContext['sandbox']['sub_spells']['module_create']['finished'] < 1.0) {
          $this->batchContext['message'] = $this->t('Create module');

          break;
        }

        $this->batchContext['message'] = $this->t('Install required modules');
        $this->batchContext['sandbox']['current_step'] = 'module_install';
        $this->batchContext['sandbox']['finished'] = 0.4;
        drupal_flush_all_caches();
        break;

      case 'module_install':
        $this->doItModuleInstall();
        drupal_flush_all_caches();

        $this->batchContext['message'] = $this->t('Code generation in progress');
        $this->batchContext['sandbox']['current_step'] = 'code';
        $this->batchContext['sandbox']['finished'] = 0.8;
        break;

      case 'code':
        $this->doItGenerateCode();
        $this->batchContext['message'] = $this->t('Finished');
        $this->batchContext['sandbox']['current_step'] = '_finished';
        $this->batchContext['sandbox']['finished'] = 1.0;
        break;

      case '_finished':
        $this->batchContext['sandbox']['finished'] = 1.0;
        break;
    }

    return $this;
  }

  protected function doItModuleInstall(): static {
    foreach ($this->getModuleNamesToInstall() as $moduleName) {
      $this->ensureModuleInstalled($moduleName);
    }

    return $this;
  }

  /**
   * @throws \Twig\Error\SyntaxError
   * @throws \Twig\Error\RuntimeError
   * @throws \Twig\Error\LoaderError
   */
  protected function doItGenerateCode(): static {
    $conf = $this->getConfiguration();

    $module = $conf['module']['machine_name'];
    $moduleDir = $this->moduleList->getPath($module);
    $fileNames = [
      "$moduleDir/$module.module" => '@devel_wizard/spell/entity_type_group/module.php.twig',
      "$moduleDir/src/Routing/RouteSubscriber.php" => '@devel_wizard/spell/entity_type_group/route-subscriber.php.twig',
      "$moduleDir/src/Plugin/Group/Relation/{$conf['contentEntityType']['idUpperCamel']}.php" => '@devel_wizard/spell/entity_type_group/plugin-group-relation.php.twig',
      "$moduleDir/{$conf['groupPermissionProvider']['filePath']}" => '@devel_wizard/spell/entity_type_group/plugin-group-relation-handler-permission-provider.php.twig',
    ];

    foreach ($fileNames as $filePath => $tplName) {
      $fileContent = $this->twig->render($tplName, $conf);
      $this->fs->dumpFile($filePath, $fileContent);
      $this->messageFilesystemEntryCreate($filePath);
    }

    $filePath = "$moduleDir/$module.services.yml";
    $this->utils->ymlFileReplace(
      $filePath,
      [
        "$module.route_subscriber" => [
          'class' => "Drupal\\$module\\Routing\\RouteSubscriber",
          'tags' => [
            ['name' => 'event_subscriber'],
          ],
        ],
        "group.relation_handler.permission_provider.{$conf['groupRelationPlugin']['id']}" => [
          'class' => $conf['groupPermissionProvider']['classFqn'],
          'shared' => FALSE,
          'arguments' => [
            '@group.relation_handler.permission_provider',
          ],
        ],
      ],
      ['services'],
    );
    $this->messageFilesystemEntryUpdate($filePath);

    return $this;
  }

  protected function getModuleNamesToInstall(): array {
    $conf = $this->getConfiguration();

    $modules = [];
    if (in_array($conf['module']['type'], ['custom', 'sub_module'])) {
      $modules[] = $conf['module']['machine_name'];
    }

    // @todo Standalone module steps:
    //   - Add to composer.json#repositories
    //   - composer require
    //   This workflow is not supported yet.
    return $modules;
  }

}

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

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