From ceb378cf99452e71a5df8ab680b3045f08d00b37 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 29 Mar 2025 15:52:15 +0000 Subject: [PATCH 001/144] Added collection of plugin interface filepath in plugin type data. --- Task/Collect/CollectorBase.php | 19 +++++++++++++++++++ Task/Collect/PluginTypesCollector.php | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/Task/Collect/CollectorBase.php b/Task/Collect/CollectorBase.php index c179046e..8edf5f80 100644 --- a/Task/Collect/CollectorBase.php +++ b/Task/Collect/CollectorBase.php @@ -131,6 +131,25 @@ protected function findFiles(string $mask): array { return $files; } + /** + * Converts an absolute filepath into relative to the Drupal app root. + * + * @param string $filepath + * The absolute filepath. + * + * @return string + * The given filepath made relative to the Drupal app root, without an + * initial '/'. + */ + protected function makeFilepathRelative(string $filepath): string { + static $cwd; + if (!isset($cwd)) { + $cwd = getcwd(); + } + + return str_replace($cwd . '/', '', $filepath); + } + /** * Gets the first line from a docblock string. * diff --git a/Task/Collect/PluginTypesCollector.php b/Task/Collect/PluginTypesCollector.php index 53f75d2b..53997371 100644 --- a/Task/Collect/PluginTypesCollector.php +++ b/Task/Collect/PluginTypesCollector.php @@ -224,6 +224,8 @@ protected function getPluginManagerServices() { * E.g., 'Plugin/Filter'. * - 'plugin_interface': The interface that plugin classes must implement, * as a qualified name (but without initial '\'). + * - 'plugin_interface_filepath': The filepath of the interface class, + * relative to the Drupal app root. * - 'plugin_definition_annotation_name': The class that the plugin * annotation uses, as a qualified name (but without initial '\'). * E.g, 'Drupal\filter\Annotation\Filter'. @@ -473,6 +475,14 @@ protected function addPluginTypeServiceData(&$data) { $this->addPluginTypeServiceDataYaml($data, $service, $discovery); break; } + + // Add the file location of the interface file, to form an API link. + if ($data['plugin_interface']) { + $plugin_interface_reflection = new \ReflectionClass($data['plugin_interface']); + $interface_filepath = $plugin_interface_reflection->getFileName(); + + $data['plugin_interface_filepath'] = $this->makeFilepathRelative($interface_filepath); + } } /** From b6515ba2a8b8e19ac956e70e52ede43219b4b6e1 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 29 Mar 2025 15:52:41 +0000 Subject: [PATCH 002/144] Updated test sample data. --- Test/sample_hook_definitions/11/plugins_processed.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Test/sample_hook_definitions/11/plugins_processed.php b/Test/sample_hook_definitions/11/plugins_processed.php index ce5087e2..92a7cc63 100644 --- a/Test/sample_hook_definitions/11/plugins_processed.php +++ b/Test/sample_hook_definitions/11/plugins_processed.php @@ -19,6 +19,7 @@ 'yaml_properties' => array ( ), + 'plugin_interface_filepath' => 'core/lib/Drupal/Core/Block/BlockPluginInterface.php', 'base_class' => 'Drupal\\Core\\Block\\BlockBase', 'base_class_has_di' => false, 'config_schema_prefix' => 'block.settings.', @@ -168,6 +169,7 @@ 'yaml_properties' => array ( ), + 'plugin_interface_filepath' => 'core/lib/Drupal/Core/Render/Element/ElementInterface.php', 'base_class' => 'Drupal\\Core\\Render\\Element\\RenderElementBase', 'base_class_has_di' => false, 'plugin_properties' => @@ -220,6 +222,7 @@ 'yaml_properties' => array ( ), + 'plugin_interface_filepath' => 'core/lib/Drupal/Core/Field/FormatterInterface.php', 'base_class' => 'Drupal\\Core\\Field\\FormatterBase', 'base_class_has_di' => true, 'config_schema_prefix' => 'field.formatter.settings.', @@ -402,6 +405,7 @@ 'yaml_properties' => array ( ), + 'plugin_interface_filepath' => 'core/modules/filter/src/Plugin/FilterInterface.php', 'base_class' => 'Drupal\\filter\\Plugin\\FilterBase', 'base_class_has_di' => false, 'config_schema_prefix' => 'filter_settings.', @@ -527,6 +531,7 @@ 'yaml_properties' => array ( ), + 'plugin_interface_filepath' => 'core/modules/image/src/ImageEffectInterface.php', 'base_class' => 'Drupal\\image\\ImageEffectBase', 'base_class_has_di' => true, 'config_schema_prefix' => 'image.effect.', @@ -827,6 +832,7 @@ 'yaml_properties' => array ( ), + 'plugin_interface_filepath' => 'core/modules/views/src/Plugin/views/ViewsHandlerInterface.php', 'base_class' => 'Drupal\\views\\Plugin\\views\\area\\AreaPluginBase', 'base_class_has_di' => true, 'plugin_properties' => From 06033ff5f22522e7b482f2e7acc331725b2cc9c0 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 30 Mar 2025 08:29:06 +0100 Subject: [PATCH 003/144] Added option definition extension with api URL. --- Definition/OptionDefinition.php | 44 +++++++++++++++++++++++++++++++++ Generator/EntityTypeBase.php | 2 +- Generator/PluginType.php | 2 +- Generator/Profile.php | 2 +- Generator/RootComponent.php | 2 +- Task/Analyse/TestTraits.php | 2 +- Task/OptionsProviderTrait.php | 2 +- Task/ReportHookData.php | 2 +- Task/ReportHookGroups.php | 2 +- Task/ReportPluginData.php | 1 + 10 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 Definition/OptionDefinition.php diff --git a/Definition/OptionDefinition.php b/Definition/OptionDefinition.php new file mode 100644 index 00000000..b01ce8e4 --- /dev/null +++ b/Definition/OptionDefinition.php @@ -0,0 +1,44 @@ + Date: Sun, 30 Mar 2025 08:29:24 +0100 Subject: [PATCH 004/144] Added helper to create URLs to api.d.o. --- Task/ReportHookDataFolder.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Task/ReportHookDataFolder.php b/Task/ReportHookDataFolder.php index 8da8fb1e..d6e2bd2b 100644 --- a/Task/ReportHookDataFolder.php +++ b/Task/ReportHookDataFolder.php @@ -70,4 +70,25 @@ public function listHookFiles() { return $files; } + /** + * Gets a URL to api.d.o for a class-like. + * + * @param string $class_like_filepath + * The filepath to the class-like, relative to the Drupal app root. + * @param string $type + * The type, e.g. 'interface'. + * + * @return string + * A URL to the page on api.d.o for the given class-like, for the current + * major version of Drupal. + */ + protected function createClassLikeApiUrl(string $class_like_filepath, string $type): string { + return + 'https://api.drupal.org/api/drupal/' . + str_replace('/', '!', $class_like_filepath) . + "/$type/" . + basename($class_like_filepath, '.php') . + '/' . $this->environment->getCoreMajorVersion(); + } + } From 62915d0d8754425cce32bb2ccb555102aebd4eba Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 30 Mar 2025 08:29:40 +0100 Subject: [PATCH 005/144] Added API link to plugin type options. --- Task/ReportPluginData.php | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Task/ReportPluginData.php b/Task/ReportPluginData.php index db698298..13c20cac 100644 --- a/Task/ReportPluginData.php +++ b/Task/ReportPluginData.php @@ -28,11 +28,6 @@ class ReportPluginData extends ReportHookDataFolder */ protected $sanity_level = 'component_data_processed'; - /** - * The name of the method providing an array of options as $value => $label. - */ - protected static $optionsMethod = 'listPluginNamesOptions'; - /** * Cached plugin type data. * @@ -124,6 +119,31 @@ public function listPluginDataBySubdirectory() { return $plugin_types_data_by_subdirectory; } + /** + * {@inheritdoc} + */ + public function getOptions(): array { + $data = $this->listPluginData(); + + $options = []; + foreach ($data as $plugin_type_name => $plugin_type_info) { + $url = NULL; + if (isset($plugin_type_info['plugin_interface_filepath'])) { + if (str_starts_with($plugin_type_info['plugin_interface_filepath'], 'core')) { + $url = $this->createClassLikeApiUrl($plugin_type_info['plugin_interface_filepath'], 'interface'); + } + } + + $options[$plugin_type_name] = OptionDefinition::create( + $plugin_type_name, + $plugin_type_info['type_label'], + api_url: $url ?? NULL, + ); + } + + return $options; + } + /** * Get plugin types as a list of options. * From f78a8f27784eac5242629eced5be5a790ff5dd0d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 30 Mar 2025 11:31:20 +0100 Subject: [PATCH 006/144] Changed method to abstract. --- Task/Collect/HooksCollector.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Task/Collect/HooksCollector.php b/Task/Collect/HooksCollector.php index ad53e21a..547f9f52 100644 --- a/Task/Collect/HooksCollector.php +++ b/Task/Collect/HooksCollector.php @@ -122,9 +122,7 @@ public function mergeComponentData($existing_data, $new_data) { * [module] => node * @endcode */ - protected function gatherHookDocumentationFiles($system_listing) { - // Needs to be overridden by subclasses. - } + abstract protected function gatherHookDocumentationFiles($system_listing); /** * Builds complete hook data array from downloaded files and stores in a file. From 1f0be585603c93048d4cc0a39ac84a6401606a0f Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 30 Mar 2025 11:31:28 +0100 Subject: [PATCH 007/144] Changed variable name. --- Task/Collect/HooksCollector11.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Task/Collect/HooksCollector11.php b/Task/Collect/HooksCollector11.php index 2f074084..ebf4b0ab 100644 --- a/Task/Collect/HooksCollector11.php +++ b/Task/Collect/HooksCollector11.php @@ -105,7 +105,7 @@ public function getJobList() { */ protected function gatherHookDocumentationFiles($api_files) { // Get the hooks directory. - $directory = \DrupalCodeBuilder\Factory::getEnvironment()->getDataDirectory(); + $data_directory = \DrupalCodeBuilder\Factory::getEnvironment()->getDataDirectory(); // Get Drupal root folder as a file path. // DRUPAL_ROOT is defined both by Drupal and Drush. @@ -136,7 +136,7 @@ protected function gatherHookDocumentationFiles($api_files) { $hook_files[$filename] = [ 'original' => $drupal_root . '/' . $file['uri'], // no idea if useful - 'path' => $directory . '/' . $file['filename'], + 'path' => $data_directory . '/' . $file['filename'], 'destination' => '%module.module', // Default. We override this below. 'group' => $file['group'], 'module' => $file['module'], From 87bc1d361edbbc99fed6ae864caed1c45954a5b2 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 11:30:39 +0100 Subject: [PATCH 008/144] Added description and plugin class path to element types collection. --- Task/Collect/ElementTypesCollector.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Task/Collect/ElementTypesCollector.php b/Task/Collect/ElementTypesCollector.php index 0f0603f8..b993c8b8 100644 --- a/Task/Collect/ElementTypesCollector.php +++ b/Task/Collect/ElementTypesCollector.php @@ -50,6 +50,9 @@ public function getJobList() { * - 'type': The type ID. * - 'label': The label, which is the same as the type. * - 'form': Whether the element is a form input element. + * - class_filepath: The filepath to the element plugin's class. + * - description: A description of the plugin, taken from the plugin class + * docblock. */ public function collect($job_list) { $element_types = \Drupal::service('plugin.manager.element_info')->getDefinitions(); @@ -59,10 +62,21 @@ public function collect($job_list) { // We could use getInfo() on the plugin manager here, but it instantiates // each plugin which exhausts memory. $form = is_a($definition['class'], \Drupal\Core\Render\Element\FormElementInterface::class, TRUE); + + $plugin_class_reflection = new \ReflectionClass($definition['class']); + if ($docblock = $plugin_class_reflection->getDocComment()) { + $description = $this->getDocblockFirstLine($docblock); + } + else { + $description = ''; + } + $data[$id] = [ 'type' => $id, 'label' => $id, 'form' => $form, + 'class_filepath' => $this->makeFilepathRelative($plugin_class_reflection->getFileName()), + 'description' => $description, ]; } From f3a65f1fa6f408a1149a07376064f3b56f3173bb Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 11:34:49 +0100 Subject: [PATCH 009/144] Added description and API link to element types report. --- Task/ReportElementTypes.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Task/ReportElementTypes.php b/Task/ReportElementTypes.php index 17c3ad38..70aa5ba4 100644 --- a/Task/ReportElementTypes.php +++ b/Task/ReportElementTypes.php @@ -3,13 +3,13 @@ namespace DrupalCodeBuilder\Task; use MutableTypedData\Definition\OptionSetDefininitionInterface; +use DrupalCodeBuilder\Definition\OptionDefinition; use DrupalCodeBuilder\Task\Report\SectionReportInterface; /** * Task handler for reporting on render element types. */ class ReportElementTypes extends ReportHookDataFolder implements OptionSetDefininitionInterface, SectionReportInterface { - use OptionsProviderTrait; use SectionReportSimpleCountTrait; protected $data; @@ -35,6 +35,29 @@ public function getInfo(): array { ]; } + /** + * {@inheritdoc} + */ + public function getOptions(): array { + if (!isset($this->data)) { + $this->data = $this->environment->getStorage()->retrieve($this->getInfo()['key']); + } + + $options = []; + foreach ($this->data as $id => $item) { + $url = $this->createClassLikeApiUrl($item['class_filepath'], 'class'); + + $options[$id] = OptionDefinition::create( + $id, + $item['label'], + description: $item['description'], + api_url: $url, + ); + } + + return $options; + } + /** * {@inheritdoc} */ From 9fb1e41b4d1977db96734ac286d92d3e7f7ac588 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 11:31:01 +0100 Subject: [PATCH 010/144] Updated test sample data. --- Test/sample_hook_definitions/10/element_types_processed.php | 6 ++++++ Test/sample_hook_definitions/11/element_types_processed.php | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Test/sample_hook_definitions/10/element_types_processed.php b/Test/sample_hook_definitions/10/element_types_processed.php index 9443b8d5..a9289bba 100644 --- a/Test/sample_hook_definitions/10/element_types_processed.php +++ b/Test/sample_hook_definitions/10/element_types_processed.php @@ -5,17 +5,23 @@ 'type' => 'machine_name', 'label' => 'machine_name', 'form' => true, + 'class_filepath' => 'core/lib/Drupal/Core/Render/Element/MachineName.php', + 'description' => 'Provides a machine name render element.', ), 'textarea' => array ( 'type' => 'textarea', 'label' => 'textarea', 'form' => true, + 'class_filepath' => 'core/lib/Drupal/Core/Render/Element/Textarea.php', + 'description' => 'Provides a form element for input of multiple-line text.', ), 'textfield' => array ( 'type' => 'textfield', 'label' => 'textfield', 'form' => true, + 'class_filepath' => 'core/lib/Drupal/Core/Render/Element/Textfield.php', + 'description' => 'Provides a one-line text field form element.', ), ); \ No newline at end of file diff --git a/Test/sample_hook_definitions/11/element_types_processed.php b/Test/sample_hook_definitions/11/element_types_processed.php index cda37947..41802ffe 100644 --- a/Test/sample_hook_definitions/11/element_types_processed.php +++ b/Test/sample_hook_definitions/11/element_types_processed.php @@ -5,17 +5,23 @@ 'type' => 'machine_name', 'label' => 'machine_name', 'form' => true, + 'class_filepath' => 'core/lib/Drupal/Core/Render/Element/MachineName.php', + 'description' => 'Provides a machine name render element.', ), 'textarea' => array ( 'type' => 'textarea', 'label' => 'textarea', 'form' => true, + 'class_filepath' => 'core/lib/Drupal/Core/Render/Element/Textarea.php', + 'description' => 'Provides a form element for input of multiple-line text.', ), 'textfield' => array ( 'type' => 'textfield', 'label' => 'textfield', 'form' => true, + 'class_filepath' => 'core/lib/Drupal/Core/Render/Element/Textfield.php', + 'description' => 'Provides a one-line text field form element.', ), ); \ No newline at end of file From 692bcbcf125baf98827f2fe0934b4e263ed1003d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 13:16:24 +0100 Subject: [PATCH 011/144] Added interface path to service tag types collection. --- Task/Collect/ServiceTagTypesCollector.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Task/Collect/ServiceTagTypesCollector.php b/Task/Collect/ServiceTagTypesCollector.php index 5a9e94ae..57faba3e 100644 --- a/Task/Collect/ServiceTagTypesCollector.php +++ b/Task/Collect/ServiceTagTypesCollector.php @@ -77,6 +77,8 @@ public function getJobList() { * - 'service_id_collector: The collector has service IDs. * - 'interface': The fully-qualified name (without leading slash) of the * interface that each tagged service must implement. + * - interface_filepath: (optional) The filepath to the interface, relative + * to the Drupal app root. * - 'methods': An array of the methods of this interface, in the same * format as returned by MethodCollector::collectMethods(). */ @@ -94,6 +96,7 @@ public function collect($job_list) { 'interface' => 'Symfony\Component\EventDispatcher\EventSubscriberInterface', 'methods' => $this->methodCollector->collectMethods('Symfony\Component\EventDispatcher\EventSubscriberInterface'), // TODO: services of this type should go in the EventSubscriber namespace. + // No point adding the filepath, as api.d.o doesn't parse vendor code. ]; foreach ($collectors_info as $service_name => $tag_infos) { @@ -134,6 +137,13 @@ public function collect($job_list) { // Hope there's only one interface... $service_interfaces = $service_class_reflection->getInterfaceNames(); + + // ThemeNegotiatorInterface doesn't even behave this way, rabbithole + // for later. + if (empty($service_interfaces)) { + continue; + } + $collected_services_interface = array_shift($service_interfaces); if ($collected_services_interface) { @@ -169,10 +179,13 @@ public function collect($job_list) { $interface_methods = $this->methodCollector->collectMethods($collected_services_interface); } + $interface_reflection = new \ReflectionClass($collected_services_interface); + $data[$tag] = [ 'label' => $label, 'collector_type' => $collector_type, 'interface' => $collected_services_interface, + 'interface_filepath' => $this->makeFilepathRelative($interface_reflection->getFileName()), 'methods' => $interface_methods ?? [], ]; } From 01d1665907c5e57a34cc7e72019334a30963c857 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 13:16:35 +0100 Subject: [PATCH 012/144] Updated test sample data. --- Test/sample_hook_definitions/11/service_tag_types_processed.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Test/sample_hook_definitions/11/service_tag_types_processed.php b/Test/sample_hook_definitions/11/service_tag_types_processed.php index d9af70be..bc8d0307 100644 --- a/Test/sample_hook_definitions/11/service_tag_types_processed.php +++ b/Test/sample_hook_definitions/11/service_tag_types_processed.php @@ -5,6 +5,7 @@ 'label' => 'Breadcrumb builder', 'collector_type' => 'service_collector', 'interface' => 'Drupal\\Core\\Breadcrumb\\BreadcrumbBuilderInterface', + 'interface_filepath' => 'core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php', 'methods' => array ( 'applies' => From 6af5cd5cd69e881fb3ecad380200d834782aa8a9 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 13:35:25 +0100 Subject: [PATCH 013/144] Added support for API URLs to presets. --- Definition/PropertyDefinition.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Definition/PropertyDefinition.php b/Definition/PropertyDefinition.php index dc51b366..2882df32 100644 --- a/Definition/PropertyDefinition.php +++ b/Definition/PropertyDefinition.php @@ -2,8 +2,9 @@ namespace DrupalCodeBuilder\Definition; +use DrupalCodeBuilder\Definition\OptionDefinition; use MutableTypedData\Definition\DataDefinition as BasePropertyDefinition; -use MutableTypedData\Definition\OptionDefinition; +use MutableTypedData\Definition\OptionDefinition as BaseOptionDefinition; use MutableTypedData\Definition\PropertyListInterface; use MutableTypedData\Exception\InvalidDefinitionException; @@ -45,7 +46,7 @@ public function getDeltaDefinition(): self { return $delta_definition; } - public function addOption(OptionDefinition $option): self { + public function addOption(BaseOptionDefinition $option): self { if ($this->optionsProvider) { throw new InvalidDefinitionException("Can't add options if using an options provider."); } @@ -212,7 +213,13 @@ public function setPresets(...$presets) :self { $options = []; foreach ($presets as $key => $preset) { - $option = OptionDefinition::create($key, $preset['label'], $preset['description'] ?? NULL); + $option = OptionDefinition::create( + $key, + $preset['label'], + $preset['description'] ?? NULL, + // TODO: These are only supported for old-style array definitions! + api_url: $preset['api_url'] ?? NULL, + ); $options[] = $option; } } From b8eaba758985710bce9c97e2e34784491749949c Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 13:35:42 +0100 Subject: [PATCH 014/144] Added method to list service tag data. --- Task/ReportServiceTags.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Task/ReportServiceTags.php b/Task/ReportServiceTags.php index 9789137e..5d3f13d8 100644 --- a/Task/ReportServiceTags.php +++ b/Task/ReportServiceTags.php @@ -28,6 +28,28 @@ public function getInfo(): array { ]; } + /** + * {@inheritdoc} + */ + public function listServiceTagData(): array { + if (!isset($this->data)) { + $this->data = $this->environment->getStorage()->retrieve('service_tag_types'); + } + + $data = []; + foreach ($this->data as $tag => $item) { + if (isset($item['interface_filepath'])) { + if (str_starts_with($item['interface_filepath'], 'core')) { + $item['api_url'] = $this->createClassLikeApiUrl($item['interface_filepath'], 'interface'); + } + } + + $data[$tag] = $item; + } + + return $data; + } + /** * {@inheritdoc} */ From 672ca2889685b3d2bd55ae3e76209411e03c6d20 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 13:36:13 +0100 Subject: [PATCH 015/144] Changed service generator definition to add API URL to presets. --- Generator/Service.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Generator/Service.php b/Generator/Service.php index c72993b5..c4576af7 100644 --- a/Generator/Service.php +++ b/Generator/Service.php @@ -23,8 +23,8 @@ class Service extends PHPClassFileWithInjection implements AdoptableInterface { */ public static function addToGeneratorDefinition(PropertyListInterface $definition) { // Create the presets definition for service tag type property. - $task_handler_report_services = \DrupalCodeBuilder\Factory::getTask('ReportServiceData'); - $service_types_data = $task_handler_report_services->listServiceTypeData(); + $task_handler_report_service_tags = \DrupalCodeBuilder\Factory::getTask('ReportServiceTags'); + $service_types_data = $task_handler_report_service_tags->listServiceTagData(); $presets = []; foreach ($service_types_data as $type_tag => $type_data) { // Form the suggested service name from the last portion of the tag, thus: @@ -35,6 +35,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $presets[$type_tag] = [ // Option label. 'label' => $type_data['label'], + 'api_url' => $type_data['api_url'] ?? '', 'data' => [ // Values that are forced on other properties. // These are set in the process stage. From 5cdea422bca476b400b683ebf301549ce1f38cb8 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 30 Mar 2025 13:28:25 +0100 Subject: [PATCH 016/144] Added collection of original api.php file path to hooks data. --- Task/Collect/HooksCollector.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Task/Collect/HooksCollector.php b/Task/Collect/HooksCollector.php index 547f9f52..e99e5abe 100644 --- a/Task/Collect/HooksCollector.php +++ b/Task/Collect/HooksCollector.php @@ -101,8 +101,7 @@ public function mergeComponentData($existing_data, $new_data) { * Each item has the following properties: * - path: The full path to this file * - url: (internal to this handler) URL to download this file from. - * - original: (probably not used; just here for interest) the full path this - * file was copied from. + * - original: The full path this file was copied from. * - destination: The module code file where the hooks from this hook data * file should be saved by code generation. This may contain placeholders, * for instance, '%module.views.inc'. @@ -292,6 +291,9 @@ protected function processHookData($hook_file_data) { 'dependencies' => $hook_dependencies, 'group' => $group, 'core' => $file_data['core'] ?? NULL, + 'original_file_path' => !empty($file_data['original']) + ? $this->makeFilepathRelative($file_data['original']) + : NULL, 'file_path' => $file_data['path'], 'body' => $hook_data_raw['bodies'][$key], ]; From 2c0be0a39f2ad38a24b839d34041715abb1b1a84 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 30 Mar 2025 13:31:04 +0100 Subject: [PATCH 017/144] Updated test sample data. --- .../11/hooks_processed.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Test/sample_hook_definitions/11/hooks_processed.php b/Test/sample_hook_definitions/11/hooks_processed.php index 2a6bf81b..5bc2b9b8 100644 --- a/Test/sample_hook_definitions/11/hooks_processed.php +++ b/Test/sample_hook_definitions/11/hooks_processed.php @@ -16,6 +16,7 @@ ), 'group' => 'block', 'core' => true, + 'original_file_path' => 'core/modules/block/block.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/block.api.php', 'body' => ' // Remove the contextual links on all blocks that provide them. @@ -38,6 +39,7 @@ ), 'group' => 'block', 'core' => true, + 'original_file_path' => 'core/modules/block/block.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/block.api.php', 'body' => ' // Change the title of the specific block. @@ -58,6 +60,7 @@ ), 'group' => 'block', 'core' => true, + 'original_file_path' => 'core/modules/block/block.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/block.api.php', 'body' => ' // Add the \'user\' cache context to some blocks. @@ -80,6 +83,7 @@ ), 'group' => 'block', 'core' => true, + 'original_file_path' => 'core/modules/block/block.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/block.api.php', 'body' => ' // Explicitly enable placeholdering of the specific block. @@ -100,6 +104,7 @@ ), 'group' => 'block', 'core' => true, + 'original_file_path' => 'core/modules/block/block.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/block.api.php', 'body' => ' // Example code that would prevent displaying the \'Powered by Drupal\' block in @@ -126,6 +131,7 @@ ), 'group' => 'block', 'core' => true, + 'original_file_path' => 'core/modules/block/block.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/block.api.php', 'body' => ' foreach ($definitions as $id => $definition) { @@ -155,6 +161,7 @@ ), 'group' => 'core:form', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Form/form.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_form.api.php', 'body' => ' $node_storage = \\Drupal::entityTypeManager()->getStorage(\'node\'); @@ -212,6 +219,7 @@ ), 'group' => 'core:form', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Form/form.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_form.api.php', 'body' => ' if ($success) { @@ -253,6 +261,7 @@ ), 'group' => 'core:form', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Form/form.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_form.api.php', 'body' => ' // Inject any new status messages into the content area. @@ -275,6 +284,7 @@ ), 'group' => 'core:form', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Form/form.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_form.api.php', 'body' => ' if (isset($form[\'type\']) && $form[\'type\'][\'#value\'] . \'_node_settings\' == $form_id) { @@ -304,6 +314,7 @@ ), 'group' => 'core:form', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Form/form.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_form.api.php', 'body' => ' // Modification for the form with the given form ID goes here. For example, if @@ -332,6 +343,7 @@ ), 'group' => 'core:form', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Form/form.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_form.api.php', 'body' => ' // Modification for the form with the given BASE_FORM_ID goes here. For @@ -360,6 +372,7 @@ ), 'group' => 'core:form', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Form/form.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_form.api.php', 'body' => ' ', @@ -381,6 +394,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' $hooks[\'token_info\'] = [ @@ -406,6 +420,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' if ($hook == \'form_alter\') { @@ -434,6 +449,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Only fill this in if the .info.yml file does not define a \'datestamp\'. @@ -456,6 +472,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' my_module_cache_clear(); @@ -475,6 +492,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' if (in_array(\'lousy_module\', $modules)) { @@ -499,6 +517,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Set general module variables. @@ -519,6 +538,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' my_module_cache_clear(); @@ -538,6 +558,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' if (in_array(\'lousy_module\', $modules)) { @@ -563,6 +584,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Delete remaining general module variables. @@ -583,6 +605,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Here, we define a variable to allow tasks to indicate that a particular, @@ -659,6 +682,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Replace the entire site configuration form provided by Drupal core @@ -680,6 +704,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // For non-batch updates, the signature can simply be: @@ -750,6 +775,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Example of updating some content. @@ -782,6 +808,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' return [ @@ -805,6 +832,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Indicate that the my_module_update_8001() function provided by this module @@ -840,6 +868,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // We\'ve removed the 8.x-1.x version of my_module, including database updates. @@ -861,6 +890,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' return [ @@ -891,6 +921,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Adjust weight so that the theme Updater gets a chance to handle a given @@ -912,6 +943,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' $requirements = []; @@ -972,6 +1004,7 @@ ), 'group' => 'core:module', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Extension/module.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_module.api.php', 'body' => ' // Change the title from \'PHP\' to \'PHP version\'. @@ -1001,6 +1034,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Add a checkbox to toggle the breadcrumb trail. @@ -1026,6 +1060,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' static $hooks; @@ -1075,6 +1110,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // This example is from node_preprocess_html(). It adds the node type to @@ -1100,6 +1136,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' $suggestions = []; @@ -1123,6 +1160,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Add an interface-language specific suggestion to all theme hooks. @@ -1143,6 +1181,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' if (empty($variables[\'header\'])) { @@ -1164,6 +1203,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' foreach ($theme_list as $theme) { @@ -1185,6 +1225,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Remove some state entries depending on the theme. @@ -1207,6 +1248,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Extension for template base names in Twig. @@ -1227,6 +1269,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' $twig_service = \\Drupal::service(\'twig\'); @@ -1248,6 +1291,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Decrease the default size of textfields. @@ -1270,6 +1314,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Use a custom class for the LayoutBuilder element. @@ -1290,6 +1335,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Swap out jQuery to use an updated version of the library. @@ -1310,6 +1356,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' $libraries = []; @@ -1381,6 +1428,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Manipulate settings. @@ -1403,6 +1451,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Add settings. @@ -1428,6 +1477,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Update imaginary library \'foo\' to version 2.0. @@ -1474,6 +1524,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Remove defaults.css file. @@ -1495,6 +1546,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Unconditionally attach an asset to the page. @@ -1520,6 +1572,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Conditionally remove an asset. @@ -1543,6 +1596,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' $page_top[\'my_module\'] = [\'#markup\' => \'This is the top.\']; @@ -1562,6 +1616,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' $page_bottom[\'my_module\'] = [\'#markup\' => \'This is the bottom.\']; @@ -1581,6 +1636,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' return [ @@ -1614,6 +1670,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' // Kill the next/previous my_module topic navigation links. @@ -1638,6 +1695,7 @@ ), 'group' => 'core:theme', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Render/theme.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_theme.api.php', 'body' => ' $variables[\'is_admin\'] = \\Drupal::currentUser()->hasPermission(\'access administration pages\'); @@ -1660,6 +1718,7 @@ ), 'group' => 'core:token', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Utility/token.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_token.api.php', 'body' => ' $token_service = \\Drupal::token(); @@ -1732,6 +1791,7 @@ ), 'group' => 'core:token', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Utility/token.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_token.api.php', 'body' => ' if ($context[\'type\'] == \'node\' && !empty($context[\'data\'][\'node\'])) { @@ -1760,6 +1820,7 @@ ), 'group' => 'core:token', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Utility/token.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_token.api.php', 'body' => ' $type = [ @@ -1811,6 +1872,7 @@ ), 'group' => 'core:token', 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Utility/token.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_token.api.php', 'body' => ' // Modify description of node tokens for our site. @@ -1848,6 +1910,7 @@ ), 'group' => 'help', 'core' => true, + 'original_file_path' => 'core/modules/help/help.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/help.api.php', 'body' => ' switch ($route_name) { @@ -1875,6 +1938,7 @@ ), 'group' => 'help', 'core' => true, + 'original_file_path' => 'core/modules/help/help.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/help.api.php', 'body' => ' // Alter the header for the module overviews section. @@ -1897,6 +1961,7 @@ ), 'group' => 'help', 'core' => true, + 'original_file_path' => 'core/modules/help/help.api.php', 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/help.api.php', 'body' => ' // Alter the help topic to be displayed on admin/help. From 7b06b69cdcf7c5527777eeec2299cd7abb452da6 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 30 Mar 2025 13:31:23 +0100 Subject: [PATCH 018/144] Added API link to hook options. --- Task/ReportHookData.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Task/ReportHookData.php b/Task/ReportHookData.php index 5752bb2b..025b67cf 100644 --- a/Task/ReportHookData.php +++ b/Task/ReportHookData.php @@ -122,10 +122,19 @@ public function getOptions(): array { $data = $this->listHookData(); foreach ($data as $group => $hooks) { foreach ($hooks as $key => $hook) { + if (!empty($hook['core']) && isset($hook['original_file_path'])) { + $url = 'https://api.drupal.org/api/drupal/' . + str_replace('/', '!', $hook['original_file_path']) . + '/function/' . + $hook['name'] . + '/' . $this->environment->getCoreMajorVersion(); + } + $options[$hook['name']] = OptionDefinition::create( $hook['name'], $hook['name'], - $hook['description'] ?? '' + description: $hook['description'] ?? '', + api_url: $url ?? NULL, ); } } From 7d1d3034f4221b3509ad38c3e045e885b3673c1c Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 11:24:35 +0100 Subject: [PATCH 019/144] Fixed check for obligate procedural hooks split over two parts of code. --- Generator/Hooks.php | 19 ++----------------- Task/Collect/HooksCollector.php | 11 ++++++++++- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Generator/Hooks.php b/Generator/Hooks.php index 13af5978..49689451 100644 --- a/Generator/Hooks.php +++ b/Generator/Hooks.php @@ -28,19 +28,6 @@ */ class Hooks extends BaseGenerator { - /** - * Theme hooks which remain procedural. - * - * TODO: Move this to analysis? Although there's no sodding documentation. - */ - const PROCEDURAL_HOOKS = [ - 'hook_theme', - 'hook_theme_suggestion_HOOK', - 'hook_preprocess_hook', - 'hook_process_hook', - 'hook_theme_suggestions_HOOK_alter', - ]; - /** * {@inheritdoc} */ @@ -157,12 +144,10 @@ protected function addHookComponents(array &$components, array $hook_info): void // If the hook implementation type is set to procedural, then it's // procedural. ($this->component_data->hook_implementation_type->value == 'procedural') - // Hooks marked as procedural in analysis data. + // Hooks marked as obligate procedural in analysis data. || !empty($hook_info['procedural']) // Hooks that go in the .install file are always procedural. - || ($hook_info['destination'] == '%module.install') - // Other random hooks that aren't documented as such are always procedural. - || in_array($hook_info['name'], static::PROCEDURAL_HOOKS); + || ($hook_info['destination'] == '%module.install'); if ($use_procedural_hook) { $this->addProceduralHookComponent($components, $hook_info); diff --git a/Task/Collect/HooksCollector.php b/Task/Collect/HooksCollector.php index e99e5abe..14ebecce 100644 --- a/Task/Collect/HooksCollector.php +++ b/Task/Collect/HooksCollector.php @@ -252,7 +252,7 @@ protected function processHookData($hook_file_data) { // we can't call that because we need this information on lower versions of // core to properly generate forward-compatible hooks with the legacy // option. - $obligate_procedural_hooks = [ + $hooks_collector_pass_obligate_procedural_hooks = [ 'cache_flush', 'hook_info', 'install', @@ -267,6 +267,15 @@ protected function processHookData($hook_file_data) { 'update_last_removed', ]; + // These are not enforced by HookCollectorPass, but the hook + // documentation states they must be procedural. + $unenforced_obligate_procedural_hooks = [ + 'theme_suggestion_HOOK', + 'theme_suggestions_HOOK_alter', + ]; + + $obligate_procedural_hooks = array_merge($hooks_collector_pass_obligate_procedural_hooks, $unenforced_obligate_procedural_hooks); + $procedural = ( in_array($short_name, $obligate_procedural_hooks) || From 40e33e09493f797b679354cdaca4d6085bb2cb94 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 14:49:29 +0100 Subject: [PATCH 020/144] Updated test sample data. --- Test/sample_hook_definitions/11/hooks_processed.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/sample_hook_definitions/11/hooks_processed.php b/Test/sample_hook_definitions/11/hooks_processed.php index 5bc2b9b8..5ec3bf91 100644 --- a/Test/sample_hook_definitions/11/hooks_processed.php +++ b/Test/sample_hook_definitions/11/hooks_processed.php @@ -1175,7 +1175,7 @@ 'description' => 'Alters named suggestions for a specific theme hook.', 'destination' => '%module.module', 'has_return' => false, - 'procedural' => false, + 'procedural' => true, 'dependencies' => array ( ), From 025e0337da30e0ab77182f95df0503d54951f7cf Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 6 Apr 2025 22:58:46 +0100 Subject: [PATCH 021/144] Changed hook_theme() to use a separate hook body generator for contents from other components. --- .../{HookTheme.php => HookBodyHookTheme.php} | 18 +++++----- Generator/HookImplementationBase.php | 34 +++++++++++++++++++ Generator/HookImplementationProcedural.php | 12 ++++--- Generator/ThemeHook.php | 2 +- Test/Unit/ComponentPluginsAnnotated9Test.php | 2 ++ Test/Unit/ComponentThemeHook10Test.php | 2 ++ Test/Unit/ComponentThemeHook11Test.php | 28 +++++++++------ 7 files changed, 73 insertions(+), 25 deletions(-) rename Generator/{HookTheme.php => HookBodyHookTheme.php} (58%) diff --git a/Generator/HookTheme.php b/Generator/HookBodyHookTheme.php similarity index 58% rename from Generator/HookTheme.php rename to Generator/HookBodyHookTheme.php index 993a1428..85d22aa5 100644 --- a/Generator/HookTheme.php +++ b/Generator/HookBodyHookTheme.php @@ -3,18 +3,22 @@ namespace DrupalCodeBuilder\Generator; /** - * Generator for hook_theme() implementation. + * Generator for hook_theme() implementation body lines. + * + * @see \DrupalCodeBuilder\Generator\HookImplementationBase */ -class HookTheme extends HookImplementationProcedural { +class HookBodyHookTheme extends PHPFunctionBodyLines { /** * {@inheritdoc} */ - protected function getFunctionBody(): array { - // If we have no children, i.e. no ThemeHook components, then hand over to - // the parent, which will output the default hook code. + public function getContents(): array { + // If we have no children, i.e. no ThemeHook components, then return a + // return of empty array. if ($this->containedComponents->isEmpty()) { - return parent::getFunctionBody(); + return [ + 'return [];', + ]; } $code = []; @@ -24,8 +28,6 @@ protected function getFunctionBody(): array { } $code[] = '];'; - $this->component_data->body_indented = FALSE; - return $code; } diff --git a/Generator/HookImplementationBase.php b/Generator/HookImplementationBase.php index 7b1fb5dc..bfcbaac9 100644 --- a/Generator/HookImplementationBase.php +++ b/Generator/HookImplementationBase.php @@ -2,11 +2,22 @@ namespace DrupalCodeBuilder\Generator; +use CaseConverter\CaseString; use MutableTypedData\Definition\PropertyListInterface; use DrupalCodeBuilder\Definition\PropertyDefinition; /** * Abstract base class for hook implementations. + * + * This is specialised with child classes for: + * - class method hooks + * - procedural hooks + * - specific hooks that collect contents which are only procedural, e.g. + * hook_menu() + * - hook_updateN() which needs to change the function name. + * + * Furthermore, hooks that collect contents and can be procedural or OO use + * a hook body class, e.g. hook_theme(). */ abstract class HookImplementationBase extends PHPFunction { @@ -61,6 +72,29 @@ public function getMergeTag() { return $this->component_data['hook_name']; } + /** + * {@inheritdoc} + */ + public function requiredComponents(): array { + $components = parent::requiredComponents(); + + // Determine if there is a hook body generator for this hook. + // We need dynamic hook bodies to be a separate generator so they are + // orthogonal to hook implementations being prodecural/class methods. + $long_hook_name = $this->component_data->hook_name->value; + $hook_class_name = 'HookBody' . CaseString::snake($long_hook_name)->pascal(); + // Make the fully qualified class name. + $hook_class = $this->classHandler->getGeneratorClass($hook_class_name); + if (class_exists($hook_class)) { + $components['body'] = [ + 'component_type' => $hook_class_name, + 'containing_component' => '%requester', + ]; + } + + return $components; + } + /** * {@inheritdoc} */ diff --git a/Generator/HookImplementationProcedural.php b/Generator/HookImplementationProcedural.php index 292bfd46..58f2d3ea 100644 --- a/Generator/HookImplementationProcedural.php +++ b/Generator/HookImplementationProcedural.php @@ -13,14 +13,16 @@ class HookImplementationProcedural extends HookImplementationBase { * {@inheritdoc} */ public function requiredComponents(): array { + $components = parent::requiredComponents(); + $code_file = $this->component_data['code_file']; - return [ - 'code_file' => [ - 'component_type' => 'ExtensionCodeFile', - 'filename' => $code_file, - ], + $components['code_file'] = [ + 'component_type' => 'ExtensionCodeFile', + 'filename' => $code_file, ]; + + return $components; } /** diff --git a/Generator/ThemeHook.php b/Generator/ThemeHook.php index 307756c5..55743b91 100644 --- a/Generator/ThemeHook.php +++ b/Generator/ThemeHook.php @@ -53,7 +53,7 @@ public function requiredComponents(): array { * {@inheritdoc} */ function containingComponent() { - return '%self:hooks:hook_theme'; + return '%self:hooks:hook_theme:body'; } /** diff --git a/Test/Unit/ComponentPluginsAnnotated9Test.php b/Test/Unit/ComponentPluginsAnnotated9Test.php index 6bebaafd..c8f4d5a0 100644 --- a/Test/Unit/ComponentPluginsAnnotated9Test.php +++ b/Test/Unit/ComponentPluginsAnnotated9Test.php @@ -405,6 +405,7 @@ function testPluginWithOnlyId() { 'short_description' => 'Test Module description', 'hooks' => [ ], + 'hook_implementation_type' => 'procedural', 'plugins' => [ 0 => [ 'plugin_type' => 'element_info', @@ -847,6 +848,7 @@ function testRenderElement() { 'root_name' => $module_name, 'readable_name' => 'Test module', 'short_description' => 'Test Module description', + 'hook_implementation_type' => 'procedural', 'plugins' => [ 0 => [ 'plugin_type' => 'element_info', diff --git a/Test/Unit/ComponentThemeHook10Test.php b/Test/Unit/ComponentThemeHook10Test.php index cd2bb4e7..11552953 100644 --- a/Test/Unit/ComponentThemeHook10Test.php +++ b/Test/Unit/ComponentThemeHook10Test.php @@ -32,6 +32,8 @@ function testThemeHook() { 'short_description' => 'Test Module description', 'hooks' => [ ], + // Set this to procedural; the OO version is tested on D11. + 'hook_implementation_type' => 'procedural', 'theme_hooks' => [ $theme_hook_name, ], diff --git a/Test/Unit/ComponentThemeHook11Test.php b/Test/Unit/ComponentThemeHook11Test.php index f70b87cf..39b69c62 100644 --- a/Test/Unit/ComponentThemeHook11Test.php +++ b/Test/Unit/ComponentThemeHook11Test.php @@ -32,31 +32,37 @@ function testThemeHook() { 'short_description' => 'Test Module description', 'hooks' => [ ], + // Set this to OO only, so we don't have the extra legacy code. + 'hook_implementation_type' => 'oo', 'theme_hooks' => [ $theme_hook_name, ], 'readme' => FALSE, ]; $files = $this->generateModuleFiles($module_data); - $file_names = array_keys($files); - $this->assertCount(3, $files, "Expected number of files is returned."); - $this->assertArrayHasKey("$module_name.info.yml", $files, "The files list has a .info.yml file."); - $this->assertArrayHasKey("$module_name.module", $files, "The files list has a .module file."); - $this->assertArrayHasKey("templates/my-themeable.html.twig", $files, "The files list has a twig file."); + $this->assertFiles([ + 'testmodule.info.yml', + 'templates/my-themeable.html.twig', + 'src/Hook/TestmoduleHooks.php', + ], $files); - // Check the .module file. - $module_file = $files["$module_name.module"]; - $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $module_file); + // Check the hooks file. + $hooks_file = $files['src/Hook/TestmoduleHooks.php']; + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $hooks_file); $php_tester->assertDrupalCodingStandards(); - $php_tester->assertHasHookImplementation('hook_theme', $module_name); + $php_tester->assertHasMethod('theme'); + // TODO: Attribute testing. + $this->assertStringContainsString("#[Hook('theme')]", $hooks_file); + + $method_tester = $php_tester->getMethodTester('theme'); // Check that the hook_theme() implementation has the generated code. // This covers the specialized HookTheme hook generator class getting used. - $this->assertFunctionCode($module_file, "{$module_name}_theme", "'$theme_hook_name' =>"); - $this->assertFunctionCode($module_file, "{$module_name}_theme", "'render element' => 'elements',"); + $method_tester->assertHasLine("'$theme_hook_name' =>"); + $method_tester->assertHasLine("'render element' => 'elements',"); // TODO: check the other file contents. } From 5fa77398799f45c62ee77a7af9942728b00b2b25 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 25 Apr 2025 18:27:32 +0100 Subject: [PATCH 022/144] Fixed missing docs. --- Definition/PropertyDefinition.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Definition/PropertyDefinition.php b/Definition/PropertyDefinition.php index 2882df32..60787dd9 100644 --- a/Definition/PropertyDefinition.php +++ b/Definition/PropertyDefinition.php @@ -236,6 +236,21 @@ public function getPresets() :array { return $this->presets; } + /** + * Sets a processing callback. + * + * Processing is applied to a component's data when it is instantiated from + * input data. + * + * Note that processing is not applied to default values! + * + * @param callable $callback + * The callback to apply to data. + * + * @return self + * + * @see \DrupalCodeBuilder\Task\Generate\ComponentCollector::processComponentData() + */ public function setProcessing(callable $callback): self { $this->processing = $callback; From 2a5f552fe3936d00938d686b07def6b7a16fa2e8 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Tue, 25 Mar 2025 22:06:50 +0000 Subject: [PATCH 023/144] Added static caching of flat hook info. --- Task/ReportHookData.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Task/ReportHookData.php b/Task/ReportHookData.php index 025b67cf..fb946230 100644 --- a/Task/ReportHookData.php +++ b/Task/ReportHookData.php @@ -23,6 +23,8 @@ class ReportHookData extends ReportHookDataFolder implements OptionSetDefininiti */ protected $sanity_level = 'component_data_processed'; + protected $declarations; + /** * {@inheritdoc} */ @@ -211,19 +213,21 @@ public function listHookOptionsStructured() { * - 'body': The hook function body, taken from the API file. */ function getHookDeclarations() { - $data = $this->listHookData(); + if (!isset($this->declarations)) { + $data = $this->listHookData(); - $return = []; - foreach ($data as $group => $hooks) { - foreach ($hooks as $key => $hook) { - // Standardize to lowercase. - $hook_name = strtolower($hook['name']); + $this->declarations = []; + foreach ($data as $group => $hooks) { + foreach ($hooks as $key => $hook) { + // Standardize to lowercase. + $hook_name = strtolower($hook['name']); - $return[$hook_name] = $hook; + $this->declarations[$hook_name] = $hook; + } } } - return $return; + return $this->declarations; } } From a29b5aa1fde4c5db50244d1a777497e6dc6e487e Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 4 Apr 2025 22:23:33 +0100 Subject: [PATCH 024/144] Added report task for hooks that support class method implementations. --- .../DrupalCodeBuilderCompiledContainer.php | 594 +++++++++--------- Task/ReportHookClassMethodData.php | 48 ++ 2 files changed, 355 insertions(+), 287 deletions(-) create mode 100644 Task/ReportHookClassMethodData.php diff --git a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php index ce078509..ce940839 100644 --- a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php +++ b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php @@ -89,121 +89,124 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry44' => 'get83', 'ReportFieldTypes' => 'get84', 'subEntry45' => 'get85', - 'ReportHookData' => 'get86', + 'ReportHookClassMethodData' => 'get86', 'subEntry46' => 'get87', - 'ReportHookDataFolder' => 'get88', + 'ReportHookData' => 'get88', 'subEntry47' => 'get89', - 'ReportHookGroups' => 'get90', + 'ReportHookDataFolder' => 'get90', 'subEntry48' => 'get91', - 'ReportHookPresets' => 'get92', + 'ReportHookGroups' => 'get92', 'subEntry49' => 'get93', - 'ReportPluginData' => 'get94', + 'ReportHookPresets' => 'get94', 'subEntry50' => 'get95', - 'ReportServiceData' => 'get96', + 'ReportPluginData' => 'get96', 'subEntry51' => 'get97', - 'ReportServiceTags' => 'get98', + 'ReportServiceData' => 'get98', 'subEntry52' => 'get99', - 'ReportSummary' => 'get100', + 'ReportServiceTags' => 'get100', 'subEntry53' => 'get101', - 'subEntry54' => 'get102', - 'subEntry55' => 'get103', - 'subEntry56' => 'get104', - 'subEntry57' => 'get105', - 'subEntry58' => 'get106', - 'subEntry59' => 'get107', - 'subEntry60' => 'get108', - 'subEntry61' => 'get109', - 'subEntry62' => 'get110', - 'subEntry63' => 'get111', - 'subEntry64' => 'get112', - 'subEntry65' => 'get113', - 'Testing\\CollectTesting10' => 'get114', + 'ReportSummary' => 'get102', + 'subEntry54' => 'get103', + 'subEntry55' => 'get104', + 'subEntry56' => 'get105', + 'subEntry57' => 'get106', + 'subEntry58' => 'get107', + 'subEntry59' => 'get108', + 'subEntry60' => 'get109', + 'subEntry61' => 'get110', + 'subEntry62' => 'get111', + 'subEntry63' => 'get112', + 'subEntry64' => 'get113', + 'subEntry65' => 'get114', 'subEntry66' => 'get115', 'subEntry67' => 'get116', - 'subEntry68' => 'get117', - 'subEntry69' => 'get118', - 'subEntry70' => 'get119', - 'subEntry71' => 'get120', - 'subEntry72' => 'get121', - 'subEntry73' => 'get122', - 'subEntry74' => 'get123', - 'subEntry75' => 'get124', - 'subEntry76' => 'get125', - 'subEntry77' => 'get126', - 'subEntry78' => 'get127', - 'Testing\\CollectTesting11' => 'get128', + 'Testing\\CollectTesting10' => 'get117', + 'subEntry68' => 'get118', + 'subEntry69' => 'get119', + 'subEntry70' => 'get120', + 'subEntry71' => 'get121', + 'subEntry72' => 'get122', + 'subEntry73' => 'get123', + 'subEntry74' => 'get124', + 'subEntry75' => 'get125', + 'subEntry76' => 'get126', + 'subEntry77' => 'get127', + 'subEntry78' => 'get128', 'subEntry79' => 'get129', 'subEntry80' => 'get130', - 'subEntry81' => 'get131', - 'subEntry82' => 'get132', - 'subEntry83' => 'get133', - 'subEntry84' => 'get134', - 'subEntry85' => 'get135', - 'subEntry86' => 'get136', - 'subEntry87' => 'get137', - 'subEntry88' => 'get138', - 'subEntry89' => 'get139', - 'subEntry90' => 'get140', - 'subEntry91' => 'get141', - 'Testing\\CollectTesting7' => 'get142', + 'Testing\\CollectTesting11' => 'get131', + 'subEntry81' => 'get132', + 'subEntry82' => 'get133', + 'subEntry83' => 'get134', + 'subEntry84' => 'get135', + 'subEntry85' => 'get136', + 'subEntry86' => 'get137', + 'subEntry87' => 'get138', + 'subEntry88' => 'get139', + 'subEntry89' => 'get140', + 'subEntry90' => 'get141', + 'subEntry91' => 'get142', 'subEntry92' => 'get143', 'subEntry93' => 'get144', - 'Testing\\CollectTesting8' => 'get145', + 'Testing\\CollectTesting7' => 'get145', 'subEntry94' => 'get146', 'subEntry95' => 'get147', - 'subEntry96' => 'get148', - 'subEntry97' => 'get149', - 'subEntry98' => 'get150', - 'subEntry99' => 'get151', - 'subEntry100' => 'get152', - 'subEntry101' => 'get153', - 'subEntry102' => 'get154', - 'subEntry103' => 'get155', - 'subEntry104' => 'get156', - 'subEntry105' => 'get157', - 'subEntry106' => 'get158', - 'Testing\\CollectTesting9' => 'get159', + 'Testing\\CollectTesting8' => 'get148', + 'subEntry96' => 'get149', + 'subEntry97' => 'get150', + 'subEntry98' => 'get151', + 'subEntry99' => 'get152', + 'subEntry100' => 'get153', + 'subEntry101' => 'get154', + 'subEntry102' => 'get155', + 'subEntry103' => 'get156', + 'subEntry104' => 'get157', + 'subEntry105' => 'get158', + 'subEntry106' => 'get159', 'subEntry107' => 'get160', 'subEntry108' => 'get161', - 'subEntry109' => 'get162', - 'subEntry110' => 'get163', - 'subEntry111' => 'get164', - 'subEntry112' => 'get165', - 'subEntry113' => 'get166', - 'subEntry114' => 'get167', - 'subEntry115' => 'get168', - 'subEntry116' => 'get169', - 'subEntry117' => 'get170', - 'subEntry118' => 'get171', - 'subEntry119' => 'get172', - 'Generate|module' => 'get173', - 'Generate|profile' => 'get174', - 'Collect\\HooksCollector' => 'get175', - 'Collect' => 'get176', - 'Collect.unversioned' => 'get177', - 'subEntry120' => 'get178', - 'subEntry121' => 'get179', - 'subEntry122' => 'get180', - 'subEntry123' => 'get181', - 'subEntry124' => 'get182', - 'subEntry125' => 'get183', - 'subEntry126' => 'get184', - 'subEntry127' => 'get185', - 'subEntry128' => 'get186', - 'subEntry129' => 'get187', - 'subEntry130' => 'get188', - 'subEntry131' => 'get189', - 'subEntry132' => 'get190', - 'Testing\\CollectTesting' => 'get191', - 'DrupalCodeBuilder\\Task\\Collect\\HooksCollector' => 'get192', - 'DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler' => 'get193', - 'subEntry133' => 'get194', - 'DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter' => 'get195', - 'DrupalCodeBuilder\\Task\\Collect\\MethodCollector' => 'get196', - 'DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser' => 'get197', - 'subEntry134' => 'get198', - 'DrupalCodeBuilder\\Task\\ReportHookData' => 'get199', - 'subEntry135' => 'get200', + 'Testing\\CollectTesting9' => 'get162', + 'subEntry109' => 'get163', + 'subEntry110' => 'get164', + 'subEntry111' => 'get165', + 'subEntry112' => 'get166', + 'subEntry113' => 'get167', + 'subEntry114' => 'get168', + 'subEntry115' => 'get169', + 'subEntry116' => 'get170', + 'subEntry117' => 'get171', + 'subEntry118' => 'get172', + 'subEntry119' => 'get173', + 'subEntry120' => 'get174', + 'subEntry121' => 'get175', + 'Generate|module' => 'get176', + 'Generate|profile' => 'get177', + 'Collect\\HooksCollector' => 'get178', + 'Collect' => 'get179', + 'Collect.unversioned' => 'get180', + 'subEntry122' => 'get181', + 'subEntry123' => 'get182', + 'subEntry124' => 'get183', + 'subEntry125' => 'get184', + 'subEntry126' => 'get185', + 'subEntry127' => 'get186', + 'subEntry128' => 'get187', + 'subEntry129' => 'get188', + 'subEntry130' => 'get189', + 'subEntry131' => 'get190', + 'subEntry132' => 'get191', + 'subEntry133' => 'get192', + 'subEntry134' => 'get193', + 'Testing\\CollectTesting' => 'get194', + 'DrupalCodeBuilder\\Task\\Collect\\HooksCollector' => 'get195', + 'DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler' => 'get196', + 'subEntry135' => 'get197', + 'DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter' => 'get198', + 'DrupalCodeBuilder\\Task\\Collect\\MethodCollector' => 'get199', + 'DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser' => 'get200', + 'subEntry136' => 'get201', + 'DrupalCodeBuilder\\Task\\ReportHookData' => 'get202', + 'subEntry137' => 'get203', ); protected function get1() @@ -720,7 +723,7 @@ protected function get87() protected function get86() { - $object = new \DrupalCodeBuilder\Task\ReportHookData($this->get87()); + $object = new \DrupalCodeBuilder\Task\ReportHookClassMethodData($this->get87()); return $object; } @@ -731,29 +734,29 @@ protected function get89() protected function get88() { - $object = new \DrupalCodeBuilder\Task\ReportHookDataFolder($this->get89()); + $object = new \DrupalCodeBuilder\Task\ReportHookData($this->get89()); return $object; } protected function get91() { - return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\ReportHookData'); + return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } protected function get90() { - $object = new \DrupalCodeBuilder\Task\ReportHookGroups($this->get91()); + $object = new \DrupalCodeBuilder\Task\ReportHookDataFolder($this->get91()); return $object; } protected function get93() { - return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); + return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\ReportHookData'); } protected function get92() { - $object = new \DrupalCodeBuilder\Task\ReportHookPresets($this->get93()); + $object = new \DrupalCodeBuilder\Task\ReportHookGroups($this->get93()); return $object; } @@ -764,7 +767,7 @@ protected function get95() protected function get94() { - $object = new \DrupalCodeBuilder\Task\ReportPluginData($this->get95()); + $object = new \DrupalCodeBuilder\Task\ReportHookPresets($this->get95()); return $object; } @@ -775,7 +778,7 @@ protected function get97() protected function get96() { - $object = new \DrupalCodeBuilder\Task\ReportServiceData($this->get97()); + $object = new \DrupalCodeBuilder\Task\ReportPluginData($this->get97()); return $object; } @@ -786,7 +789,7 @@ protected function get99() protected function get98() { - $object = new \DrupalCodeBuilder\Task\ReportServiceTags($this->get99()); + $object = new \DrupalCodeBuilder\Task\ReportServiceData($this->get99()); return $object; } @@ -795,438 +798,455 @@ protected function get101() return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } + protected function get100() + { + $object = new \DrupalCodeBuilder\Task\ReportServiceTags($this->get101()); + return $object; + } + protected function get103() + { + return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); + } + + protected function get105() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get104() + protected function get106() { return $this->delegateContainer->get('ReportAdminRoutes'); } - protected function get105() + protected function get107() { return $this->delegateContainer->get('ReportDataTypes'); } - protected function get106() + protected function get108() { return $this->delegateContainer->get('ReportElementTypes'); } - protected function get107() + protected function get109() { return $this->delegateContainer->get('ReportEntityTypes'); } - protected function get108() + protected function get110() { return $this->delegateContainer->get('ReportEventNames'); } - protected function get109() + protected function get111() { return $this->delegateContainer->get('ReportFieldTypes'); } - protected function get110() + protected function get112() + { + return $this->delegateContainer->get('ReportHookClassMethodData'); + } + + protected function get113() { return $this->delegateContainer->get('ReportHookData'); } - protected function get111() + protected function get114() { return $this->delegateContainer->get('ReportPluginData'); } - protected function get112() + protected function get115() { return $this->delegateContainer->get('ReportServiceData'); } - protected function get113() + protected function get116() { return $this->delegateContainer->get('ReportServiceTags'); } - protected function get102() + protected function get104() { return [ - 'Analyse\\TestTraits' => $this->get103(), - 'ReportAdminRoutes' => $this->get104(), - 'ReportDataTypes' => $this->get105(), - 'ReportElementTypes' => $this->get106(), - 'ReportEntityTypes' => $this->get107(), - 'ReportEventNames' => $this->get108(), - 'ReportFieldTypes' => $this->get109(), - 'ReportHookData' => $this->get110(), - 'ReportPluginData' => $this->get111(), - 'ReportServiceData' => $this->get112(), - 'ReportServiceTags' => $this->get113(), + 'Analyse\\TestTraits' => $this->get105(), + 'ReportAdminRoutes' => $this->get106(), + 'ReportDataTypes' => $this->get107(), + 'ReportElementTypes' => $this->get108(), + 'ReportEntityTypes' => $this->get109(), + 'ReportEventNames' => $this->get110(), + 'ReportFieldTypes' => $this->get111(), + 'ReportHookClassMethodData' => $this->get112(), + 'ReportHookData' => $this->get113(), + 'ReportPluginData' => $this->get114(), + 'ReportServiceData' => $this->get115(), + 'ReportServiceTags' => $this->get116(), ]; } - protected function get100() + protected function get102() { - $object = new \DrupalCodeBuilder\Task\ReportSummary($this->get101()); - $object->setReportHelpers($this->get102()); + $object = new \DrupalCodeBuilder\Task\ReportSummary($this->get103()); + $object->setReportHelpers($this->get104()); return $object; } - protected function get115() + protected function get118() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get117() + protected function get120() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get118() + protected function get121() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get119() + protected function get122() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get120() + protected function get123() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get121() + protected function get124() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get122() + protected function get125() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get123() + protected function get126() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get124() + protected function get127() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get125() + protected function get128() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get126() + protected function get129() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get127() + protected function get130() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get116() + protected function get119() { return [ - 'Analyse\\TestTraits' => $this->get117(), - 'Collect\\AdminRoutesCollector' => $this->get118(), - 'Collect\\DataTypesCollector' => $this->get119(), - 'Collect\\ElementTypesCollector' => $this->get120(), - 'Collect\\EntityTypesCollector' => $this->get121(), - 'Collect\\EventNamesCollector' => $this->get122(), - 'Collect\\FieldTypesCollector' => $this->get123(), - 'Collect\\PluginTypesCollector' => $this->get124(), - 'Collect\\ServiceTagTypesCollector' => $this->get125(), - 'Collect\\ServicesCollector' => $this->get126(), - 'Collect\\HooksCollector' => $this->get127(), + 'Analyse\\TestTraits' => $this->get120(), + 'Collect\\AdminRoutesCollector' => $this->get121(), + 'Collect\\DataTypesCollector' => $this->get122(), + 'Collect\\ElementTypesCollector' => $this->get123(), + 'Collect\\EntityTypesCollector' => $this->get124(), + 'Collect\\EventNamesCollector' => $this->get125(), + 'Collect\\FieldTypesCollector' => $this->get126(), + 'Collect\\PluginTypesCollector' => $this->get127(), + 'Collect\\ServiceTagTypesCollector' => $this->get128(), + 'Collect\\ServicesCollector' => $this->get129(), + 'Collect\\HooksCollector' => $this->get130(), ]; } - protected function get114() + protected function get117() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting10($this->get115()); - $object->setCollectors($this->get116()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting10($this->get118()); + $object->setCollectors($this->get119()); return $object; } - protected function get129() + protected function get132() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get131() + protected function get134() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get132() + protected function get135() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get133() + protected function get136() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get134() + protected function get137() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get135() + protected function get138() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get136() + protected function get139() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get137() + protected function get140() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get138() + protected function get141() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get139() + protected function get142() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get140() + protected function get143() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get141() + protected function get144() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get130() + protected function get133() { return [ - 'Analyse\\TestTraits' => $this->get131(), - 'Collect\\AdminRoutesCollector' => $this->get132(), - 'Collect\\DataTypesCollector' => $this->get133(), - 'Collect\\ElementTypesCollector' => $this->get134(), - 'Collect\\EntityTypesCollector' => $this->get135(), - 'Collect\\EventNamesCollector' => $this->get136(), - 'Collect\\FieldTypesCollector' => $this->get137(), - 'Collect\\PluginTypesCollector' => $this->get138(), - 'Collect\\ServiceTagTypesCollector' => $this->get139(), - 'Collect\\ServicesCollector' => $this->get140(), - 'Collect\\HooksCollector' => $this->get141(), + 'Analyse\\TestTraits' => $this->get134(), + 'Collect\\AdminRoutesCollector' => $this->get135(), + 'Collect\\DataTypesCollector' => $this->get136(), + 'Collect\\ElementTypesCollector' => $this->get137(), + 'Collect\\EntityTypesCollector' => $this->get138(), + 'Collect\\EventNamesCollector' => $this->get139(), + 'Collect\\FieldTypesCollector' => $this->get140(), + 'Collect\\PluginTypesCollector' => $this->get141(), + 'Collect\\ServiceTagTypesCollector' => $this->get142(), + 'Collect\\ServicesCollector' => $this->get143(), + 'Collect\\HooksCollector' => $this->get144(), ]; } - protected function get128() + protected function get131() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting11($this->get129()); - $object->setCollectors($this->get130()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting11($this->get132()); + $object->setCollectors($this->get133()); return $object; } - protected function get143() + protected function get146() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get144() + protected function get147() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\HooksCollector'); } - protected function get142() + protected function get145() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting7($this->get143(), $this->get144()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting7($this->get146(), $this->get147()); return $object; } - protected function get146() + protected function get149() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get148() + protected function get151() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get149() + protected function get152() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get150() + protected function get153() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get151() + protected function get154() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get152() + protected function get155() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get153() + protected function get156() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get154() + protected function get157() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get155() + protected function get158() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get156() + protected function get159() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get157() + protected function get160() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get158() + protected function get161() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get147() + protected function get150() { return [ - 'Analyse\\TestTraits' => $this->get148(), - 'Collect\\AdminRoutesCollector' => $this->get149(), - 'Collect\\DataTypesCollector' => $this->get150(), - 'Collect\\ElementTypesCollector' => $this->get151(), - 'Collect\\EntityTypesCollector' => $this->get152(), - 'Collect\\EventNamesCollector' => $this->get153(), - 'Collect\\FieldTypesCollector' => $this->get154(), - 'Collect\\PluginTypesCollector' => $this->get155(), - 'Collect\\ServiceTagTypesCollector' => $this->get156(), - 'Collect\\ServicesCollector' => $this->get157(), - 'Collect\\HooksCollector' => $this->get158(), + 'Analyse\\TestTraits' => $this->get151(), + 'Collect\\AdminRoutesCollector' => $this->get152(), + 'Collect\\DataTypesCollector' => $this->get153(), + 'Collect\\ElementTypesCollector' => $this->get154(), + 'Collect\\EntityTypesCollector' => $this->get155(), + 'Collect\\EventNamesCollector' => $this->get156(), + 'Collect\\FieldTypesCollector' => $this->get157(), + 'Collect\\PluginTypesCollector' => $this->get158(), + 'Collect\\ServiceTagTypesCollector' => $this->get159(), + 'Collect\\ServicesCollector' => $this->get160(), + 'Collect\\HooksCollector' => $this->get161(), ]; } - protected function get145() + protected function get148() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting8($this->get146()); - $object->setCollectors($this->get147()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting8($this->get149()); + $object->setCollectors($this->get150()); return $object; } - protected function get160() + protected function get163() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get162() + protected function get165() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get163() + protected function get166() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get164() + protected function get167() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get165() + protected function get168() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get166() + protected function get169() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get167() + protected function get170() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get168() + protected function get171() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get169() + protected function get172() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get170() + protected function get173() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get171() + protected function get174() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get172() + protected function get175() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get161() + protected function get164() { return [ - 'Analyse\\TestTraits' => $this->get162(), - 'Collect\\AdminRoutesCollector' => $this->get163(), - 'Collect\\DataTypesCollector' => $this->get164(), - 'Collect\\ElementTypesCollector' => $this->get165(), - 'Collect\\EntityTypesCollector' => $this->get166(), - 'Collect\\EventNamesCollector' => $this->get167(), - 'Collect\\FieldTypesCollector' => $this->get168(), - 'Collect\\PluginTypesCollector' => $this->get169(), - 'Collect\\ServiceTagTypesCollector' => $this->get170(), - 'Collect\\ServicesCollector' => $this->get171(), - 'Collect\\HooksCollector' => $this->get172(), + 'Analyse\\TestTraits' => $this->get165(), + 'Collect\\AdminRoutesCollector' => $this->get166(), + 'Collect\\DataTypesCollector' => $this->get167(), + 'Collect\\ElementTypesCollector' => $this->get168(), + 'Collect\\EntityTypesCollector' => $this->get169(), + 'Collect\\EventNamesCollector' => $this->get170(), + 'Collect\\FieldTypesCollector' => $this->get171(), + 'Collect\\PluginTypesCollector' => $this->get172(), + 'Collect\\ServiceTagTypesCollector' => $this->get173(), + 'Collect\\ServicesCollector' => $this->get174(), + 'Collect\\HooksCollector' => $this->get175(), ]; } - protected function get159() + protected function get162() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting9($this->get160()); - $object->setCollectors($this->get161()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting9($this->get163()); + $object->setCollectors($this->get164()); return $object; } - protected function get173() + protected function get176() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1236,7 +1256,7 @@ protected function get173() ]); } - protected function get174() + protected function get177() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1246,7 +1266,7 @@ protected function get174() ]); } - protected function get175() + protected function get178() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1254,7 +1274,7 @@ protected function get175() ], 'Collect\\HooksCollector'); } - protected function get176() + protected function get179() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1262,91 +1282,91 @@ protected function get176() ], 'Collect'); } - protected function get178() + protected function get181() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get180() + protected function get183() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get181() + protected function get184() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get182() + protected function get185() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get183() + protected function get186() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get184() + protected function get187() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get185() + protected function get188() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get186() + protected function get189() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get187() + protected function get190() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get188() + protected function get191() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get189() + protected function get192() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get190() + protected function get193() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get179() + protected function get182() { return [ - 'Analyse\\TestTraits' => $this->get180(), - 'Collect\\AdminRoutesCollector' => $this->get181(), - 'Collect\\DataTypesCollector' => $this->get182(), - 'Collect\\ElementTypesCollector' => $this->get183(), - 'Collect\\EntityTypesCollector' => $this->get184(), - 'Collect\\EventNamesCollector' => $this->get185(), - 'Collect\\FieldTypesCollector' => $this->get186(), - 'Collect\\PluginTypesCollector' => $this->get187(), - 'Collect\\ServiceTagTypesCollector' => $this->get188(), - 'Collect\\ServicesCollector' => $this->get189(), - 'Collect\\HooksCollector' => $this->get190(), + 'Analyse\\TestTraits' => $this->get183(), + 'Collect\\AdminRoutesCollector' => $this->get184(), + 'Collect\\DataTypesCollector' => $this->get185(), + 'Collect\\ElementTypesCollector' => $this->get186(), + 'Collect\\EntityTypesCollector' => $this->get187(), + 'Collect\\EventNamesCollector' => $this->get188(), + 'Collect\\FieldTypesCollector' => $this->get189(), + 'Collect\\PluginTypesCollector' => $this->get190(), + 'Collect\\ServiceTagTypesCollector' => $this->get191(), + 'Collect\\ServicesCollector' => $this->get192(), + 'Collect\\HooksCollector' => $this->get193(), ]; } - protected function get177() + protected function get180() { - $object = new \DrupalCodeBuilder\Task\Collect($this->get178()); - $object->setCollectors($this->get179()); + $object = new \DrupalCodeBuilder\Task\Collect($this->get181()); + $object->setCollectors($this->get182()); return $object; } - protected function get191() + protected function get194() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1354,53 +1374,53 @@ protected function get191() ], 'Testing\\CollectTesting'); } - protected function get192() + protected function get195() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get194() + protected function get197() { return $this->delegateContainer->get('generator_classmap'); } - protected function get193() + protected function get196() { - $object = new DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get194()); + $object = new DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get197()); return $object; } - protected function get195() + protected function get198() { $object = new DrupalCodeBuilder\Task\Collect\ContainerBuilderGetter(); return $object; } - protected function get196() + protected function get199() { $object = new DrupalCodeBuilder\Task\Collect\MethodCollector(); return $object; } - protected function get198() + protected function get201() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get197() + protected function get200() { - $object = new DrupalCodeBuilder\Task\Collect\CodeAnalyser($this->get198()); + $object = new DrupalCodeBuilder\Task\Collect\CodeAnalyser($this->get201()); return $object; } - protected function get200() + protected function get203() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get199() + protected function get202() { - $object = new DrupalCodeBuilder\Task\ReportHookData($this->get200()); + $object = new DrupalCodeBuilder\Task\ReportHookData($this->get203()); return $object; } diff --git a/Task/ReportHookClassMethodData.php b/Task/ReportHookClassMethodData.php new file mode 100644 index 00000000..f2f9e95c --- /dev/null +++ b/Task/ReportHookClassMethodData.php @@ -0,0 +1,48 @@ +listHookData(); + foreach ($data as $group => $hooks) { + foreach ($hooks as $key => $hook) { + // Skip an obligate procedural hook. + if (!empty($hook['procedural'])) { + continue; + } + + if ($hook['core'] && isset($hook['original_file_path'])) { + $url = 'https://api.drupal.org/api/drupal/' . + str_replace('/', '!', $hook['original_file_path']) . + '/function/' . + $hook['name'] . + '/' . $this->environment->getCoreMajorVersion(); + } + + $options[$hook['name']] = OptionDefinition::create( + $hook['name'], + $hook['name'], + description: $hook['description'] ?? '', + api_url: $url ?? NULL, + ); + } + } + + return $options; + } + +} From 2d12b4a936107cb406bd0f6e2e2c9a4734d71c6a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 26 Apr 2025 10:13:22 +0100 Subject: [PATCH 025/144] Added hooks classes property to module generator; changed hook implementation generator to be mutable with literal and tokenised variants. --- Generator/HookImplementationBase.php | 174 +++++++++++++++----- Generator/HookImplementationClassMethod.php | 171 ++++++++++++++++--- Generator/HookImplementationProcedural.php | 63 ++++++- Generator/HookUpdateN.php | 28 ++-- Generator/Hooks.php | 44 +---- Generator/HooksClass.php | 80 +++++++++ Generator/Module.php | 4 + Generator/Module8.php | 1 + Generator/PHPFunction.php | 3 +- Task/ReportHookData.php | 24 ++- Test/Unit/ComponentHooks11Test.php | 162 ++++++++++++++++++ Test/Unit/ComponentPHPFile10Test.php | 3 + 12 files changed, 630 insertions(+), 127 deletions(-) create mode 100644 Generator/HooksClass.php diff --git a/Generator/HookImplementationBase.php b/Generator/HookImplementationBase.php index bfcbaac9..0c2fc315 100644 --- a/Generator/HookImplementationBase.php +++ b/Generator/HookImplementationBase.php @@ -3,8 +3,10 @@ namespace DrupalCodeBuilder\Generator; use CaseConverter\CaseString; -use MutableTypedData\Definition\PropertyListInterface; use DrupalCodeBuilder\Definition\PropertyDefinition; +use MutableTypedData\Data\DataItem; +use MutableTypedData\Definition\PropertyListInterface; +use MutableTypedData\Definition\VariantDefinition; /** * Abstract base class for hook implementations. @@ -21,55 +23,143 @@ */ abstract class HookImplementationBase extends PHPFunction { + /** + * {@inheritdoc} + */ + protected static $dataType = 'mutable'; + /** * {@inheritdoc} */ public static function addToGeneratorDefinition(PropertyListInterface $definition) { - parent::addToGeneratorDefinition($definition); - - $definition->addProperties([ - // The name of the file that this hook implementation should be placed - // into. - // For HookImplementationClassMethod this is unused, but simpler to have - // this here rather than have Hooks decide whether to set it or not. Plus - // we might use it at some point to decide which class to use. - 'code_file' => PropertyDefinition::create('string') + // Make a dummy property list to get parent properties. + $common_properties = PropertyDefinition::create('complex'); + parent::addToGeneratorDefinition($common_properties); + + $variants = [ + 'literal' => VariantDefinition::create() + ->setLabel('Literal'), + 'tokenized' => VariantDefinition::create() + ->setLabel('Tokenized'), + ]; + + $definition->setProperties([ + 'hook_name' => PropertyDefinition::create('string') + ->setLabel('Hook') + ->setRequired(TRUE) + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportHookData')), + ]) + ->setVariantMappingProvider(\DrupalCodeBuilder\Factory::getTask('ReportHookData')) + ->setVariants($variants); + + $variants['literal']->addProperties([ + // The short hook name. + 'short_hook_name' => PropertyDefinition::create('string') ->setInternal(TRUE) - ->setLiteralDefault('%module.module'), - // The long hook name. - 'hook_name' => PropertyDefinition::create('string'), - // The first docblock line from the hook's api.php definition. - 'description' => PropertyDefinition::create('string'), + ->setCallableDefault(fn ($component_data) => preg_replace('@^hook_@', '', $component_data->getParent()->hook_name->value)), ]); - $definition->getProperty('function_docblock_lines')->getDefault() - // Expression Language lets us define arrays, which is nice. - ->setExpression("['Implements ' ~ get('..:hook_name') ~ '().']"); - - // This appears to be necessary even though it's not used. WTF! - $definition->getProperty('function_name') - ->setCallableDefault(function ($component_data) { - $long_hook_name = $component_data->getParent()->hook_name->value; - $short_hook_name = preg_replace('@^hook_@', '', $long_hook_name); - $function_name = '%module_' . $short_hook_name; - return $function_name; - }); - - // Hook bodies are just sample code from the code documentation, so if - // there are contained components, these should override the sample code. - $definition->getProperty('body_overriden_by_contained') - ->setLiteralDefault(TRUE); - - // Hook implementations have no @return documentation. - $definition->getProperty('return')->getProperty('omit_return_tag') - ->setLiteralDefault(TRUE); - } + $variants['tokenized']->addProperties([ + 'hook_name_parameters' => PropertyDefinition::create('string') + ->setLabel('Hook name replacement parameters') + ->setDescription("Replacement values for the tokens in a hook name such as 'FORM_ID' or 'ENTITY_TYPE'. Enter the values to replace these in their order in the hook name.") + ->setMultiple(TRUE), + // The short hook name, with tokens replaced with any given parameters. + // We replace tokens only once, in this property. Hook function / method + // name then derive from this. For class method hooks, this is also used + // for the attribute. + 'short_hook_name' => PropertyDefinition::create('string') + ->setInternal(TRUE) + ->setCallableDefault(function ($component_data) { + $short_hook_name = preg_replace('@^hook_@', '', $component_data->getParent()->hook_name->value); + + $hook_name_parameters = $component_data->getParent()->hook_name_parameters->values(); + + // Split on an UPPER_CASE token in the hook name, without the bounding + // underscores, and include the token in the pieces. For example, + // 'form_FORM_ID_alter' becomes: + // - form_ + // - FORM_ID + // - _alter + $pieces = preg_split( + '@(?<=_)([[:upper:]_]+)(?=_)@', + $short_hook_name, + flags: PREG_SPLIT_DELIM_CAPTURE, + ); + + // Because hook names never start with a token, we know that the + // tokens are all the odd-indexed pieces, and the fixed portions the + // even-indexed. + foreach ($pieces as $i => &$piece) { + if ($i % 2 == 1) { + // An odd-indexed piece is replaced with hook name parameter if + // one exists. + if ($hook_name_parameters) { + // Use up each parameter, so the array empties out. + $parameter = array_shift($hook_name_parameters); + $piece = $parameter; + } + } + } + + $short_hook_name = implode('', $pieces); + return $short_hook_name; + }) + ]); - /** - * {@inheritdoc} - */ - public function getMergeTag() { - return $this->component_data['hook_name']; + foreach ($variants as $variant) { + $variant->addProperties($common_properties->getProperties()); + + $variant->addProperties([ + // The name of the file that this hook implementation should be placed + // into. + // For HookImplementationClassMethod this is unused, but simpler to have + // this here rather than have Hooks decide whether to set it or not. Plus + // we might use it at some point to decide which class to use. + 'code_file' => PropertyDefinition::create('string') + ->setInternal(TRUE) + ->setLiteralDefault('%module.module'), + // The long hook name. + 'hook_name' => PropertyDefinition::create('string') + ->setLabel('Hook') + ->setRequired(TRUE) + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportHookData')), + 'hook_info' => PropertyDefinition::create('mapping') + ->setInternal(TRUE) + ->setCallableDefault(function ($component_data) { + $task_handler_report = \DrupalCodeBuilder\Factory::getTask('ReportHookData'); + $hook_info = $task_handler_report->getHookDeclarations()[strtolower($component_data->getParent()->hook_name->value)]; + return $hook_info; + }), + // The first docblock line from the hook's api.php definition. + 'description' => PropertyDefinition::create('string') + ->setInternal(TRUE), + ]); + + $variant->getProperty('function_docblock_lines')->getDefault() + // Expression Language lets us define arrays, which is nice. + ->setExpression("['Implements ' ~ get('..:hook_name') ~ '().']"); + + // This appears to be necessary even though it's not used. WTF! + $variant->getProperty('function_name') + ->setCallableDefault(function ($component_data) { + $short_hook_name = $component_data->getParent()->short_hook_name->value; + $function_name = '%module_' . $short_hook_name; + return $function_name; + }); + + // Hook bodies are just sample code from the code documentation, so if + // there are contained components, these should override the sample code. + $variant->getProperty('body_overriden_by_contained') + ->setLiteralDefault(TRUE); + + $variant->getProperty('body_indented') + ->setLiteralDefault(TRUE); + + // Hook implementations have no @return documentation. + $variant->getProperty('return')->getProperty('omit_return_tag') + ->setLiteralDefault(TRUE); + } } /** diff --git a/Generator/HookImplementationClassMethod.php b/Generator/HookImplementationClassMethod.php index dc3743f8..c4a722c4 100644 --- a/Generator/HookImplementationClassMethod.php +++ b/Generator/HookImplementationClassMethod.php @@ -2,14 +2,14 @@ namespace DrupalCodeBuilder\Generator; -use MutableTypedData\Definition\PropertyListInterface; +use CaseConverter\CaseString; use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Generator\Render\PhpAttributes; +use MutableTypedData\Data\DataItem; +use MutableTypedData\Definition\PropertyListInterface; /** * Generator for a single OO hook implementation. - * - * This should not be requested directly; use the Hooks component instead. */ class HookImplementationClassMethod extends HookImplementationBase { @@ -19,39 +19,168 @@ class HookImplementationClassMethod extends HookImplementationBase { public static function addToGeneratorDefinition(PropertyListInterface $definition) { parent::addToGeneratorDefinition($definition); - $definition->addProperties([ - 'hook_method_name' => PropertyDefinition::create('string') - ->setInternal(TRUE), - ]); + // Change the options provider to exclude obligate procedural hooks. + $definition->getProperty('hook_name') + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportHookClassMethodData')); + + $variants = $definition->getVariants(); + + // For a literal hook, the method name is the camel case version of the + // short hook name, e.g. 'formAlter()'. + $variants['literal']->addProperty(PropertyDefinition::create('string') + ->setName('hook_method_name') + ->setInternal(TRUE) + ->setCallableDefault(function ($component_data) { + $short_hook_name = $component_data->getParent()->short_hook_name->value; + return CaseString::snake($short_hook_name)->camel(); + }), + ); + + $variants['tokenized']->addProperty(PropertyDefinition::create('string') + ->setName('hook_method_name') + ->setInternal(TRUE) + ->setCallableDefault(function ($component_data) { + // The short_hook_name already has had tokens replaced. + $short_hook_name = $component_data->getParent()->short_hook_name->value; + return CaseString::snake($short_hook_name)->camel(); + }), + ); + + // Add or update properties common to both variants. + foreach ($variants as $variant) { + // The address to get the class component that holds this method. + $variant->addProperty(PropertyDefinition::create('string') + ->setName('class_component_address') + ->setInternal(TRUE) + ); + + // This needs both a default and processing, as in some cases this gets + // set by the requester. + // TODO: always derive this in default. + $variant->getProperty('declaration') + ->setCallableDefault(function ($component_data) { + $hook_info = $component_data->getParent()->hook_info->value; + + $declaration = $hook_info['definition']; + + // Run the default through processing, as that's not done + // automatically. + $processing = $component_data->getDefinition()->getProcessing(); + $component_data->set($declaration); + $processing($component_data); + + return $component_data->get(); + }) + ->setProcessing(function(DataItem $component_data) { + // Replace the hook name from the hook info's declaration with the + // method name. + $declaration = preg_replace( + '/(?<=function )hook_(\w+)/', + $component_data->getParent()->hook_method_name->get(), + $component_data->get() + ); + + // Add the 'public' modifier. + $declaration = 'public ' . $declaration; + + $component_data->set($declaration); + }); + + $variant->getProperty('body') + ->setCallableDefault(function ($component_data) { + $hook_name = $component_data->getParent()->hook_name->value; + $hook_info = $component_data->getParent()->hook_info->value; + + $template = $hook_info['body']; + + // This needs to be split into an array of lines for things such as + // PHPFile::extractFullyQualifiedClasses() to work. + $template = explode("\n", $template); + + // Trim lines from start and end of body, as hook definitions + // have newlines at start and end. + $template = array_slice($template, 1, -1); + + return $template; + }); + + $variant->addProperties([ + 'class_name' => PropertyDefinition::create('string') + ->setInternal(TRUE), + ]); + } } /** * {@inheritdoc} */ - public function getContents(): array { - // Replace the hook name from the hook info's declaration with the method - // name. - $this->component_data->declaration->value = preg_replace( - '/(?<=function )hook_(\w+)/', - $this->component_data->hook_method_name->value, - $this->component_data->declaration->value - ); + public function getMergeTag() { + // Allow multiple copies of the same hook. + return NULL; + } + + /** + * {@inheritdoc} + */ + public function requiredComponents(): array { + $components = parent::requiredComponents(); - // Add the 'public' prefix. - $this->component_data->declaration->value = 'public ' . $this->component_data->declaration->value; + // Add a legacy procedural hook if required. + // Declaring the hooks class as a service for legacy is handled in + // HooksClass. + if ($this->component_data->getItem('module:hook_implementation_type')->value == 'oo_legacy') { + $hook_name = $this->component_data->hook_name->value; - return parent::getContents(); + $mb_task_handler_report = \DrupalCodeBuilder\Factory::getTask('ReportHookData'); + $hook_info = $mb_task_handler_report->getHookDeclarations()[strtolower($this->component_data->hook_name->value)]; + + $component_name = $hook_name . '_legacy'; + + $class_name_component_address = $this->component_data->class_component_address->value; + $hooks_class_name = $this->component_data->getItem($class_name_component_address)->qualified_class_name->value; + + // The legacy hook body is just a call to the hook method. + $hook_method_name = $this->component_data->hook_method_name->value; + // Get the parameters. + $matches = []; + preg_match_all('@(\$\w+)@', $hook_info['definition'], $matches); + $arguments = implode(', ', $matches[0]); + + // Use a return statement if the hook returns a value. + $return = !empty($hook_info['has_return']) ? 'return ' : ''; + + $legacy_method_body_line = "{$return}\Drupal::service(\\{$hooks_class_name}::class)->{$hook_method_name}({$arguments});"; + + $components[$component_name] = [ + // We don't need to check for specialised hook generators, as there's no + // special function body for the legacy hook. + 'component_type' => 'HookImplementationProcedural', + 'code_file' => $hook_info['destination'], + 'hook_name' => $hook_name, + 'short_hook_name' => $this->component_data->short_hook_name->value, + 'attribute' => 'Drupal\Core\Hook\LegacyHook', + 'description' => $hook_info['description'], + 'function_docblock_lines' => [ + 'Legacy hook implementation.', + '@todo Remove this method when support for Drupal core < 11.1 is dropped.', + ], + 'body' => [$legacy_method_body_line], + // The code is a single string, already indented. Ensure we don't + // indent it again. + 'body_indented' => FALSE, + ]; + } + + return $components; } /** * {@inheritdoc} */ protected function getFunctionAttributes(): array { - $short_hook_name = preg_replace('/^hook_/', '', $this->component_data->hook_name->value); - $attribute = PhpAttributes::method( '\Drupal\Core\Hook\Attribute\Hook', - $short_hook_name, + $this->component_data->short_hook_name->value, ); return [$attribute]; } diff --git a/Generator/HookImplementationProcedural.php b/Generator/HookImplementationProcedural.php index 58f2d3ea..f37cb099 100644 --- a/Generator/HookImplementationProcedural.php +++ b/Generator/HookImplementationProcedural.php @@ -2,13 +2,60 @@ namespace DrupalCodeBuilder\Generator; +use DrupalCodeBuilder\Definition\PropertyDefinition; +use MutableTypedData\Data\DataItem; +use MutableTypedData\Definition\PropertyListInterface; + /** * Generator for a single procedural function hook implementation. - * - * This should not be requested directly; use the Hooks component instead. */ class HookImplementationProcedural extends HookImplementationBase { + /** + * {@inheritdoc} + */ + public static function addToGeneratorDefinition(PropertyListInterface $definition) { + parent::addToGeneratorDefinition($definition); + + $variants = $definition->getVariants(); + + foreach ($variants as $variant) { + // The address to get the class component that holds this method. + $variant->addProperty(PropertyDefinition::create('string') + ->setName('class_component_address') + ->setInternal(TRUE) + ); + + $variant->getProperty('declaration') + ->setCallableDefault(function ($component_data) { + $hook_info = $component_data->getParent()->hook_info->value; + + $declaration = $hook_info['definition']; + + // Run the default through processing, as that's not done + // automatically. + $processing = $component_data->getDefinition()->getProcessing(); + $component_data->set($declaration); + $processing($component_data); + + return $component_data->get(); + }) + ->setProcessing(function(DataItem $component_data) { + $short_hook_name = $component_data->getParent()->short_hook_name->get(); + + // Replace the hook name from the hook info's declaration with the + // short hook name and the module prefix. + $declaration = preg_replace( + '/(?<=function )hook_(\w+)/', + '%module_' . $short_hook_name, + $component_data->get() + ); + + $component_data->set($declaration); + }); + } + } + /** * {@inheritdoc} */ @@ -28,18 +75,16 @@ public function requiredComponents(): array { /** * {@inheritdoc} */ - function containingComponent() { - return '%self:code_file'; + public function getMergeTag() { + // Use the short hook name, as that has tokens replaced. + return $this->component_data->short_hook_name->value; } /** * {@inheritdoc} */ - public function getContents(): array { - // Replace the 'hook_' part of the function declaration. - $this->component_data->declaration->value = preg_replace('/(?<=function )hook/', '%module', $this->component_data->declaration->value); - - return parent::getContents(); + function containingComponent() { + return '%self:code_file'; } } diff --git a/Generator/HookUpdateN.php b/Generator/HookUpdateN.php index e3750629..62b083d0 100644 --- a/Generator/HookUpdateN.php +++ b/Generator/HookUpdateN.php @@ -17,16 +17,20 @@ class HookUpdateN extends HookImplementationProcedural { public static function addToGeneratorDefinition(PropertyListInterface $definition) { parent::addToGeneratorDefinition($definition); - // The next schema number to use for the hook implementation. - $definition->addProperty(PropertyDefinition::create('string') - ->setName('schema_number') - ->setInternal(TRUE) - ->setCallableDefault(function ($component_data) { - // Get overwritten by detectExistence() if existing implementations are - // found. - return (\DrupalCodeBuilder\Factory::getEnvironment()->getCoreMajorVersion() * 1000) + 1; - }) - ); + // This hook is plain, not tokenised, so we don't need to add the property + // on both variants. + foreach ($definition->getVariants() as $variant) { + // The next schema number to use for the hook implementation. + $variant->addProperty(PropertyDefinition::create('string') + ->setName('schema_number') + ->setInternal(TRUE) + ->setCallableDefault(function ($component_data) { + // Get overwritten by detectExistence() if existing implementations are + // found. + return (\DrupalCodeBuilder\Factory::getEnvironment()->getCoreMajorVersion() * 1000) + 1; + }) + ); + } } /** @@ -72,9 +76,9 @@ public function detectExistence(DrupalExtension $extension) { */ public function getContents(): array { // Replace the '_N' part of the function declaration. - $this->component_data->declaration->value = preg_replace('/(?<=hook_update_)N/', $this->component_data->schema_number->value, $this->component_data->declaration->value); + $this->component_data->declaration->value = preg_replace('/(?<=_update_)N/', $this->component_data->schema_number->value, $this->component_data->declaration->value); // Also do the function name. - $this->component_data->function_name->value = preg_replace('/(?<=update_)N/', $this->component_data->schema_number->value, $this->component_data->function_name->value); + $this->component_data->function_name->value = preg_replace('/(?<=_update_)N/', $this->component_data->schema_number->value, $this->component_data->function_name->value); return parent::getContents(); } diff --git a/Generator/Hooks.php b/Generator/Hooks.php index 49689451..5cfa4215 100644 --- a/Generator/Hooks.php +++ b/Generator/Hooks.php @@ -99,8 +99,6 @@ public function requiredComponents(): array { // Add components for each hook. foreach ($file_hook_list as $hook_name => $hook) { - $hook['short_hook_name'] = preg_replace('@^hook_@', '', $hook_name); - // The body for the hook implementation can come either template code, // or the hook documentation's example code. (Note that this will be // overridden further down the line if the HookImplementation component @@ -187,11 +185,6 @@ protected function addOoHookComponents(array &$components, array $hook_info): vo ], ]; - // Make the method name out of the short hook name in camel case. - // TODO this is crap with e.g. hook_form_FORM_ID_alter becomes - // formFORMIDAlter(). - $hook_method_name = CaseString::snake($hook_info['short_hook_name'])->camel(); - // Make the class method hook. This must have the same name as the component // added in self::addProceduralHookComponent(), so that other generators // that set this hook as their containing_component work in both cases. @@ -200,7 +193,6 @@ protected function addOoHookComponents(array &$components, array $hook_info): vo 'component_type' => 'HookImplementationClassMethod', 'code_file' => $hook_info['destination'], 'hook_name' => $hook_name, - 'hook_method_name' => $hook_method_name, 'declaration' => $hook_info['definition'], 'description' => $hook_info['description'], // Set the hook template as the method body. @@ -209,6 +201,7 @@ protected function addOoHookComponents(array &$components, array $hook_info): vo // indent it again. 'body_indented' => TRUE, 'containing_component' => '%requester:hooks_class', + 'class_component_address' => '..:..:requests:hooks_class', ]; } @@ -222,14 +215,12 @@ protected function addOoHookComponents(array &$components, array $hook_info): vo * @param array $hook_info * The array of hook info. */ - protected function addProceduralHookComponent(array &$components, array $hook_info, ?string $component_name = NULL): void { + protected function addProceduralHookComponent(array &$components, array $hook_info): void { $hook_name = $hook_info['name']; - $component_name ??= $hook_name; - $hook_class_name = $this->getHookImplementationComponentType($hook_info); // Add a procedural hook implementation. - $components[$component_name] = [ + $components[$hook_name] = [ 'component_type' => $hook_class_name, 'code_file' => $hook_info['destination'], 'hook_name' => $hook_name, @@ -258,35 +249,6 @@ protected function addLegacyProceduralHookComponent(array &$components, array $h $component_name = $hook_name . '_legacy'; $hooks_class_name = $this->component_data->getItem('module:root_name_pascal')->value . 'Hooks'; - // Start with the procedural hook component. - $this->addProceduralHookComponent($components, $hook_info, $component_name); - - // Make the method name out of the short hook name in camel case. - // TODO this is crap with e.g. hook_form_FORM_ID_alter becomes - // formFORMIDAlter(). - $hook_method_name = CaseString::snake($hook_info['short_hook_name'])->camel(); - - $components[$component_name]['attribute'] = 'Drupal\Core\Hook\LegacyHook'; - - $components[$component_name]['function_docblock_lines'] = [ - 'Legacy hook implementation.', - '@todo Remove this method when support for Drupal core < 11.1 is dropped.', - ]; - - // Replace the hook body with a call to the Hooks class. - // Get the parameters. - $matches = []; - preg_match_all('@(\$\w+)@', $hook_info['definition'], $matches); - $arguments = implode(', ', $matches[0]); - - // Use a return statement if the hook returns a value. - $return = !empty($hook_info['has_return']) ? 'return ' : ''; - - $components[$component_name]['body'] = [ - "{$return}\Drupal::service(\Drupal\%extension\Hook\\{$hooks_class_name}::class)->{$hook_method_name}({$arguments});", - ]; - $components[$component_name]['body_indented'] = FALSE; - // Explicitly declare the Hooks class as a service. // ARGH, can't use the 'Service' generator, as that will want to create a // class! diff --git a/Generator/HooksClass.php b/Generator/HooksClass.php new file mode 100644 index 00000000..b119492f --- /dev/null +++ b/Generator/HooksClass.php @@ -0,0 +1,80 @@ +getProperty('service_tag_type')->setInternal(TRUE); + $definition->getProperty('service_name')->setInternal(TRUE); + $definition->getProperty('decorates')->setInternal(TRUE); + $definition->getProperty('tags')->setInternal(TRUE); + + // Move the form class name property to the top, and make it user-set rather + // than internal with a default. + $definition->getProperty('plain_class_name') + ->setLabel("Hooks class name") + ->setInternal(FALSE) + ->setDescription("The hooks class's plain class name, e.g. \"MyHooks\".") + ->removeDefault(); + $definition->getProperty('relative_namespace') + ->setDefault(DefaultDefinition::create() + ->setLiteral('Hook') + ); + + // The service name is the same as the class name. + $definition->getProperty('service_name_prefix')->setLiteralDefault(''); + $definition->getProperty('service_name')->setExpressionDefault("get('..:qualified_class_name')"); + $definition->getProperty('autowire')->setLiteralDefault(TRUE); + + $definition->getProperty('class_docblock_lines') + ->setDefault( + DefaultDefinition::create() + ->setLiteral(['Contains hook implementations for the %readable %base.']) + ); + + $definition->addPropertyBefore( + 'injected_services', + MergingGeneratorDefinition::createFromGeneratorType('HookImplementationClassMethod') + ->setName('hook_methods') + ->setDescription('Hook implementations in this class. The same hook can be added multiple times.') + ->setLabel('Hook implementations') + ->setMultiple(TRUE) + ->setProcessing(function(DataItem $component_data) { + $component_data->containing_component = '%requester'; + $component_data->class_component_address = '..:..'; + }), + ); + } + + /** + * {@inheritdoc} + */ + public function requiredComponents(): array { + $components = parent::requiredComponents(); + + // If there's no legacy support, this service class doesn't need to be + // declared. + if ($this->component_data->getItem('module:hook_implementation_type')->value == 'oo') { + unset($components['%module.services.yml']); + } + + return $components; + } + +} + diff --git a/Generator/Module.php b/Generator/Module.php index 51cc97d3..948780d8 100644 --- a/Generator/Module.php +++ b/Generator/Module.php @@ -222,6 +222,10 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio 'oo_legacy' => 'Both types, with legacy support for Drupal core < 11.1', ]) ->setLiteralDefault('oo_legacy'), + 'hook_classes' => MergingGeneratorDefinition::createFromGeneratorType('HooksClass') + ->setLabel('Hook classes') + ->setDescription('Classes that hold hook implementation methods. Will also generate legacy procedural functions if Hook implementation type is set to do so.') + ->setMultiple(TRUE), 'hooks' => PropertyDefinition::create('string') ->setLabel('Hook implementations') ->setMultiple(TRUE) diff --git a/Generator/Module8.php b/Generator/Module8.php index cacaab45..3f5da043 100644 --- a/Generator/Module8.php +++ b/Generator/Module8.php @@ -26,6 +26,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLiteralDefault('procedural'); $definition->removeProperty('lifecycle'); + $definition->removeProperty('hook_classes'); } } diff --git a/Generator/PHPFunction.php b/Generator/PHPFunction.php index ced5a705..a335d78a 100644 --- a/Generator/PHPFunction.php +++ b/Generator/PHPFunction.php @@ -520,7 +520,8 @@ protected function getFunctionBody(): array { // There may be both property data and contained components. Contained // components override the body if it is set and if // 'body_overriden_by_contained' is TRUE. - $has_body_from_component_data = !$this->component_data->body->isEmpty(); + // Check values() rather than isEmpty() so defaults get applied. + $has_body_from_component_data = !empty($this->component_data->body->values()); $has_body_from_contained_components = $this->hasContainedComponentsOfContentType('line'); $let_body_from_contained_components_override_body_from_component_data = diff --git a/Task/ReportHookData.php b/Task/ReportHookData.php index fb946230..59c9528e 100644 --- a/Task/ReportHookData.php +++ b/Task/ReportHookData.php @@ -9,6 +9,7 @@ use DrupalCodeBuilder\Definition\OptionDefinition; use MutableTypedData\Definition\OptionSetDefininitionInterface; +use DrupalCodeBuilder\Definition\VariantMappingProviderInterface; use DrupalCodeBuilder\Task\Report\SectionReportInterface; /** @@ -16,7 +17,8 @@ * * TODO: revisit some of these and clean up names / clean up how many we have. */ -class ReportHookData extends ReportHookDataFolder implements OptionSetDefininitionInterface, SectionReportInterface { +class ReportHookData extends ReportHookDataFolder + implements OptionSetDefininitionInterface, VariantMappingProviderInterface, SectionReportInterface { /** * The sanity level this task requires to operate. @@ -144,6 +146,26 @@ public function getOptions(): array { return $options; } + /** + * {@inheritdoc} + */ + public function getVariantMapping(): array { + $mapping = []; + + $data = $this->listHookData(); + foreach ($data as $group => $hooks) { + foreach ($hooks as $key => $hook) { + $mapping[$key] = preg_match('@[[:upper:]]@', $key) ? 'tokenized' : 'literal'; + } + } + + // Special case for hook_update_N(): the uppercase 'N' is not a token, as + // it's derived automatically. + $mapping['hook_update_N'] = 'literal'; + + return $mapping; + } + /** * Get hooks as a list of options. * diff --git a/Test/Unit/ComponentHooks11Test.php b/Test/Unit/ComponentHooks11Test.php index 57fab9da..2eeedb2a 100644 --- a/Test/Unit/ComponentHooks11Test.php +++ b/Test/Unit/ComponentHooks11Test.php @@ -19,6 +19,168 @@ class ComponentHooks11Test extends TestBase { */ protected $drupalMajorVersion = 11; + /** + * Tests hooks in a custom class, without legacy support. + */ + public function testHookClassesOnlyOO(): void { + $module_name = 'test_module'; + $module_data = [ + 'base' => 'module', + 'root_name' => $module_name, + 'readable_name' => 'Test Module', + 'short_description' => 'Test Module description', + 'hook_implementation_type' => 'oo', + + 'hook_classes' => [ + 0 => [ + 'plain_class_name' => 'AlphaHooks', + 'injected_services' => [ + 'current_user', + 'entity_type.manager', + ], + 'hook_methods' => [ + 0 => [ + 'hook_name' => 'hook_form_alter', + ], + 1 => [ + 'hook_name' => 'hook_form_FORM_ID_alter', + 'hook_name_parameters' => [ + 'node_form', + ], + ], + 2 => [ + 'hook_name' => 'hook_form_FORM_ID_alter', + 'hook_name_parameters' => [ + 'user_form', + ], + ], + ], + ], + ], + 'readme' => FALSE, + ]; + + $files = $this->generateModuleFiles($module_data); + + $this->assertFiles([ + 'test_module.info.yml', + 'src/Hook/AlphaHooks.php', + ], $files); + + $hooks_file = $files['src/Hook/AlphaHooks.php']; + + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $hooks_file); + $php_tester->assertDrupalCodingStandards([ + // Temporarily exclude the sniff for comment lines being too long, as a + // comment in hook_form_alter() violates this. + // TODO: remove this when https://www.drupal.org/project/drupal/issues/2924184 + // is fixed. + 'Drupal.Files.LineLength.TooLong', + // Sample code for hook_form_FORM_ID_alter() violates this. + 'Drupal.Commenting.InlineComment.SpacingAfter', + ]); + + $php_tester->assertHasClass('Drupal\test_module\Hook\AlphaHooks'); + $php_tester->assertHasMethod('formAlter'); + $php_tester->assertHasMethod('formNodeFormAlter'); + $php_tester->assertHasMethod('formUserFormAlter'); + + // TODO: Attribute testing. + $this->assertStringContainsString("#[Hook('form_alter')]", $hooks_file); + $this->assertStringContainsString("#[Hook('form_node_form_alter')]", $hooks_file); + $this->assertStringContainsString("#[Hook('form_user_form_alter')]", $hooks_file); + } + + /** + * Tests hooks in a custom class, with legacy support. + */ + public function testHookClassesWithLegacy(): void { + $module_name = 'test_module'; + $module_data = [ + 'base' => 'module', + 'root_name' => $module_name, + 'readable_name' => 'Test Module', + 'short_description' => 'Test Module description', + 'hook_implementation_type' => 'oo_legacy', + 'hook_classes' => [ + 0 => [ + 'plain_class_name' => 'AlphaHooks', + 'injected_services' => [ + 'current_user', + 'entity_type.manager', + ], + 'hook_methods' => [ + 0 => [ + 'hook_name' => 'hook_form_alter', + ], + 1 => [ + 'hook_name' => 'hook_form_FORM_ID_alter', + 'hook_name_parameters' => [ + 'node_form', + ], + ], + 2 => [ + 'hook_name' => 'hook_form_FORM_ID_alter', + 'hook_name_parameters' => [ + 'user_form', + ], + ], + ], + ], + ], + 'readme' => FALSE, + ]; + + $files = $this->generateModuleFiles($module_data); + + $this->assertFiles([ + 'test_module.info.yml', + 'test_module.module', + 'test_module.services.yml', + 'src/Hook/AlphaHooks.php', + ], $files); + + $hooks_file = $files['src/Hook/AlphaHooks.php']; + + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $hooks_file); + $php_tester->assertDrupalCodingStandards([ + // Temporarily exclude the sniff for comment lines being too long, as a + // comment in hook_form_alter() violates this. + // TODO: remove this when https://www.drupal.org/project/drupal/issues/2924184 + // is fixed. + 'Drupal.Files.LineLength.TooLong', + // Sample code for hook_form_FORM_ID_alter() violates this. + 'Drupal.Commenting.InlineComment.SpacingAfter', + ]); + + $php_tester->assertHasClass('Drupal\test_module\Hook\AlphaHooks'); + $php_tester->assertHasMethod('formAlter'); + + // TODO: Attribute testing. + $this->assertStringContainsString("#[Hook('form_alter')]", $hooks_file); + + $module_file = $files['test_module.module']; + + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $module_file); + $php_tester->assertDrupalCodingStandards([ + // Generated method name for hook with name tokens violates this. + 'Drupal.NamingConventions.ValidFunctionName.InvalidName', + ]); + $php_tester->assertFileDocblockHasLine("Contains hook implementations for the Test Module module."); + + $function_tester = $php_tester->getFunctionTester('test_module_form_alter'); + $function_tester->getDocBlockTester()->assertHasLine('Legacy hook implementation.'); + $function_tester->assertHasLine('\Drupal::service(AlphaHooks::class)->formAlter($form, $form_state, $form_id);'); + + $function_tester = $php_tester->getFunctionTester('test_module_form_node_form_alter'); + $function_tester->getDocBlockTester()->assertHasLine('Legacy hook implementation.'); + $function_tester->assertHasLine('\Drupal::service(AlphaHooks::class)->formNodeFormAlter($form, $form_state, $form_id);'); + + $function_tester = $php_tester->getFunctionTester('test_module_form_user_form_alter'); + $function_tester->getDocBlockTester()->assertHasLine('Legacy hook implementation.'); + $function_tester->assertHasLine('\Drupal::service(AlphaHooks::class)->formUserFormAlter($form, $form_state, $form_id);'); + } + /** * Tests procedural hooks can also be generated on 11. */ diff --git a/Test/Unit/ComponentPHPFile10Test.php b/Test/Unit/ComponentPHPFile10Test.php index 47c43239..29aca965 100644 --- a/Test/Unit/ComponentPHPFile10Test.php +++ b/Test/Unit/ComponentPHPFile10Test.php @@ -205,6 +205,9 @@ public function testClassImportsOrder() { EOT, ], ]); + $report_hook_data->getVariantMapping(Argument::any())->willReturn([ + 'hook_cake' => 'literal', + ]); $report_hook_data->getOptions(Argument::any())->willReturn([ 'hook_cake' => 'hook_cake()', ]); From ea2c0b767a26b4ee606c79c956c8abfc26868af6 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Thu, 8 May 2025 14:13:09 +0100 Subject: [PATCH 026/144] Fixed hooks with a token at the start of the short hook name not getting tokens replaced correctly. Fixes #396. --- Generator/HookImplementationBase.php | 29 +- Task/Collect/HooksCollector11.php | 2 + Test/Unit/ComponentHooks11Test.php | 18 + .../11/hooks_processed.php | 1764 +++++++++++++++++ 4 files changed, 1803 insertions(+), 10 deletions(-) diff --git a/Generator/HookImplementationBase.php b/Generator/HookImplementationBase.php index 0c2fc315..257625cb 100644 --- a/Generator/HookImplementationBase.php +++ b/Generator/HookImplementationBase.php @@ -75,25 +75,34 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $hook_name_parameters = $component_data->getParent()->hook_name_parameters->values(); - // Split on an UPPER_CASE token in the hook name, without the bounding - // underscores, and include the token in the pieces. For example, + // Split on the token boundaries, leaving the underscore outside the + // token. For example, // 'form_FORM_ID_alter' becomes: // - form_ // - FORM_ID // - _alter + // and 'ENTITY_TYPE_view' becomes + // - ENTITY_TYPE + // - _view $pieces = preg_split( - '@(?<=_)([[:upper:]_]+)(?=_)@', + '@( + # Token at the start of the hook name. + \b (?=[[:upper:]_]) + | + # Left edge of a token. + (?<=[[:lower:]]_) (?=[[:upper:]_]) + | + # Right edge of a token. + (?<=[[:upper:]_]) (?=_[[:lower:]]) + )@x', $short_hook_name, - flags: PREG_SPLIT_DELIM_CAPTURE, ); - // Because hook names never start with a token, we know that the - // tokens are all the odd-indexed pieces, and the fixed portions the - // even-indexed. + // Replace the tokens. foreach ($pieces as $i => &$piece) { - if ($i % 2 == 1) { - // An odd-indexed piece is replaced with hook name parameter if - // one exists. + // Replace each upper-cased piece with a parameter if there are + // enough parameters. + if (preg_match('@^[[:upper:]_]+$@', $piece)) { if ($hook_name_parameters) { // Use up each parameter, so the array empties out. $parameter = array_shift($hook_name_parameters); diff --git a/Task/Collect/HooksCollector11.php b/Task/Collect/HooksCollector11.php index ebf4b0ab..f4ff1d3e 100644 --- a/Task/Collect/HooksCollector11.php +++ b/Task/Collect/HooksCollector11.php @@ -17,6 +17,8 @@ class HooksCollector11 extends HooksCollector { 'CORE_module.api.php' => TRUE, // Need this for hook_form_alter(). 'CORE_form.api.php' => TRUE, + // Need this for hook_ENTITY_TYPE_view(). + 'CORE_entity.api.php' => TRUE, // Need this for hook_tokens(). 'CORE_token.api.php' => TRUE, // Need this for hook_help(). diff --git a/Test/Unit/ComponentHooks11Test.php b/Test/Unit/ComponentHooks11Test.php index 2eeedb2a..6e2f57f2 100644 --- a/Test/Unit/ComponentHooks11Test.php +++ b/Test/Unit/ComponentHooks11Test.php @@ -54,6 +54,12 @@ public function testHookClassesOnlyOO(): void { 'user_form', ], ], + 3 => [ + 'hook_name' => 'hook_ENTITY_TYPE_view', + 'hook_name_parameters' => [ + 'node', + ], + ], ], ], ], @@ -84,11 +90,13 @@ public function testHookClassesOnlyOO(): void { $php_tester->assertHasMethod('formAlter'); $php_tester->assertHasMethod('formNodeFormAlter'); $php_tester->assertHasMethod('formUserFormAlter'); + $php_tester->assertHasMethod('nodeView'); // TODO: Attribute testing. $this->assertStringContainsString("#[Hook('form_alter')]", $hooks_file); $this->assertStringContainsString("#[Hook('form_node_form_alter')]", $hooks_file); $this->assertStringContainsString("#[Hook('form_user_form_alter')]", $hooks_file); + $this->assertStringContainsString("#[Hook('node_view')]", $hooks_file); } /** @@ -125,6 +133,12 @@ public function testHookClassesWithLegacy(): void { 'user_form', ], ], + 3 => [ + 'hook_name' => 'hook_ENTITY_TYPE_view', + 'hook_name_parameters' => [ + 'node', + ], + ], ], ], ], @@ -179,6 +193,10 @@ public function testHookClassesWithLegacy(): void { $function_tester = $php_tester->getFunctionTester('test_module_form_user_form_alter'); $function_tester->getDocBlockTester()->assertHasLine('Legacy hook implementation.'); $function_tester->assertHasLine('\Drupal::service(AlphaHooks::class)->formUserFormAlter($form, $form_state, $form_id);'); + + $function_tester = $php_tester->getFunctionTester('test_module_node_view'); + $function_tester->getDocBlockTester()->assertHasLine('Legacy hook implementation.'); + $function_tester->assertHasLine('\Drupal::service(AlphaHooks::class)->nodeView($build, $entity, $display, $view_mode);'); } /** diff --git a/Test/sample_hook_definitions/11/hooks_processed.php b/Test/sample_hook_definitions/11/hooks_processed.php index 5ec3bf91..6d5ac345 100644 --- a/Test/sample_hook_definitions/11/hooks_processed.php +++ b/Test/sample_hook_definitions/11/hooks_processed.php @@ -140,6 +140,1770 @@ // provided by this custom module. } } +', + ), + ), + 'core:entity' => + array ( + 'hook_entity_access' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_access', + 'definition' => 'function hook_entity_access(\\Drupal\\Core\\Entity\\EntityInterface $entity, $operation, \\Drupal\\Core\\Session\\AccountInterface $account)', + 'description' => 'Control entity operation access.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // No opinion. + return \\Drupal\\Core\\Access\\AccessResult::neutral(); +', + ), + 'hook_ENTITY_TYPE_access' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_access', + 'definition' => 'function hook_ENTITY_TYPE_access(\\Drupal\\Core\\Entity\\EntityInterface $entity, $operation, \\Drupal\\Core\\Session\\AccountInterface $account)', + 'description' => 'Control entity operation access for a specific entity type.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // No opinion. + return \\Drupal\\Core\\Access\\AccessResult::neutral(); +', + ), + 'hook_entity_create_access' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_create_access', + 'definition' => 'function hook_entity_create_access(\\Drupal\\Core\\Session\\AccountInterface $account, array $context, $entity_bundle)', + 'description' => 'Control entity create access.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // No opinion. + return \\Drupal\\Core\\Access\\AccessResult::neutral(); +', + ), + 'hook_ENTITY_TYPE_create_access' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_create_access', + 'definition' => 'function hook_ENTITY_TYPE_create_access(\\Drupal\\Core\\Session\\AccountInterface $account, array $context, $entity_bundle)', + 'description' => 'Control entity create access for a specific entity type.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // No opinion. + return \\Drupal\\Core\\Access\\AccessResult::neutral(); +', + ), + 'hook_entity_type_build' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_type_build', + 'definition' => 'function hook_entity_type_build(array &$entity_types)', + 'description' => 'Add to entity type definitions.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + /** @var \\Drupal\\Core\\Entity\\EntityTypeInterface[] $entity_types */ + // Add a form for a custom node form without overriding the default + // node form. To override the default node form, use hook_entity_type_alter(). + $entity_types[\'node\']->setFormClass(\'my_module_foo\', \'Drupal\\my_module\\NodeFooForm\'); +', + ), + 'hook_entity_type_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_type_alter', + 'definition' => 'function hook_entity_type_alter(array &$entity_types)', + 'description' => 'Alter the entity type definitions.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + /** @var \\Drupal\\Core\\Entity\\EntityTypeInterface[] $entity_types */ + // Set the controller class for nodes to an alternate implementation of the + // Drupal\\Core\\Entity\\EntityStorageInterface interface. + $entity_types[\'node\']->setStorageClass(\'Drupal\\my_module\\MyCustomNodeStorage\'); +', + ), + 'hook_entity_view_mode_info_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_view_mode_info_alter', + 'definition' => 'function hook_entity_view_mode_info_alter(&$view_modes)', + 'description' => 'Alter the view modes for entity types.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $view_modes[\'user\'][\'full\'][\'status\'] = TRUE; +', + ), + 'hook_entity_bundle_info' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_bundle_info', + 'definition' => 'function hook_entity_bundle_info()', + 'description' => 'Describe the bundles for entity types.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $bundles[\'user\'][\'user\'][\'label\'] = t(\'User\'); + return $bundles; +', + ), + 'hook_entity_bundle_info_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_bundle_info_alter', + 'definition' => 'function hook_entity_bundle_info_alter(&$bundles)', + 'description' => 'Alter the bundles for entity types.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $bundles[\'user\'][\'user\'][\'label\'] = t(\'Full account\'); + // Override the bundle class for the "article" node type in a custom module. + $bundles[\'node\'][\'article\'][\'class\'] = \'Drupal\\my_module\\Entity\\Article\'; +', + ), + 'hook_entity_bundle_create' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_bundle_create', + 'definition' => 'function hook_entity_bundle_create($entity_type_id, $bundle)', + 'description' => 'Act on entity_bundle_create().', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // When a new bundle is created, the menu needs to be rebuilt to add the + // Field UI menu item tabs. + \\Drupal::service(\'router.builder\')->setRebuildNeeded(); +', + ), + 'hook_entity_bundle_delete' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_bundle_delete', + 'definition' => 'function hook_entity_bundle_delete($entity_type_id, $bundle)', + 'description' => 'Act on entity_bundle_delete().', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Remove the settings associated with the bundle in my_module.settings. + $config = \\Drupal::config(\'my_module.settings\'); + $bundle_settings = $config->get(\'bundle_settings\'); + if (isset($bundle_settings[$entity_type_id][$bundle])) { + unset($bundle_settings[$entity_type_id][$bundle]); + $config->set(\'bundle_settings\', $bundle_settings); + } +', + ), + 'hook_entity_create' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_create', + 'definition' => 'function hook_entity_create(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Acts when creating a new entity.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + \\Drupal::logger(\'example\')->info(\'Entity created: @label\', [\'@label\' => $entity->label()]); +', + ), + 'hook_ENTITY_TYPE_create' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_create', + 'definition' => 'function hook_ENTITY_TYPE_create(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Acts when creating a new entity of a specific type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + \\Drupal::logger(\'example\')->info(\'ENTITY_TYPE created: @label\', [\'@label\' => $entity->label()]); +', + ), + 'hook_entity_revision_create' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_revision_create', + 'definition' => 'function hook_entity_revision_create(\\Drupal\\Core\\Entity\\EntityInterface $new_revision, \\Drupal\\Core\\Entity\\EntityInterface $entity, $keep_untranslatable_fields)', + 'description' => 'Respond to entity revision creation.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Retain the value from an untranslatable field, which are by default + // synchronized from the default revision. + $new_revision->set(\'untranslatable_field\', $entity->get(\'untranslatable_field\')); +', + ), + 'hook_ENTITY_TYPE_revision_create' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_revision_create', + 'definition' => 'function hook_ENTITY_TYPE_revision_create(\\Drupal\\Core\\Entity\\EntityInterface $new_revision, \\Drupal\\Core\\Entity\\EntityInterface $entity, $keep_untranslatable_fields)', + 'description' => 'Respond to entity revision creation.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Retain the value from an untranslatable field, which are by default + // synchronized from the default revision. + $new_revision->set(\'untranslatable_field\', $entity->get(\'untranslatable_field\')); +', + ), + 'hook_entity_preload' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_preload', + 'definition' => 'function hook_entity_preload(array $ids, $entity_type_id)', + 'description' => 'Act on an array of entity IDs before they are loaded.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $entities = []; + + foreach ($ids as $id) { + $entities[] = my_module_swap_revision($id); + } + + return $entities; +', + ), + 'hook_entity_load' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_load', + 'definition' => 'function hook_entity_load(array $entities, $entity_type_id)', + 'description' => 'Act on entities when loaded.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + foreach ($entities as $entity) { + $entity->foo = my_module_add_something($entity); + } +', + ), + 'hook_ENTITY_TYPE_load' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_load', + 'definition' => 'function hook_ENTITY_TYPE_load($entities)', + 'description' => 'Act on entities of a specific type when loaded.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + foreach ($entities as $entity) { + $entity->foo = my_module_add_something($entity); + } +', + ), + 'hook_entity_storage_load' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_storage_load', + 'definition' => 'function hook_entity_storage_load(array $entities, $entity_type)', + 'description' => 'Act on content entities when loaded from the storage.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + foreach ($entities as $entity) { + $entity->foo = my_module_add_something_uncached($entity); + } +', + ), + 'hook_ENTITY_TYPE_storage_load' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_storage_load', + 'definition' => 'function hook_ENTITY_TYPE_storage_load(array $entities)', + 'description' => 'Act on content entities of a given type when loaded from the storage.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + foreach ($entities as $entity) { + $entity->foo = my_module_add_something_uncached($entity); + } +', + ), + 'hook_entity_presave' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_presave', + 'definition' => 'function hook_entity_presave(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Act on an entity before it is created or updated.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($entity instanceof \\Drupal\\Core\\Entity\\ContentEntityInterface && $entity->isTranslatable()) { + $route_match = \\Drupal::routeMatch(); + \\Drupal::service(\'content_translation.synchronizer\')->synchronizeFields($entity, $entity->language()->getId(), $route_match->getParameter(\'source_langcode\')); + } +', + ), + 'hook_ENTITY_TYPE_presave' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_presave', + 'definition' => 'function hook_ENTITY_TYPE_presave(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Act on a specific type of entity before it is created or updated.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($entity->isTranslatable()) { + $route_match = \\Drupal::routeMatch(); + \\Drupal::service(\'content_translation.synchronizer\')->synchronizeFields($entity, $entity->language()->getId(), $route_match->getParameter(\'source_langcode\')); + } +', + ), + 'hook_entity_insert' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_insert', + 'definition' => 'function hook_entity_insert(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to creation of a new entity.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Insert the new entity into a fictional table of all entities. + \\Drupal::database()->insert(\'example_entity\') + ->fields([ + \'type\' => $entity->getEntityTypeId(), + \'id\' => $entity->id(), + \'created\' => \\Drupal::time()->getRequestTime(), + \'updated\' => \\Drupal::time()->getRequestTime(), + ]) + ->execute(); +', + ), + 'hook_ENTITY_TYPE_insert' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_insert', + 'definition' => 'function hook_ENTITY_TYPE_insert(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to creation of a new entity of a particular type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Insert the new entity into a fictional table of this type of entity. + \\Drupal::database()->insert(\'example_entity\') + ->fields([ + \'id\' => $entity->id(), + \'created\' => \\Drupal::time()->getRequestTime(), + \'updated\' => \\Drupal::time()->getRequestTime(), + ]) + ->execute(); +', + ), + 'hook_entity_update' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_update', + 'definition' => 'function hook_entity_update(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to updates to an entity.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Update the entity\'s entry in a fictional table of all entities. + \\Drupal::database()->update(\'example_entity\') + ->fields([ + \'updated\' => \\Drupal::time()->getRequestTime(), + ]) + ->condition(\'type\', $entity->getEntityTypeId()) + ->condition(\'id\', $entity->id()) + ->execute(); +', + ), + 'hook_ENTITY_TYPE_update' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_update', + 'definition' => 'function hook_ENTITY_TYPE_update(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to updates to an entity of a particular type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Update the entity\'s entry in a fictional table of this type of entity. + \\Drupal::database()->update(\'example_entity\') + ->fields([ + \'updated\' => \\Drupal::time()->getRequestTime(), + ]) + ->condition(\'id\', $entity->id()) + ->execute(); +', + ), + 'hook_entity_translation_create' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_translation_create', + 'definition' => 'function hook_entity_translation_create(\\Drupal\\Core\\Entity\\EntityInterface $translation)', + 'description' => 'Acts when creating a new entity translation.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + \\Drupal::logger(\'example\')->info(\'Entity translation created: @label\', [\'@label\' => $translation->label()]); +', + ), + 'hook_ENTITY_TYPE_translation_create' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_translation_create', + 'definition' => 'function hook_ENTITY_TYPE_translation_create(\\Drupal\\Core\\Entity\\EntityInterface $translation)', + 'description' => 'Acts when creating a new entity translation of a specific type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + \\Drupal::logger(\'example\')->info(\'ENTITY_TYPE translation created: @label\', [\'@label\' => $translation->label()]); +', + ), + 'hook_entity_translation_insert' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_translation_insert', + 'definition' => 'function hook_entity_translation_insert(\\Drupal\\Core\\Entity\\EntityInterface $translation)', + 'description' => 'Respond to creation of a new entity translation.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $variables = [ + \'@language\' => $translation->language()->getName(), + \'@label\' => $translation->getUntranslated()->label(), + ]; + \\Drupal::logger(\'example\')->notice(\'The @language translation of @label has just been stored.\', $variables); +', + ), + 'hook_ENTITY_TYPE_translation_insert' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_translation_insert', + 'definition' => 'function hook_ENTITY_TYPE_translation_insert(\\Drupal\\Core\\Entity\\EntityInterface $translation)', + 'description' => 'Respond to creation of a new entity translation of a particular type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $variables = [ + \'@language\' => $translation->language()->getName(), + \'@label\' => $translation->getUntranslated()->label(), + ]; + \\Drupal::logger(\'example\')->notice(\'The @language translation of @label has just been stored.\', $variables); +', + ), + 'hook_entity_translation_delete' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_translation_delete', + 'definition' => 'function hook_entity_translation_delete(\\Drupal\\Core\\Entity\\EntityInterface $translation)', + 'description' => 'Respond to entity translation deletion.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $variables = [ + \'@language\' => $translation->language()->getName(), + \'@label\' => $translation->label(), + ]; + \\Drupal::logger(\'example\')->notice(\'The @language translation of @label has just been deleted.\', $variables); +', + ), + 'hook_ENTITY_TYPE_translation_delete' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_translation_delete', + 'definition' => 'function hook_ENTITY_TYPE_translation_delete(\\Drupal\\Core\\Entity\\EntityInterface $translation)', + 'description' => 'Respond to entity translation deletion of a particular type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $variables = [ + \'@language\' => $translation->language()->getName(), + \'@label\' => $translation->label(), + ]; + \\Drupal::logger(\'example\')->notice(\'The @language translation of @label has just been deleted.\', $variables); +', + ), + 'hook_entity_predelete' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_predelete', + 'definition' => 'function hook_entity_predelete(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Act before entity deletion.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $connection = \\Drupal::database(); + // Count references to this entity in a custom table before they are removed + // upon entity deletion. + $id = $entity->id(); + $type = $entity->getEntityTypeId(); + $count = \\Drupal::database()->select(\'example_entity_data\') + ->condition(\'type\', $type) + ->condition(\'id\', $id) + ->countQuery() + ->execute() + ->fetchField(); + + // Log the count in a table that records this statistic for deleted entities. + $connection->merge(\'example_deleted_entity_statistics\') + ->keys([\'type\' => $type, \'id\' => $id]) + ->fields([\'count\' => $count]) + ->execute(); +', + ), + 'hook_ENTITY_TYPE_predelete' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_predelete', + 'definition' => 'function hook_ENTITY_TYPE_predelete(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Act before entity deletion of a particular entity type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $connection = \\Drupal::database(); + // Count references to this entity in a custom table before they are removed + // upon entity deletion. + $id = $entity->id(); + $type = $entity->getEntityTypeId(); + $count = \\Drupal::database()->select(\'example_entity_data\') + ->condition(\'type\', $type) + ->condition(\'id\', $id) + ->countQuery() + ->execute() + ->fetchField(); + + // Log the count in a table that records this statistic for deleted entities. + $connection->merge(\'example_deleted_entity_statistics\') + ->keys([\'type\' => $type, \'id\' => $id]) + ->fields([\'count\' => $count]) + ->execute(); +', + ), + 'hook_entity_delete' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_delete', + 'definition' => 'function hook_entity_delete(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to entity deletion.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Delete the entity\'s entry from a fictional table of all entities. + \\Drupal::database()->delete(\'example_entity\') + ->condition(\'type\', $entity->getEntityTypeId()) + ->condition(\'id\', $entity->id()) + ->execute(); +', + ), + 'hook_ENTITY_TYPE_delete' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_delete', + 'definition' => 'function hook_ENTITY_TYPE_delete(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to entity deletion of a particular type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Delete the entity\'s entry from a fictional table of all entities. + \\Drupal::database()->delete(\'example_entity\') + ->condition(\'type\', $entity->getEntityTypeId()) + ->condition(\'id\', $entity->id()) + ->execute(); +', + ), + 'hook_entity_revision_delete' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_revision_delete', + 'definition' => 'function hook_entity_revision_delete(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to entity revision deletion.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $referenced_files_by_field = _editor_get_file_uuids_by_field($entity); + foreach ($referenced_files_by_field as $field => $uuids) { + _editor_delete_file_usage($uuids, $entity, 1); + } +', + ), + 'hook_ENTITY_TYPE_revision_delete' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_revision_delete', + 'definition' => 'function hook_ENTITY_TYPE_revision_delete(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Respond to entity revision deletion of a particular type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $referenced_files_by_field = _editor_get_file_uuids_by_field($entity); + foreach ($referenced_files_by_field as $field => $uuids) { + _editor_delete_file_usage($uuids, $entity, 1); + } +', + ), + 'hook_entity_view' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_view', + 'definition' => 'function hook_entity_view(array &$build, \\Drupal\\Core\\Entity\\EntityInterface $entity, \\Drupal\\Core\\Entity\\Display\\EntityViewDisplayInterface $display, $view_mode)', + 'description' => 'Act on entities being assembled before rendering.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Only do the extra work if the component is configured to be displayed. + // This assumes a \'my_module_addition\' extra field has been defined for the + // entity bundle in hook_entity_extra_field_info(). + if ($display->getComponent(\'my_module_addition\')) { + $build[\'my_module_addition\'] = [ + \'#markup\' => my_module_addition($entity), + \'#theme\' => \'my_module_my_additional_field\', + ]; + } +', + ), + 'hook_ENTITY_TYPE_view' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_view', + 'definition' => 'function hook_ENTITY_TYPE_view(array &$build, \\Drupal\\Core\\Entity\\EntityInterface $entity, \\Drupal\\Core\\Entity\\Display\\EntityViewDisplayInterface $display, $view_mode)', + 'description' => 'Act on entities of a particular type being assembled before rendering.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Only do the extra work if the component is configured to be displayed. + // This assumes a \'my_module_addition\' extra field has been defined for the + // entity bundle in hook_entity_extra_field_info(). + if ($display->getComponent(\'my_module_addition\')) { + $build[\'my_module_addition\'] = [ + \'#markup\' => my_module_addition($entity), + \'#theme\' => \'my_module_my_additional_field\', + ]; + } +', + ), + 'hook_entity_view_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_view_alter', + 'definition' => 'function hook_entity_view_alter(array &$build, \\Drupal\\Core\\Entity\\EntityInterface $entity, \\Drupal\\Core\\Entity\\Display\\EntityViewDisplayInterface $display)', + 'description' => 'Alter the results of the entity build array.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($build[\'#view_mode\'] == \'full\' && isset($build[\'an_additional_field\'])) { + // Change its weight. + $build[\'an_additional_field\'][\'#weight\'] = -10; + + // Add a #post_render callback to act on the rendered HTML of the entity. + // The object must implement \\Drupal\\Core\\Security\\TrustedCallbackInterface. + $build[\'#post_render\'][] = \'\\Drupal\\my_module\\NodeCallback::postRender\'; + } +', + ), + 'hook_ENTITY_TYPE_view_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_view_alter', + 'definition' => 'function hook_ENTITY_TYPE_view_alter(array &$build, \\Drupal\\Core\\Entity\\EntityInterface $entity, \\Drupal\\Core\\Entity\\Display\\EntityViewDisplayInterface $display)', + 'description' => 'Alter the results of the entity build array for a particular entity type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($build[\'#view_mode\'] == \'full\' && isset($build[\'an_additional_field\'])) { + // Change its weight. + $build[\'an_additional_field\'][\'#weight\'] = -10; + + // Add a #post_render callback to act on the rendered HTML of the entity. + $build[\'#post_render\'][] = \'my_module_node_post_render\'; + } +', + ), + 'hook_entity_prepare_view' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_prepare_view', + 'definition' => 'function hook_entity_prepare_view($entity_type_id, array $entities, array $displays, $view_mode)', + 'description' => 'Act on entities as they are being prepared for view.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Load a specific node into the user object for later theming. + if (!empty($entities) && $entity_type_id == \'user\') { + // Only do the extra work if the component is configured to be + // displayed. This assumes a \'my_module_addition\' extra field has been + // defined for the entity bundle in hook_entity_extra_field_info(). + $ids = []; + foreach ($entities as $id => $entity) { + if ($displays[$entity->bundle()]->getComponent(\'my_module_addition\')) { + $ids[] = $id; + } + } + if ($ids) { + $nodes = my_module_get_user_nodes($ids); + foreach ($ids as $id) { + $entities[$id]->user_node = $nodes[$id]; + } + } + } +', + ), + 'hook_entity_view_mode_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_view_mode_alter', + 'definition' => 'function hook_entity_view_mode_alter(&$view_mode, \\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Change the view mode of an entity that is being displayed.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // For nodes, change the view mode when it is teaser. + if ($entity->getEntityTypeId() == \'node\' && $view_mode == \'teaser\') { + $view_mode = \'my_custom_view_mode\'; + } +', + ), + 'hook_ENTITY_TYPE_view_mode_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_view_mode_alter', + 'definition' => 'function hook_ENTITY_TYPE_view_mode_alter(string &$view_mode, \\Drupal\\Core\\Entity\\EntityInterface $entity): void', + 'description' => 'Change the view mode of a specific entity type currently being displayed.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Change the view mode to teaser. + if ($view_mode == \'full\') { + $view_mode = \'teaser\'; + } +', + ), + 'hook_ENTITY_TYPE_build_defaults_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_build_defaults_alter', + 'definition' => 'function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \\Drupal\\Core\\Entity\\EntityInterface $entity, $view_mode)', + 'description' => 'Alter entity renderable values before cache checking during rendering.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + +', + ), + 'hook_entity_build_defaults_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_build_defaults_alter', + 'definition' => 'function hook_entity_build_defaults_alter(array &$build, \\Drupal\\Core\\Entity\\EntityInterface $entity, $view_mode)', + 'description' => 'Alter entity renderable values before cache checking during rendering.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + +', + ), + 'hook_entity_view_display_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_view_display_alter', + 'definition' => 'function hook_entity_view_display_alter(\\Drupal\\Core\\Entity\\Display\\EntityViewDisplayInterface $display, array $context)', + 'description' => 'Alter the settings used for displaying an entity.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Leave field labels out of the search index. + if ($context[\'entity_type\'] == \'node\' && $context[\'view_mode\'] == \'search_index\') { + foreach ($display->getComponents() as $name => $options) { + if (isset($options[\'label\'])) { + $options[\'label\'] = \'hidden\'; + $display->setComponent($name, $options); + } + } + } +', + ), + 'hook_entity_display_build_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_display_build_alter', + 'definition' => 'function hook_entity_display_build_alter(&$build, $context)', + 'description' => 'Alter the render array generated by an EntityDisplay for an entity.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + /** @var \\Drupal\\Core\\Entity\\\\Drupal\\Core\\Entity\\ContentEntityInterface $entity */ + $entity = $context[\'entity\']; + if ($entity->getEntityTypeId() === \'my_entity\' && $entity->bundle() === \'display_build_alter_bundle\') { + $build[\'entity_display_build_alter\'][\'#markup\'] = \'Content added in hook_entity_display_build_alter for entity id \' . $entity->id(); + } +', + ), + 'hook_entity_prepare_form' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_prepare_form', + 'definition' => 'function hook_entity_prepare_form(\\Drupal\\Core\\Entity\\EntityInterface $entity, $operation, \\Drupal\\Core\\Form\\FormStateInterface $form_state)', + 'description' => 'Acts on an entity object about to be shown on an entity form.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($operation == \'edit\') { + $entity->label->value = \'Altered label\'; + $form_state->set(\'label_altered\', TRUE); + } +', + ), + 'hook_ENTITY_TYPE_prepare_form' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_prepare_form', + 'definition' => 'function hook_ENTITY_TYPE_prepare_form(\\Drupal\\Core\\Entity\\EntityInterface $entity, $operation, \\Drupal\\Core\\Form\\FormStateInterface $form_state)', + 'description' => 'Acts on a particular type of entity object about to be in an entity form.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($operation == \'edit\') { + $entity->label->value = \'Altered label\'; + $form_state->set(\'label_altered\', TRUE); + } +', + ), + 'hook_entity_form_mode_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_form_mode_alter', + 'definition' => 'function hook_entity_form_mode_alter(&$form_mode, \\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Change the form mode used to build an entity form.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Change the form mode for users with Administrator role. + if ($entity->getEntityTypeId() == \'user\' && $entity->hasRole(\'administrator\')) { + $form_mode = \'my_custom_form_mode\'; + } +', + ), + 'hook_ENTITY_TYPE_form_mode_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_form_mode_alter', + 'definition' => 'function hook_ENTITY_TYPE_form_mode_alter(string &$form_mode, \\Drupal\\Core\\Entity\\EntityInterface $entity): void', + 'description' => 'Change the form mode of a specific entity type currently being displayed.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Change the form mode for nodes with \'article\' bundle. + if ($entity->bundle() == \'article\') { + $form_mode = \'custom_article_form_mode\'; + } +', + ), + 'hook_entity_form_display_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_form_display_alter', + 'definition' => 'function hook_entity_form_display_alter(\\Drupal\\Core\\Entity\\Display\\EntityFormDisplayInterface $form_display, array $context)', + 'description' => 'Alter the settings used for displaying an entity form.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Hide the \'user_picture\' field from the register form. + if ($context[\'entity_type\'] == \'user\' && $context[\'form_mode\'] == \'register\') { + $form_display->setComponent(\'user_picture\', [ + \'region\' => \'hidden\', + ]); + } +', + ), + 'hook_entity_base_field_info' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_base_field_info', + 'definition' => 'function hook_entity_base_field_info(\\Drupal\\Core\\Entity\\EntityTypeInterface $entity_type)', + 'description' => 'Provides custom base field definitions for a content entity type.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($entity_type->id() == \'node\') { + $fields = []; + $fields[\'my_module_text\'] = \\Drupal\\Core\\Field\\BaseFieldDefinition::create(\'string\') + ->setLabel(t(\'The text\')) + ->setDescription(t(\'A text property added by my_module.\')) + ->setComputed(TRUE) + ->setClass(\'\\Drupal\\my_module\\EntityComputedText\'); + + return $fields; + } +', + ), + 'hook_entity_base_field_info_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_base_field_info_alter', + 'definition' => 'function hook_entity_base_field_info_alter(&$fields, \\Drupal\\Core\\Entity\\EntityTypeInterface $entity_type)', + 'description' => 'Alter base field definitions for a content entity type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Alter the my_module_text field to use a custom class. + if ($entity_type->id() == \'node\' && !empty($fields[\'my_module_text\'])) { + $fields[\'my_module_text\']->setClass(\'\\Drupal\\another_module\\EntityComputedText\'); + } +', + ), + 'hook_entity_bundle_field_info' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_bundle_field_info', + 'definition' => 'function hook_entity_bundle_field_info(\\Drupal\\Core\\Entity\\EntityTypeInterface $entity_type, $bundle, array $base_field_definitions)', + 'description' => 'Provides field definitions for a specific bundle within an entity type.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Add a property only to nodes of the \'article\' bundle. + if ($entity_type->id() == \'node\' && $bundle == \'article\') { + $fields = []; + $storage_definitions = my_module_entity_field_storage_info($entity_type); + $fields[\'my_module_bundle_field\'] = \\Drupal\\Core\\Field\\FieldDefinition::createFromFieldStorageDefinition($storage_definitions[\'my_module_bundle_field\']) + ->setLabel(t(\'Bundle Field\')); + return $fields; + } + +', + ), + 'hook_entity_bundle_field_info_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_bundle_field_info_alter', + 'definition' => 'function hook_entity_bundle_field_info_alter(&$fields, \\Drupal\\Core\\Entity\\EntityTypeInterface $entity_type, $bundle)', + 'description' => 'Alter bundle field definitions.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($entity_type->id() == \'node\' && $bundle == \'article\' && !empty($fields[\'my_module_text\'])) { + // Alter the my_module_text field to use a custom class. + $fields[\'my_module_text\']->setClass(\'\\Drupal\\another_module\\EntityComputedText\'); + } +', + ), + 'hook_entity_field_storage_info' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_field_storage_info', + 'definition' => 'function hook_entity_field_storage_info(\\Drupal\\Core\\Entity\\EntityTypeInterface $entity_type)', + 'description' => 'Provides field storage definitions for a content entity type.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if (\\Drupal::entityTypeManager()->getStorage($entity_type->id()) instanceof \\Drupal\\Core\\Entity\\DynamicallyFieldableEntityStorageInterface) { + // Query by filtering on the ID as this is more efficient than filtering + // on the entity_type property directly. + $ids = \\Drupal::entityQuery(\'field_storage_config\') + ->condition(\'id\', $entity_type->id() . \'.\', \'STARTS_WITH\') + ->execute(); + // Fetch all fields and key them by field name. + $field_storages = FieldStorageConfig::loadMultiple($ids); + $result = []; + foreach ($field_storages as $field_storage) { + $result[$field_storage->getName()] = $field_storage; + } + + return $result; + } +', + ), + 'hook_entity_field_storage_info_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_field_storage_info_alter', + 'definition' => 'function hook_entity_field_storage_info_alter(&$fields, \\Drupal\\Core\\Entity\\EntityTypeInterface $entity_type)', + 'description' => 'Alter field storage definitions for a content entity type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Alter the max_length setting. + if ($entity_type->id() == \'node\' && !empty($fields[\'my_module_text\'])) { + $fields[\'my_module_text\']->setSetting(\'max_length\', 128); + } +', + ), + 'hook_entity_operation' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_operation', + 'definition' => 'function hook_entity_operation(\\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Declares entity operations.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $operations = []; + $operations[\'translate\'] = [ + \'title\' => t(\'Translate\'), + \'url\' => \\Drupal\\Core\\Url::fromRoute(\'foo_module.entity.translate\'), + \'weight\' => 50, + ]; + + return $operations; +', + ), + 'hook_entity_operation_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_operation_alter', + 'definition' => 'function hook_entity_operation_alter(array &$operations, \\Drupal\\Core\\Entity\\EntityInterface $entity)', + 'description' => 'Alter entity operations.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Alter the title and weight. + $operations[\'translate\'][\'title\'] = t(\'Translate @entity_type\', [ + \'@entity_type\' => $entity->getEntityTypeId(), + ]); + $operations[\'translate\'][\'weight\'] = 99; +', + ), + 'hook_entity_field_access' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_field_access', + 'definition' => 'function hook_entity_field_access($operation, \\Drupal\\Core\\Field\\FieldDefinitionInterface $field_definition, \\Drupal\\Core\\Session\\AccountInterface $account, ?\\Drupal\\Core\\Field\\FieldItemListInterface $items = NULL)', + 'description' => 'Control access to fields.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($field_definition->getName() == \'field_of_interest\' && $operation == \'edit\') { + return \\Drupal\\Core\\Access\\AccessResult::allowedIfHasPermission($account, \'update field of interest\'); + } + return \\Drupal\\Core\\Access\\AccessResult::neutral(); +', + ), + 'hook_entity_field_access_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_field_access_alter', + 'definition' => 'function hook_entity_field_access_alter(array &$grants, array $context)', + 'description' => 'Alter the default access behavior for a given field.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + /** @var \\Drupal\\Core\\Field\\FieldDefinitionInterface $field_definition */ + $field_definition = $context[\'field_definition\']; + if ($field_definition->getName() == \'field_of_interest\' && $grants[\'node\']->isForbidden()) { + // Override node module\'s restriction to no opinion (neither allowed nor + // forbidden). We don\'t want to provide our own access hook, we only want to + // take out node module\'s part in the access handling of this field. We also + // don\'t want to switch node module\'s grant to + // AccessResultInterface::isAllowed() , because the grants of other modules + // should still decide on their own if this field is accessible or not + $grants[\'node\'] = \\Drupal\\Core\\Access\\AccessResult::neutral()->inheritCacheability($grants[\'node\']); + } +', + ), + 'hook_entity_field_values_init' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_field_values_init', + 'definition' => 'function hook_entity_field_values_init(\\Drupal\\Core\\Entity\\FieldableEntityInterface $entity)', + 'description' => 'Acts when initializing a fieldable entity object.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($entity instanceof \\Drupal\\Core\\Entity\\\\Drupal\\Core\\Entity\\ContentEntityInterface && !$entity->foo->value) { + $entity->foo->value = \'some_initial_value\'; + } +', + ), + 'hook_ENTITY_TYPE_field_values_init' => + array ( + 'type' => 'hook', + 'name' => 'hook_ENTITY_TYPE_field_values_init', + 'definition' => 'function hook_ENTITY_TYPE_field_values_init(\\Drupal\\Core\\Entity\\FieldableEntityInterface $entity)', + 'description' => 'Acts when initializing a fieldable entity object.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if (!$entity->foo->value) { + $entity->foo->value = \'some_initial_value\'; + } +', + ), + 'hook_entity_extra_field_info' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_extra_field_info', + 'definition' => 'function hook_entity_extra_field_info()', + 'description' => 'Exposes "pseudo-field" components on content entities.', + 'destination' => '%module.module', + 'has_return' => true, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $extra = []; + $module_language_enabled = \\Drupal::moduleHandler()->moduleExists(\'language\'); + $description = t(\'Node module element\'); + + foreach (\\Drupal\\node\\Entity\\NodeType::loadMultiple() as $bundle) { + + // Add also the \'language\' select if Language module is enabled and the + // bundle has multilingual support. + // Visibility of the ordering of the language selector is the same as on the + // node/add form. + if ($module_language_enabled) { + $configuration = \\Drupal\\language\\Entity\\ContentLanguageSettings::loadByEntityTypeBundle(\'node\', $bundle->id()); + if ($configuration->isLanguageAlterable()) { + $extra[\'node\'][$bundle->id()][\'form\'][\'language\'] = [ + \'label\' => t(\'Language\'), + \'description\' => $description, + \'weight\' => 0, + ]; + } + } + $extra[\'node\'][$bundle->id()][\'display\'][\'language\'] = [ + \'label\' => t(\'Language\'), + \'description\' => $description, + \'weight\' => 0, + \'visible\' => FALSE, + ]; + } + + return $extra; +', + ), + 'hook_entity_extra_field_info_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_extra_field_info_alter', + 'definition' => 'function hook_entity_extra_field_info_alter(&$info)', + 'description' => 'Alter "pseudo-field" components on content entities.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + // Force node title to always be at the top of the list by default. + foreach (\\Drupal\\node\\Entity\\NodeType::loadMultiple() as $bundle) { + if (isset($info[\'node\'][$bundle->id()][\'form\'][\'title\'])) { + $info[\'node\'][$bundle->id()][\'form\'][\'title\'][\'weight\'] = -20; + } + } +', + ), + 'hook_entity_query_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_query_alter', + 'definition' => 'function hook_entity_query_alter(\\Drupal\\Core\\Entity\\Query\\QueryInterface $query): void', + 'description' => 'Alter an entity query.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + if ($query->hasTag(\'entity_reference\')) { + $entityType = \\Drupal::entityTypeManager()->getDefinition($query->getEntityTypeId()); + $query->sort($entityType->getKey(\'id\'), \'desc\'); + } +', + ), + 'hook_entity_query_ENTITY_TYPE_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_query_ENTITY_TYPE_alter', + 'definition' => 'function hook_entity_query_ENTITY_TYPE_alter(\\Drupal\\Core\\Entity\\Query\\QueryInterface $query): void', + 'description' => 'Alter an entity query for a specific entity type.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $query->condition(\'id\', \'1\', \'<>\'); +', + ), + 'hook_entity_query_tag__TAG_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_query_tag__TAG_alter', + 'definition' => 'function hook_entity_query_tag__TAG_alter(\\Drupal\\Core\\Entity\\Query\\QueryInterface $query): void', + 'description' => 'Alter an entity query that has a specific tag.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $entityType = \\Drupal::entityTypeManager()->getDefinition($query->getEntityTypeId()); + $query->sort($entityType->getKey(\'id\'), \'desc\'); +', + ), + 'hook_entity_query_tag__ENTITY_TYPE__TAG_alter' => + array ( + 'type' => 'hook', + 'name' => 'hook_entity_query_tag__ENTITY_TYPE__TAG_alter', + 'definition' => 'function hook_entity_query_tag__ENTITY_TYPE__TAG_alter(\\Drupal\\Core\\Entity\\Query\\QueryInterface $query): void', + 'description' => 'Alter an entity query for a specific entity type that has a specific tag.', + 'destination' => '%module.module', + 'has_return' => false, + 'procedural' => false, + 'dependencies' => + array ( + ), + 'group' => 'core:entity', + 'core' => true, + 'original_file_path' => 'core/lib/Drupal/Core/Entity/entity.api.php', + 'file_path' => '/Users/joachim/Sites/dcb-repos-9/repos/drupal-code-builder/Test/sample_hook_definitions/11/CORE_entity.api.php', + 'body' => ' + $query->condition(\'id\', \'1\', \'<>\'); ', ), ), From 9bf34b8cbe30fff6f5b1156c984a4819847a3057 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 9 May 2025 09:56:39 +0100 Subject: [PATCH 027/144] Added assertion for method attribute. --- Test/Unit/Parsing/PHPMethodTester.php | 35 +++++++++++++++++++++++++++ Test/Unit/Parsing/PHPTester.php | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Test/Unit/Parsing/PHPMethodTester.php b/Test/Unit/Parsing/PHPMethodTester.php index 98669781..76286ccb 100644 --- a/Test/Unit/Parsing/PHPMethodTester.php +++ b/Test/Unit/Parsing/PHPMethodTester.php @@ -277,6 +277,41 @@ public function assertReturnType($type, $message = NULL) { Assert::assertEquals($type, $this->methodNode->returnType); } + /** + * Asserts that the method has an attribute of the given class. + * + * @param string $expected_attribute_class + * The full class name of the expected attribute class, WITH the leading '\' + * @param string $message + * (optional) The assertion message. + */ + public function assertHasAttribute(string $expected_attribute_class, $message = NULL) { + assert(str_starts_with($expected_attribute_class, '\\')); + + $message ??= "Attribute $expected_attribute_class not found on the method or function."; + + Assert::assertNotEmpty($this->methodNode->attrGroups, $message); + + // AFAICT, an AttributeGroup only has a single attribute in it, despite the + // class name -- even if there are multiple copies of the same attribute + // class, for instance. + /** @var \PhpParser\Node\AttributeGroup $attribute */ + foreach ($this->methodNode->attrGroups as $attribute) { + if (substr_count($expected_attribute_class, '\\') > 1) { + $full_attribute_class_name = '\\' . $this->fileTester->resolveImportedClassLike($attribute->attrs[0]->name->name); + } + else { + $full_attribute_class_name = '\\' . $attribute->attrs[0]->name->name; + } + + if ($expected_attribute_class === $full_attribute_class_name) { + return; + } + } + + Assert::fail($message); + } + /** * Asserts the function body is not empty. * diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index 4a958a10..ab2eb1d5 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -479,7 +479,7 @@ public function assertInterfaceHasParents($expected_parent_interface_full_names, * @return string * The full name, without the leading '\'. */ - protected function resolveImportedClassLike($name) { + public function resolveImportedClassLike($name) { foreach ($this->parser_nodes['imports'] as $use_node) { if ($use_node->uses[0]->name->getLast() === $name) { return $use_node->uses[0]->name->toString(); From 4a4441a3f93d6823cc8a6ef122cc5e8bd187b1b7 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 9 May 2025 12:29:12 +0100 Subject: [PATCH 028/144] Added testing of method attribute parameters. --- Test/Unit/ComponentHooks11Test.php | 34 +++++------ Test/Unit/ParserPHPMethodTest.php | 81 +++++++++++++++++++++++++++ Test/Unit/Parsing/PHPMethodTester.php | 21 ++++++- 3 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 Test/Unit/ParserPHPMethodTest.php diff --git a/Test/Unit/ComponentHooks11Test.php b/Test/Unit/ComponentHooks11Test.php index 6e2f57f2..c2463f4b 100644 --- a/Test/Unit/ComponentHooks11Test.php +++ b/Test/Unit/ComponentHooks11Test.php @@ -87,16 +87,14 @@ public function testHookClassesOnlyOO(): void { ]); $php_tester->assertHasClass('Drupal\test_module\Hook\AlphaHooks'); - $php_tester->assertHasMethod('formAlter'); - $php_tester->assertHasMethod('formNodeFormAlter'); - $php_tester->assertHasMethod('formUserFormAlter'); - $php_tester->assertHasMethod('nodeView'); - - // TODO: Attribute testing. - $this->assertStringContainsString("#[Hook('form_alter')]", $hooks_file); - $this->assertStringContainsString("#[Hook('form_node_form_alter')]", $hooks_file); - $this->assertStringContainsString("#[Hook('form_user_form_alter')]", $hooks_file); - $this->assertStringContainsString("#[Hook('node_view')]", $hooks_file); + $php_tester->getMethodTester('formAlter') + ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['form_alter']); + $php_tester->getMethodTester('formNodeFormAlter') + ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['form_node_form_alter']); + $php_tester->getMethodTester('formUserFormAlter') + ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['form_user_form_alter']); + $php_tester->getMethodTester('nodeView') + ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['node_view']); } /** @@ -168,10 +166,8 @@ public function testHookClassesWithLegacy(): void { ]); $php_tester->assertHasClass('Drupal\test_module\Hook\AlphaHooks'); - $php_tester->assertHasMethod('formAlter'); - - // TODO: Attribute testing. - $this->assertStringContainsString("#[Hook('form_alter')]", $hooks_file); + $php_tester->getMethodTester('formAlter') + ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['form_alter']); $module_file = $files['test_module.module']; @@ -266,14 +262,12 @@ public function testOOHooks() { ]); $php_tester->assertHasClass('Drupal\test_module\Hook\TestModuleHooks'); - $php_tester->assertHasMethod('formAlter'); - $php_tester->assertHasMethod('blockAccess'); + $php_tester->getMethodTester('formAlter') + ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['form_alter']); + $php_tester->getMethodTester('blockAccess') + ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['block_access']); $php_tester->assertNotHasMethod('install'); - // TODO: Attribute testing. - $this->assertStringContainsString("#[Hook('form_alter')]", $hooks_file); - $this->assertStringContainsString("#[Hook('block_access')]", $hooks_file); - // Check the .install file has a procedural implementation for // hook_install(). $install_file = $files['test_module.install']; diff --git a/Test/Unit/ParserPHPMethodTest.php b/Test/Unit/ParserPHPMethodTest.php new file mode 100644 index 00000000..8ae29f7c --- /dev/null +++ b/Test/Unit/ParserPHPMethodTest.php @@ -0,0 +1,81 @@ +getMethodTester('myMethod'); + + // Specifying no expected parameters omit checking parameters entirely. + $this->assertAssertion(TRUE, $method_tester, 'assertHasAttribute', '\Some\Other\Space\AttributeClass'); + $this->assertAssertion(TRUE, $method_tester, 'assertHasAttribute', '\Some\Other\Space\AttributeClass', ['cake']); + + $this->assertAssertion(FALSE, $method_tester, 'assertHasAttribute', '\Some\Other\Space\AttributeClass', ['wrong']); + $this->assertAssertion(FALSE, $method_tester, 'assertHasAttribute', '\Some\Other\Space\AttributeClass', ['cake', 'too many']); + } + + /** + * Helper for tests that test custom assertions. + * + * @param bool $pass + * Whether the assertion should pass with the given parameters: TRUE if it + * should pass, FALSE if it should fail. + * @param object $php_tester + * The PHP tester, on which to call the assertion method. + * @param string $assertion_name + * The name of the assertion method. It is expected to be on the given + * object. + * @param mixed ...$assertion_parameters + * Remaining parameters are passed to the assertion method. + */ + protected function assertAssertion($pass, $php_tester, $assertion_name, ...$assertion_parameters) { + $message_parameters = print_r($assertion_parameters, TRUE); + + try { + $php_tester->$assertion_name(...$assertion_parameters); + + // We get here if the assertion passed. + if (!$pass) { + $this->fail("The assertion {$assertion_name}() should fail with the following parameters: {$message_parameters}"); + } + } + catch (ExpectationFailedException|AssertionFailedError $e) { + // We get here if the assertion failed. + if ($pass) { + $failure_message = $e->getMessage(); + $this->fail("The assertion {$assertion_name}() should pass with the following parameters:\n{$message_parameters}. Got failure:\n{$failure_message}."); + } + } + + // This is just to stop PHPUnit complaining that the test does not perform + // any assertions. + $this->assertTrue(TRUE); + } + +} diff --git a/Test/Unit/Parsing/PHPMethodTester.php b/Test/Unit/Parsing/PHPMethodTester.php index 76286ccb..d4cfda40 100644 --- a/Test/Unit/Parsing/PHPMethodTester.php +++ b/Test/Unit/Parsing/PHPMethodTester.php @@ -282,10 +282,15 @@ public function assertReturnType($type, $message = NULL) { * * @param string $expected_attribute_class * The full class name of the expected attribute class, WITH the leading '\' + * @param string[] $expected_attribute_parameters + * (optional) An array of the attribute's expected parameters. Only scalar + * parameter values are supported. If omitted, does not assert that there + * are no parameters, but if specified, asserts the size of the array + * matches the number of parameters. * @param string $message * (optional) The assertion message. */ - public function assertHasAttribute(string $expected_attribute_class, $message = NULL) { + public function assertHasAttribute(string $expected_attribute_class, array $expected_attribute_parameters = [], $message = NULL) { assert(str_starts_with($expected_attribute_class, '\\')); $message ??= "Attribute $expected_attribute_class not found on the method or function."; @@ -305,6 +310,20 @@ public function assertHasAttribute(string $expected_attribute_class, $message = } if ($expected_attribute_class === $full_attribute_class_name) { + // Found the attribute. We return out of this condition, so that this + // method can fail if the expected attribute wasn't found among the + // attributes. + if (empty($expected_attribute_parameters)) { + return; + } + + + // Test its parameters. + Assert::assertEquals(count($expected_attribute_parameters), count($attribute->attrs[0]->args)); + foreach ($expected_attribute_parameters as $index => $parameter_value) { + Assert::assertEquals($parameter_value, $attribute->attrs[0]->args[$index]->value->value); + } + return; } } From afea23ededf0bec94b77fef256422e9481f67301 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 9 May 2025 13:02:36 +0100 Subject: [PATCH 029/144] Changed Hooks generator to use HooksClass component instead of generic class component; fixes requesting a hooks class with the same name as the automatic one. Fixes #395. --- Generator/Hooks.php | 46 ++---------------------------- Test/Unit/ComponentHooks11Test.php | 40 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Generator/Hooks.php b/Generator/Hooks.php index 5cfa4215..58c8ea35 100644 --- a/Generator/Hooks.php +++ b/Generator/Hooks.php @@ -151,12 +151,9 @@ protected function addHookComponents(array &$components, array $hook_info): void $this->addProceduralHookComponent($components, $hook_info); } else { + // The HooksClass and HookImplementationClassMethod generators take care + // of adding legacy components if needed. $this->addOoHookComponents($components, $hook_info); - - // If we want legacy procedural hooks too. - if ($this->component_data->hook_implementation_type->value == 'oo_legacy') { - $this->addLegacyProceduralHookComponent($components, $hook_info); - } } } @@ -177,7 +174,7 @@ protected function addOoHookComponents(array &$components, array $hook_info): vo // Make the hooks class. $components['hooks_class'] = [ - 'component_type' => 'PHPClassFile', + 'component_type' => 'HooksClass', 'plain_class_name' => $hooks_class_name, 'relative_namespace' => 'Hook', 'class_docblock_lines' => [ @@ -234,43 +231,6 @@ protected function addProceduralHookComponent(array &$components, array $hook_in ]; } - /** - * Adds the components for a legacy procedural hook. - * - * Helper for addHookComponents(). - * - * @param array &$components - * The array of requested components, passed by reference. - * @param array $hook_info - * The array of hook info. - */ - protected function addLegacyProceduralHookComponent(array &$components, array $hook_info): void { - $hook_name = $hook_info['name']; - $component_name = $hook_name . '_legacy'; - $hooks_class_name = $this->component_data->getItem('module:root_name_pascal')->value . 'Hooks'; - - // Explicitly declare the Hooks class as a service. - // ARGH, can't use the 'Service' generator, as that will want to create a - // class! - $yaml_data = [ - 'services' => [ - // Argh DRY class name! - // TODO: move the class name to being created in this generator. - 'Drupal\%extension\Hook\\' . $hooks_class_name => [ - 'class' => 'Drupal\%extension\Hook\\' . $hooks_class_name, - 'autowire' => TRUE, - ], - ], - ]; - $components['%module.services.yml'] = [ - 'component_type' => 'YMLFile', - // Probably have to use this deprecated token so the component merge - // works? - 'filename' => '%module.services.yml', - 'yaml_data' => $yaml_data, - ]; - } - /** * Gets the component type for the implementation of a hook. * diff --git a/Test/Unit/ComponentHooks11Test.php b/Test/Unit/ComponentHooks11Test.php index c2463f4b..deb23b12 100644 --- a/Test/Unit/ComponentHooks11Test.php +++ b/Test/Unit/ComponentHooks11Test.php @@ -389,6 +389,46 @@ public function testHookImplementationLegacy() { $function_tester->assertHasLine('\Drupal::service(TestModuleHooks::class)->blockViewAlter($build, $block);'); } + /** + * Tests generation of legacy hooks with an explicit hooks class of same name. + */ + public function testHookImplementationLegacyWithSameHooksClass() { + $module_name = 'test_module'; + $module_data = [ + 'base' => 'module', + 'root_name' => $module_name, + 'readable_name' => 'Test Module', + 'short_description' => 'Test Module description', + 'hook_implementation_type' => 'oo_legacy', + 'hooks' => [ + 'hook_block_access', + 'hook_block_view_alter', + ], + 'hook_classes' => [ + 0 => [ + // Hooks class name is the same as the one that will be generated + // automatically. + 'plain_class_name' => 'TestModuleHooks', + 'hook_methods' => [ + 0 => [ + 'hook_name' => 'hook_form_alter', + ], + ], + ], + ], + 'readme' => FALSE, + ]; + + $files = $this->generateModuleFiles($module_data); + + $this->assertFiles([ + 'test_module.info.yml', + 'test_module.module', + 'test_module.services.yml', + 'src/Hook/TestModuleHooks.php', + ], $files); + } + /** * Tests generation of legacy hooks merges with other generated services. */ From cf402ce8360ab99d3608836a530a8568879aaf26 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 10 May 2025 10:10:36 +0100 Subject: [PATCH 030/144] Added class constant for the interface to add to a class with DI. --- Generator/Controller.php | 18 ++++--------- Generator/Form.php | 5 ++++ Generator/PHPClassFileWithInjection.php | 33 +++++++++++++++++++++++ Generator/PluginClassBase.php | 5 ++++ Generator/PluginClassDiscovery.php | 16 +++++------ Generator/PluginType.php | 9 +------ Test/Unit/ComponentDrushCommand11Test.php | 1 + Test/Unit/ComponentService10Test.php | 1 + 8 files changed, 59 insertions(+), 29 deletions(-) diff --git a/Generator/Controller.php b/Generator/Controller.php index 5159a129..ffb886ad 100644 --- a/Generator/Controller.php +++ b/Generator/Controller.php @@ -9,6 +9,11 @@ */ class Controller extends PHPClassFileWithInjection { + /** + * {@inheritdoc} + */ + protected const CLASS_DI_INTERFACE = '\Drupal\Core\DependencyInjection\ContainerInjectionInterface'; + /** * {@inheritdoc} */ @@ -19,17 +24,4 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLiteralDefault(TRUE); } - /** - * Produces the class declaration. - */ - function classDeclaration() { - if (isset($this->containedComponents['injected_service'])) { - // Numeric key will clobber, so make something up! - // TODO: fix! - $this->component_data->interfaces->add(['ContainerInjectionInterface' => '\Drupal\Core\DependencyInjection\ContainerInjectionInterface']); - } - - return parent::classDeclaration(); - } - } diff --git a/Generator/Form.php b/Generator/Form.php index 3f91b95b..cecf238a 100644 --- a/Generator/Form.php +++ b/Generator/Form.php @@ -19,6 +19,11 @@ */ class Form extends PHPClassFileWithInjection implements AdoptableInterface { + /** + * {@inheritdoc} + */ + protected const CLASS_DI_INTERFACE = '\Drupal\Core\DependencyInjection\ContainerInjectionInterface'; + /** * {@inheritdoc} */ diff --git a/Generator/PHPClassFileWithInjection.php b/Generator/PHPClassFileWithInjection.php index f3e1dfd7..284fe958 100644 --- a/Generator/PHPClassFileWithInjection.php +++ b/Generator/PHPClassFileWithInjection.php @@ -11,6 +11,15 @@ */ class PHPClassFileWithInjection extends PHPClassFile { + /** + * The interface to use for the class if it has injected services. + * + * Set to NULL to not add an interface for a class with injected services. + * + * @var string|null + */ + protected const CLASS_DI_INTERFACE = NULL; + /** * The interface to use for the static create() method's container parameter. * @@ -199,6 +208,30 @@ public function requiredComponents(): array { return $components; } + /** + * Produces the class declaration. + */ + function classDeclaration() { + if (static::CLASS_DI_INTERFACE && $this->needsDiInterface()) { + // Numeric key will clobber, so make something up! + // TODO: fix! + $this->component_data->interfaces->add(['ContainerInjectionInterface' => static::CLASS_DI_INTERFACE]); + } + + return parent::classDeclaration(); + } + + /** + * Determines whether the DI interface should be added. + * + * This is not called if static::CLASS_DI_INTERFACE is NULL. + * + * @return bool + */ + protected function needsDiInterface(): bool { + return !$this->component_data->injected_services->isEmpty(); + } + /** * The parameters for the base class. * diff --git a/Generator/PluginClassBase.php b/Generator/PluginClassBase.php index 0d94bf7d..10c215d8 100644 --- a/Generator/PluginClassBase.php +++ b/Generator/PluginClassBase.php @@ -10,6 +10,11 @@ */ class PluginClassBase extends PHPClassFileWithInjection { + /** + * {@inheritdoc} + */ + protected const CLASS_DI_INTERFACE = '\Drupal\Core\Plugin\ContainerFactoryPluginInterface'; + /** * The plugin type data. * diff --git a/Generator/PluginClassDiscovery.php b/Generator/PluginClassDiscovery.php index c1455cfb..7f0f6210 100644 --- a/Generator/PluginClassDiscovery.php +++ b/Generator/PluginClassDiscovery.php @@ -299,7 +299,13 @@ function classDeclaration() { $this->component_data->parent_class_name->value = '\\' . $this->plugin_type_data['base_class']; } - // Set the DI interface if needed. + return parent::classDeclaration(); + } + + /** + * {@inheritdoc} + */ + protected function needsDiInterface(): bool { $use_di_interface = FALSE; // We need the DI interface if this class injects services, unless a parent // class also does so. @@ -323,13 +329,7 @@ function classDeclaration() { } } - if ($use_di_interface) { - // Numeric key will clobber, so make something up! - // TODO: fix! - $this->component_data->interfaces->add(['ContainerFactoryPluginInterface' => '\Drupal\Core\Plugin\ContainerFactoryPluginInterface']); - } - - return parent::classDeclaration(); + return $use_di_interface; } /** diff --git a/Generator/PluginType.php b/Generator/PluginType.php index 32b99ccd..fff87982 100644 --- a/Generator/PluginType.php +++ b/Generator/PluginType.php @@ -420,19 +420,12 @@ public function requiredComponents(): array { ] ]; - $base_class_interfaces = [ - $this->component_data->interface->value, - ]; - if (!$this->component_data->base_class_injected_services->isEmpty()) { - $base_class_interfaces[] = '\Drupal\Core\Plugin\ContainerFactoryPluginInterface'; - } - $components['base_class'] = [ 'component_type' => 'PluginClassBase', 'plain_class_name' => $this->component_data['base_class_short_name'], 'relative_namespace' => 'Plugin\\' . $this->component_data['plugin_relative_namespace'], 'parent_class_name' => '\Drupal\Component\Plugin\PluginBase', - 'interfaces' => $base_class_interfaces, + 'interfaces' => [$this->component_data->interface->value], 'injected_services' => $this->component_data->base_class_injected_services->values(), 'use_static_factory_method' => TRUE, // Abstract for annotation or attribute plugins, where each plugin diff --git a/Test/Unit/ComponentDrushCommand11Test.php b/Test/Unit/ComponentDrushCommand11Test.php index 2b436e71..7652daad 100644 --- a/Test/Unit/ComponentDrushCommand11Test.php +++ b/Test/Unit/ComponentDrushCommand11Test.php @@ -195,6 +195,7 @@ function testCommandGenerationWithServices() { $php_tester->assertDrupalCodingStandards(); $php_tester->assertHasClass('Drupal\test_module\Commands\TestModuleCommands'); $php_tester->assertClassHasParent('Drush\Commands\DrushCommands'); + $php_tester->assertNotClassHasInterfaces(['Drupal\Core\DependencyInjection\ContainerInjectionInterface']); $php_tester->getClassDocBlockTester()->assertHasLine('Test module Drush commands.'); $php_tester->assertHasMethod('alpha'); $php_tester->assertHasMethod('beta'); diff --git a/Test/Unit/ComponentService10Test.php b/Test/Unit/ComponentService10Test.php index 975ddd04..8715ae8c 100644 --- a/Test/Unit/ComponentService10Test.php +++ b/Test/Unit/ComponentService10Test.php @@ -510,6 +510,7 @@ function testServiceGenerationWithServices($injected_services, bool $property_pr $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $service_class_file); $php_tester->assertDrupalCodingStandards(); $php_tester->assertHasClass('Drupal\test_module\MyService'); + $php_tester->assertNotClassHasInterfaces(['Drupal\Core\DependencyInjection\ContainerInjectionInterface']); // Check service injection. $php_tester->assertInjectedServices($assert_injected_services, $property_promotion); From a9f2c1ab649cfbfe59160c7a0baefbb8b80ea804 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 10 May 2025 10:12:30 +0100 Subject: [PATCH 031/144] Changed class property to a constant. --- Generator/DrushCommandsClass.php | 2 +- Generator/PHPClassFileWithInjection.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Generator/DrushCommandsClass.php b/Generator/DrushCommandsClass.php index baaa1935..57612fe2 100644 --- a/Generator/DrushCommandsClass.php +++ b/Generator/DrushCommandsClass.php @@ -12,7 +12,7 @@ class DrushCommandsClass extends PHPClassFileWithInjection { /** * {@inheritdoc} */ - protected string $containerInterface = '\\Psr\\Container\\ContainerInterface'; + protected const CONTAINER_INTERFACE = '\\Psr\\Container\\ContainerInterface'; /** * {@inheritdoc} diff --git a/Generator/PHPClassFileWithInjection.php b/Generator/PHPClassFileWithInjection.php index 284fe958..25936d60 100644 --- a/Generator/PHPClassFileWithInjection.php +++ b/Generator/PHPClassFileWithInjection.php @@ -25,7 +25,7 @@ class PHPClassFileWithInjection extends PHPClassFile { * * @var string */ - protected string $containerInterface = '\\Symfony\\Component\\DependencyInjection\\ContainerInterface'; + protected const CONTAINER_INTERFACE = '\\Symfony\\Component\\DependencyInjection\\ContainerInterface'; /** * Forces the requesting of a constructor method component. @@ -106,7 +106,7 @@ public function requiredComponents(): array { $create_parameters = [ [ 'name' => 'container', - 'typehint' => $this->containerInterface, + 'typehint' => static::CONTAINER_INTERFACE, ], ]; From e3f5d6f0e1876f6b9b2ae4c98eacb72d6f1a2835 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 10 May 2025 13:06:33 +0100 Subject: [PATCH 032/144] Removed mostly duplicated test. --- Test/Unit/ComponentRouteCallback9Test.php | 65 ----------------------- 1 file changed, 65 deletions(-) delete mode 100644 Test/Unit/ComponentRouteCallback9Test.php diff --git a/Test/Unit/ComponentRouteCallback9Test.php b/Test/Unit/ComponentRouteCallback9Test.php deleted file mode 100644 index e47e59c0..00000000 --- a/Test/Unit/ComponentRouteCallback9Test.php +++ /dev/null @@ -1,65 +0,0 @@ - 'module', - 'root_name' => 'test_module', - 'readable_name' => 'Test Module', - 'dynamic_routes' => [ - 0 => [ - 'provider_class_short_name' => 'MyRouteProvider', - ], - 1 => [ - 'provider_class_short_name' => 'OtherRouteProvider', - ], - ], - 'readme' => FALSE, - ]; - - $files = $this->generateModuleFiles($module_data); - - $this->assertFiles([ - 'test_module.info.yml', - 'test_module.routing.yml', - 'src/Routing/MyRouteProvider.php', - 'src/Routing/OtherRouteProvider.php', - ], $files); - - $routing_file = $files['test_module.routing.yml']; - $yaml_tester = new YamlTester($routing_file); - - $yaml_tester->assertHasProperty('route_callbacks', "The routing file has the callbacks property."); - $yaml_tester->assertPropertyHasValue(['route_callbacks', 0], '\Drupal\test_module\Routing\MyRouteProvider::routes', "The routing file declares the route path."); - $yaml_tester->assertPropertyHasValue(['route_callbacks', 1], '\Drupal\test_module\Routing\OtherRouteProvider::routes', "The routing file declares the route path."); - - $provider_file = $files['src/Routing/MyRouteProvider.php']; - - $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $provider_file); - $php_tester->assertDrupalCodingStandards(); - $php_tester->assertHasClass('Drupal\test_module\Routing\MyRouteProvider'); - - $method_tester = $php_tester->getMethodTester('routes'); - $method_tester->getDocBlockTester()->assertHasLine('Returns an array of routes.'); - } - -} From 7d9683cb6aa9129412d8ba3dcf816a807f5225e0 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 10 May 2025 13:22:44 +0100 Subject: [PATCH 033/144] Changed route provider generator to inherit from PHP class generator, so it can have injected services. --- ...eCallback.php => DynamicRouteProvider.php} | 75 +++++++------------ Generator/Module.php | 2 +- ...> ComponentDynamicRouteProvider11Test.php} | 10 +-- 3 files changed, 31 insertions(+), 56 deletions(-) rename Generator/{RouteCallback.php => DynamicRouteProvider.php} (52%) rename Test/Unit/{ComponentRouteCallback10Test.php => ComponentDynamicRouteProvider11Test.php} (87%) diff --git a/Generator/RouteCallback.php b/Generator/DynamicRouteProvider.php similarity index 52% rename from Generator/RouteCallback.php rename to Generator/DynamicRouteProvider.php index c8519bd2..c17248b8 100644 --- a/Generator/RouteCallback.php +++ b/Generator/DynamicRouteProvider.php @@ -10,7 +10,12 @@ /** * Generator a dynamic route provider. */ -class RouteCallback extends BaseGenerator { +class DynamicRouteProvider extends PHPClassFileWithInjection { + + /** + * {@inheritdoc} + */ + protected const CLASS_DI_INTERFACE = '\Drupal\Core\DependencyInjection\ContainerInjectionInterface'; /** * {@inheritdoc} @@ -18,27 +23,19 @@ class RouteCallback extends BaseGenerator { public static function addToGeneratorDefinition(PropertyListInterface $definition) { parent::addToGeneratorDefinition($definition); - $definition->addProperties([ - 'provider_class_short_name' => PropertyDefinition::create('string') - ->setLabel('The short class name of the route provider') - ->setRequired(TRUE) - ->setLiteralDefault('RouteProvider'), - 'provider_qualified_class_name' => PropertyDefinition::create('string') - ->setRequired(TRUE) - ->setInternal(TRUE) - ->setDefault(DefaultDefinition::create() - ->setCallable(function (DataItem $component_data) { - $default = implode('\\', [ - 'Drupal', - $component_data->getParent()->root_component_name->value, - 'Routing', - $component_data->getParent()->provider_class_short_name->value, - ]); - return $default; - }) - ->setDependencies('..:provider_class_short_name') - ), - ]); + $definition->getProperty('relative_namespace') + ->setLiteralDefault('Routing'); + + $definition->getProperty('plain_class_name') + ->setLabel('The short class name of the route provider') + ->setRequired(TRUE) + ->setLiteralDefault('RouteProvider'); + + $definition->getProperty('relative_class_name') + ->setInternal(TRUE); + + $definition->getProperty('use_static_factory_method') + ->setLiteralDefault(TRUE); } /** @@ -51,19 +48,17 @@ public function requiredComponents(): array { // components. $components['%module.routing.yml'] = [ 'component_type' => 'Routing', - ]; - - $components['route_provider'] = [ - 'component_type' => 'PHPClassFile', - 'plain_class_name' => $this->component_data['provider_class_short_name'], - 'relative_namespace' => 'Routing', - 'docblock_first_line' => "Defines dynamic routes.", + 'yaml_data' => [ + 'route_callbacks' => [ + '\\' . $this->component_data->qualified_class_name->value . '::routes', + ], + ], ]; $components["route_provider_method"] = [ 'component_type' => 'PHPFunction', 'function_name' => 'routes', - 'containing_component' => "%requester:route_provider", + 'containing_component' => "%requester", 'prefixes' => ['public'], 'return' => [ 'return_type' => 'array', @@ -93,24 +88,4 @@ public function requiredComponents(): array { return $components; } - /** - * {@inheritdoc} - */ - function containingComponent() { - return '%self:%module.routing.yml'; - } - - /** - * {@inheritdoc} - */ - public function getContents(): array { - $routing_data = [ - 'route_callbacks' => [ - '\\' . $this->component_data['provider_qualified_class_name'] . '::routes', - ], - ]; - - return $routing_data; - } - } diff --git a/Generator/Module.php b/Generator/Module.php index 948780d8..026965a0 100644 --- a/Generator/Module.php +++ b/Generator/Module.php @@ -279,7 +279,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio 'router_items' => MergingGeneratorDefinition::createFromGeneratorType('RouterItem') ->setLabel("Routes") ->setMultiple(TRUE), - 'dynamic_routes' => MergingGeneratorDefinition::createFromGeneratorType('RouteCallback') + 'dynamic_routes' => MergingGeneratorDefinition::createFromGeneratorType('DynamicRouteProvider') ->setLabel('Dynamic route providers') ->setMultiple(TRUE), 'library' => MergingGeneratorDefinition::createFromGeneratorType('Library') diff --git a/Test/Unit/ComponentRouteCallback10Test.php b/Test/Unit/ComponentDynamicRouteProvider11Test.php similarity index 87% rename from Test/Unit/ComponentRouteCallback10Test.php rename to Test/Unit/ComponentDynamicRouteProvider11Test.php index 1b3c4c2a..4cfb970e 100644 --- a/Test/Unit/ComponentRouteCallback10Test.php +++ b/Test/Unit/ComponentDynamicRouteProvider11Test.php @@ -10,27 +10,27 @@ * * @group yaml */ -class ComponentRouteCallback10Test extends TestBase { +class ComponentDynamicRouteProvider11Test extends TestBase { /** * {@inheritdoc} */ - protected $drupalMajorVersion = 9; + protected $drupalMajorVersion = 11; /** * Test generating a module info file. */ - public function testRouteCallback() { + public function testDynamicRouteProvider() { $module_data = [ 'base' => 'module', 'root_name' => 'test_module', 'readable_name' => 'Test Module', 'dynamic_routes' => [ 0 => [ - 'provider_class_short_name' => 'MyRouteProvider', + 'plain_class_name' => 'MyRouteProvider', ], 1 => [ - 'provider_class_short_name' => 'OtherRouteProvider', + 'plain_class_name' => 'OtherRouteProvider', ], ], 'readme' => FALSE, From bbd33346b9b34011bcab99e4bec3b9ed6db37a6f Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 12 May 2025 17:35:43 +0100 Subject: [PATCH 034/144] Fixed lifecycle property missing a description. Fixes #397. --- Generator/RootComponent.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Generator/RootComponent.php b/Generator/RootComponent.php index 86bf794d..f003c474 100644 --- a/Generator/RootComponent.php +++ b/Generator/RootComponent.php @@ -117,6 +117,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setInternal(TRUE), 'lifecycle' => PropertyDefinition::create('string') ->setLabel('Lifecycle') + ->setDescription("Describes the stability of a module. Modules with a lifecycle value set will show with a warning on the module installation form.") ->setOptions( OptionDefinition::create('experimental', 'Experimental', weight: 0), OptionDefinition::create('deprecated', 'Deprecated', weight: 10), From f7f055f58f5d8fe7556a410da901ccd20ab565e4 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 10 May 2025 22:39:57 +0100 Subject: [PATCH 035/144] Added to docs. --- Task/Generate/ComponentClassHandler.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Task/Generate/ComponentClassHandler.php b/Task/Generate/ComponentClassHandler.php index 5811df1e..92114423 100644 --- a/Task/Generate/ComponentClassHandler.php +++ b/Task/Generate/ComponentClassHandler.php @@ -16,7 +16,15 @@ class ComponentClassHandler { * * @param array $generator_classmap * The classmap of version-specific generator classes. Keys are the base - * class name, then the version, value is the short class name. + * class name, then the version, value is the short class name of the + * version-specific class. For example: + * @code + * [ + * 'AdminSettingsForm' => [ + * 7 => AdminSettingsForm7, + * ] + * ] + * @endcode */ public function __construct( #[Inject('generator_classmap')] From 1acb6a89163f4a69f5037184195c38a442081279 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 16 May 2025 09:31:54 +0100 Subject: [PATCH 036/144] Added docs. --- Generator/InjectedService.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Generator/InjectedService.php b/Generator/InjectedService.php index 26d73ee2..fc39b819 100644 --- a/Generator/InjectedService.php +++ b/Generator/InjectedService.php @@ -163,6 +163,8 @@ public function requiredComponents(): array { ]; } + // Functions lines for the 'create' method get put inside the static + // create call: see PHPClassFileWithInjection. $components['create_line'] = [ 'component_type' => 'PHPFunctionBodyLines', 'containing_component' => '%requester:%requester:create', From c7af0c571652cc1c9454a563102de169ffc6b3e2 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 16 May 2025 09:19:58 +0100 Subject: [PATCH 037/144] Added dev requirement for nikic/php-parser 5 for tests. --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 4a070c70..429a737b 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "phpunit/phpunit": "^9", "drupal/coder": "^8.3", "mikey179/vfsstream": "^1.6.11", + "nikic/php-parser": "^5.0", "squizlabs/php_codesniffer": "^3", "symfony/yaml": "^6", "symfony/var-dumper": "^6", From fe2796976ca34c6b2ebe47a45232fa0f64794643 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 16 May 2025 09:32:39 +0100 Subject: [PATCH 038/144] Removed unused variables. --- Generator/InjectedService.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Generator/InjectedService.php b/Generator/InjectedService.php index fc39b819..b94c3341 100644 --- a/Generator/InjectedService.php +++ b/Generator/InjectedService.php @@ -144,23 +144,10 @@ public function requiredComponents(): array { if ($this->component_data->class_has_static_factory->value) { if ($service_info['type'] == 'service') { $container_extraction = "\$container->get('{$service_info['id']}'),"; - - $property_assignment = [ - 'id' => $service_info['id'], - 'property_name' => $service_info['property_name'], - 'variable_name' => $service_info['variable_name'], - ]; } else { // Pseudoservice: needs to be extracted from a real service. $container_extraction = "\$container->get('{$service_info['real_service']}')->{$service_info['service_method']}('{$service_info['variant']}'),"; - - $property_assignment = [ - 'id' => $service_info['id'], - 'property_name' => $service_info['property_name'], - 'variable_name' => $service_info['variable_name'], - 'parameter_extraction' => "{$service_info['real_service_variable_name']}->{$service_info['service_method']}('{$service_info['variant']}')", - ]; } // Functions lines for the 'create' method get put inside the static From 0fd355d799ddf412a7b538bf0a9a520f6f7795fa Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 26 Jul 2025 16:31:01 +0200 Subject: [PATCH 039/144] Added descriptions to entity UI options. Fixes #398. --- Generator/EntityTypeBase.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Generator/EntityTypeBase.php b/Generator/EntityTypeBase.php index 97387495..aa771317 100644 --- a/Generator/EntityTypeBase.php +++ b/Generator/EntityTypeBase.php @@ -81,13 +81,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio // - the menu tasks 'entity_ui' => PropertyDefinition::create('string') ->setLabel('Provide UI') - ->setDescription("Whether this entity has a UI. If selected, this will override the route provider, default form, list builder, and admin permission options if they are left empty.") - ->setOptionsArray([ + ->setDescription("Whether this entity has a UI for listing, creating, editing, and viewing the entities. If selected, this will override the route provider, default form, list builder, and admin permission options if they are left empty.") + ->setOptions( // An empty value means processing won't be called. - '' => 'No UI', - 'default' => 'Default UI', - 'admin' => 'Admin UI', - ]) + new OptionDefinition('', 'No UI'), + new OptionDefinition('default', 'Default UI', 'Uses the site theme for viewing and editing entities.'), + new OptionDefinition('admin', 'Admin UI', 'Uses the admin theme for viewing and editing entities.'), + ) ->setProcessing(function(DataItem $component_data) { $entity_data = $component_data->getParent(); if ($entity_data->handler_route_provider->isEmpty() || From 7171cf7e947a5fe107b27db3d94f2537461decde Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 2 Aug 2025 16:34:38 +0100 Subject: [PATCH 040/144] Added docs. --- Definition/PropertyDefinition.php | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Definition/PropertyDefinition.php b/Definition/PropertyDefinition.php index 60787dd9..a4d2c299 100644 --- a/Definition/PropertyDefinition.php +++ b/Definition/PropertyDefinition.php @@ -10,6 +10,14 @@ /** * Extends the basic property definition with DCB extras. + * + * These include: + * + * - Option providers + * - Variant mapping providers + * - Presets + * - Processing + * - Auto-acquisition */ class PropertyDefinition extends BasePropertyDefinition implements PropertyListInterface, \ArrayAccess { @@ -35,6 +43,9 @@ public function getComponentType(): string { return $this->componentType; } + /** + * {@inheritdoc} + */ public function getDeltaDefinition(): self { $delta_definition = parent::getDeltaDefinition(); @@ -46,6 +57,18 @@ public function getDeltaDefinition(): self { return $delta_definition; } + /** + * Adds an option to this definition's list of options. + * + * This may not be called if this definition uses an options provider. + * + * @param \MutableTypedData\Definition\OptionDefinition $option + * + * @return self + * + * @throws \MutableTypedData\Exception\InvalidDefinitionException + * Throws an exception if this definition uses an options provider. + */ public function addOption(BaseOptionDefinition $option): self { if ($this->optionsProvider) { throw new InvalidDefinitionException("Can't add options if using an options provider."); @@ -54,10 +77,17 @@ public function addOption(BaseOptionDefinition $option): self { return parent::addOption($option); } + /** + * {@inheritdoc} + */ public function hasOptions(): bool { + // Handle options providers. return parent::hasOptions() || !empty($this->optionsProvider); } + /** + * {@inheritdoc} + */ public function getOptions(): array { if (!$this->options && $this->optionsProvider) { $this->options = $this->optionsProvider->getOptions(); @@ -74,15 +104,29 @@ public function removeDefault(): self { return $this; } + /** + * Sets the variant mapping provider. + * + * @param VariantMappingProviderInterface $provider + * The provider object. + * + * @return self + */ public function setVariantMappingProvider(VariantMappingProviderInterface $provider): self { $this->variantMappingProvider = $provider; return $this; } + /** + * {@inheritdoc} + */ public function hasVariantMapping(): bool { return parent::hasVariantMapping() || $this->variantMappingProvider; } + /** + * {@inheritdoc} + */ public function getVariantMapping(): ?array { if (!$this->variantMapping && $this->variantMappingProvider) { $this->variantMapping = $this->variantMappingProvider->getVariantMapping(); From 8f3078ffb1fb682234aef307a343f39467784f7d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 2 Aug 2025 16:51:10 +0100 Subject: [PATCH 041/144] Renamed trait. --- Definition/PropertyDefinition.php | 2 +- .../{PropertyInsertTrait.php => PropertyManipulationTrait.php} | 2 +- Definition/VariantGeneratorDefinition.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename Definition/{PropertyInsertTrait.php => PropertyManipulationTrait.php} (98%) diff --git a/Definition/PropertyDefinition.php b/Definition/PropertyDefinition.php index a4d2c299..1bf4f97d 100644 --- a/Definition/PropertyDefinition.php +++ b/Definition/PropertyDefinition.php @@ -21,7 +21,7 @@ */ class PropertyDefinition extends BasePropertyDefinition implements PropertyListInterface, \ArrayAccess { - use PropertyInsertTrait; + use PropertyManipulationTrait; // TODO: can this be done with defaults instead?? protected $acquiringExpression = FALSE; diff --git a/Definition/PropertyInsertTrait.php b/Definition/PropertyManipulationTrait.php similarity index 98% rename from Definition/PropertyInsertTrait.php rename to Definition/PropertyManipulationTrait.php index 5b245797..20a62b1b 100644 --- a/Definition/PropertyInsertTrait.php +++ b/Definition/PropertyManipulationTrait.php @@ -8,7 +8,7 @@ /** * Provides methods to insert properties. */ -trait PropertyInsertTrait { +trait PropertyManipulationTrait { /** * Adds properties before the named property. diff --git a/Definition/VariantGeneratorDefinition.php b/Definition/VariantGeneratorDefinition.php index a6942473..d5d5d152 100644 --- a/Definition/VariantGeneratorDefinition.php +++ b/Definition/VariantGeneratorDefinition.php @@ -19,7 +19,7 @@ */ class VariantGeneratorDefinition extends VariantDefinition implements PropertyListInterface { - use PropertyInsertTrait; + use PropertyManipulationTrait; /** * Whether properties have been obtained from the generator class yet. From 74013419f829d63aacf6a63f0d1eb5d3006a4e44 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 2 Aug 2025 16:59:18 +0100 Subject: [PATCH 042/144] Added helpers to rearrange properties. --- Definition/PropertyManipulationTrait.php | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Definition/PropertyManipulationTrait.php b/Definition/PropertyManipulationTrait.php index 20a62b1b..9a6c59d9 100644 --- a/Definition/PropertyManipulationTrait.php +++ b/Definition/PropertyManipulationTrait.php @@ -50,6 +50,66 @@ public function addPropertyAfter(string $after, PropertyDefinition ...$propertie return $this; } + /** + * Moves a property to be before another. + * + * @param string $move_property_name + * The name of the property to move. + * @param string $before_property_name + * The name of the property before which the moved property should go. + * + * @throws \InvalidArgumentException + * Throws an exception if either property does not exist. + * + * @return \MutableTypedData\Definition\DataDefinition + * Returns the same instance for chaining. + */ + public function movePropertyBefore(string $move_property_name, string $before_property_name): self { + if (!isset($this->properties[$move_property_name])) { + throw new \InvalidArgumentException("No property '$move_property_name'."); + } + if (!isset($this->properties[$before_property_name])) { + throw new \InvalidArgumentException("No property '$before_property_name'."); + } + // TODO: Check not being put before variant property. + + $moved_property = $this->properties[$move_property_name]; + unset($this->properties[$move_property_name]); + + InsertArray::insertBefore($this->properties, $before_property_name, [$move_property_name => $moved_property]); + + return $this; + } + + /** + * Moves a property to be after another. + * + * @param string $move_property_name + * The name of the property to move. + * @param string $after_property_name + * The name of the property after which the moved property should go. + * + * @throws \InvalidArgumentException + * Throws an exception if either property does not exist. + * + * @return \MutableTypedData\Definition\DataDefinition + * Returns the same instance for chaining. + */ + public function movePropertyAfter(string $move_property_name, string $after_property_name): void { + if (!isset($this->properties[$move_property_name])) { + throw new \InvalidArgumentException("No property '$move_property_name'."); + } + if (!isset($this->properties[$after_property_name])) { + throw new \InvalidArgumentException("No property '$after_property_name'."); + } + // TODO: Check variant property is not being moved. + + $moved_property = $this->properties[$move_property_name]; + unset($this->properties[$move_property_name]); + + InsertArray::insertAfter($this->properties, $after_property_name, [$move_property_name => $moved_property]); + } + /** * Helper for inserting properties. * From c6db685091f79df101f6c3e20727c47467dfad89 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 2 Aug 2025 17:02:29 +0100 Subject: [PATCH 043/144] Fixed ordering of injected services property in form and admin settings form generators. Fixes #400. --- Generator/Form.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Generator/Form.php b/Generator/Form.php index cecf238a..5e6eeb01 100644 --- a/Generator/Form.php +++ b/Generator/Form.php @@ -54,6 +54,9 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio // Put the rest of the parent definitions after ours. $definition->addProperties($properties); + // Move the injected services property lower down. + $definition->movePropertyAfter('injected_services', 'form_route'); + $definition->getProperty('use_static_factory_method') ->setLiteralDefault(TRUE); From acb56c163700ce43b288d190e542d2073e5489de Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 2 Aug 2025 17:07:12 +0100 Subject: [PATCH 044/144] Fixed missing docs. --- Definition/VariantMappingProviderInterface.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Definition/VariantMappingProviderInterface.php b/Definition/VariantMappingProviderInterface.php index 87f50d31..d9d5953c 100644 --- a/Definition/VariantMappingProviderInterface.php +++ b/Definition/VariantMappingProviderInterface.php @@ -7,6 +7,13 @@ */ interface VariantMappingProviderInterface { + /** + * Gets the variant mapping. + * + * @return array + * An array in the same format as + * \MutableTypedData\Definition\DataDefinition::setVariantMapping(). + */ public function getVariantMapping(): array; } From 5da3ab3b0050b0bc8f1e44568f7a8f02ff75af08 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 3 Aug 2025 14:23:12 +0100 Subject: [PATCH 045/144] Fixed analysis crash when entity label is a string. Fixes #403. --- Task/Collect/EntityTypesCollector.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Task/Collect/EntityTypesCollector.php b/Task/Collect/EntityTypesCollector.php index da274208..3f78b9c6 100644 --- a/Task/Collect/EntityTypesCollector.php +++ b/Task/Collect/EntityTypesCollector.php @@ -61,8 +61,13 @@ public function collect($job_list) { $data = []; foreach ($entity_types as $id => $entity_type) { + $label = $entity_type->getLabel(); + if (is_object($label)) { + $label = $label->getUntranslatedString(); + } + $data[$id] = [ - 'label' => $entity_type->getLabel()->getUntranslatedString(), + 'label' => $label, 'group' => $entity_type->getGroup(), ]; From 00f7d582c066789c229654880f64145022622346 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 3 Aug 2025 14:53:25 +0100 Subject: [PATCH 046/144] Added to docs. --- Environment/EnvironmentInterface.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Environment/EnvironmentInterface.php b/Environment/EnvironmentInterface.php index e42b78da..0f28d678 100644 --- a/Environment/EnvironmentInterface.php +++ b/Environment/EnvironmentInterface.php @@ -216,10 +216,13 @@ public function getExtensionPath($type, $name); public function getContainer(); /** - * Gets the Drupal root. + * Gets the Drupal application root. + * + * This is the web root where Drupal is installed, rather than the Composer + * project root. * * @return string - * The root path. + * The absolute root path. */ public function getRoot(); From fe307b8c277a2acf2f9407598762956b3e502799 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 3 Aug 2025 22:29:22 +0100 Subject: [PATCH 047/144] Fixed crash in analysis when event name has no docblock. --- Task/Collect/EventNamesCollector.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Task/Collect/EventNamesCollector.php b/Task/Collect/EventNamesCollector.php index 032772f8..53a2e05d 100644 --- a/Task/Collect/EventNamesCollector.php +++ b/Task/Collect/EventNamesCollector.php @@ -53,6 +53,8 @@ public function collect($job_list) { // We can instantiate this just the once, because the keys of the $events // array have the fully-qualified class names. $visitor = new class extends NodeVisitorAbstract { + // Array keyed by qualified event constants, whose values are the docblock + // text or NULL if the event constant is missing documentation. public $events = []; public function enterNode(Node $node) { @@ -60,7 +62,7 @@ public function enterNode(Node $node) { $class_name = '\\' . $node->namespacedName->toString(); foreach ($node->getConstants() as $constant_node) { - $this->events[$class_name . '::' . $constant_node->consts[0]->name->name] = $constant_node->getDocComment()->getReformattedText(); + $this->events[$class_name . '::' . $constant_node->consts[0]->name->name] = $constant_node->getDocComment()?->getReformattedText(); } return NodeTraverser::STOP_TRAVERSAL; @@ -90,7 +92,14 @@ public function enterNode(Node $node) { } // Use just the first line of the docblock. - array_walk($visitor->events, fn (&$docblock) => $docblock = $this->getDocblockFirstLine($docblock)); + array_walk( + $visitor->events, + fn (&$docblock, $name) => $docblock = $docblock + // If there's a docblock, take the first line. + ? $this->getDocblockFirstLine($docblock) + // If there isn't, take the constant name. + : explode('::', $name)[1] + ); return $visitor->events; } From bd5ba16975be5ef1ca06059a4eb1484f5dd1aa1b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 4 Aug 2025 23:38:31 +0100 Subject: [PATCH 048/144] Fixed typo in docs. --- Generator/DrushCommandsClass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/DrushCommandsClass.php b/Generator/DrushCommandsClass.php index 57612fe2..233bec4b 100644 --- a/Generator/DrushCommandsClass.php +++ b/Generator/DrushCommandsClass.php @@ -5,7 +5,7 @@ use MutableTypedData\Definition\PropertyListInterface; /** - * Generator for a class holding Drus commands. + * Generator for a class holding Drush commands. */ class DrushCommandsClass extends PHPClassFileWithInjection { From f383695a0fc6040c44dfbb8bfc8e99946c13eeff Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 4 Aug 2025 23:38:53 +0100 Subject: [PATCH 049/144] Fixed namespace of Drush command class for Drush 13. Fixes #357. --- Generator/DrushCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/DrushCommand.php b/Generator/DrushCommand.php index 40a796d6..6639ee44 100644 --- a/Generator/DrushCommand.php +++ b/Generator/DrushCommand.php @@ -168,7 +168,7 @@ public function requiredComponents(): array { // Makes this get matched up with the data definition. 'use_data_definition' => TRUE, 'plain_class_name' => CaseString::snake($this->component_data->root_component_name->value)->pascal() . 'Commands', - 'relative_namespace' => 'Commands', + 'relative_namespace' => 'Drush\Commands', 'parent_class_name' => '\Drush\Commands\DrushCommands', 'injected_services' => $this->component_data['injected_services'], 'docblock_first_line' => "%sentence Drush commands.", From a8ae389de706688743144bb0953eff88884f7b77 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 16:12:06 +0100 Subject: [PATCH 050/144] Fixed error in docs. --- Task/ReportPluginData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Task/ReportPluginData.php b/Task/ReportPluginData.php index 13c20cac..d7b711e3 100644 --- a/Task/ReportPluginData.php +++ b/Task/ReportPluginData.php @@ -13,7 +13,7 @@ use DrupalCodeBuilder\Task\Report\SectionReportInterface; /** - * Task handler for reporting on hook data. + * Task handler for reporting on plugin data. * * TODO: revisit some of these and clean up names / clean up how many we have. * Consider merging into a ReportComponentData Task. From 80029095c37a71808f72af30eb38a9e4e37d2789 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 16:12:39 +0100 Subject: [PATCH 051/144] Added to docs. --- Generator/AdoptableInterface.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Generator/AdoptableInterface.php b/Generator/AdoptableInterface.php index 10c5cbc9..76ca7522 100644 --- a/Generator/AdoptableInterface.php +++ b/Generator/AdoptableInterface.php @@ -27,10 +27,11 @@ interface AdoptableInterface { public static function findAdoptableComponents(DrupalExtension $extension): array; /** - * Adopt a component from an existing extension. + * Adopts a component from an existing extension. * * @param \MutableTypedData\Data\DataItem $component_data - * The existing root component data. + * The existing root component data. Values should be added to this for the + * component being adopted. * @param \DrupalCodeBuilder\File\DrupalExtension $extension * The existing extension to analyse. * @param string $property_name From d842a7718a9099250e8ea5bfdaa2466cccdd98fd Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 16:18:41 +0100 Subject: [PATCH 052/144] Added helper for loading a class from a Drupal extension. --- File/DrupalExtension.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/File/DrupalExtension.php b/File/DrupalExtension.php index b06d7d68..49d83c09 100644 --- a/File/DrupalExtension.php +++ b/File/DrupalExtension.php @@ -251,4 +251,36 @@ protected function getFileContents($relative_file_path) { return file_get_contents($this->getRealPath($relative_file_path)); } + /** + * Loads the file for a class from this extension. + * + * For extensions which are not currently enabled, Drupal's autoloader will + * not be able to find the class. This will load the class even if the + * extension is not enabled. + * + * @param string $class_name + * The fully-qualified class name. + * + * @internal + */ + public function loadClass(string $class_name): void { + // Trim the class name up to the extension name piece. + $relative_class_name = preg_replace("@Drupal\\\\{$this->name}\\\\@", '', $class_name); + $relative_path = 'src/' . str_replace('\\', '/', $relative_class_name) . '.php'; + + $this->includeFile($relative_path); + } + + /** + * Includes a file from this extension. + * + * @param string $relative_file_path + * The filepath relative to the extension folder. + * + * @internal + */ + public function includeFile(string $relative_file_path): void { + include_once $this->getRealPath($relative_file_path); + } + } From ef4022cccfd496e17d8f63f1f0c365eac5c3a781 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 19:56:25 +0100 Subject: [PATCH 053/144] Added inactive properties for plugin type annotation properties. --- Generator/PluginType.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Generator/PluginType.php b/Generator/PluginType.php index fff87982..0d81c632 100644 --- a/Generator/PluginType.php +++ b/Generator/PluginType.php @@ -91,6 +91,24 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setDependencies('..:plugin_type') ) ->setValidators('machine_name'), + // Stupidly named so they switch between both class-based discovery + // variants. + 'attribute_properties' => PropertyDefinition::create('complex') + ->setLabel("Annotation properties") + ->setDescription("Properties for the plugin type's annotation class. These do not produce generated code, but are here to facilitate the conversion to attribute plugin types.") + ->setMultiple(TRUE) + ->setProperties([ + 'name' => PropertyDefinition::create('string') + ->setLabel('Parameter name') + ->setRequired(TRUE), + 'type' => PropertyDefinition::create('string') + ->setLabel('Parameter type') + ->setRequired(TRUE) + ->setLiteralDefault('string'), + 'description' => PropertyDefinition::create('string') + ->setLabel('Parameter description') + ->setLiteralDefault('TODO: parameter description.'), + ]), ]), 'attribute' => VariantDefinition::create() ->setLabel('Attribute plugin') From 93c5d4646f73e86ce9f11efc7f4c7576859426c4 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 19:57:29 +0100 Subject: [PATCH 054/144] Added adoption of annotation-based plugin types. --- Generator/PluginType.php | 126 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/Generator/PluginType.php b/Generator/PluginType.php index 0d81c632..e4855e05 100644 --- a/Generator/PluginType.php +++ b/Generator/PluginType.php @@ -8,12 +8,14 @@ use DrupalCodeBuilder\Definition\OptionDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Definition\MergingGeneratorDefinition; +use DrupalCodeBuilder\File\DrupalExtension; +use MutableTypedData\Data\DataItem; use MutableTypedData\Definition\VariantDefinition; /** * Generator for a plugin type. */ -class PluginType extends BaseGenerator { +class PluginType extends BaseGenerator implements AdoptableInterface { use NameFormattingTrait; @@ -291,6 +293,128 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio } } + /** + * {@inheritdoc} + */ + public static function findAdoptableComponents(DrupalExtension $extension): array { + $services_filename = $extension->name . '.services.yml'; + if (!$extension->hasFile($services_filename)) { + return []; + } + + $yaml = $extension->getFileYaml($services_filename); + $service_names = array_keys($yaml['services']); + $plugin_manager_names = array_filter($service_names, fn ($name) => str_starts_with($name, 'plugin.manager.')); + $plugin_manager_names = array_map(fn ($name) => str_replace('plugin.manager.', '', $name), $plugin_manager_names); + return array_combine($plugin_manager_names, $plugin_manager_names); + } + + /** + * {@inheritdoc} + */ + public static function adoptComponent(DataItem $component_data, DrupalExtension $extension, string $property_name, string $name): void { + $services_filename = $extension->name . '.services.yml'; + $yaml = $extension->getFileYaml($services_filename); + $service_yaml = $yaml['services']['plugin.manager.' . $name]; + $manager_class = $service_yaml['class']; + + // The module might not be enabled, so we can't rely on Drupal's autoloader + // to find the class. + $extension->loadClass($manager_class); + + try { + $constructor_reflection = new \DrupalCodeBuilder\Utility\CodeAnalysis\Method($manager_class, '__construct'); + } + catch (\ReflectionException $e) { + // TODO: complain. + return; + } + + // Create the data for the plugin type. + $plugin_type_data_array = [ + 'plugin_type' => $name, + ]; + + $constructor_body = $constructor_reflection->getBody(); + + // Check for Attribute first, because of BC-compatible hybrids. + if (str_contains($constructor_body, "Drupal\\{$extension->name}\\Attribute")) { + // TODO + $plugin_type_data_array['discovery_type'] = 'attribute'; + + $matches = []; + preg_match("@Drupal\\\\{$extension->name}\\\\Attribute\\\\(\w+)@", $constructor_body, $matches); + } + elseif (str_contains($constructor_body, "Drupal\\{$extension->name}\\Annotation")) { + $plugin_type_data_array['discovery_type'] = 'annotation'; + + $matches = []; + preg_match("@Plugin/([\w/]+)@", $constructor_body, $matches); + $plugin_type_data_array['plugin_subdirectory'] = $matches['1'] ?? ''; + + $matches = []; + preg_match("@Drupal\\\\{$extension->name}\\\\Annotation\\\\([[\w\\]]+)@", $constructor_body, $matches); + $annotation_class = $matches[0]; + $plugin_type_data_array['annotation_class'] = $matches['1'] ?? ''; + + $extension->loadClass($annotation_class); + $annotation_class_reflection = new \ReflectionClass($annotation_class); + + // Get the properties from the annotation. + $annotation_property_relections = $annotation_class_reflection->getProperties(); + foreach ($annotation_property_relections as $property_reflection) { + $name = $property_reflection->getName(); + + // Skip the properties which are added automatically. + if (in_array($name, ['id', 'label', 'description'])) { + continue; + } + + $property_data = [ + 'name' => $name, + ]; + + // Get the type of the property, defaulting to 'string' if we can't. + if ($property_reflection->hasType()) { + $property_data['type'] = (string) $property_reflection->getType(); + } + elseif ($docblock = $property_reflection->getDocComment()) { + $matches = []; + preg_match('/@var (\S+)$/m', $docblock, $matches); + if (!isset($matches[1])) { + $property_data['type'] = 'string'; + continue; + } + + $type = $matches[1]; + + if (str_ends_with($type, '[]')) { + $type = 'array'; + } + + $property_data['type'] = $type; + } + else { + // No sodding docs! + $property_data['type'] = 'string'; + } + $plugin_type_data_array['attribute_properties'][] = $property_data; + } + } + else { + // TODO! + $plugin_type_data_array['discovery_type'] = 'yaml'; + } + + // TODO: Find an existing component and merge into it -- see Service + // generator for example. + + // Bit of a WTF: this requires this generator class to know it's being used + // as a multi-valued item in the Module generator. + $item_data = $component_data->getItem($property_name)->createItem(); + $item_data->set($plugin_type_data_array); + } + /** * {@inheritdoc} */ From 4aeb59ea501f5ef20ea6480a2c57bea84fd71333 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 19:57:59 +0100 Subject: [PATCH 055/144] Changed properties to readonly. --- File/DrupalExtension.php | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/File/DrupalExtension.php b/File/DrupalExtension.php index 49d83c09..fd2d32f1 100644 --- a/File/DrupalExtension.php +++ b/File/DrupalExtension.php @@ -18,28 +18,18 @@ class DrupalExtension { /** * The extension type, e.g. 'module'. - * - * TODO Make readonly in PHP 8.1. - * - * @var string */ - public $type; + public readonly string $type; /** * The extension name. - * - * TODO Make readonly in PHP 8.1. - * - * @var string */ - public $name; + public readonly string $name; /** * The given extension path. - * - * @var string */ - protected $path; + protected readonly string $path; /** * Constructs a new extension. From 634be8893a03b9b845b12bdcf3f17365b1532b40 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 20:00:22 +0100 Subject: [PATCH 056/144] Fixed tests. Follow-up to #357. --- Test/Unit/ComponentDrushCommand11Test.php | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Test/Unit/ComponentDrushCommand11Test.php b/Test/Unit/ComponentDrushCommand11Test.php index 7652daad..2f8ec428 100644 --- a/Test/Unit/ComponentDrushCommand11Test.php +++ b/Test/Unit/ComponentDrushCommand11Test.php @@ -46,14 +46,14 @@ public function testBasicCommandGeneration() { $this->assertFiles([ 'test_module.info.yml', - 'src/Commands/TestModuleCommands.php', + 'src/Drush/Commands/TestModuleCommands.php', ], $files); - $command_class_file = $files["src/Commands/TestModuleCommands.php"]; + $command_class_file = $files["src/Drush/Commands/TestModuleCommands.php"]; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $command_class_file); $php_tester->assertDrupalCodingStandards(); - $php_tester->assertHasClass('Drupal\test_module\Commands\TestModuleCommands'); + $php_tester->assertHasClass('Drupal\test_module\Drush\Commands\TestModuleCommands'); $php_tester->assertClassHasParent('Drush\Commands\DrushCommands'); $php_tester->getClassDocBlockTester()->assertHasLine('Test module Drush commands.'); $php_tester->assertHasMethod('alpha'); @@ -111,10 +111,10 @@ function testCommandGenerationWithParameters() { $this->assertFiles([ 'test_module.info.yml', - 'src/Commands/TestModuleCommands.php', + 'src/Drush/Commands/TestModuleCommands.php', ], $files); - $command_class_file = $files["src/Commands/TestModuleCommands.php"]; + $command_class_file = $files["src/Drush/Commands/TestModuleCommands.php"]; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $command_class_file); $php_tester->assertDrupalCodingStandards([ @@ -122,7 +122,7 @@ function testCommandGenerationWithParameters() { // See https://www.drupal.org/project/coder/issues/3475912. 'Drupal.Arrays.Array.LongLineDeclaration', ]); - $php_tester->assertHasClass('Drupal\test_module\Commands\TestModuleCommands'); + $php_tester->assertHasClass('Drupal\test_module\Drush\Commands\TestModuleCommands'); $php_tester->assertClassHasParent('Drush\Commands\DrushCommands'); $php_tester->getClassDocBlockTester()->assertHasLine('Test module Drush commands.'); $php_tester->assertHasMethod('alpha'); @@ -186,14 +186,14 @@ function testCommandGenerationWithServices() { $this->assertFiles([ 'test_module.info.yml', - 'src/Commands/TestModuleCommands.php', + 'src/Drush/Commands/TestModuleCommands.php', ], $files); - $command_class_file = $files["src/Commands/TestModuleCommands.php"]; + $command_class_file = $files["src/Drush/Commands/TestModuleCommands.php"]; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $command_class_file); $php_tester->assertDrupalCodingStandards(); - $php_tester->assertHasClass('Drupal\test_module\Commands\TestModuleCommands'); + $php_tester->assertHasClass('Drupal\test_module\Drush\Commands\TestModuleCommands'); $php_tester->assertClassHasParent('Drush\Commands\DrushCommands'); $php_tester->assertNotClassHasInterfaces(['Drupal\Core\DependencyInjection\ContainerInjectionInterface']); $php_tester->getClassDocBlockTester()->assertHasLine('Test module Drush commands.'); @@ -255,14 +255,14 @@ public function testCommandGenerationWithInflection() { $this->assertFiles([ 'test_module.info.yml', - 'src/Commands/TestModuleCommands.php', + 'src/Drush/Commands/TestModuleCommands.php', ], $files); - $command_class_file = $files["src/Commands/TestModuleCommands.php"]; + $command_class_file = $files["src/Drush/Commands/TestModuleCommands.php"]; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $command_class_file); $php_tester->assertDrupalCodingStandards(); - $php_tester->assertHasClass('Drupal\test_module\Commands\TestModuleCommands'); + $php_tester->assertHasClass('Drupal\test_module\Drush\Commands\TestModuleCommands'); $php_tester->assertClassHasParent('Drush\Commands\DrushCommands'); $php_tester->getClassDocBlockTester()->assertHasLine('Test module Drush commands.'); $php_tester->assertHasMethod('alpha'); From dfa9d8b41e38435d294028fee3f10635a4dbdf4a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 22 Aug 2025 22:12:28 +0100 Subject: [PATCH 057/144] =?UTF-8?q?Fixed=20surplus=20=E2=80=98definition?= =?UTF-8?q?=E2=80=99=20property=20with=20adoption=20of=20annotation=20plug?= =?UTF-8?q?in=20type.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Generator/PluginType.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Generator/PluginType.php b/Generator/PluginType.php index e4855e05..1313c9c2 100644 --- a/Generator/PluginType.php +++ b/Generator/PluginType.php @@ -370,6 +370,11 @@ public static function adoptComponent(DataItem $component_data, DrupalExtension continue; } + // Skip the 'definition' property from the parent class. + if ($name == 'definition') { + continue; + } + $property_data = [ 'name' => $name, ]; From 075a136c614c9c9b593e8e2dcc90981fa253a4ae Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 09:57:14 +0100 Subject: [PATCH 058/144] Fixed plugin type managers appearing in list of adoptable services. --- Generator/Service.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Generator/Service.php b/Generator/Service.php index c4576af7..89127868 100644 --- a/Generator/Service.php +++ b/Generator/Service.php @@ -197,6 +197,11 @@ public static function findAdoptableComponents(DrupalExtension $extension): arra $yaml = $extension->getFileYaml($services_filename); $service_names = array_keys($yaml['services']); + + // Filter out plugin managers, as these are adopted as part of a plugin + // type. + $service_names = array_filter($service_names, fn ($name) => !str_starts_with($name, 'plugin.manager.')); + return array_combine($service_names, $service_names); } From 61f1e9a5bf77afb1489e1b10ba942658461a0e1b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 10:02:52 +0100 Subject: [PATCH 059/144] Fixed service generator subclasses which are used as properties returning service as adoptable components. --- Generator/HooksClass.php | 10 ++++++++++ Generator/ServiceEventSubscriber.php | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/Generator/HooksClass.php b/Generator/HooksClass.php index b119492f..bccec363 100644 --- a/Generator/HooksClass.php +++ b/Generator/HooksClass.php @@ -4,6 +4,7 @@ use DrupalCodeBuilder\Definition\MergingGeneratorDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; +use DrupalCodeBuilder\File\DrupalExtension; use MutableTypedData\Data\DataItem; use MutableTypedData\Definition\DefaultDefinition; use MutableTypedData\Definition\PropertyListInterface; @@ -61,6 +62,15 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ); } + /** + * {@inheritdoc} + */ + public static function findAdoptableComponents(DrupalExtension $extension): array { + // For now we don't adopt hook classes, so override this method so we don't + // return the same as the parent class. + return []; + } + /** * {@inheritdoc} */ diff --git a/Generator/ServiceEventSubscriber.php b/Generator/ServiceEventSubscriber.php index fb27daec..f3dc7b57 100644 --- a/Generator/ServiceEventSubscriber.php +++ b/Generator/ServiceEventSubscriber.php @@ -4,6 +4,7 @@ use CaseConverter\CaseString; use DrupalCodeBuilder\Definition\PropertyDefinition; +use DrupalCodeBuilder\File\DrupalExtension; use MutableTypedData\Definition\PropertyListInterface; use MutableTypedData\Definition\OptionsSortOrder; @@ -55,6 +56,15 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLiteralDefault('EventSubscriber'); } + /** + * {@inheritdoc} + */ + public static function findAdoptableComponents(DrupalExtension $extension): array { + // For now we don't adopt event subscribers, so override this method so we + // don't return the same as the parent class. + return []; + } + /** * {@inheritdoc} */ From 87bb0ba123361d44cf0e63612412bbca207dab1a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 10:09:24 +0100 Subject: [PATCH 060/144] Updated comment. --- Generator/PluginTypeManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/PluginTypeManager.php b/Generator/PluginTypeManager.php index a4be268c..eefec7c0 100644 --- a/Generator/PluginTypeManager.php +++ b/Generator/PluginTypeManager.php @@ -108,7 +108,7 @@ public function requiredComponents(): array { $components['service_cache.discovery']['omit_assignment'] = TRUE; } - // Only annotation type plugins call the parent constructor. + // Only class-based discovery plugins call the parent constructor. $code = []; if ($this->component_data->discovery_type->value == 'annotation') { $code[] = 'parent::__construct('; From cc28a501b3f10c4edfe0a160004a5ef00e3f1a8b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 10:29:15 +0100 Subject: [PATCH 061/144] Fixed non-default annotation or attribute class name not used in plugin type manager. Fixes #406. --- Generator/PluginType.php | 5 ++++ Generator/PluginTypeManager.php | 15 ++++++----- Test/Unit/ComponentPluginType10Test.php | 33 +++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Generator/PluginType.php b/Generator/PluginType.php index 1313c9c2..111d0b63 100644 --- a/Generator/PluginType.php +++ b/Generator/PluginType.php @@ -449,6 +449,11 @@ public function requiredComponents(): array { // TODO: a service should be able to detect the parent class name from // service definitions.... if we had all of them. $components['manager']['parent_class_name'] = '\Drupal\Core\Plugin\DefaultPluginManager'; + + $components['manager']['metadata_class'] = match ($this->component_data->discovery_type->value) { + 'attribute' => $this->component_data->attribute_class->value, + 'annotation' => $this->component_data->annotation_class->value, + }; } else { // YAML plugin managers need some services injecting. diff --git a/Generator/PluginTypeManager.php b/Generator/PluginTypeManager.php index eefec7c0..0be4a2f0 100644 --- a/Generator/PluginTypeManager.php +++ b/Generator/PluginTypeManager.php @@ -41,6 +41,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setAutoAcquiredFromRequester() ); } + + // The short class name of the attribute or annotation. + // Can't acquire this, as the property in the requester is on the variant. + $definition->addProperty( + PropertyDefinition::create('string') + ->setName('metadata_class') + ); } /** @@ -120,9 +127,7 @@ public function requiredComponents(): array { 'Drupal', $this->component_data['root_component_name'], 'Annotation', - // We can't acquire the annotation class name, as it's a mutable - // property and so not always present. Use this instead. - $this->component_data['plugin_plain_class_name'], + $this->component_data['metadata_class'], ]) . '::class'; $code[] = ');'; $code[] = ''; @@ -137,9 +142,7 @@ public function requiredComponents(): array { 'Drupal', $this->component_data['root_component_name'], 'Attribute', - // We can't acquire the attribute class name, as it's a mutable - // property and so not always present. Use this instead. - $this->component_data['plugin_plain_class_name'], + $this->component_data['metadata_class'], ]) . '::class'; // Don't bother setting an annotation for BC, since we're generating a new // plugin type. diff --git a/Test/Unit/ComponentPluginType10Test.php b/Test/Unit/ComponentPluginType10Test.php index f0a44805..7b929c4c 100644 --- a/Test/Unit/ComponentPluginType10Test.php +++ b/Test/Unit/ComponentPluginType10Test.php @@ -156,6 +156,39 @@ function testAttributePluginTypeBasic() { 'module_handler' => 'Drupal\Core\Extension\ModuleHandlerInterface', ]); $constructor_tester->assertPromotedParameters([], 'No plugin manager parameters are promoted.'); + + // Test with values instead of defaults. + $module_data = [ + 'base' => 'module', + 'root_name' => 'test_module', + 'readable_name' => 'Test module', + 'short_description' => 'Test Module description', + 'hooks' => [ + ], + 'plugin_types' => [ + 0 => [ + 'discovery_type' => 'attribute', + 'plugin_type' => 'cat_feeder', + 'attribute_class' => 'Caaaaat', + 'attribute_properties' => [ + 0 => [ + 'name' => 'fishiness', + 'type' => 'int', + 'description' => 'How fishy is it?', + ], + ], + ] + ], + 'readme' => FALSE, + ]; + $files = $this->generateModuleFiles($module_data); + + $this->assertArrayHasKey('src/Attribute/Caaaaat.php', $files); + + $plugin_manager_file = $files["src/CatFeederManager.php"]; + // We know the syntax is fine from the earlier assertion, so just check the + // right class is there. + $this->assertStringContainsString('Caaaaat::class', $plugin_manager_file); } /** From 5f0b69d6945b54045feacfeb6ab9d76ecae7ac9d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 10:42:27 +0100 Subject: [PATCH 062/144] Renamed variable. --- Generator/PHPFile.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Generator/PHPFile.php b/Generator/PHPFile.php index 8710b515..f10a73f6 100644 --- a/Generator/PHPFile.php +++ b/Generator/PHPFile.php @@ -162,12 +162,12 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes * leading slash is immaterial. Duplicates are removed. */ function imports($imported_classes = []) { - $imports = []; + $import_lines = []; if ($imported_classes) { foreach ($imported_classes as $fully_qualified_class_name) { $fully_qualified_class_name = ltrim($fully_qualified_class_name, '\\'); - $imports[] = "use $fully_qualified_class_name;"; + $import_lines[] = "use $fully_qualified_class_name;"; } // Bit of a hack. We have to perform token replacement before sorting the @@ -175,7 +175,7 @@ function imports($imported_classes = []) { // replacement is done later, during file assembly. Fortunately, in // class names we can be certain that only the %extension and %Pascal // tokens are used, so hackily replace those now. - $imports = str_replace( + $import_lines = str_replace( [ '%extension', '%Pascal' @@ -184,19 +184,19 @@ function imports($imported_classes = []) { $this->component_data->root_component_name->value, CaseString::snake($this->component_data->root_component_name->value)->pascal(), ], - $imports, + $import_lines, ); // Sort the imported classes. - natcasesort($imports); + natcasesort($import_lines); // Remove duplicates. - $imports = array_unique($imports); + $import_lines = array_unique($import_lines); - $imports[] = ''; + $import_lines[] = ''; } - return $imports; + return $import_lines; } /** From 1234db39de8c03e5a94312bd8327601b0202e90a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 11:08:47 +0100 Subject: [PATCH 063/144] Added comment to plugin type manager about annotation class for BC. --- Generator/PluginTypeManager.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Generator/PluginTypeManager.php b/Generator/PluginTypeManager.php index 0be4a2f0..aa7a8a5d 100644 --- a/Generator/PluginTypeManager.php +++ b/Generator/PluginTypeManager.php @@ -143,9 +143,15 @@ public function requiredComponents(): array { $this->component_data['root_component_name'], 'Attribute', $this->component_data['metadata_class'], - ]) . '::class'; - // Don't bother setting an annotation for BC, since we're generating a new - // plugin type. + ]) . '::class,'; + // Add a comment about the annotation class for BC, for the case where + // this is an adopted plugin type that's being generated rather than a new + // one. + // Actually adding the annotation class is too complicated because of the + // class name clash - see + // https://github.com/drupal-code-builder/drupal-code-builder/issues/388. + $code[] = ' // @todo: Add the annotation class as a parameter here if this plugin'; + $code[] = ' // type supports annotations for BC.'; $code[] = ');'; $code[] = ''; } From 9a6fe914bb3b78eb481ca0cdd6eab7ff31baeb49 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 13:09:58 +0100 Subject: [PATCH 064/144] Fixed handling of multiple lines in class extraction test. --- Test/Unit/ComponentPHPFile10Test.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Test/Unit/ComponentPHPFile10Test.php b/Test/Unit/ComponentPHPFile10Test.php index 29aca965..16723698 100644 --- a/Test/Unit/ComponentPHPFile10Test.php +++ b/Test/Unit/ComponentPHPFile10Test.php @@ -46,8 +46,7 @@ public function testQualifiedClassNameExtraction($code, $expected_changed_code, $data_item = $this->prophesize(\MutableTypedData\Data\DataItem::class); $php_file_generator = new PHPFile($data_item->reveal()); - // Our code is a single line, but the method expects an array of lines. - $code_lines = [$code]; + $code_lines = explode("\n", $code); $imported_classes = []; @@ -62,8 +61,7 @@ public function testQualifiedClassNameExtraction($code, $expected_changed_code, } $this->assertEquals($expected_qualified_class_names, $imported_classes, "The qualified class name was extracted."); - $changed_code = array_pop($code_lines); - $this->assertEquals($expected_changed_code, $changed_code, "The code was changed to use the short class name."); + $this->assertEquals(explode("\n", $expected_changed_code), $code_lines, "The code was changed to use the short class name."); } } @@ -115,10 +113,14 @@ public static function providerQualifiedClassNameExtraction() { 'Foo\Bar', ], 'repeated' => [ - '$foo = new \Foo\Bar(); - $bar = new \Foo\Bar();', - '$foo = new Bar(); - $bar = new Bar();', + <<<'EOT' + $foo = new \Foo\Bar(); + $bar = new \Foo\Bar(); + EOT, + <<<'EOT' + $foo = new Bar(); + $bar = new Bar(); + EOT, 'Foo\Bar', ], 'current' => [ From 9a0dd53168d005ca17d373d156c65ed0ed9e6770 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 13:23:30 +0100 Subject: [PATCH 065/144] Added repeats in the same line to class extraction test. --- Test/Unit/ComponentPHPFile10Test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Test/Unit/ComponentPHPFile10Test.php b/Test/Unit/ComponentPHPFile10Test.php index 16723698..e099b403 100644 --- a/Test/Unit/ComponentPHPFile10Test.php +++ b/Test/Unit/ComponentPHPFile10Test.php @@ -114,11 +114,11 @@ public static function providerQualifiedClassNameExtraction() { ], 'repeated' => [ <<<'EOT' - $foo = new \Foo\Bar(); + call(new \Foo\Bar(), new \Foo\Bar()); $bar = new \Foo\Bar(); EOT, <<<'EOT' - $foo = new Bar(); + call(new Bar(), new Bar()); $bar = new Bar(); EOT, 'Foo\Bar', From b82157f905288550fc22fedf2514577487288576 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 13:31:25 +0100 Subject: [PATCH 066/144] Changed class extraction to wrap strings to replace in markers. --- Generator/PHPFile.php | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Generator/PHPFile.php b/Generator/PHPFile.php index f10a73f6..97cb9962 100644 --- a/Generator/PHPFile.php +++ b/Generator/PHPFile.php @@ -113,6 +113,12 @@ abstract function phpCodeBody(); protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes, $current_namespace = '') { $current_namespace_pieces = explode('\\', $current_namespace); + // An array of replacements to make in the entire class code once all names + // have been extraced and clashes resolved. The search values are the full + // class names with markers surrounding them, to prevent inadvertent + // replacement. + $replacements = []; + foreach ($class_code as &$line) { // Skip lines which are part of a comment block. if (preg_match('@^\s*\*@', $line)) { @@ -127,6 +133,9 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes continue; } + // Replacements to make in the current line. + $line_marker_replacements = []; + $matches = []; // Do not match after a ' or ", as then the class name is a quoted string // and should be left alone. @@ -138,7 +147,18 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes foreach ($matches as $match_set) { $fully_qualified_class_name = $match_set[0]; $class_name = $match_set[1]; - $line = preg_replace('@' . preg_quote($fully_qualified_class_name) . '@', $class_name, $line); + + // Form a replacement string, which uses surrounding markers to ensure + // that we don't replace the class name in comments or typehints. + $marker_wrapped_search_string = '@IMPORT' . $fully_qualified_class_name . 'IMPORT@'; + + // Build an array of replacements for the line, so we do all the + // replacements in one go. This prevents re-wrapping a repeated class + // with the markers. + $line_marker_replacements[$fully_qualified_class_name] = $marker_wrapped_search_string; + + // Add the marker-wrapped name to the list of all replacements. + $replacements[$marker_wrapped_search_string] = $class_name; $fully_qualified_class_name = ltrim($fully_qualified_class_name, '\\'); $namespace_pieces = array_slice(explode('\\', $fully_qualified_class_name), 0, -1); @@ -146,9 +166,15 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes if ($namespace_pieces != $current_namespace_pieces) { $imported_classes[] = ltrim($fully_qualified_class_name, '\\'); } - } + } // foreach matches + + $line = str_replace(array_keys($line_marker_replacements), array_values($line_marker_replacements), $line); } - } + } // foreach line + + // Replace the marker-wrapped full classes with the short classes in the + // whole code. + $class_code = str_replace(array_keys($replacements), array_values($replacements), $class_code); // Remove duplicates. $imported_classes = array_unique($imported_classes); From 6ba37e1c57b282cb003804e075e96aa927219318 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 18:09:38 +0100 Subject: [PATCH 067/144] Added aliasing of repeated short class names from different vendors. --- Generator/ExtensionCodeFile.php | 2 +- Generator/PHPFile.php | 71 ++++++++++++++----- Test/Unit/ComponentPHPFile10Test.php | 21 ++++-- Test/Unit/ComponentPluginsAttribute11Test.php | 45 ++++++++++++ 4 files changed, 116 insertions(+), 23 deletions(-) diff --git a/Generator/ExtensionCodeFile.php b/Generator/ExtensionCodeFile.php index fda0e084..df82268d 100644 --- a/Generator/ExtensionCodeFile.php +++ b/Generator/ExtensionCodeFile.php @@ -216,7 +216,7 @@ function phpCodeBody() { foreach ($existing_import_nodes as $import_node) { $existing_import = $import_node->uses[0]->name->toString(); - $imported_classes[] = $existing_import; + $imported_classes[$existing_import] = NULL; } } diff --git a/Generator/PHPFile.php b/Generator/PHPFile.php index 97cb9962..2b8b1baa 100644 --- a/Generator/PHPFile.php +++ b/Generator/PHPFile.php @@ -102,10 +102,13 @@ abstract function phpCodeBody(); * * @param &$class_code * An array of PHP code lines to work on. All namespaced classes will be - * replaced with plain classes. + * replaced with plain classes or aliases. * @param &$imported_classes - * An array to populate with the fully-qualified classnames which are - * removed. These are without the initial namespace separator. + * An array to populate with the list of fully-qualified classnames which + * have been replaced with short classes in the class code. Keys are + * fully-qualified classnames without the initial namespace separator. Values + * are either the class alias used to replace the full class, or NULL if the + * short class was used as a replacement. * @param string $current_namespace * (optional) The namespace of the current file, without the initial '\'. If * omitted, no comparison of namespace is done. @@ -118,6 +121,10 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes // class names with markers surrounding them, to prevent inadvertent // replacement. $replacements = []; + // An array of the classes that are to be replaced. Keys are the full class + // name and value are the short class name. This is used to detect short + // name clashes. + $replaced_classes = []; foreach ($class_code as &$line) { // Skip lines which are part of a comment block. @@ -157,14 +164,18 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes // with the markers. $line_marker_replacements[$fully_qualified_class_name] = $marker_wrapped_search_string; - // Add the marker-wrapped name to the list of all replacements. + // Add the marker-wrapped name to the list of all replacements, and + // the full class name to the list of all classes. $replacements[$marker_wrapped_search_string] = $class_name; + $replaced_classes[$fully_qualified_class_name] = $class_name; - $fully_qualified_class_name = ltrim($fully_qualified_class_name, '\\'); - $namespace_pieces = array_slice(explode('\\', $fully_qualified_class_name), 0, -1); + $namespace_pieces = array_slice(explode('\\', $fully_qualified_class_name), 1, -1); + // If the class isn't in the current namespace, then add it to the + // list of imported classes. We don't yet trim the leading '\' so + // that we can compare the keys when resolving clashes. if ($namespace_pieces != $current_namespace_pieces) { - $imported_classes[] = ltrim($fully_qualified_class_name, '\\'); + $imported_classes[$fully_qualified_class_name] = NULL; } } // foreach matches @@ -172,28 +183,57 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes } } // foreach line + // Resolve any clashes in short class names. To use an alias instead of the + // short class name, we: + // - replace it in the $replacements array + // - set it as a value in the $imported_classes array. + $clashes = []; + foreach ($replaced_classes as $full_class => $short_class) { + $clashes[$short_class][] = $full_class; + } + $clashes = array_filter($clashes, fn ($array) => count($array) > 1); + + foreach ($clashes as $short_class => $clash_set) { + // Rule 1: A non-Drupal class loses out to the Drupal class, and gets an + // alias with a prefix of its top-level namespace. + foreach ($clash_set as $full_class) { + if (!str_starts_with($full_class, '\Drupal')) { + $pieces = explode('\\', $full_class); + $alias = $pieces[1] . $short_class; + // ARGH! need to remake the marker-wrapped name! + $replacements['@IMPORT' . $full_class . 'IMPORT@'] = $alias; + + // Set the alias into the list of imported classes. + $imported_classes[$full_class] = $alias; + } + } + } + // Replace the marker-wrapped full classes with the short classes in the // whole code. $class_code = str_replace(array_keys($replacements), array_values($replacements), $class_code); - // Remove duplicates. - $imported_classes = array_unique($imported_classes); + // Trim the initial '\' from the list of imported classes. + $new_keys = array_map(fn ($full_class) => ltrim($full_class, '\\'), array_keys($imported_classes)); + $imported_classes = array_combine($new_keys, array_values($imported_classes)); } /** * Produces the namespace import statements. * * @param $imported_classes - * (optional) An array of fully-qualified class names. The presence of the - * leading slash is immaterial. Duplicates are removed. + * An array of fully-qualified class names and aliases. Keys are fully-qualified class names, + * either with or without the leading slash. Values are one of: + * - NULL to indicate there is no alias. + * - The class name alias to use. */ - function imports($imported_classes = []) { + function imports($imported_classes) { $import_lines = []; if ($imported_classes) { - foreach ($imported_classes as $fully_qualified_class_name) { + foreach ($imported_classes as $fully_qualified_class_name => $alias) { $fully_qualified_class_name = ltrim($fully_qualified_class_name, '\\'); - $import_lines[] = "use $fully_qualified_class_name;"; + $import_lines[] = "use $fully_qualified_class_name" . ($alias ? " as $alias" : '') . ';'; } // Bit of a hack. We have to perform token replacement before sorting the @@ -216,9 +256,6 @@ function imports($imported_classes = []) { // Sort the imported classes. natcasesort($import_lines); - // Remove duplicates. - $import_lines = array_unique($import_lines); - $import_lines[] = ''; } diff --git a/Test/Unit/ComponentPHPFile10Test.php b/Test/Unit/ComponentPHPFile10Test.php index e099b403..2a9ac40d 100644 --- a/Test/Unit/ComponentPHPFile10Test.php +++ b/Test/Unit/ComponentPHPFile10Test.php @@ -56,10 +56,13 @@ public function testQualifiedClassNameExtraction($code, $expected_changed_code, $this->assertEmpty($imported_classes, "No class name was extracted."); } else { - if (!is_array($expected_qualified_class_names)) { - $expected_qualified_class_names = [$expected_qualified_class_names]; + if (is_array($expected_qualified_class_names)) { + $this->assertEquals($expected_qualified_class_names, $imported_classes, "The qualified class name was extracted."); + } + else { + $this->assertCount(1, $imported_classes, "The qualified class name was extracted."); + $this->assertArrayHasKey($expected_qualified_class_names, $imported_classes, "The qualified class name was extracted."); } - $this->assertEquals($expected_qualified_class_names, $imported_classes, "The qualified class name was extracted."); $this->assertEquals(explode("\n", $expected_changed_code), $code_lines, "The code was changed to use the short class name."); } @@ -93,8 +96,8 @@ public static function providerQualifiedClassNameExtraction() { 'function myfunc(\Foo\Bar $param_1, \Bar\Bax\Biz $param_2, \BuiltIn $param_3) {', 'function myfunc(Bar $param_1, Biz $param_2, \BuiltIn $param_3) {', [ - 'Foo\Bar', - 'Bar\Bax\Biz', + 'Foo\Bar' => NULL, + 'Bar\Bax\Biz' => NULL, ] ], 'static call' => [ @@ -123,6 +126,14 @@ public static function providerQualifiedClassNameExtraction() { EOT, 'Foo\Bar', ], + 'clash-vendor' => [ + '\Other\Foo\Bar::class; \Drupal\foo\Bar::class', + 'OtherBar::class; Bar::class', + [ + 'Drupal\foo\Bar' => NULL, + 'Other\Foo\Bar' => 'OtherBar', + ] + ], 'current' => [ '$foo = new \Current\Namespace\Bar()', '$foo = new Bar()', diff --git a/Test/Unit/ComponentPluginsAttribute11Test.php b/Test/Unit/ComponentPluginsAttribute11Test.php index e57dbb9c..92373fb9 100644 --- a/Test/Unit/ComponentPluginsAttribute11Test.php +++ b/Test/Unit/ComponentPluginsAttribute11Test.php @@ -562,6 +562,51 @@ function testPluginsGenerationWithOtherSchema() { // TODO: assert deeper into the YAML. } + /** + * Test the validation constraint plugin variant. + */ + public function testPluginValidationConstraint(): void { + // Create a module. + $module_name = 'test_module'; + $module_data = [ + 'base' => 'module', + 'root_name' => $module_name, + 'readable_name' => 'Test module', + 'short_description' => 'Test Module description', + 'plugins' => [ + 0 => [ + 'plugin_type' => 'validation.constraint', + 'plugin_name' => 'alpha', + ], + ], + 'readme' => FALSE, + ]; + $files = $this->generateModuleFiles($module_data); + + $this->assertFiles([ + "$module_name.info.yml", + "src/Plugin/Validation/Constraint/Alpha.php", + "src/Plugin/Validation/Constraint/AlphaValidator.php", + ], $files); + + $plugin = $files['src/Plugin/Validation/Constraint/Alpha.php']; + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $plugin); + $php_tester->assertDrupalCodingStandards(); + $php_tester->assertHasClass('Drupal\test_module\Plugin\Validation\Constraint\Alpha'); + // TODO: Quick hack because class tests don't support import aliases. + // $php_tester->assertClassHasParent('Symfony\Component\Validator\Constraint'); + $this->assertStringContainsString('use Symfony\Component\Validator\Constraint as SymfonyConstraint;', $plugin); + $this->assertStringContainsString('extends SymfonyConstraint', $plugin); + + $validator = $files["src/Plugin/Validation/Constraint/AlphaValidator.php"]; + + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $validator); + $php_tester->assertDrupalCodingStandards(); + $php_tester->assertHasClass('Drupal\test_module\Plugin\Validation\Constraint\AlphaValidator'); + $php_tester->assertClassHasParent('Symfony\Component\Validator\ConstraintValidator'); + $php_tester->assertHasMethod('validate'); + } + } namespace Drupal\Component\Plugin\Exception; From 9b6aebbcc88c743e77863233815f548c0dc672ed Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 19:27:09 +0100 Subject: [PATCH 068/144] Added aliasing of repeated short class names from the current component. --- Generator/PHPFile.php | 15 ++++++++++ Test/Unit/ComponentPHPFile10Test.php | 42 ++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Generator/PHPFile.php b/Generator/PHPFile.php index 2b8b1baa..5d609327 100644 --- a/Generator/PHPFile.php +++ b/Generator/PHPFile.php @@ -193,6 +193,8 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes } $clashes = array_filter($clashes, fn ($array) => count($array) > 1); + $component_namespace = '\Drupal\\' . $this->component_data->root_component_name->value; + foreach ($clashes as $short_class => $clash_set) { // Rule 1: A non-Drupal class loses out to the Drupal class, and gets an // alias with a prefix of its top-level namespace. @@ -207,6 +209,19 @@ protected function extractFullyQualifiedClasses(&$class_code, &$imported_classes $imported_classes[$full_class] = $alias; } } + + // Rule 2: if multiple classes belong to the current module, prefix each + // one with the immediate parent namespace. + $clash_set_clashes_in_current_component = array_filter($clash_set, fn ($full_class) => str_starts_with($full_class, $component_namespace)); + if (count($clash_set_clashes_in_current_component) > 1) { + foreach ($clash_set_clashes_in_current_component as $full_class) { + $pieces = explode('\\', $full_class); + $alias = implode('', array_slice($pieces, -2)); + + $replacements['@IMPORT' . $full_class . 'IMPORT@'] = $alias; + $imported_classes[$full_class] = $alias; + } + } } // Replace the marker-wrapped full classes with the short classes in the diff --git a/Test/Unit/ComponentPHPFile10Test.php b/Test/Unit/ComponentPHPFile10Test.php index 2a9ac40d..1667450a 100644 --- a/Test/Unit/ComponentPHPFile10Test.php +++ b/Test/Unit/ComponentPHPFile10Test.php @@ -5,6 +5,9 @@ use DrupalCodeBuilder\File\CodeFile; use DrupalCodeBuilder\Generator\PHPFile as RealPHPFile; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; +use MutableTypedData\DataItemFactory; +use MutableTypedData\Definition\DataDefinition; +use PHPUnit\Framework\Attributes\BeforeClass; use Prophecy\Argument; /** @@ -19,6 +22,30 @@ class ComponentPHPFile10Test extends TestBase { */ protected $drupalMajorVersion = 10; + /** + * The dummy component data item. + * + * @var \MutableTypedData\Data\DataItem + */ + private static $mockedComponent; + + /** + * Sets up a mocked component data item. + * + * This is needed for self::testQualifiedClassNameExtraction(). + */ + #[BeforeClass] + public static function setUpMockedComponent() { + $definition = DataDefinition::create('complex') + ->setLabel('Component') + ->setProperties([ + 'root_component_name' => DataDefinition::create('string'), + ]); + + static::$mockedComponent = DataItemFactory::createFromDefinition($definition); + static::$mockedComponent->root_component_name = 'my_module'; + } + /** * Test the qualified class name extraction. * @@ -42,9 +69,10 @@ public function testQualifiedClassNameExtraction($code, $expected_changed_code, $method = new \ReflectionMethod(PHPFile::class, 'extractFullyQualifiedClasses'); $method->setAccessible(TRUE); - // Create a PHP file generator with some dummy constructor parameters. - $data_item = $this->prophesize(\MutableTypedData\Data\DataItem::class); - $php_file_generator = new PHPFile($data_item->reveal()); + // Pass a component data item to PHPFile. Prophecy won't work as + // PHPFile::extractFullyQualifiedClasses() accesses a property, so we create + // one from a dummy defininition. + $php_file_generator = new PHPFile(static::$mockedComponent); $code_lines = explode("\n", $code); @@ -134,6 +162,14 @@ public static function providerQualifiedClassNameExtraction() { 'Other\Foo\Bar' => 'OtherBar', ] ], + 'clash-module' => [ + '\Drupal\my_module\Foo\Bar::class; \Drupal\my_module\Biz\Bar::class', + 'FooBar::class; BizBar::class', + [ + 'Drupal\my_module\Foo\Bar' => 'FooBar', + 'Drupal\my_module\Biz\Bar' => 'BizBar', + ] + ], 'current' => [ '$foo = new \Current\Namespace\Bar()', '$foo = new Bar()', From b2060367876065cc04a2a2f095f819033964f362 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 24 Aug 2025 19:46:49 +0100 Subject: [PATCH 069/144] Added BC handling of annotations to plugin type manager. --- Generator/PluginTypeManager.php | 20 ++++++++----- Test/Unit/ComponentPluginType10Test.php | 40 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/Generator/PluginTypeManager.php b/Generator/PluginTypeManager.php index aa7a8a5d..2d833400 100644 --- a/Generator/PluginTypeManager.php +++ b/Generator/PluginTypeManager.php @@ -144,14 +144,18 @@ public function requiredComponents(): array { 'Attribute', $this->component_data['metadata_class'], ]) . '::class,'; - // Add a comment about the annotation class for BC, for the case where - // this is an adopted plugin type that's being generated rather than a new - // one. - // Actually adding the annotation class is too complicated because of the - // class name clash - see - // https://github.com/drupal-code-builder/drupal-code-builder/issues/388. - $code[] = ' // @todo: Add the annotation class as a parameter here if this plugin'; - $code[] = ' // type supports annotations for BC.'; + $annotation_class_name = $this->makeQualifiedClassName([ + 'Drupal', + $this->component_data['root_component_name'], + 'Annotation', + $this->component_data['metadata_class'], + ]); + // Add BC support for annotations if an annotation class exists. This may + // happen when re-generating or adopting an existing plugin type. + if (class_exists($annotation_class_name)) { + $code[] = ' // @todo: Remove this parameter if not supporting BC annotation plugins.'; + $code[] = " " . '\\' . $annotation_class_name . '::class,'; + } $code[] = ');'; $code[] = ''; } diff --git a/Test/Unit/ComponentPluginType10Test.php b/Test/Unit/ComponentPluginType10Test.php index 7b929c4c..7d6a4574 100644 --- a/Test/Unit/ComponentPluginType10Test.php +++ b/Test/Unit/ComponentPluginType10Test.php @@ -191,6 +191,40 @@ function testAttributePluginTypeBasic() { $this->assertStringContainsString('Caaaaat::class', $plugin_manager_file); } + /** + * Tests a plugin type with BC handling for annotations. + * + * This relies on the + * \Drupal\test_module_plugin_type_with_bc\Annotation\Unique fixture class at + * the end of this file. + */ + function testAttributePluginTypeBCHandling() { + $module_data = [ + 'base' => 'module', + // Use unique names so the fixture doesn't clash. + 'root_name' => 'test_module_plugin_type_with_bc', + 'readable_name' => 'Test module', + 'short_description' => 'Test Module description', + 'hooks' => [], + 'plugin_types' => [ + 0 => [ + 'discovery_type' => 'attribute', + 'plugin_type' => 'unique', + ], + ], + 'readme' => FALSE, + ]; + $files = $this->generateModuleFiles($module_data); + + $plugin_manager_file = $files["src/UniqueManager.php"]; + + $this->assertStringContainsString('use Drupal\test_module_plugin_type_with_bc\Attribute\Unique as AttributeUnique;', $plugin_manager_file); + $this->assertStringContainsString('use Drupal\test_module_plugin_type_with_bc\Annotation\Unique as AnnotationUnique;', $plugin_manager_file); + + $this->assertStringContainsString('AttributeUnique::class', $plugin_manager_file); + $this->assertStringContainsString('AnnotationUnique::class', $plugin_manager_file); + } + /** * Test Plugin Type component. */ @@ -618,3 +652,9 @@ function testAttributePluginTypeWithServices() { } } + +/** + * Fixture class for testAttributePluginTypeBCHandling(). + */ +namespace Drupal\test_module_plugin_type_with_bc\Annotation; +class Unique {} From 4ab026d40300994bc88417cf2612044a2d7d118c Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Tue, 26 Aug 2025 19:22:21 +0100 Subject: [PATCH 070/144] Added to docs. --- Test/Unit/ComponentPluginType10Test.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Test/Unit/ComponentPluginType10Test.php b/Test/Unit/ComponentPluginType10Test.php index 7d6a4574..d6145031 100644 --- a/Test/Unit/ComponentPluginType10Test.php +++ b/Test/Unit/ComponentPluginType10Test.php @@ -6,7 +6,7 @@ use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** - * Tests the Plugin Type generator class. + * Tests generation of plugin types. * * @group yaml * @group di @@ -21,7 +21,7 @@ class ComponentPluginType10Test extends TestBase { protected $drupalMajorVersion = 10; /** - * Test Plugin Type component for attribute plugins. + * Test a plugin type with attribute plugins. */ function testAttributePluginTypeBasic() { // Create a module. @@ -192,7 +192,7 @@ function testAttributePluginTypeBasic() { } /** - * Tests a plugin type with BC handling for annotations. + * Tests a plugin type with attributes and BC handling for annotations. * * This relies on the * \Drupal\test_module_plugin_type_with_bc\Annotation\Unique fixture class at @@ -226,7 +226,7 @@ function testAttributePluginTypeBCHandling() { } /** - * Test Plugin Type component. + * Test a plugin type for annotation plugins. */ function testAnnotationPluginTypeBasic() { // Create a module. From 7163fb2f88856e185f14e38a4a9135b7ca77e5fa Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 30 Aug 2025 09:54:14 +0100 Subject: [PATCH 071/144] Fixed hook classes appearing as adoptable services. Fixes #407. --- Generator/Service.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Generator/Service.php b/Generator/Service.php index 89127868..4e63727b 100644 --- a/Generator/Service.php +++ b/Generator/Service.php @@ -198,9 +198,18 @@ public static function findAdoptableComponents(DrupalExtension $extension): arra $yaml = $extension->getFileYaml($services_filename); $service_names = array_keys($yaml['services']); - // Filter out plugin managers, as these are adopted as part of a plugin - // type. - $service_names = array_filter($service_names, fn ($name) => !str_starts_with($name, 'plugin.manager.')); + // Filter out services that are adopted by other components, or aren't yet + // adoptable. + $service_names = array_filter( + $service_names, + fn ($name) => !( + // Filter out plugin managers, as these are adopted as part of a plugin + // type. + str_starts_with($name, 'plugin.manager.') || + // Filter out hook classes. + str_contains($name, '\Hook\\') + ), + ); return array_combine($service_names, $service_names); } From 717e23e33b4862a3029f80d527433ce6e01ea95b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 7 Sep 2025 18:10:58 +0100 Subject: [PATCH 072/144] Fixed hook_post_update_NAME() not going in right file. --- Task/Collect/HooksCollector11.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Task/Collect/HooksCollector11.php b/Task/Collect/HooksCollector11.php index f4ff1d3e..d2f8d87a 100644 --- a/Task/Collect/HooksCollector11.php +++ b/Task/Collect/HooksCollector11.php @@ -243,6 +243,9 @@ protected function getAdditionalHookInfo() { 'hook_update_last_removed', 'hook_uninstall', ], + '%module.post_update.php' => [ + 'hook_post_update_NAME', + ], ], ], ]; From b6039432437452d92d4f991c42a86f4617ce4bdf Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 13 Sep 2025 22:59:55 +0100 Subject: [PATCH 073/144] Added strict_types to PHP files. Fixes #362. --- Generator/ExtensionCodeFile.php | 20 ++++++++++++++++++++ Generator/PHPClassFile.php | 12 ++++++++++++ Generator/PHPFile.php | 4 +++- Test/Unit/ComponentAPI10Test.php | 1 + Test/Unit/ComponentModule10Test.php | 1 + Test/Unit/ComponentService10Test.php | 1 + 6 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Generator/ExtensionCodeFile.php b/Generator/ExtensionCodeFile.php index df82268d..02e1b752 100644 --- a/Generator/ExtensionCodeFile.php +++ b/Generator/ExtensionCodeFile.php @@ -4,6 +4,7 @@ use DrupalCodeBuilder\File\CodeFile; use DrupalCodeBuilder\File\DrupalExtension; +use DrupalCodeBuilder\Generator\Render\DocBlock; /** * Generator class for procedural code files. @@ -59,6 +60,25 @@ public function getFileInfo(): CodeFile { ); } + /** + * Return the file doxygen header and any custom header code. + */ + function codeHeader() { + $docblock = DocBlock::file(); + + $docblock[] = $this->fileDocblockSummary(); + + $code = $docblock->render(); + // Blank line after the file docblock. + $code[] = ''; + + // Coding standards need this to go AFTER the @file docblock. + $code[] = 'declare(strict_types=1);'; + $code[] = ''; + + return $code; + } + /** * Return the main body of the file code. * diff --git a/Generator/PHPClassFile.php b/Generator/PHPClassFile.php index f0457fc0..f47e7392 100644 --- a/Generator/PHPClassFile.php +++ b/Generator/PHPClassFile.php @@ -235,6 +235,18 @@ public function getFileInfo(): CodeFile { ); } + /** + * {@inheritdoc} + */ + function fileHeader() { + return [ + "assertDrupalCodingStandards(); // TODO: expand the docblock assertion for these. + $this->assertStringNotContainsString('declare(strict_types=1);', $api_file); $this->assertStringContainsString("Hooks provided by the Test Module module.", $api_file, 'The API file contains the correct docblock header.'); $this->assertStringContainsString("@addtogroup hooks", $api_file, 'The API file contains the addtogroup docblock tag.'); $this->assertStringContainsString('@} End of "addtogroup hooks".', $api_file, 'The API file contains the closing addtogroup docblock tag.'); diff --git a/Test/Unit/ComponentModule10Test.php b/Test/Unit/ComponentModule10Test.php index 0185c302..92ac3077 100644 --- a/Test/Unit/ComponentModule10Test.php +++ b/Test/Unit/ComponentModule10Test.php @@ -164,6 +164,7 @@ function testHelptextOption() { $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $module_file); $php_tester->assertDrupalCodingStandards(); + $this->assertStringContainsString('declare(strict_types=1);', $module_file); $php_tester->assertHasHookImplementation('hook_help', $module_name); $this->assertFunctionCode($module_file, $module_name . '_help', $help_text, "The hook_help() implementation contains the requested help text."); diff --git a/Test/Unit/ComponentService10Test.php b/Test/Unit/ComponentService10Test.php index 8715ae8c..603c9add 100644 --- a/Test/Unit/ComponentService10Test.php +++ b/Test/Unit/ComponentService10Test.php @@ -64,6 +64,7 @@ public function testBasicServiceGeneration() { $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $service_class_file); $php_tester->assertDrupalCodingStandards(); + $this->assertStringContainsString('declare(strict_types=1);', $service_class_file); $php_tester->assertHasClass('Drupal\test_module\MyService'); } From 973888ea9edaf34897631bccded525d65b026c85 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 13 Sep 2025 23:09:15 +0100 Subject: [PATCH 074/144] Fixed entity add task links should be in sentence case. Fixes #328. --- Generator/EntityTypeBase.php | 2 +- Test/Unit/ComponentConfigEntityType10Test.php | 2 +- Test/Unit/ComponentContentEntityType10Test.php | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Generator/EntityTypeBase.php b/Generator/EntityTypeBase.php index aa771317..e3643656 100644 --- a/Generator/EntityTypeBase.php +++ b/Generator/EntityTypeBase.php @@ -462,7 +462,7 @@ public function requiredComponents(): array { 'prefix_name' => FALSE, 'plugin_name' => "entity.{$this->component_data['entity_type_id']}.add", 'plugin_properties' => [ - 'title' => 'Add ' . $this->component_data['entity_type_label'], + 'title' => 'Add ' . strtolower($this->component_data->entity_type_label->value), 'route_name' => "entity.{$this->component_data['entity_type_id']}.add_form", // Media module sets 10 for its tab; go further along. 'weight' => 15, diff --git a/Test/Unit/ComponentConfigEntityType10Test.php b/Test/Unit/ComponentConfigEntityType10Test.php index 3c1a9ed6..d945785d 100644 --- a/Test/Unit/ComponentConfigEntityType10Test.php +++ b/Test/Unit/ComponentConfigEntityType10Test.php @@ -451,7 +451,7 @@ public function testConfigEntityTypeWithUI() { $yaml_tester = new YamlTester($action_links_file); $yaml_tester->assertHasProperty('entity.kitty_cat.add', 'The entity type has an add action link.'); - $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'title'], 'Add Kitty Cat'); + $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'title'], 'Add kitty cat'); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'route_name'], 'entity.kitty_cat.add_form'); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'appears_on'], ['entity.kitty_cat.collection']); diff --git a/Test/Unit/ComponentContentEntityType10Test.php b/Test/Unit/ComponentContentEntityType10Test.php index 87bf74fd..2b17a30d 100644 --- a/Test/Unit/ComponentContentEntityType10Test.php +++ b/Test/Unit/ComponentContentEntityType10Test.php @@ -1465,7 +1465,7 @@ public function testContentEntityTypeWithUI() { $yaml_tester = new YamlTester($action_links_file); $yaml_tester->assertHasProperty('entity.kitty_cat.add', 'The content entity type has an add action link.'); - $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'title'], 'Add Kitty Cat'); + $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'title'], 'Add kitty cat'); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'route_name'], 'entity.kitty_cat.add_form', "The route for adding a content entity is for the add form."); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'appears_on'], ['entity.kitty_cat.collection']); @@ -1695,11 +1695,11 @@ public function testEntityTypeWithUIAndBundleEntity() { $yaml_tester = new YamlTester($action_links_file); $yaml_tester->assertHasProperty('entity.kitty_cat_type.add', 'The bundle entity type has an add action link.'); - $yaml_tester->assertPropertyHasValue(['entity.kitty_cat_type.add', 'title'], 'Add Kitty Cat Type'); + $yaml_tester->assertPropertyHasValue(['entity.kitty_cat_type.add', 'title'], 'Add kitty cat type'); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat_type.add', 'route_name'], 'entity.kitty_cat_type.add_form'); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat_type.add', 'appears_on'], ['entity.kitty_cat_type.collection']); $yaml_tester->assertHasProperty('entity.kitty_cat.add', 'The content entity type has an add action link.'); - $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'title'], 'Add Kitty Cat'); + $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'title'], 'Add kitty cat'); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'route_name'], 'entity.kitty_cat.add_page', "The route for adding a content entity is for the add page, rather than the add form."); $yaml_tester->assertPropertyHasValue(['entity.kitty_cat.add', 'appears_on'], ['entity.kitty_cat.collection']); From e291ebf6c9ef72de21ab62c8bbd3fc45b37de76a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 13 Sep 2025 23:22:30 +0100 Subject: [PATCH 075/144] Fixed ordering of getEditableConfigNames() method in admin settings form. Fixes #317. --- Generator/AdminSettingsForm.php | 19 ++++++++++--------- Test/Unit/ComponentAdminSettings10Test.php | 17 +++++++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Generator/AdminSettingsForm.php b/Generator/AdminSettingsForm.php index 834582be..698ded37 100644 --- a/Generator/AdminSettingsForm.php +++ b/Generator/AdminSettingsForm.php @@ -6,6 +6,7 @@ use MutableTypedData\Definition\DefaultDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\File\DrupalExtension; +use DrupalCodeBuilder\Utility\InsertArray; use MutableTypedData\Definition\OptionsSortOrder; /** @@ -86,6 +87,15 @@ public static function findAdoptableComponents(DrupalExtension $extension): arra public function requiredComponents(): array { $components = parent::requiredComponents(); + InsertArray::insertBefore($components, 'buildForm', ['getEditableConfigNames' => [ + 'component_type' => 'PHPFunction', + 'function_name' => 'getEditableConfigNames', + 'containing_component' => '%requester', + 'docblock_inherit' => TRUE, + 'declaration' => 'protected function getEditableConfigNames()', + 'body' => "return ['%module.settings'];", + ]]); + // Restore the call to the parent method. $components['buildForm']['body'] = [ "£form = parent::buildForm(£form, £form_state);", @@ -115,15 +125,6 @@ public function requiredComponents(): array { '£config->save();', ]; - $components['getEditableConfigNames'] = [ - 'component_type' => 'PHPFunction', - 'function_name' => 'getEditableConfigNames', - 'containing_component' => '%requester', - 'docblock_inherit' => TRUE, - 'declaration' => 'protected function getEditableConfigNames()', - 'body' => "return ['%module.settings'];", - ]; - $task_handler_report_admin_routes = \DrupalCodeBuilder\Factory::getTask('ReportAdminRoutes'); $admin_routes = $task_handler_report_admin_routes->listAdminRoutes(); diff --git a/Test/Unit/ComponentAdminSettings10Test.php b/Test/Unit/ComponentAdminSettings10Test.php index a432255b..5bc86d4a 100644 --- a/Test/Unit/ComponentAdminSettings10Test.php +++ b/Test/Unit/ComponentAdminSettings10Test.php @@ -61,20 +61,25 @@ function test8AdminSettingsGenerationTest() { $php_tester->assertHasClass('Drupal\test_module\Form\AdminSettingsForm'); $php_tester->assertClassHasParent('Drupal\Core\Form\ConfigFormBase'); + $php_tester->assertHasMethodOrder([ + 'getFormId', + 'getEditableConfigNames', + 'buildForm', + 'validateForm', + 'submitForm', + ]); + $method_tester = $php_tester->getMethodTester('getFormId'); $method_tester->getDocBlockTester()->assertHasInheritdoc(); $method_tester->assertReturnsString('test_module_settings_form'); - $form_builder_tester = $php_tester->getFormBuilderTester('buildForm', 1); - $form_builder_tester->assertElementCount(1); - - $php_tester->assertHasMethod('validateForm'); - $php_tester->assertHasMethod('submitForm'); - $method_tester = $php_tester->getMethodTester('getEditableConfigNames'); $method_tester->getDocBlockTester()->assertHasInheritdoc(); $method_tester->assertHasNoParameters(); + $form_builder_tester = $php_tester->getFormBuilderTester('buildForm', 1); + $form_builder_tester->assertElementCount(1); + // Check the schema file. $config_schema_file = $files['config/schema/test_module.schema.yml']; $yaml_tester = new YamlTester($config_schema_file); From c90cc2f0d426b524a0c930efb3fd3c64495cb555 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 14 Sep 2025 09:58:45 +0100 Subject: [PATCH 076/144] Fixed title of admin settings form page. Fixes #316. --- Generator/AdminSettingsForm.php | 2 +- Test/Unit/ComponentAdminSettings10Test.php | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Generator/AdminSettingsForm.php b/Generator/AdminSettingsForm.php index 698ded37..11f14253 100644 --- a/Generator/AdminSettingsForm.php +++ b/Generator/AdminSettingsForm.php @@ -139,7 +139,7 @@ public function requiredComponents(): array { // OK to use a token here, as the YAML value for this will be quoted // anyway. 'path' => $settings_form_path, - 'title' => 'Administer %lower', + 'title' => '%Module settings' , 'controller' => [ 'controller_type' => 'form', 'routing_value' => '\\' . $this->component_data['qualified_class_name'], diff --git a/Test/Unit/ComponentAdminSettings10Test.php b/Test/Unit/ComponentAdminSettings10Test.php index 5bc86d4a..cd362c9a 100644 --- a/Test/Unit/ComponentAdminSettings10Test.php +++ b/Test/Unit/ComponentAdminSettings10Test.php @@ -97,7 +97,7 @@ function test8AdminSettingsGenerationTest() { $yaml_tester->assertHasProperty($expected_route_name, "The routing file has the property for the admin route."); $yaml_tester->assertPropertyHasValue([$expected_route_name, 'path'], '/admin/config/system/test_module', "The routing file declares the route path."); $yaml_tester->assertPropertyHasValue([$expected_route_name, 'defaults', '_form'], '\Drupal\test_module\Form\AdminSettingsForm', "The routing file declares the route form."); - $yaml_tester->assertPropertyHasValue([$expected_route_name, 'defaults', '_title'], 'Administer test module', "The routing file declares the route title."); + $yaml_tester->assertPropertyHasValue([$expected_route_name, 'defaults', '_title'], 'Test Module settings', "The routing file declares the route title."); $yaml_tester->assertPropertyHasValue([$expected_route_name, 'requirements', '_permission'], 'administer test_module', "The routing file declares the route permission."); // Check the menu links file. @@ -208,7 +208,9 @@ function testAdminSettingsOtherRouterItemsTest() { $yaml_tester->assertHasProperty($expected_route_name, "The routing file has the property for the admin route."); $yaml_tester->assertPropertyHasValue([$expected_route_name, 'path'], '/admin/config/system/test_module', "The routing file declares the route path."); $yaml_tester->assertPropertyHasValue([$expected_route_name, 'defaults', '_form'], '\Drupal\test_module\Form\AdminSettingsForm', "The routing file declares the route form."); - $yaml_tester->assertPropertyHasValue([$expected_route_name, 'defaults', '_title'], 'Administer test module', "The routing file declares the route title."); + // This isn't title case because the module readable name isn't in title + // case. + $yaml_tester->assertPropertyHasValue([$expected_route_name, 'defaults', '_title'], 'Test module settings', "The routing file declares the route title."); $yaml_tester->assertPropertyHasValue([$expected_route_name, 'requirements', '_permission'], 'administer test_module', "The routing file declares the route permission."); $yaml_tester->assertPropertyHasValue(['test_module.requested.route.path', 'path'], "/requested/route/path", "The routing file declares the requested path."); From 2e68f7ebee35ca47d5eecdbad4f5097417f76f09 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 14 Sep 2025 11:20:49 +0100 Subject: [PATCH 077/144] Fixed error in test when folder exists. --- Test/Integration/Installation/InstallationTestBase.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Test/Integration/Installation/InstallationTestBase.php b/Test/Integration/Installation/InstallationTestBase.php index 7099e511..e9d16a78 100644 --- a/Test/Integration/Installation/InstallationTestBase.php +++ b/Test/Integration/Installation/InstallationTestBase.php @@ -125,7 +125,9 @@ protected function getModuleParentFolderPath(): string { protected function writeModuleFiles($module_name, $files) { $module_folder = $this->getModuleParentFolderPath() . '/' . $module_name; - mkdir($module_folder, 0777, TRUE); + if (!file_exists($module_folder)) { + mkdir($module_folder, 0777, TRUE); + } foreach ($files as $filepath => $code) { $relative_file_dir = dirname($filepath); From e2b52c5cbf563a552ad3524cd6cf976532ffac01 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 14 Sep 2025 11:31:35 +0100 Subject: [PATCH 078/144] =?UTF-8?q?Fixed=20content=20entity=20types=20with?= =?UTF-8?q?out=20a=20bundle=20entity=20type=20don=E2=80=99t=20have=20a=20F?= =?UTF-8?q?ield=20UI=20base=20route.=20Fixes=20#254.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Generator/ContentEntityType.php | 33 ++++++++++++++++++- .../Installation/ContentEntityTypeTest.php | 10 ++++-- .../Unit/ComponentContentEntityType10Test.php | 2 +- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Generator/ContentEntityType.php b/Generator/ContentEntityType.php index 609e2e60..488aa02a 100644 --- a/Generator/ContentEntityType.php +++ b/Generator/ContentEntityType.php @@ -205,7 +205,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio return 'entity.' . $entity_data->bundle_entity_type_id->value . '.edit_form'; } else { - return 'entity.' . $entity_data->entity_type_id->value . '.admin_form'; + return 'entity.' . $entity_data->entity_type_id->value . '.settings'; } }) ->setDependencies('..:functionality') @@ -520,6 +520,37 @@ public function requiredComponents(): array { } } + // Add the Field UI base route definition and controller if there is no + // bundle entity type to provide a bundle entity collection as the Field UI + // base route. + if ($this->component_data->field_ui_base_route->value && $this->component_data->bundle_entity->isEmpty()) { + $components['field_ui_base_route'] = [ + 'component_type' => 'RouterItem', + 'route_name' => $this->component_data->field_ui_base_route->value, + 'path' => '/admin/structure/' . $this->component_data->entity_type_id->value, + 'title' => $this->component_data->entity_type_label->value . ' settings', + 'controller' => [ + 'controller_type' => 'controller', + 'use_base' => TRUE, + ], + 'access' => [ + 'access_type' => 'permission', + 'routing_value' => $this->component_data->admin_permission_name->value, + ], + ]; + + // This will merge with the controller requested by the RouterItem. + $components['settings_controller'] = [ + 'component_type' => 'Controller', + 'relative_class_name' => RouterItem::controllerRelativeClassFromRoutePath($components['field_ui_base_route']['path']), + 'class_docblock_lines' => [ + 'Controller class for the FieldUI base route.', + "This needs to exist for FieldUI to attach its routes to. If the entity type has general settings, this route can be the config form for them instead.", + "You can also get rid of this route and use https://www.drupal.org/project/entity_admin_handlers to provide it automatically.", + ], + ]; + } + return $components; } diff --git a/Test/Integration/Installation/ContentEntityTypeTest.php b/Test/Integration/Installation/ContentEntityTypeTest.php index 4d7cf199..9c048b09 100644 --- a/Test/Integration/Installation/ContentEntityTypeTest.php +++ b/Test/Integration/Installation/ContentEntityTypeTest.php @@ -41,6 +41,7 @@ public function testSimpleContentEntityType() { 'entity_type_id' => 'kitty_cat', 'functionality' => [ 'owner', + 'fieldable', ], 'base_fields' => [ 0 => [ @@ -61,15 +62,20 @@ public function testSimpleContentEntityType() { $this->installModule($module_name); - // Get the entity type definition to check the entity class properly defines - // it. \Drupal::service('entity_type.manager')->clearCachedDefinitions(); + \Drupal::service('router.route_provider')->reset(); + // Get the entity type definition to check the entity class properly defines + // it. /** @var \Drupal\Core\Entity\EntityTypeInterface $definition */ $definition = \Drupal::service('entity_type.manager')->getDefinition('kitty_cat'); $this->assertIsObject($definition); $this->assertEquals('kitty_cat', $definition->id()); $this->assertEquals('Kitty Cat', $definition->getLabel()); + $this->assertEquals('entity.kitty_cat.settings', $definition->get('field_ui_base_route')); + + $route = \Drupal::service('router.route_provider')->getRouteByName('entity.kitty_cat.settings'); + $this->assertNotNull($route); } } diff --git a/Test/Unit/ComponentContentEntityType10Test.php b/Test/Unit/ComponentContentEntityType10Test.php index 2b17a30d..400b19da 100644 --- a/Test/Unit/ComponentContentEntityType10Test.php +++ b/Test/Unit/ComponentContentEntityType10Test.php @@ -139,7 +139,7 @@ public function testBasicContentEntityType() { $annotation_tester->assertPropertyHasValue('base_table', 'kitty_cat'); $annotation_tester->assertPropertyHasValue(['handlers', 'list_builder'], 'Drupal\Core\Entity\EntityListBuilder'); $annotation_tester->assertPropertyHasValue('admin_permission', 'administer kitty_cat entities'); - $annotation_tester->assertPropertyHasValue('field_ui_base_route', 'entity.kitty_cat.admin_form'); + $annotation_tester->assertPropertyHasValue('field_ui_base_route', 'entity.kitty_cat.settings'); $annotation_tester->assertHasProperties([ 'id', 'label', From f8012f3f208bead99bc8388e42e0d422aa6d6217 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Wed, 17 Sep 2025 21:43:17 +0100 Subject: [PATCH 079/144] Fixed tests for e2b52c5c. --- Test/Unit/ComponentContentEntityType10Test.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Test/Unit/ComponentContentEntityType10Test.php b/Test/Unit/ComponentContentEntityType10Test.php index 400b19da..af07f958 100644 --- a/Test/Unit/ComponentContentEntityType10Test.php +++ b/Test/Unit/ComponentContentEntityType10Test.php @@ -63,6 +63,8 @@ public function testBasicContentEntityType() { 'src/Entity/KittyCat.php', 'src/Entity/KittyCatInterface.php', 'test_module.permissions.yml', + 'src/Controller/AdminStructureKittyCatController.php', + 'test_module.routing.yml', ], $files); $entity_interface_file = $files['src/Entity/KittyCatInterface.php']; @@ -401,6 +403,8 @@ public function testEntityTypeWithTranslation() { 'src/Entity/KittyCat.php', 'src/Entity/KittyCatInterface.php', 'test_module.permissions.yml', + 'src/Controller/AdminStructureKittyCatController.php', + 'test_module.routing.yml', ], $files); $entity_class_file = $files['src/Entity/KittyCat.php']; @@ -524,6 +528,8 @@ public function testEntityTypeWithRevisions() { 'src/Entity/KittyCat.php', 'src/Entity/KittyCatInterface.php', 'test_module.permissions.yml', + 'src/Controller/AdminStructureKittyCatController.php', + 'test_module.routing.yml', ], $files); $entity_class_file = $files['src/Entity/KittyCat.php']; From c2bcaaa53c55020096babaa0ea53b355e6a8e550 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Wed, 17 Sep 2025 21:49:05 +0100 Subject: [PATCH 080/144] Added options for custom plugin class with DI to Yaml plugins. Fixes #411. --- Generator/PluginClassBase.php | 18 ++++++- Generator/PluginClassDiscovery.php | 3 -- Generator/PluginYamlDiscovery.php | 66 ++++++++++++++++++++++++ Test/Unit/ComponentPluginsYAML10Test.php | 66 ++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 5 deletions(-) diff --git a/Generator/PluginClassBase.php b/Generator/PluginClassBase.php index 10c215d8..08d326ba 100644 --- a/Generator/PluginClassBase.php +++ b/Generator/PluginClassBase.php @@ -2,11 +2,15 @@ namespace DrupalCodeBuilder\Generator; +use MutableTypedData\Definition\PropertyListInterface; + /** * General class for plugin classes. * - * Used for plugin base classes, and as a base class for class-based discovery - * plugins. + * Used for: + * - plugin base classes + * - base class for class-based discovery plugins + * - custom plugin class for Yaml-based discovery plugins. */ class PluginClassBase extends PHPClassFileWithInjection { @@ -48,6 +52,16 @@ class PluginClassBase extends PHPClassFileWithInjection { ] ]; + /** + * {@inheritdoc} + */ + public static function addToGeneratorDefinition(PropertyListInterface $definition) { + parent::addToGeneratorDefinition($definition); + + $definition->getProperty('use_static_factory_method') + ->setLiteralDefault(TRUE); + } + /** * {@inheritdoc} */ diff --git a/Generator/PluginClassDiscovery.php b/Generator/PluginClassDiscovery.php index 7f0f6210..631f975e 100644 --- a/Generator/PluginClassDiscovery.php +++ b/Generator/PluginClassDiscovery.php @@ -42,9 +42,6 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $definition->getProperty('relative_class_name')->setInternal(TRUE); - $definition->getProperty('use_static_factory_method') - ->setLiteralDefault(TRUE); - $definition->addPropertyBefore( 'plain_class_name', PropertyDefinition::create('string') diff --git a/Generator/PluginYamlDiscovery.php b/Generator/PluginYamlDiscovery.php index f786dfdf..1bc3d92c 100644 --- a/Generator/PluginYamlDiscovery.php +++ b/Generator/PluginYamlDiscovery.php @@ -51,6 +51,47 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio 'Deriver'; }) ), + 'plugin_custom_class' => PropertyDefinition::create('boolean') + ->setLabel('Use a custom plugin class') + ->setDescription("Adds a custom class for the plugin which inherits from the default."), + 'plugin_custom_class_parent' => PropertyDefinition::create('string') + ->setInternal(TRUE) + ->setCallableDefault(function ($component_data) { + $plugin_class_parent = '\\' . ( + $component_data->getParent()->plugin_type_data->value['base_class'] + ?? + $component_data->getParent()->plugin_type_data->value['yaml_properties']['class'] + ); + return $plugin_class_parent; + }), + 'plugin_custom_relative_class_name' => PropertyDefinition::create('string') + ->setInternal(TRUE) + ->setDefault( + DefaultDefinition::create() + ->setCallable(function (DataItem $component_data) { + // We need to use a reasonable namespace beneath Plugin for the + // class. Deriving it from the base class is too complex, as the + // class could be in the top-level namespace, or in Plugin. + // Instead, taking the type ID and forming namespaces from its + // dot-separated pieces is a best guess. + $plugin_type_id = $component_data->getParent()->plugin_type_data->value['type_id']; + + $suffix_pieces = array_map( + fn ($piece) => CaseString::snake($piece)->pascal(), + explode('.', $plugin_type_id), + ); + + $plugin_subdir = implode('\\', $suffix_pieces); + + $component_data->value = 'Plugin\\' . $plugin_subdir . '\\' . CaseString::snake($component_data->getParent()->plugin_name->value)->pascal(); + }) + ->setDependencies('..:plugin_custom_class') + ), + 'injected_services' => PropertyDefinition::create('string') + ->setLabel('Injected services for custom class') + ->setDescription("Services to inject if using a custom plugin class.") + ->setMultiple(TRUE) + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')), 'prefix_name' => PropertyDefinition::create('boolean') ->setInternal(TRUE) ->setLiteralDefault(TRUE), @@ -125,6 +166,12 @@ public static function defaultPluginProperties($data_item) { $plugin_properties_with_defaults[$property_name] = $property_default; } } + + // Set the class if we're generating a custom plugin class. + if (!empty($data_item->getParent()->plugin_custom_class->value)) { + $plugin_properties_with_defaults['class'] = '\\Drupal\%module\\' . $data_item->getParent()->plugin_custom_relative_class_name->value; + } + return $plugin_properties_without_defaults + $plugin_properties_with_defaults; } @@ -168,6 +215,25 @@ public function requiredComponents(): array { ]; } + if (!empty($this->component_data->plugin_custom_class->value)) { + $plugin_class_parent = '\\' . ( + $this->component_data['plugin_type_data']['base_class'] + ?? + $this->component_data['plugin_type_data']['yaml_properties']['class'] + ); + + $components['plugin_custom_class'] = [ + 'component_type' => 'PluginClassBase', + 'class_docblock_lines' => [ + 'Plugin class for ' . $this->component_data->plugin_name->value . '.', + ], + // Use relative class name so we only compute one value. + 'relative_class_name' => $this->component_data->plugin_custom_relative_class_name->value, + 'parent_class_name' => $this->component_data->plugin_custom_class_parent->value, + 'injected_services' => $this->component_data->injected_services->values(), + ]; + } + return $components; } diff --git a/Test/Unit/ComponentPluginsYAML10Test.php b/Test/Unit/ComponentPluginsYAML10Test.php index 041689a9..1eca1f6c 100644 --- a/Test/Unit/ComponentPluginsYAML10Test.php +++ b/Test/Unit/ComponentPluginsYAML10Test.php @@ -102,6 +102,72 @@ function testYamlPluginsGenerationDeriver() { $yaml_tester->assertPropertyHasValue(['test_module.alpha', 'deriver'], '\Drupal\test_module\Plugin\Derivative\AlphaMenuLinkDeriver'); } + /** + * Tests a custom plugin class with DI. + * + * @group di + */ + public function testCustomPluginClass(): void { + // Create a module. + $module_data = [ + 'base' => 'module', + 'root_name' => 'test_module', + 'readable_name' => 'Test module', + 'short_description' => 'Test Module description', + 'hooks' => [ + ], + 'plugins' => [ + 0 => [ + 'plugin_type' => 'menu.link', + 'plugin_name' => 'alpha', + 'plugin_custom_class' => TRUE, + 'injected_services' => [ + 'current_user', + ], + ] + ], + 'readme' => FALSE, + ]; + $files = $this->generateModuleFiles($module_data); + + $this->assertFiles([ + 'test_module.info.yml', + 'test_module.links.menu.yml', + 'src/Plugin/Menu/Link/Alpha.php', + ], $files); + + $plugins_file = $files['test_module.links.menu.yml']; + + $yaml_tester = new YamlTester($plugins_file); + $yaml_tester->assertHasProperty('test_module.alpha'); + $yaml_tester->assertPropertyHasValue(['test_module.alpha', 'class'], '\Drupal\test_module\Plugin\Menu\Link\Alpha'); + + $plugin_file = $files['src/Plugin/Menu/Link/Alpha.php']; + + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $plugin_file); + $php_tester->assertDrupalCodingStandards(); + $php_tester->assertHasClass('Drupal\test_module\Plugin\Menu\Link\Alpha'); + $php_tester->assertClassHasParent('Drupal\Core\Menu\MenuLinkBase'); + + // Check service injection. + $php_tester->assertClassHasInterfaces([ + 'Drupal\Core\Plugin\ContainerFactoryPluginInterface', + ]); + $php_tester->assertInjectedServicesWithFactory([ + [ + 'typehint' => 'Drupal\Core\Session\AccountProxyInterface', + 'service_name' => 'current_user', + 'property_name' => 'currentUser', + 'parameter_name' => 'current_user', + ], + ]); + $php_tester->assertConstructorBaseParameters([ + 'configuration' => 'array', + 'plugin_id' => NULL, + 'plugin_definition' => NULL, + ]); + } + /** * Test PluginYAML component with annotated plugins too. */ From f5f82962a6ed6bf9b050b15c340071f549fd4f56 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Tue, 30 Sep 2025 08:20:02 +0100 Subject: [PATCH 081/144] Fixed adopting service with no arguments crashes. --- Generator/Service.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Generator/Service.php b/Generator/Service.php index 4e63727b..ba714924 100644 --- a/Generator/Service.php +++ b/Generator/Service.php @@ -228,12 +228,15 @@ public static function adoptComponent(DataItem $component_data, DrupalExtension $value = [ 'service_name' => preg_replace("@^{$extension->name}\.@", '', $name), - 'injected_services' => array_map(fn ($service_name) => ltrim($service_name, '@'), $service_yaml['arguments']), // These properties are hidden in the UI but will be stored anyway. 'plain_class_name' => end($class_name_pieces), 'relative_namespace' => implode('\\', array_slice($class_name_pieces, 2, -1)), ]; + if (!empty($service_yaml['arguments'])) { + $value['injected_services'] = array_map(fn ($service_name) => ltrim($service_name, '@'), $service_yaml['arguments']); + } + foreach ($component_data->getItem($property_name) as $delta => $delta_item) { if ($delta_item->service_name->value == $value['service_name']) { $merge_delta = $delta; From c5b20310c18371a4f44fda7175b924d4d885868d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 17:17:14 +0000 Subject: [PATCH 082/144] Updated PHP version for CircleCI image. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 90dc34bb..c7f48c4a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor docker: # Specify the version you desire here - - image: cimg/php:8.0.8 + - image: cimg/php:8.3.27 # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images From cac99971cbfddf49a64b39c5a111beafe6024edd Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 17:36:37 +0000 Subject: [PATCH 083/144] Removed unused test package; test using it was removed in 10423eb1. --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 429a737b..4be4bd51 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,7 @@ "symfony/yaml": "^6", "symfony/var-dumper": "^6", "morrislaptop/var-dumper-with-context": "^0.1.0", - "phpspec/prophecy-phpunit": "^2.0", - "dms/phpunit-arraysubset-asserts": "^0.3.0" + "phpspec/prophecy-phpunit": "^2.0" }, "autoload": { "psr-4": { From 60c3cd4d8f234fc89b58d8678d5a4e6b6a90bbda Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 17:37:15 +0000 Subject: [PATCH 084/144] Updated phpunit requirement for attributes. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4be4bd51..429ab336 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "symfony/finder": "^4.0 || ^5 || ^6 || ^7" }, "require-dev": { - "phpunit/phpunit": "^9", + "phpunit/phpunit": "^10", "drupal/coder": "^8.3", "mikey179/vfsstream": "^1.6.11", "nikic/php-parser": "^5.0", From ba5319f9c478d44717f4a091398a8a5ad04b6fa2 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 18:12:07 +0000 Subject: [PATCH 085/144] Fixed DCB config data for PHPCS not found when installed as project. --- Test/Constraint/CodeAdheresToCodingStandards.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Test/Constraint/CodeAdheresToCodingStandards.php b/Test/Constraint/CodeAdheresToCodingStandards.php index c33dbf50..b5b2c315 100644 --- a/Test/Constraint/CodeAdheresToCodingStandards.php +++ b/Test/Constraint/CodeAdheresToCodingStandards.php @@ -161,6 +161,11 @@ protected function setUpPHPCS($excluded_sniffs) { $runner->config->setConfigData('installed_paths', implode(',', [ static::$composerVendorDir . '/drupal/coder/coder_sniffer', static::$composerVendorDir . '/slevomat/coding-standard', + // Need to register our config data dir for both the case where this repo + // is the main project in CI testing, and the case where this repo is + // installed as a library in local development. PHPCS does not seem to + // mind a directory that doesn't exist. + getcwd() . '/Test/PHP_CodeSniffer', static::$composerVendorDir . '/drupal-code-builder/drupal-code-builder/Test/PHP_CodeSniffer', ])); From e0733b6a89c1b4f3f3e12d2178b42f7452dbe99d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 18:35:50 +0000 Subject: [PATCH 086/144] Upgraded test requirements to drupal/coder 9.0 alpha and PHPCS 4.0. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 429ab336..dffbcf07 100644 --- a/composer.json +++ b/composer.json @@ -13,10 +13,10 @@ }, "require-dev": { "phpunit/phpunit": "^10", - "drupal/coder": "^8.3", + "drupal/coder": "^9.0@alpha", "mikey179/vfsstream": "^1.6.11", "nikic/php-parser": "^5.0", - "squizlabs/php_codesniffer": "^3", + "squizlabs/php_codesniffer": "^4.0", "symfony/yaml": "^6", "symfony/var-dumper": "^6", "morrislaptop/var-dumper-with-context": "^0.1.0", From 5c949acc380e03a0ee6a7bb3c57b9e9c6bcc5133 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Thu, 6 Nov 2025 16:53:37 +0000 Subject: [PATCH 087/144] Added long array sniff to exclusions for older versions of core. --- Test/Unit/Parsing/PHPTester.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index ab2eb1d5..d8294f67 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -126,6 +126,11 @@ public function assertDrupalCodingStandards(array $excluded_sniffs = []) { $excluded_sniffs[] = 'Drupal.Classes.ClassFileName.NoMatch'; } + if ($this->drupalMajorVersion <= 7) { + // Code for Drupal 7 and earlier uses long array syntax. + $excluded_sniffs[] = 'Generic.Arrays.DisallowLongArraySyntax'; + } + $constraint = new CodeAdheresToCodingStandards($this->drupalMajorVersion, $excluded_sniffs, $this->phpCodeFilePath); Assert::assertThat($this->phpCode, $constraint, "The code file {$this->phpCodeFilePath} adheres to Drupal coding standards."); From f61d9d3ed9629bc7f4671ebb61e54d8cbf56b16e Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Thu, 6 Nov 2025 20:29:16 +0000 Subject: [PATCH 088/144] Added long array sniff to exclusions for simpletest test. --- Test/Unit/ComponentTests8Test.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Test/Unit/ComponentTests8Test.php b/Test/Unit/ComponentTests8Test.php index f1bf2fc5..f360d69b 100644 --- a/Test/Unit/ComponentTests8Test.php +++ b/Test/Unit/ComponentTests8Test.php @@ -65,7 +65,11 @@ function testModuleGenerationTests() { $tests_file = $files['src/Tests/TestModuleTestCase.php']; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $tests_file); - $php_tester->assertDrupalCodingStandards($this->phpcsExcludedSniffs); + + $excluded = $this->phpcsExcludedSniffs; + $excluded[] = 'Generic.Arrays.DisallowLongArraySyntax'; + + $php_tester->assertDrupalCodingStandards($excluded); $php_tester->assertHasClass('TestModuleTestCase', "The test class file contains the correct class"); $php_tester->assertHasMethods(['getInfo', 'setUp', 'testTodoChangeThisName']); } From 08f0b9fa8ed44f4a88ad4d11a170a1dfc645a76d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Thu, 6 Nov 2025 20:59:09 +0000 Subject: [PATCH 089/144] Updated class filename match sniff name. --- Test/Unit/Parsing/PHPTester.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index d8294f67..82cf8e13 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -123,7 +123,7 @@ public function assertDrupalCodingStandards(array $excluded_sniffs = []) { if (empty($this->phpCodeFilePath)) { // Exclude this sniff if we don't have access to the file name. - $excluded_sniffs[] = 'Drupal.Classes.ClassFileName.NoMatch'; + $excluded_sniffs[] = 'Squiz.Classes.ClassFileName.NoMatch'; } if ($this->drupalMajorVersion <= 7) { From 26f1f81cba3b97083294c32b219e695d5c7f1f1f Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 16:44:36 +0000 Subject: [PATCH 090/144] =?UTF-8?q?Removed=20temporarily=20SlevomatCodingS?= =?UTF-8?q?tandard.Commenting.ForbiddenComments=20sniff=20that=E2=80=99s?= =?UTF-8?q?=20buggy=20on=20PHPCS=204.x.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Test/Unit/Parsing/PHPTester.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index 82cf8e13..61eb90f5 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -121,6 +121,10 @@ public function assertDrupalCodingStandards(array $excluded_sniffs = []) { // to stand out. $excluded_sniffs[] = 'Drupal.Commenting.TodoComment.TodoFormat'; + // Temporarily remove this sniff because it's buggy with PHPCS 4.x. + // @see https://github.com/slevomat/coding-standard/issues/1810 + $excluded_sniffs[] = 'SlevomatCodingStandard.Commenting.ForbiddenComments'; + if (empty($this->phpCodeFilePath)) { // Exclude this sniff if we don't have access to the file name. $excluded_sniffs[] = 'Squiz.Classes.ClassFileName.NoMatch'; From 991925566cd2e9cada905e6fe3b418c789a93c3b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 18:48:13 +0000 Subject: [PATCH 091/144] Added class filename match sniff to exclusions for Drupal 7. --- Test/Unit/Parsing/PHPTester.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index 61eb90f5..460bdf56 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -133,6 +133,10 @@ public function assertDrupalCodingStandards(array $excluded_sniffs = []) { if ($this->drupalMajorVersion <= 7) { // Code for Drupal 7 and earlier uses long array syntax. $excluded_sniffs[] = 'Generic.Arrays.DisallowLongArraySyntax'; + + // Drupal 7 typically has classes in .inc files, which do not match the + // class name. + $excluded_sniffs[] = 'Squiz.Classes.ClassFileName.NoMatch'; } $constraint = new CodeAdheresToCodingStandards($this->drupalMajorVersion, $excluded_sniffs, $this->phpCodeFilePath); From 05208397e3a3a92e20db9e2b098dc09f87b4be57 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 2 Nov 2025 11:24:31 +0000 Subject: [PATCH 092/144] Changed typehint parameter to be explicitly optional. --- Test/Unit/Parsing/PHPTester.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index 460bdf56..ea4b5818 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -785,15 +785,15 @@ public function assertClassHasConstant(string $name, string $message = NULL) { * * @param string $property_name * The name of the property, without the initial '$'. - * @param string $typehint - * The typehint for the property, without the initial '\' if a class or - * interface. + * @param string|null $typehint + * (optional) The typehint for the property, without the initial '\' if a + * class or interface. * @param mixed $default * (optional) The expected default value of the property, as a PHP value. * @param string $message * (optional) The assertion message. */ - public function assertClassHasPublicProperty($property_name, $typehint, $default = NULL, $message = NULL) { + public function assertClassHasPublicProperty($property_name, ?string $typehint = NULL, $default = NULL, $message = NULL) { $message = $message ?? "The class defines the public property \${$property_name}"; $this->assertClassHasProperty($property_name, $typehint, $default, $message); @@ -807,15 +807,15 @@ public function assertClassHasPublicProperty($property_name, $typehint, $default * * @param string $property_name * The name of the property, without the initial '$'. - * @param string $typehint - * The typehint for the property, without the initial '\' if a class or - * interface. + * @param string|null $typehint + * (optional) The typehint for the property, without the initial '\' if a + * class or interface. * @param mixed $default * (optional) The expected default value of the property, as a PHP value. * @param string $message * (optional) The assertion message. */ - public function assertClassHasProtectedProperty($property_name, $typehint, $default = NULL, $message = NULL) { + public function assertClassHasProtectedProperty($property_name, ?string $typehint = NULL, $default = NULL, $message = NULL) { $message = $message ?? "The class defines the protected property \${$property_name}"; $this->assertClassHasProperty($property_name, $typehint, $default, $message); @@ -837,7 +837,7 @@ public function assertClassHasProtectedProperty($property_name, $typehint, $defa * @param string $message * (optional) The assertion message. */ - public function assertClassHasProperty($property_name, $typehint, $default, $message = NULL) { + public function assertClassHasProperty($property_name, ?string $typehint = NULL, $default = NULL, $message = NULL) { $message = $message ?? "The class defines the property \${$property_name}"; Assert::assertArrayHasKey($property_name, $this->parser_nodes['properties'], $message); From a465a3ee29e6eea7173fcc8c2899220f338d03dc Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 8 Nov 2025 20:55:54 +0000 Subject: [PATCH 093/144] Fixed $module property of PHPUnit tests not using inheritdoc tag. Fixes #414. --- Generator/PHPUnitTest.php | 2 +- Test/Unit/ComponentTestsPHPUnit10Test.php | 10 +++++----- Test/Unit/ComponentTestsPHPUnit9Test.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Generator/PHPUnitTest.php b/Generator/PHPUnitTest.php index 97046845..16baf153 100644 --- a/Generator/PHPUnitTest.php +++ b/Generator/PHPUnitTest.php @@ -368,7 +368,7 @@ protected function collectSectionBlocks() { 'modules', 'array', [ - 'docblock_first_line' => 'The modules to enable.', + 'docblock_inherit' => TRUE, 'prefixes' => ['protected', 'static'], 'default' => $test_install_modules, 'break_array_value' => TRUE, diff --git a/Test/Unit/ComponentTestsPHPUnit10Test.php b/Test/Unit/ComponentTestsPHPUnit10Test.php index 51b5bd5d..8a8b44e9 100644 --- a/Test/Unit/ComponentTestsPHPUnit10Test.php +++ b/Test/Unit/ComponentTestsPHPUnit10Test.php @@ -69,7 +69,7 @@ function testModuleGenerationTestsWithModuleDependencies() { $php_tester->assertDrupalCodingStandards($this->phpcsExcludedSniffs); $php_tester->assertHasClass('Drupal\Tests\test_module\Kernel\MyTest'); $php_tester->assertHasMethods(['setUp', 'testMyTest']); - $php_tester->assertClassHasProtectedProperty('modules', 'array', [ + $php_tester->assertClassHasProtectedProperty('modules', NULL, [ 'system', 'user', 'dependency_one', @@ -150,7 +150,7 @@ function testModuleGenerationKernelTest() { $php_tester->assertDrupalCodingStandards($this->phpcsExcludedSniffs); $php_tester->assertHasClass('Drupal\Tests\test_module\Kernel\MyTest'); $php_tester->assertClassHasParent('Drupal\KernelTests\KernelTestBase'); - $php_tester->assertClassHasProtectedProperty('modules', 'array', [ + $php_tester->assertClassHasProtectedProperty('modules', NULL, [ 'system', 'user', 'test_module', @@ -193,7 +193,7 @@ function testBrowserTest() { $php_tester->assertDrupalCodingStandards($this->phpcsExcludedSniffs); $php_tester->assertHasClass('Drupal\Tests\test_module\Functional\MyTest'); $php_tester->assertClassHasParent('Drupal\Tests\BrowserTestBase'); - $php_tester->assertClassHasProtectedProperty('modules', 'array', [ + $php_tester->assertClassHasProtectedProperty('modules', NULL, [ 'system', 'user', 'test_module', @@ -237,7 +237,7 @@ function testJavaScriptTest() { $php_tester->assertDrupalCodingStandards($this->phpcsExcludedSniffs); $php_tester->assertHasClass('Drupal\Tests\test_module\FunctionalJavascript\MyTest'); $php_tester->assertClassHasParent('Drupal\FunctionalJavascriptTests\WebDriverTestBase'); - $php_tester->assertClassHasProtectedProperty('modules', 'array', [ + $php_tester->assertClassHasProtectedProperty('modules', NULL, [ 'system', 'user', 'test_module', @@ -518,7 +518,7 @@ function testModuleGenerationTestsWithTestModuleComponents() { 'generated_module', 'my_test', ]; - $php_tester->assertClassHasProtectedProperty('modules', 'array', $expected_modules_property_value); + $php_tester->assertClassHasProtectedProperty('modules', NULL, $expected_modules_property_value); } /** diff --git a/Test/Unit/ComponentTestsPHPUnit9Test.php b/Test/Unit/ComponentTestsPHPUnit9Test.php index 6133c726..3155128d 100644 --- a/Test/Unit/ComponentTestsPHPUnit9Test.php +++ b/Test/Unit/ComponentTestsPHPUnit9Test.php @@ -69,7 +69,7 @@ function testModuleGenerationTestsWithModuleDependencies() { $php_tester->assertDrupalCodingStandards($this->phpcsExcludedSniffs); $php_tester->assertHasClass('Drupal\Tests\test_module\Kernel\MyTest'); $php_tester->assertHasMethods(['setUp', 'testMyTest']); - $php_tester->assertClassHasProtectedProperty('modules', 'array', [ + $php_tester->assertClassHasProtectedProperty('modules', NULL, [ 'system', 'user', 'dependency_one', @@ -150,7 +150,7 @@ function testModuleGenerationKernelTest() { $php_tester->assertDrupalCodingStandards($this->phpcsExcludedSniffs); $php_tester->assertHasClass('Drupal\Tests\test_module\Kernel\MyTest'); $php_tester->assertClassHasParent('Drupal\KernelTests\KernelTestBase'); - $php_tester->assertClassHasProtectedProperty('modules', 'array', [ + $php_tester->assertClassHasProtectedProperty('modules', NULL, [ 'system', 'user', 'test_module', @@ -442,7 +442,7 @@ function testModuleGenerationTestsWithTestModuleComponents() { 'generated_module', 'my_test', ]; - $php_tester->assertClassHasProtectedProperty('modules', 'array', $expected_modules_property_value); + $php_tester->assertClassHasProtectedProperty('modules', NULL, $expected_modules_property_value); } /** From 22bb8d981f68d7a28f41faa67fa8ce407640d227 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 24 Feb 2024 18:21:04 +0000 Subject: [PATCH 094/144] Added environment to class handler task. --- .../DrupalCodeBuilderCompiledContainer.php | 596 +++++++++--------- Task/Generate/ComponentClassHandler.php | 6 +- 2 files changed, 309 insertions(+), 293 deletions(-) diff --git a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php index ce940839..b7740d0c 100644 --- a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php +++ b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php @@ -73,40 +73,40 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry36' => 'get67', 'Generate\\ComponentClassHandler' => 'get68', 'subEntry37' => 'get69', - 'Generate\\ComponentCollector' => 'get70', - 'subEntry38' => 'get71', + 'subEntry38' => 'get70', + 'Generate\\ComponentCollector' => 'get71', 'subEntry39' => 'get72', - 'Generate\\FileAssembler' => 'get73', - 'ReportAdminRoutes' => 'get74', - 'subEntry40' => 'get75', - 'ReportDataTypes' => 'get76', - 'subEntry41' => 'get77', - 'ReportElementTypes' => 'get78', - 'subEntry42' => 'get79', - 'ReportEntityTypes' => 'get80', - 'subEntry43' => 'get81', - 'ReportEventNames' => 'get82', - 'subEntry44' => 'get83', - 'ReportFieldTypes' => 'get84', - 'subEntry45' => 'get85', - 'ReportHookClassMethodData' => 'get86', - 'subEntry46' => 'get87', - 'ReportHookData' => 'get88', - 'subEntry47' => 'get89', - 'ReportHookDataFolder' => 'get90', - 'subEntry48' => 'get91', - 'ReportHookGroups' => 'get92', - 'subEntry49' => 'get93', - 'ReportHookPresets' => 'get94', - 'subEntry50' => 'get95', - 'ReportPluginData' => 'get96', - 'subEntry51' => 'get97', - 'ReportServiceData' => 'get98', - 'subEntry52' => 'get99', - 'ReportServiceTags' => 'get100', - 'subEntry53' => 'get101', - 'ReportSummary' => 'get102', - 'subEntry54' => 'get103', + 'subEntry40' => 'get73', + 'Generate\\FileAssembler' => 'get74', + 'ReportAdminRoutes' => 'get75', + 'subEntry41' => 'get76', + 'ReportDataTypes' => 'get77', + 'subEntry42' => 'get78', + 'ReportElementTypes' => 'get79', + 'subEntry43' => 'get80', + 'ReportEntityTypes' => 'get81', + 'subEntry44' => 'get82', + 'ReportEventNames' => 'get83', + 'subEntry45' => 'get84', + 'ReportFieldTypes' => 'get85', + 'subEntry46' => 'get86', + 'ReportHookClassMethodData' => 'get87', + 'subEntry47' => 'get88', + 'ReportHookData' => 'get89', + 'subEntry48' => 'get90', + 'ReportHookDataFolder' => 'get91', + 'subEntry49' => 'get92', + 'ReportHookGroups' => 'get93', + 'subEntry50' => 'get94', + 'ReportHookPresets' => 'get95', + 'subEntry51' => 'get96', + 'ReportPluginData' => 'get97', + 'subEntry52' => 'get98', + 'ReportServiceData' => 'get99', + 'subEntry53' => 'get100', + 'ReportServiceTags' => 'get101', + 'subEntry54' => 'get102', + 'ReportSummary' => 'get103', 'subEntry55' => 'get104', 'subEntry56' => 'get105', 'subEntry57' => 'get106', @@ -120,8 +120,8 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry65' => 'get114', 'subEntry66' => 'get115', 'subEntry67' => 'get116', - 'Testing\\CollectTesting10' => 'get117', - 'subEntry68' => 'get118', + 'subEntry68' => 'get117', + 'Testing\\CollectTesting10' => 'get118', 'subEntry69' => 'get119', 'subEntry70' => 'get120', 'subEntry71' => 'get121', @@ -134,8 +134,8 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry78' => 'get128', 'subEntry79' => 'get129', 'subEntry80' => 'get130', - 'Testing\\CollectTesting11' => 'get131', - 'subEntry81' => 'get132', + 'subEntry81' => 'get131', + 'Testing\\CollectTesting11' => 'get132', 'subEntry82' => 'get133', 'subEntry83' => 'get134', 'subEntry84' => 'get135', @@ -148,11 +148,11 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry91' => 'get142', 'subEntry92' => 'get143', 'subEntry93' => 'get144', - 'Testing\\CollectTesting7' => 'get145', - 'subEntry94' => 'get146', + 'subEntry94' => 'get145', + 'Testing\\CollectTesting7' => 'get146', 'subEntry95' => 'get147', - 'Testing\\CollectTesting8' => 'get148', - 'subEntry96' => 'get149', + 'subEntry96' => 'get148', + 'Testing\\CollectTesting8' => 'get149', 'subEntry97' => 'get150', 'subEntry98' => 'get151', 'subEntry99' => 'get152', @@ -165,8 +165,8 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry106' => 'get159', 'subEntry107' => 'get160', 'subEntry108' => 'get161', - 'Testing\\CollectTesting9' => 'get162', - 'subEntry109' => 'get163', + 'subEntry109' => 'get162', + 'Testing\\CollectTesting9' => 'get163', 'subEntry110' => 'get164', 'subEntry111' => 'get165', 'subEntry112' => 'get166', @@ -179,12 +179,12 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry119' => 'get173', 'subEntry120' => 'get174', 'subEntry121' => 'get175', - 'Generate|module' => 'get176', - 'Generate|profile' => 'get177', - 'Collect\\HooksCollector' => 'get178', - 'Collect' => 'get179', - 'Collect.unversioned' => 'get180', - 'subEntry122' => 'get181', + 'subEntry122' => 'get176', + 'Generate|module' => 'get177', + 'Generate|profile' => 'get178', + 'Collect\\HooksCollector' => 'get179', + 'Collect' => 'get180', + 'Collect.unversioned' => 'get181', 'subEntry123' => 'get182', 'subEntry124' => 'get183', 'subEntry125' => 'get184', @@ -197,16 +197,18 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry132' => 'get191', 'subEntry133' => 'get192', 'subEntry134' => 'get193', - 'Testing\\CollectTesting' => 'get194', - 'DrupalCodeBuilder\\Task\\Collect\\HooksCollector' => 'get195', - 'DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler' => 'get196', - 'subEntry135' => 'get197', - 'DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter' => 'get198', - 'DrupalCodeBuilder\\Task\\Collect\\MethodCollector' => 'get199', - 'DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser' => 'get200', - 'subEntry136' => 'get201', - 'DrupalCodeBuilder\\Task\\ReportHookData' => 'get202', - 'subEntry137' => 'get203', + 'subEntry135' => 'get194', + 'Testing\\CollectTesting' => 'get195', + 'DrupalCodeBuilder\\Task\\Collect\\HooksCollector' => 'get196', + 'DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler' => 'get197', + 'subEntry136' => 'get198', + 'subEntry137' => 'get199', + 'DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter' => 'get200', + 'DrupalCodeBuilder\\Task\\Collect\\MethodCollector' => 'get201', + 'DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser' => 'get202', + 'subEntry138' => 'get203', + 'DrupalCodeBuilder\\Task\\ReportHookData' => 'get204', + 'subEntry139' => 'get205', ); protected function get1() @@ -618,635 +620,640 @@ protected function get65() } protected function get69() + { + return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); + } + + protected function get70() { return $this->delegateContainer->get('generator_classmap'); } protected function get68() { - $object = new \DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get69()); + $object = new \DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get69(), $this->get70()); return $object; } - protected function get71() + protected function get72() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get72() + protected function get73() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler'); } - protected function get70() + protected function get71() { - $object = new \DrupalCodeBuilder\Task\Generate\ComponentCollector($this->get71(), $this->get72()); + $object = new \DrupalCodeBuilder\Task\Generate\ComponentCollector($this->get72(), $this->get73()); return $object; } - protected function get73() + protected function get74() { $object = new \DrupalCodeBuilder\Task\Generate\FileAssembler(); return $object; } - protected function get75() + protected function get76() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get74() + protected function get75() { - $object = new \DrupalCodeBuilder\Task\ReportAdminRoutes($this->get75()); + $object = new \DrupalCodeBuilder\Task\ReportAdminRoutes($this->get76()); return $object; } - protected function get77() + protected function get78() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get76() + protected function get77() { - $object = new \DrupalCodeBuilder\Task\ReportDataTypes($this->get77()); + $object = new \DrupalCodeBuilder\Task\ReportDataTypes($this->get78()); return $object; } - protected function get79() + protected function get80() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get78() + protected function get79() { - $object = new \DrupalCodeBuilder\Task\ReportElementTypes($this->get79()); + $object = new \DrupalCodeBuilder\Task\ReportElementTypes($this->get80()); return $object; } - protected function get81() + protected function get82() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get80() + protected function get81() { - $object = new \DrupalCodeBuilder\Task\ReportEntityTypes($this->get81()); + $object = new \DrupalCodeBuilder\Task\ReportEntityTypes($this->get82()); return $object; } - protected function get83() + protected function get84() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get82() + protected function get83() { - $object = new \DrupalCodeBuilder\Task\ReportEventNames($this->get83()); + $object = new \DrupalCodeBuilder\Task\ReportEventNames($this->get84()); return $object; } - protected function get85() + protected function get86() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get84() + protected function get85() { - $object = new \DrupalCodeBuilder\Task\ReportFieldTypes($this->get85()); + $object = new \DrupalCodeBuilder\Task\ReportFieldTypes($this->get86()); return $object; } - protected function get87() + protected function get88() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get86() + protected function get87() { - $object = new \DrupalCodeBuilder\Task\ReportHookClassMethodData($this->get87()); + $object = new \DrupalCodeBuilder\Task\ReportHookClassMethodData($this->get88()); return $object; } - protected function get89() + protected function get90() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get88() + protected function get89() { - $object = new \DrupalCodeBuilder\Task\ReportHookData($this->get89()); + $object = new \DrupalCodeBuilder\Task\ReportHookData($this->get90()); return $object; } - protected function get91() + protected function get92() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get90() + protected function get91() { - $object = new \DrupalCodeBuilder\Task\ReportHookDataFolder($this->get91()); + $object = new \DrupalCodeBuilder\Task\ReportHookDataFolder($this->get92()); return $object; } - protected function get93() + protected function get94() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\ReportHookData'); } - protected function get92() + protected function get93() { - $object = new \DrupalCodeBuilder\Task\ReportHookGroups($this->get93()); + $object = new \DrupalCodeBuilder\Task\ReportHookGroups($this->get94()); return $object; } - protected function get95() + protected function get96() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get94() + protected function get95() { - $object = new \DrupalCodeBuilder\Task\ReportHookPresets($this->get95()); + $object = new \DrupalCodeBuilder\Task\ReportHookPresets($this->get96()); return $object; } - protected function get97() + protected function get98() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get96() + protected function get97() { - $object = new \DrupalCodeBuilder\Task\ReportPluginData($this->get97()); + $object = new \DrupalCodeBuilder\Task\ReportPluginData($this->get98()); return $object; } - protected function get99() + protected function get100() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get98() + protected function get99() { - $object = new \DrupalCodeBuilder\Task\ReportServiceData($this->get99()); + $object = new \DrupalCodeBuilder\Task\ReportServiceData($this->get100()); return $object; } - protected function get101() + protected function get102() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get100() + protected function get101() { - $object = new \DrupalCodeBuilder\Task\ReportServiceTags($this->get101()); + $object = new \DrupalCodeBuilder\Task\ReportServiceTags($this->get102()); return $object; } - protected function get103() + protected function get104() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get105() + protected function get106() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get106() + protected function get107() { return $this->delegateContainer->get('ReportAdminRoutes'); } - protected function get107() + protected function get108() { return $this->delegateContainer->get('ReportDataTypes'); } - protected function get108() + protected function get109() { return $this->delegateContainer->get('ReportElementTypes'); } - protected function get109() + protected function get110() { return $this->delegateContainer->get('ReportEntityTypes'); } - protected function get110() + protected function get111() { return $this->delegateContainer->get('ReportEventNames'); } - protected function get111() + protected function get112() { return $this->delegateContainer->get('ReportFieldTypes'); } - protected function get112() + protected function get113() { return $this->delegateContainer->get('ReportHookClassMethodData'); } - protected function get113() + protected function get114() { return $this->delegateContainer->get('ReportHookData'); } - protected function get114() + protected function get115() { return $this->delegateContainer->get('ReportPluginData'); } - protected function get115() + protected function get116() { return $this->delegateContainer->get('ReportServiceData'); } - protected function get116() + protected function get117() { return $this->delegateContainer->get('ReportServiceTags'); } - protected function get104() + protected function get105() { return [ - 'Analyse\\TestTraits' => $this->get105(), - 'ReportAdminRoutes' => $this->get106(), - 'ReportDataTypes' => $this->get107(), - 'ReportElementTypes' => $this->get108(), - 'ReportEntityTypes' => $this->get109(), - 'ReportEventNames' => $this->get110(), - 'ReportFieldTypes' => $this->get111(), - 'ReportHookClassMethodData' => $this->get112(), - 'ReportHookData' => $this->get113(), - 'ReportPluginData' => $this->get114(), - 'ReportServiceData' => $this->get115(), - 'ReportServiceTags' => $this->get116(), + 'Analyse\\TestTraits' => $this->get106(), + 'ReportAdminRoutes' => $this->get107(), + 'ReportDataTypes' => $this->get108(), + 'ReportElementTypes' => $this->get109(), + 'ReportEntityTypes' => $this->get110(), + 'ReportEventNames' => $this->get111(), + 'ReportFieldTypes' => $this->get112(), + 'ReportHookClassMethodData' => $this->get113(), + 'ReportHookData' => $this->get114(), + 'ReportPluginData' => $this->get115(), + 'ReportServiceData' => $this->get116(), + 'ReportServiceTags' => $this->get117(), ]; } - protected function get102() + protected function get103() { - $object = new \DrupalCodeBuilder\Task\ReportSummary($this->get103()); - $object->setReportHelpers($this->get104()); + $object = new \DrupalCodeBuilder\Task\ReportSummary($this->get104()); + $object->setReportHelpers($this->get105()); return $object; } - protected function get118() + protected function get119() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get120() + protected function get121() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get121() + protected function get122() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get122() + protected function get123() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get123() + protected function get124() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get124() + protected function get125() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get125() + protected function get126() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get126() + protected function get127() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get127() + protected function get128() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get128() + protected function get129() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get129() + protected function get130() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get130() + protected function get131() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get119() + protected function get120() { return [ - 'Analyse\\TestTraits' => $this->get120(), - 'Collect\\AdminRoutesCollector' => $this->get121(), - 'Collect\\DataTypesCollector' => $this->get122(), - 'Collect\\ElementTypesCollector' => $this->get123(), - 'Collect\\EntityTypesCollector' => $this->get124(), - 'Collect\\EventNamesCollector' => $this->get125(), - 'Collect\\FieldTypesCollector' => $this->get126(), - 'Collect\\PluginTypesCollector' => $this->get127(), - 'Collect\\ServiceTagTypesCollector' => $this->get128(), - 'Collect\\ServicesCollector' => $this->get129(), - 'Collect\\HooksCollector' => $this->get130(), + 'Analyse\\TestTraits' => $this->get121(), + 'Collect\\AdminRoutesCollector' => $this->get122(), + 'Collect\\DataTypesCollector' => $this->get123(), + 'Collect\\ElementTypesCollector' => $this->get124(), + 'Collect\\EntityTypesCollector' => $this->get125(), + 'Collect\\EventNamesCollector' => $this->get126(), + 'Collect\\FieldTypesCollector' => $this->get127(), + 'Collect\\PluginTypesCollector' => $this->get128(), + 'Collect\\ServiceTagTypesCollector' => $this->get129(), + 'Collect\\ServicesCollector' => $this->get130(), + 'Collect\\HooksCollector' => $this->get131(), ]; } - protected function get117() + protected function get118() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting10($this->get118()); - $object->setCollectors($this->get119()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting10($this->get119()); + $object->setCollectors($this->get120()); return $object; } - protected function get132() + protected function get133() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get134() + protected function get135() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get135() + protected function get136() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get136() + protected function get137() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get137() + protected function get138() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get138() + protected function get139() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get139() + protected function get140() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get140() + protected function get141() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get141() + protected function get142() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get142() + protected function get143() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get143() + protected function get144() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get144() + protected function get145() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get133() + protected function get134() { return [ - 'Analyse\\TestTraits' => $this->get134(), - 'Collect\\AdminRoutesCollector' => $this->get135(), - 'Collect\\DataTypesCollector' => $this->get136(), - 'Collect\\ElementTypesCollector' => $this->get137(), - 'Collect\\EntityTypesCollector' => $this->get138(), - 'Collect\\EventNamesCollector' => $this->get139(), - 'Collect\\FieldTypesCollector' => $this->get140(), - 'Collect\\PluginTypesCollector' => $this->get141(), - 'Collect\\ServiceTagTypesCollector' => $this->get142(), - 'Collect\\ServicesCollector' => $this->get143(), - 'Collect\\HooksCollector' => $this->get144(), + 'Analyse\\TestTraits' => $this->get135(), + 'Collect\\AdminRoutesCollector' => $this->get136(), + 'Collect\\DataTypesCollector' => $this->get137(), + 'Collect\\ElementTypesCollector' => $this->get138(), + 'Collect\\EntityTypesCollector' => $this->get139(), + 'Collect\\EventNamesCollector' => $this->get140(), + 'Collect\\FieldTypesCollector' => $this->get141(), + 'Collect\\PluginTypesCollector' => $this->get142(), + 'Collect\\ServiceTagTypesCollector' => $this->get143(), + 'Collect\\ServicesCollector' => $this->get144(), + 'Collect\\HooksCollector' => $this->get145(), ]; } - protected function get131() + protected function get132() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting11($this->get132()); - $object->setCollectors($this->get133()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting11($this->get133()); + $object->setCollectors($this->get134()); return $object; } - protected function get146() + protected function get147() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get147() + protected function get148() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\HooksCollector'); } - protected function get145() + protected function get146() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting7($this->get146(), $this->get147()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting7($this->get147(), $this->get148()); return $object; } - protected function get149() + protected function get150() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get151() + protected function get152() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get152() + protected function get153() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get153() + protected function get154() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get154() + protected function get155() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get155() + protected function get156() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get156() + protected function get157() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get157() + protected function get158() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get158() + protected function get159() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get159() + protected function get160() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get160() + protected function get161() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get161() + protected function get162() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get150() + protected function get151() { return [ - 'Analyse\\TestTraits' => $this->get151(), - 'Collect\\AdminRoutesCollector' => $this->get152(), - 'Collect\\DataTypesCollector' => $this->get153(), - 'Collect\\ElementTypesCollector' => $this->get154(), - 'Collect\\EntityTypesCollector' => $this->get155(), - 'Collect\\EventNamesCollector' => $this->get156(), - 'Collect\\FieldTypesCollector' => $this->get157(), - 'Collect\\PluginTypesCollector' => $this->get158(), - 'Collect\\ServiceTagTypesCollector' => $this->get159(), - 'Collect\\ServicesCollector' => $this->get160(), - 'Collect\\HooksCollector' => $this->get161(), + 'Analyse\\TestTraits' => $this->get152(), + 'Collect\\AdminRoutesCollector' => $this->get153(), + 'Collect\\DataTypesCollector' => $this->get154(), + 'Collect\\ElementTypesCollector' => $this->get155(), + 'Collect\\EntityTypesCollector' => $this->get156(), + 'Collect\\EventNamesCollector' => $this->get157(), + 'Collect\\FieldTypesCollector' => $this->get158(), + 'Collect\\PluginTypesCollector' => $this->get159(), + 'Collect\\ServiceTagTypesCollector' => $this->get160(), + 'Collect\\ServicesCollector' => $this->get161(), + 'Collect\\HooksCollector' => $this->get162(), ]; } - protected function get148() + protected function get149() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting8($this->get149()); - $object->setCollectors($this->get150()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting8($this->get150()); + $object->setCollectors($this->get151()); return $object; } - protected function get163() + protected function get164() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get165() + protected function get166() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get166() + protected function get167() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get167() + protected function get168() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get168() + protected function get169() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get169() + protected function get170() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get170() + protected function get171() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get171() + protected function get172() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get172() + protected function get173() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get173() + protected function get174() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get174() + protected function get175() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get175() + protected function get176() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get164() + protected function get165() { return [ - 'Analyse\\TestTraits' => $this->get165(), - 'Collect\\AdminRoutesCollector' => $this->get166(), - 'Collect\\DataTypesCollector' => $this->get167(), - 'Collect\\ElementTypesCollector' => $this->get168(), - 'Collect\\EntityTypesCollector' => $this->get169(), - 'Collect\\EventNamesCollector' => $this->get170(), - 'Collect\\FieldTypesCollector' => $this->get171(), - 'Collect\\PluginTypesCollector' => $this->get172(), - 'Collect\\ServiceTagTypesCollector' => $this->get173(), - 'Collect\\ServicesCollector' => $this->get174(), - 'Collect\\HooksCollector' => $this->get175(), + 'Analyse\\TestTraits' => $this->get166(), + 'Collect\\AdminRoutesCollector' => $this->get167(), + 'Collect\\DataTypesCollector' => $this->get168(), + 'Collect\\ElementTypesCollector' => $this->get169(), + 'Collect\\EntityTypesCollector' => $this->get170(), + 'Collect\\EventNamesCollector' => $this->get171(), + 'Collect\\FieldTypesCollector' => $this->get172(), + 'Collect\\PluginTypesCollector' => $this->get173(), + 'Collect\\ServiceTagTypesCollector' => $this->get174(), + 'Collect\\ServicesCollector' => $this->get175(), + 'Collect\\HooksCollector' => $this->get176(), ]; } - protected function get162() + protected function get163() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting9($this->get163()); - $object->setCollectors($this->get164()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting9($this->get164()); + $object->setCollectors($this->get165()); return $object; } - protected function get176() + protected function get177() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1256,7 +1263,7 @@ protected function get176() ]); } - protected function get177() + protected function get178() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1266,7 +1273,7 @@ protected function get177() ]); } - protected function get178() + protected function get179() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1274,7 +1281,7 @@ protected function get178() ], 'Collect\\HooksCollector'); } - protected function get179() + protected function get180() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1282,91 +1289,91 @@ protected function get179() ], 'Collect'); } - protected function get181() + protected function get182() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get183() + protected function get184() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get184() + protected function get185() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get185() + protected function get186() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get186() + protected function get187() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get187() + protected function get188() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get188() + protected function get189() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get189() + protected function get190() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get190() + protected function get191() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get191() + protected function get192() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get192() + protected function get193() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get193() + protected function get194() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get182() + protected function get183() { return [ - 'Analyse\\TestTraits' => $this->get183(), - 'Collect\\AdminRoutesCollector' => $this->get184(), - 'Collect\\DataTypesCollector' => $this->get185(), - 'Collect\\ElementTypesCollector' => $this->get186(), - 'Collect\\EntityTypesCollector' => $this->get187(), - 'Collect\\EventNamesCollector' => $this->get188(), - 'Collect\\FieldTypesCollector' => $this->get189(), - 'Collect\\PluginTypesCollector' => $this->get190(), - 'Collect\\ServiceTagTypesCollector' => $this->get191(), - 'Collect\\ServicesCollector' => $this->get192(), - 'Collect\\HooksCollector' => $this->get193(), + 'Analyse\\TestTraits' => $this->get184(), + 'Collect\\AdminRoutesCollector' => $this->get185(), + 'Collect\\DataTypesCollector' => $this->get186(), + 'Collect\\ElementTypesCollector' => $this->get187(), + 'Collect\\EntityTypesCollector' => $this->get188(), + 'Collect\\EventNamesCollector' => $this->get189(), + 'Collect\\FieldTypesCollector' => $this->get190(), + 'Collect\\PluginTypesCollector' => $this->get191(), + 'Collect\\ServiceTagTypesCollector' => $this->get192(), + 'Collect\\ServicesCollector' => $this->get193(), + 'Collect\\HooksCollector' => $this->get194(), ]; } - protected function get180() + protected function get181() { - $object = new \DrupalCodeBuilder\Task\Collect($this->get181()); - $object->setCollectors($this->get182()); + $object = new \DrupalCodeBuilder\Task\Collect($this->get182()); + $object->setCollectors($this->get183()); return $object; } - protected function get194() + protected function get195() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1374,53 +1381,58 @@ protected function get194() ], 'Testing\\CollectTesting'); } - protected function get195() + protected function get196() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get197() + protected function get198() + { + return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); + } + + protected function get199() { return $this->delegateContainer->get('generator_classmap'); } - protected function get196() + protected function get197() { - $object = new DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get197()); + $object = new DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get198(), $this->get199()); return $object; } - protected function get198() + protected function get200() { $object = new DrupalCodeBuilder\Task\Collect\ContainerBuilderGetter(); return $object; } - protected function get199() + protected function get201() { $object = new DrupalCodeBuilder\Task\Collect\MethodCollector(); return $object; } - protected function get201() + protected function get203() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get200() + protected function get202() { - $object = new DrupalCodeBuilder\Task\Collect\CodeAnalyser($this->get201()); + $object = new DrupalCodeBuilder\Task\Collect\CodeAnalyser($this->get203()); return $object; } - protected function get203() + protected function get205() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get202() + protected function get204() { - $object = new DrupalCodeBuilder\Task\ReportHookData($this->get203()); + $object = new DrupalCodeBuilder\Task\ReportHookData($this->get205()); return $object; } diff --git a/Task/Generate/ComponentClassHandler.php b/Task/Generate/ComponentClassHandler.php index 92114423..d54e233f 100644 --- a/Task/Generate/ComponentClassHandler.php +++ b/Task/Generate/ComponentClassHandler.php @@ -2,9 +2,10 @@ namespace DrupalCodeBuilder\Task\Generate; +use DI\Attribute\Inject; use DrupalCodeBuilder\Definition\MergingGeneratorDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; -use DI\Attribute\Inject; +use DrupalCodeBuilder\Environment\EnvironmentInterface; /** * Task helper for working with generator classes and instantiating them. @@ -14,6 +15,8 @@ class ComponentClassHandler { /** * Constructor. * + * @param EnvironmentInterface $environment + * The environment object. * @param array $generator_classmap * The classmap of version-specific generator classes. Keys are the base * class name, then the version, value is the short class name of the @@ -27,6 +30,7 @@ class ComponentClassHandler { * @endcode */ public function __construct( + protected EnvironmentInterface $environment, #[Inject('generator_classmap')] protected array $generator_classmap ) { } From 385a7dad0862d6eef9fc51e828e684ec21b2a5d7 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 9 Nov 2025 18:00:09 +0000 Subject: [PATCH 095/144] Added interface for generators to get the environment injected. Fixes #338. --- Generator/EnvironmentAware.php | 26 +++++++++++++++++++++++++ Generator/EnvironmentAwareTrait.php | 24 +++++++++++++++++++++++ Generator/ExtensionCodeFile.php | 6 ++++-- Generator/Hooks.php | 10 ++++++---- Task/Generate/ComponentClassHandler.php | 6 ++++++ 5 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 Generator/EnvironmentAware.php create mode 100644 Generator/EnvironmentAwareTrait.php diff --git a/Generator/EnvironmentAware.php b/Generator/EnvironmentAware.php new file mode 100644 index 00000000..84b0e31b --- /dev/null +++ b/Generator/EnvironmentAware.php @@ -0,0 +1,26 @@ +environment = $environment; + } + +} diff --git a/Generator/ExtensionCodeFile.php b/Generator/ExtensionCodeFile.php index 02e1b752..4bc6ab9c 100644 --- a/Generator/ExtensionCodeFile.php +++ b/Generator/ExtensionCodeFile.php @@ -9,7 +9,9 @@ /** * Generator class for procedural code files. */ -class ExtensionCodeFile extends PHPFile { +class ExtensionCodeFile extends PHPFile implements EnvironmentAware { + + use EnvironmentAwareTrait; /** * Whether this file is merged with existing code. @@ -265,7 +267,7 @@ function fileDocblockSummary() { * {@inheritdoc} */ function codeFooter() { - $footer = \DrupalCodeBuilder\Factory::getEnvironment()->getSetting('footer', NULL); + $footer = $this->environment->getSetting('footer', NULL); return $footer; } diff --git a/Generator/Hooks.php b/Generator/Hooks.php index 58c8ea35..db70c266 100644 --- a/Generator/Hooks.php +++ b/Generator/Hooks.php @@ -26,7 +26,9 @@ * * @see DrupalCodeBuilder\Generator\ExtensionCodeFile */ -class Hooks extends BaseGenerator { +class Hooks extends BaseGenerator implements EnvironmentAware { + + use EnvironmentAwareTrait; /** * {@inheritdoc} @@ -107,7 +109,7 @@ public function requiredComponents(): array { // Strip out INFO: comments for advanced users. // This has to be done before we split this into lines. // TODO: No need to do this if this is hook api.php file sample code! - if (!\DrupalCodeBuilder\Factory::getEnvironment()->getSetting('detail_level', 0)) { + if (!$this->environment->getSetting('detail_level', 0)) { // Used to strip INFO messages out of generated file for advanced users. $pattern = '#\s+/\* INFO:(.*?)\*/#ms'; $hook['template'] = preg_replace($pattern, '', $hook['template']); @@ -398,8 +400,8 @@ function getTemplates($requested_hook_list) { // node.hooks.template will only override that same file in the module data; // if the hook is not requested as part of a group then that file will not be considered. // (Though groups are broken for now...) - $version = \DrupalCodeBuilder\Factory::getEnvironment()->getCoreMajorVersion(); - $template_base_path_module = \DrupalCodeBuilder\Factory::getEnvironment()->getPath('templates') . '/' . $version; + $version = $this->environment->getCoreMajorVersion(); + $template_base_path_module = $this->environment->getPath('templates') . '/' . $version; //print "base path: $template_base_path_module"; // $template_base_paths['module'] // $template_base_paths['user'] diff --git a/Task/Generate/ComponentClassHandler.php b/Task/Generate/ComponentClassHandler.php index d54e233f..61ff8d6a 100644 --- a/Task/Generate/ComponentClassHandler.php +++ b/Task/Generate/ComponentClassHandler.php @@ -6,6 +6,7 @@ use DrupalCodeBuilder\Definition\MergingGeneratorDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Environment\EnvironmentInterface; +use DrupalCodeBuilder\Generator\EnvironmentAware; /** * Task helper for working with generator classes and instantiating them. @@ -103,6 +104,11 @@ public function getGenerator($component_type, $component_data) { // Inject the class handler. $generator->setClassHandler($this); + // Inject the environment if needed. + if ($generator instanceof EnvironmentAware) { + $generator->setEnvironment($this->environment); + } + return $generator; } From b3bf0e5db1c723ae8027f21603f01ac3a766b96a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 9 Nov 2025 18:08:54 +0000 Subject: [PATCH 096/144] Changed generators to only get class handler injected if they request it. --- Generator/BaseGenerator.php | 18 ----------------- Generator/ClassHandlerAware.php | 26 +++++++++++++++++++++++++ Generator/ClassHandlerAwareTrait.php | 23 ++++++++++++++++++++++ Generator/HookImplementationBase.php | 4 +++- Generator/Hooks.php | 3 ++- Generator/PluginClassDiscovery.php | 4 +++- Task/Generate/ComponentClassHandler.php | 7 +++++-- 7 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 Generator/ClassHandlerAware.php create mode 100644 Generator/ClassHandlerAwareTrait.php diff --git a/Generator/BaseGenerator.php b/Generator/BaseGenerator.php index 73012062..a8353ea2 100644 --- a/Generator/BaseGenerator.php +++ b/Generator/BaseGenerator.php @@ -7,7 +7,6 @@ use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Exception\MergeDataLossException; use DrupalCodeBuilder\File\DrupalExtension; -use DrupalCodeBuilder\Task\Generate\ComponentClassHandler; use MutableTypedData\Data\DataItem; /** @@ -160,13 +159,6 @@ abstract class BaseGenerator implements GeneratorInterface { */ protected $containedComponents = []; - /** - * The class handler. - * - * @var \DrupalCodeBuilder\Task\Generate\ComponentClassHandler - */ - protected $classHandler; - /** * The existing extension, if applicable. * @@ -207,16 +199,6 @@ function __construct(DataItem $component_data) { $this->type = preg_replace('@\d+$@', '', $short_class); } - /** - * Sets the class handler. - * - * @param \DrupalCodeBuilder\Task\Generate\ComponentClassHandler $class_handler - * The class handler. - */ - public function setClassHandler(ComponentClassHandler $class_handler) { - $this->classHandler = $class_handler; - } - /** * Gets the address of this component's data. */ diff --git a/Generator/ClassHandlerAware.php b/Generator/ClassHandlerAware.php new file mode 100644 index 00000000..6964c675 --- /dev/null +++ b/Generator/ClassHandlerAware.php @@ -0,0 +1,26 @@ +classHandler = $class_handler; + } + +} diff --git a/Generator/HookImplementationBase.php b/Generator/HookImplementationBase.php index 257625cb..debb79a5 100644 --- a/Generator/HookImplementationBase.php +++ b/Generator/HookImplementationBase.php @@ -21,7 +21,9 @@ * Furthermore, hooks that collect contents and can be procedural or OO use * a hook body class, e.g. hook_theme(). */ -abstract class HookImplementationBase extends PHPFunction { +abstract class HookImplementationBase extends PHPFunction implements ClassHandlerAware { + + use ClassHandlerAwareTrait; /** * {@inheritdoc} diff --git a/Generator/Hooks.php b/Generator/Hooks.php index db70c266..6e2b9e7d 100644 --- a/Generator/Hooks.php +++ b/Generator/Hooks.php @@ -26,8 +26,9 @@ * * @see DrupalCodeBuilder\Generator\ExtensionCodeFile */ -class Hooks extends BaseGenerator implements EnvironmentAware { +class Hooks extends BaseGenerator implements ClassHandlerAware, EnvironmentAware { + use ClassHandlerAwareTrait; use EnvironmentAwareTrait; /** diff --git a/Generator/PluginClassDiscovery.php b/Generator/PluginClassDiscovery.php index 631f975e..3a1662a0 100644 --- a/Generator/PluginClassDiscovery.php +++ b/Generator/PluginClassDiscovery.php @@ -12,7 +12,9 @@ /** * Common base class for annotation and attribute plugins. */ -abstract class PluginClassDiscovery extends PluginClassBase { +abstract class PluginClassDiscovery extends PluginClassBase implements ClassHandlerAware { + + use ClassHandlerAwareTrait; function __construct($component_data) { // Set some default properties. diff --git a/Task/Generate/ComponentClassHandler.php b/Task/Generate/ComponentClassHandler.php index 61ff8d6a..b616d2a5 100644 --- a/Task/Generate/ComponentClassHandler.php +++ b/Task/Generate/ComponentClassHandler.php @@ -6,6 +6,7 @@ use DrupalCodeBuilder\Definition\MergingGeneratorDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Environment\EnvironmentInterface; +use DrupalCodeBuilder\Generator\ClassHandlerAware; use DrupalCodeBuilder\Generator\EnvironmentAware; /** @@ -101,8 +102,10 @@ public function getGenerator($component_type, $component_data) { $generator = new $class($component_data, $this); - // Inject the class handler. - $generator->setClassHandler($this); + // Inject the class handler if needed. + if ($generator instanceof ClassHandlerAware) { + $generator->setClassHandler($this); + } // Inject the environment if needed. if ($generator instanceof EnvironmentAware) { From 02d84935e1e1ed99cce8d49a328618934a6e3559 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 2 Nov 2025 17:37:29 +0000 Subject: [PATCH 097/144] Added docs. --- Generator/Render/PhpAttributes.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Generator/Render/PhpAttributes.php b/Generator/Render/PhpAttributes.php index f628f6d3..9f2f6c75 100644 --- a/Generator/Render/PhpAttributes.php +++ b/Generator/Render/PhpAttributes.php @@ -79,14 +79,17 @@ public function render() { $class_name = $class_name_prefix . $this->attributeClassName; if (empty($this->data)) { + // Just the attribute class. $lines[] = '#[' . $class_name . ']'; } elseif (is_scalar($this->data)) { + // Attribute with a single scalar value. $lines[] = '#[' . $class_name . '(' . PhpValue::create($this->data)->renderInline() . ')]'; } elseif ($this->forceInline) { + // Attribute with an inline array. $lines[] = '#[' . $class_name . '(' . @@ -94,6 +97,7 @@ public function render() { ')]'; } else { + // Attribute with multi-line data. $lines[] = '#[' . $class_name . '('; $this->renderAttributeParameters($lines, $this->data); From 88c12965f1752187c558a399b5fefcac055f09d5 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 2 Nov 2025 17:49:48 +0000 Subject: [PATCH 098/144] Added docs. --- Generator/Render/PhpRenderer.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Generator/Render/PhpRenderer.php b/Generator/Render/PhpRenderer.php index da81da14..85464705 100644 --- a/Generator/Render/PhpRenderer.php +++ b/Generator/Render/PhpRenderer.php @@ -10,6 +10,16 @@ abstract class PhpRenderer { /** * Renders a scalar or NULL value as a PHP string. * + * To render: + * - A class or an expression starting with a class such as a class constant + * or the special '::class' expression, pass the fully-qualified classname + * or class constant as a string starting with '\\'. + * - A variable, pass the variable name as a string starting with '£' instead + * of '$'. + * - A boolean, pass either a literal boolean or a string such as 'TRUE'. + * - NULL, either the NULL value or the string 'NULL'. + * - A numeric value, either the value or the quoted value. + * * @param mixed $value * The value to render. * From 06190ec5573622b124b8207e273585ac0d5e12b6 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 2 Nov 2025 18:00:31 +0000 Subject: [PATCH 099/144] Added further testing of attribute renderer. --- Test/Unit/RenderAttributesTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Test/Unit/RenderAttributesTest.php b/Test/Unit/RenderAttributesTest.php index a9c4c2e7..32727cbe 100644 --- a/Test/Unit/RenderAttributesTest.php +++ b/Test/Unit/RenderAttributesTest.php @@ -26,6 +26,8 @@ public function testAttributeWithNesting() { 'purr' => 'value', ], 'star_trek_name' => "T'Pau", + 'fluffy' => TRUE, + 'class' => '\Drupal\my_module\KittyHandler::class', ], [ 'id' => 'The plugin ID.', @@ -44,6 +46,8 @@ public function testAttributeWithNesting() { $attribute = implode("\n", $lines); + // Note that the class won't get extracted as fully-qualified class + // extraction is handled at the PHP file level. $expected_attribute = << 'value', ], star_trek_name: "T'Pau", + fluffy: TRUE, + class: \Drupal\my_module\KittyHandler::class, )] EOT; From 0c7c8d592d719a244a97eb3c5854b406d53cebe9 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 2 Nov 2025 18:01:33 +0000 Subject: [PATCH 100/144] Fixed docs. --- Test/Unit/RenderAttributesTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Test/Unit/RenderAttributesTest.php b/Test/Unit/RenderAttributesTest.php index 32727cbe..3b256601 100644 --- a/Test/Unit/RenderAttributesTest.php +++ b/Test/Unit/RenderAttributesTest.php @@ -11,7 +11,7 @@ class RenderAttributesTest extends TestCase { /** - * Tests an annotation with nested annotations. + * Tests an attribute with nested objects. */ public function testAttributeWithNesting() { $attribute = PhpAttributes::class( @@ -68,6 +68,9 @@ class: \Drupal\my_module\KittyHandler::class, $this->assertEquals($expected_attribute, $attribute); } + /** + * Test attributes with inline values. + */ public function testAttributeInline() { // With inline implicit. $attribute = PhpAttributes::class( From 860f4162e80a9918964861fa9267ef01f487f385 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Thu, 6 Nov 2025 16:35:29 +0000 Subject: [PATCH 101/144] Added return type. --- Generator/Render/PhpAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/Render/PhpAttributes.php b/Generator/Render/PhpAttributes.php index 9f2f6c75..c8a2217a 100644 --- a/Generator/Render/PhpAttributes.php +++ b/Generator/Render/PhpAttributes.php @@ -72,7 +72,7 @@ public function forceInline(): self { /** * Renders the attribute to an array of code lines. */ - public function render() { + public function render(): array { $lines = []; $class_name_prefix = str_starts_with($this->attributeClassName, '\\') ? '' : '\\'; From 3500acd9152ce90442845019229a22c529295d53 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 9 Nov 2025 13:03:49 +0000 Subject: [PATCH 102/144] Fixed annotation renderer not handling boolean and null values. --- Generator/Render/ClassAnnotation.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Generator/Render/ClassAnnotation.php b/Generator/Render/ClassAnnotation.php index 43b759a8..9fddd077 100644 --- a/Generator/Render/ClassAnnotation.php +++ b/Generator/Render/ClassAnnotation.php @@ -157,6 +157,15 @@ protected function renderArray(&$docblock_lines, array $data, $nesting = 0, $ann $declaration_line .= "{$value},"; $docblock_lines[] = $declaration_line; } + elseif (is_bool($value) || is_null($value)) { + $declaration_line .= match ($value) { + NULL => 'NULL', + FALSE => 'FALSE', + TRUE => 'TRUE', + }; + $declaration_line .= ','; + $docblock_lines[] = $declaration_line; + } elseif (is_object($value)) { // Child annotation. The nesting level doesn't increase here, as the // child annotation class is just on the same line as the key. From 7d880f8e481f664dfb764d06cf8f271f66e16768 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Tue, 25 Nov 2025 17:25:30 +0000 Subject: [PATCH 103/144] Added description to property. --- Generator/Service.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Generator/Service.php b/Generator/Service.php index ba714924..1b257a33 100644 --- a/Generator/Service.php +++ b/Generator/Service.php @@ -121,6 +121,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ), 'decorates' => PropertyDefinition::create('string') ->setLabel('Decorated service') + ->setDescription("The name of a service that this service decorates.") ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')), // The parent service name. 'parent' => PropertyDefinition::create('string') From 5a7e9df7a10fe2f8949114bb70d12cf15b612c9b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Thu, 11 Dec 2025 14:38:16 +0000 Subject: [PATCH 104/144] Added default class name for hooks classes. --- Generator/HooksClass.php | 18 +++++++++++++++++- Test/Unit/ComponentHooks11Test.php | 16 ++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Generator/HooksClass.php b/Generator/HooksClass.php index bccec363..30cbfabb 100644 --- a/Generator/HooksClass.php +++ b/Generator/HooksClass.php @@ -31,7 +31,17 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLabel("Hooks class name") ->setInternal(FALSE) ->setDescription("The hooks class's plain class name, e.g. \"MyHooks\".") - ->removeDefault(); + ->setCallableDefault(function ($component_data) { + // Add a suffix to the default class name based on the human-readable + // index. + $delta = $component_data->getParent()->getName(); + $suffix = match ($delta) { + '0' => '', + default => $delta + 1, + }; + + return $component_data->getParent()->root_name_pascal->value . 'Hooks' . $suffix; + }); $definition->getProperty('relative_namespace') ->setDefault(DefaultDefinition::create() ->setLiteral('Hook') @@ -60,6 +70,12 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $component_data->class_component_address = '..:..'; }), ); + + $definition->addProperty(PropertyDefinition::create('string') + ->setName('root_name_pascal') + ->setInternal(TRUE) + ->setExpressionDefault("get('..:..:..:root_name_pascal')") + ); } /** diff --git a/Test/Unit/ComponentHooks11Test.php b/Test/Unit/ComponentHooks11Test.php index deb23b12..83e87436 100644 --- a/Test/Unit/ComponentHooks11Test.php +++ b/Test/Unit/ComponentHooks11Test.php @@ -33,7 +33,7 @@ public function testHookClassesOnlyOO(): void { 'hook_classes' => [ 0 => [ - 'plain_class_name' => 'AlphaHooks', + // Don't give a hooks class name to test the incrementing default. 'injected_services' => [ 'current_user', 'entity_type.manager', @@ -62,6 +62,13 @@ public function testHookClassesOnlyOO(): void { ], ], ], + 1 => [ + 'hook_methods' => [ + 0 => [ + 'hook_name' => 'hook_form_alter', + ], + ], + ], ], 'readme' => FALSE, ]; @@ -70,10 +77,11 @@ public function testHookClassesOnlyOO(): void { $this->assertFiles([ 'test_module.info.yml', - 'src/Hook/AlphaHooks.php', + 'src/Hook/TestModuleHooks.php', + 'src/Hook/TestModuleHooks2.php', ], $files); - $hooks_file = $files['src/Hook/AlphaHooks.php']; + $hooks_file = $files['src/Hook/TestModuleHooks.php']; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $hooks_file); $php_tester->assertDrupalCodingStandards([ @@ -86,7 +94,7 @@ public function testHookClassesOnlyOO(): void { 'Drupal.Commenting.InlineComment.SpacingAfter', ]); - $php_tester->assertHasClass('Drupal\test_module\Hook\AlphaHooks'); + $php_tester->assertHasClass('Drupal\test_module\Hook\TestModuleHooks'); $php_tester->getMethodTester('formAlter') ->assertHasAttribute('\Drupal\Core\Hook\Attribute\Hook', ['form_alter']); $php_tester->getMethodTester('formNodeFormAlter') From 100d1b18afca42a90808d61ab4aa462d2e994781 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 14 Dec 2025 21:25:19 +0000 Subject: [PATCH 105/144] Fixed plugin deriver class name incorrectly formed when the plugin type name contains a dot. --- Generator/PluginClassDiscovery.php | 7 ++++++- Test/Unit/ComponentPluginsAttribute11Test.php | 14 ++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Generator/PluginClassDiscovery.php b/Generator/PluginClassDiscovery.php index 3a1662a0..c2a5bcb7 100644 --- a/Generator/PluginClassDiscovery.php +++ b/Generator/PluginClassDiscovery.php @@ -97,9 +97,14 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setCallable(function (DataItem $component_data) { $plugin_data = $component_data->getParent(); + $plugin_type_id = $plugin_data->plugin_type_data->value['type_id']; + + // Plugin type ID can contain dots from the service name. + $plugin_type_id = str_replace('.', '_', $plugin_type_id); + return $plugin_data->plain_class_name->value . - CaseString::snake($plugin_data->plugin_type_data->value['type_id'])->pascal() . + CaseString::snake($plugin_type_id)->pascal() . 'Deriver'; }) ), diff --git a/Test/Unit/ComponentPluginsAttribute11Test.php b/Test/Unit/ComponentPluginsAttribute11Test.php index 92373fb9..a6bb4691 100644 --- a/Test/Unit/ComponentPluginsAttribute11Test.php +++ b/Test/Unit/ComponentPluginsAttribute11Test.php @@ -277,7 +277,9 @@ function testPluginsGenerationDeriver() { ], 'plugins' => [ 0 => [ - 'plugin_type' => 'block', + // Use a plugin type whose name has a '.' in to test the formation of + // the deriver class name. + 'plugin_type' => 'field.formatter', 'plugin_name' => 'alpha', 'deriver' => TRUE, ] @@ -286,19 +288,19 @@ function testPluginsGenerationDeriver() { ]; $files = $this->generateModuleFiles($module_data); - $this->assertArrayHasKey('src/Plugin/Derivative/AlphaBlockDeriver.php', $files); + $this->assertArrayHasKey('src/Plugin/Derivative/AlphaFieldFormatterDeriver.php', $files); - $deriver = $files['src/Plugin/Derivative/AlphaBlockDeriver.php']; + $deriver = $files['src/Plugin/Derivative/AlphaFieldFormatterDeriver.php']; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $deriver); $php_tester->assertClassHasParent('Drupal\Component\Plugin\Derivative\DeriverBase'); $php_tester->assertClassHasInterfaces(['Drupal\Core\Plugin\Discovery\ContainerDeriverInterface']); $php_tester->assertHasMethod('getDerivativeDefinitions'); // Check the plugin file declares the deriver. - $plugin_file = $files["src/Plugin/Block/Alpha.php"]; + $plugin_file = $files["src/Plugin/Field/FieldFormatter/Alpha.php"]; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $plugin_file); - $php_tester->assertImportsClassLike(['Drupal\test_module\Plugin\Derivative\AlphaBlockDeriver']); - $php_tester->assertClassAttributeHasNamedParameterValue('deriver', 'AlphaBlockDeriver::class', 'Block'); + $php_tester->assertImportsClassLike(['Drupal\test_module\Plugin\Derivative\AlphaFieldFormatterDeriver']); + $php_tester->assertClassAttributeHasNamedParameterValue('deriver', 'AlphaFieldFormatterDeriver::class', 'FieldFormatter'); } /** From 37d2c6adc7e6ebcc2afffcd1904e00eb09b5a7bc Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Tue, 25 Nov 2025 17:55:27 +0000 Subject: [PATCH 106/144] Added method for getting differentiated labels for different delta items. Fixes #341. --- Definition/DeferredGeneratorDefinition.php | 2 +- Definition/MergingGeneratorDefinition.php | 2 +- Generator/BaseGenerator.php | 20 +++++++++++ Generator/DrushCommand.php | 8 +++++ Generator/EntityTypeBase.php | 7 ++++ Generator/Form.php | 7 ++++ Generator/FormElement.php | 8 +++++ Generator/HooksClass.php | 7 ++++ Generator/Library.php | 8 +++++ Generator/PHPUnitTest.php | 12 +++++++ Generator/Permission.php | 8 +++++ Generator/Plugin.php | 18 ++++++++++ Generator/PluginType.php | 7 ++++ Generator/RouterItem.php | 7 ++++ Generator/Service.php | 7 ++++ MutableTypedData/Data/DeltaLabelTrait.php | 35 +++++++++++++++++++ .../MergeableComplexDataWithArrayAccess.php | 2 ++ .../MergeableMutableDataWithArrayAccess.php | 2 ++ 18 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 MutableTypedData/Data/DeltaLabelTrait.php diff --git a/Definition/DeferredGeneratorDefinition.php b/Definition/DeferredGeneratorDefinition.php index ab3b281c..6fdd3fc9 100644 --- a/Definition/DeferredGeneratorDefinition.php +++ b/Definition/DeferredGeneratorDefinition.php @@ -26,7 +26,7 @@ class DeferredGeneratorDefinition extends PropertyDefinition implements Generato public function __construct( string $data_type, protected string $componentType, - protected string $generatorClass, + public readonly string $generatorClass, ) { parent::__construct($data_type); } diff --git a/Definition/MergingGeneratorDefinition.php b/Definition/MergingGeneratorDefinition.php index 09d4c448..d4c71b4d 100644 --- a/Definition/MergingGeneratorDefinition.php +++ b/Definition/MergingGeneratorDefinition.php @@ -37,7 +37,7 @@ class MergingGeneratorDefinition extends PropertyDefinition implements Generator public function __construct( string $data_type, protected string $componentType, - protected string $generatorClass, + public readonly string $generatorClass, ) { parent::__construct($data_type); } diff --git a/Generator/BaseGenerator.php b/Generator/BaseGenerator.php index a8353ea2..c3aed17a 100644 --- a/Generator/BaseGenerator.php +++ b/Generator/BaseGenerator.php @@ -255,6 +255,26 @@ final public static function addBasePropertiesToPropertyDefinition(PropertyListI ]); } + /** + * Gets a diffentiated label suffix for a delta item. + * + * This allows different delta items to differentiate their labels in the UI + * with more than just a delta index. + * + * This may be called for incomplete data items (such as those added into a + * form) and so must account for even required data not being present. + * + * @param \MutableTypedData\Data\DataItem $data + * The data item to get a label suffix for. + * + * @return string|null + * The label suffix, or NULL if useful data is available. It does not need + * any initial space or separator. + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return NULL; + } + public function isRootComponent(): bool { return FALSE; } diff --git a/Generator/DrushCommand.php b/Generator/DrushCommand.php index 6639ee44..c81a4b03 100644 --- a/Generator/DrushCommand.php +++ b/Generator/DrushCommand.php @@ -11,6 +11,7 @@ use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Generator\Render\DocBlock; use DrupalCodeBuilder\Generator\Render\PhpAttributes; +use MutableTypedData\Data\DataItem; use MutableTypedData\Definition\DefaultDefinition; /** @@ -157,6 +158,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLiteralDefault(['public']); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->command_name->value ?: NULL; + } + /** * Return an array of subcomponent types. */ diff --git a/Generator/EntityTypeBase.php b/Generator/EntityTypeBase.php index e3643656..52c0ac3c 100644 --- a/Generator/EntityTypeBase.php +++ b/Generator/EntityTypeBase.php @@ -371,6 +371,13 @@ protected static function getHandlerTypes() { ]; } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->entity_type_id->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/Form.php b/Generator/Form.php index 5e6eeb01..7aa1099d 100644 --- a/Generator/Form.php +++ b/Generator/Form.php @@ -74,6 +74,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLiteralDefault('\Drupal\Core\Form\FormBase'); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->plain_class_name->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/FormElement.php b/Generator/FormElement.php index d467f258..d0ff204f 100644 --- a/Generator/FormElement.php +++ b/Generator/FormElement.php @@ -4,6 +4,7 @@ use MutableTypedData\Definition\PropertyListInterface; use DrupalCodeBuilder\Definition\PropertyDefinition; +use MutableTypedData\Data\DataItem; use MutableTypedData\Definition\DefaultDefinition; use MutableTypedData\Definition\OptionsSortOrder; @@ -41,6 +42,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ]); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->form_key->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/HooksClass.php b/Generator/HooksClass.php index 30cbfabb..72664206 100644 --- a/Generator/HooksClass.php +++ b/Generator/HooksClass.php @@ -78,6 +78,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->plain_class_name->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/Library.php b/Generator/Library.php index 7fdf1ea8..6ee6dbe2 100644 --- a/Generator/Library.php +++ b/Generator/Library.php @@ -6,6 +6,7 @@ use MutableTypedData\Definition\DefaultDefinition; use DrupalCodeBuilder\Definition\MergingGeneratorDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; +use MutableTypedData\Data\DataItem; /** * Generator for a module library. @@ -55,6 +56,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ]); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->library_name->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/PHPUnitTest.php b/Generator/PHPUnitTest.php index 16baf153..67d87b94 100644 --- a/Generator/PHPUnitTest.php +++ b/Generator/PHPUnitTest.php @@ -235,6 +235,18 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $definition->getProperty('docblock_first_line')->setLiteralDefault("Test case class TODO."); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + $label = []; + $label[] = $data->test_type->value; + $label[] = $data->plain_class_name->value; + + return implode(' - ', array_filter($label)); + return $data->test_type->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/Permission.php b/Generator/Permission.php index 720d781b..fa22883a 100644 --- a/Generator/Permission.php +++ b/Generator/Permission.php @@ -5,6 +5,7 @@ use MutableTypedData\Definition\PropertyListInterface; use MutableTypedData\Definition\DefaultDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; +use MutableTypedData\Data\DataItem; /** * Generator for module permissions on Drupal 8 and higher. @@ -46,6 +47,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ]); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->permission->value ?: NULL; + } + /** * Return an array of subcomponent types. */ diff --git a/Generator/Plugin.php b/Generator/Plugin.php index e191e9ea..421f63db 100644 --- a/Generator/Plugin.php +++ b/Generator/Plugin.php @@ -5,6 +5,7 @@ use MutableTypedData\Definition\PropertyListInterface; use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Definition\VariantGeneratorDefinition; +use MutableTypedData\Data\DataItem; use MutableTypedData\Definition\OptionsSortOrder; /** @@ -93,4 +94,21 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ]); } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + $label = []; + $label[] = $data->plugin_type->value; + + if ($data->hasProperty('plugin_label')) { + $label[] = $data->plugin_label->value; + } + else { + $label[] = $data->plugin_name->value; + } + + return implode(' - ', array_filter($label)); + } + } diff --git a/Generator/PluginType.php b/Generator/PluginType.php index 111d0b63..a414ec79 100644 --- a/Generator/PluginType.php +++ b/Generator/PluginType.php @@ -293,6 +293,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio } } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->plugin_label->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/RouterItem.php b/Generator/RouterItem.php index cc770f0c..c2d2ceee 100644 --- a/Generator/RouterItem.php +++ b/Generator/RouterItem.php @@ -407,6 +407,13 @@ public static function controllerRelativeClassFromRoutePath(string $path) { return $controller_class_name; } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->path->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/Generator/Service.php b/Generator/Service.php index 1b257a33..c159c687 100644 --- a/Generator/Service.php +++ b/Generator/Service.php @@ -187,6 +187,13 @@ public static function defaultRelativeNamespace($data_item) { } } + /** + * {@inheritdoc} + */ + public static function getDifferentiatedLabelSuffix(DataItem $data): ?string { + return $data->service_name->value ?: NULL; + } + /** * {@inheritdoc} */ diff --git a/MutableTypedData/Data/DeltaLabelTrait.php b/MutableTypedData/Data/DeltaLabelTrait.php new file mode 100644 index 00000000..584b519d --- /dev/null +++ b/MutableTypedData/Data/DeltaLabelTrait.php @@ -0,0 +1,35 @@ +getLabel(); + + // Just return the label if this isn't a delta, or if there's no associated + // generator. + if (!$this->isDelta() || empty($this->definition->generatorClass)) { + return $label; + } + + if ($qualifying_label = $this->definition->generatorClass::getDifferentiatedLabelSuffix($this)) { + $label .= ' - ' . $qualifying_label; + } + + return $label; + } + +} diff --git a/MutableTypedData/Data/MergeableComplexDataWithArrayAccess.php b/MutableTypedData/Data/MergeableComplexDataWithArrayAccess.php index 5b7f4447..5dc0fea5 100644 --- a/MutableTypedData/Data/MergeableComplexDataWithArrayAccess.php +++ b/MutableTypedData/Data/MergeableComplexDataWithArrayAccess.php @@ -8,6 +8,8 @@ class MergeableComplexDataWithArrayAccess extends ComplexData implements \ArrayA use DataItemArrayAccessTrait; + use DeltaLabelTrait; + use MergeableComplexDataTrait; } diff --git a/MutableTypedData/Data/MergeableMutableDataWithArrayAccess.php b/MutableTypedData/Data/MergeableMutableDataWithArrayAccess.php index f8e12de6..a3a01dc8 100644 --- a/MutableTypedData/Data/MergeableMutableDataWithArrayAccess.php +++ b/MutableTypedData/Data/MergeableMutableDataWithArrayAccess.php @@ -9,6 +9,8 @@ class MergeableMutableDataWithArrayAccess extends MutableData implements \ArrayA use DataItemArrayAccessTrait; + use DeltaLabelTrait; + use MergeableComplexDataTrait; /** From c639d2f669af92758f03e58305dee8f1c0bdd863 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Tue, 25 Nov 2025 18:07:53 +0000 Subject: [PATCH 107/144] Changed CodeFileInterface to inherit from Stringable. Fixes #345. --- File/CodeFile.php | 2 +- File/CodeFileInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/File/CodeFile.php b/File/CodeFile.php index 78bea945..f56edc8a 100644 --- a/File/CodeFile.php +++ b/File/CodeFile.php @@ -13,7 +13,7 @@ * This goes through a refinement process, with some properties being set later * and some which hold earlier versions of data being unset. */ -class CodeFile implements \Stringable, CodeFileInterface { +class CodeFile implements CodeFileInterface { /** * The array of body pieces. diff --git a/File/CodeFileInterface.php b/File/CodeFileInterface.php index c2aa2f69..e022ea75 100644 --- a/File/CodeFileInterface.php +++ b/File/CodeFileInterface.php @@ -8,7 +8,7 @@ * This may include the code of file on disk if this exists and the file is of * a type that we are able to merge. */ -interface CodeFileInterface { +interface CodeFileInterface extends \Stringable { /** * Gets the code for the file. From 98a840e2f6150ed38e273f78e3dcf13a0d01690a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Tue, 25 Nov 2025 18:14:45 +0000 Subject: [PATCH 108/144] Changed CodeFile::getFilePath() to be public API. Fixes #344. --- File/CodeFile.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/File/CodeFile.php b/File/CodeFile.php index f56edc8a..8d7e581d 100644 --- a/File/CodeFile.php +++ b/File/CodeFile.php @@ -165,10 +165,6 @@ public function fileIsMerged(): bool { /** * Gets the relative filepath. * - * @internal - * - * @todo Make this part of the API in 4.3.0. - * * @return string * The filepath. */ From 26fc1b25138caabfdc5661e002d15ae675df06c1 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 21 Nov 2025 14:34:24 +0000 Subject: [PATCH 109/144] Added utility class for changing order of items in an array. --- Utility/ArrayOrder.php | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Utility/ArrayOrder.php diff --git a/Utility/ArrayOrder.php b/Utility/ArrayOrder.php new file mode 100644 index 00000000..05faa5ac --- /dev/null +++ b/Utility/ArrayOrder.php @@ -0,0 +1,103 @@ + $value]); + } + + + /** + * Moves an item to the end of an array, specifying by value. + * + * @param array &$array + * The array to change. + * @param mixed $value + * The value to move. + * + * @throws \InvalidArgumentException + * Throws an exception if the value is not in the array. + */ + public static function moveValueToEnd(array &$array, mixed $value): void { + $key = array_find($array, $value); + + if ($key === FALSE) { + throw new \InvalidArgumentException('Value not found in array.'); + } + + unset($array[$key]); + + $array[$key] = $value; + } + + /** + * Moves an item to the end of an array, specifying by key. + * + * @param array &$array + * The array to change. + * @param mixed $key + * The key to move. + * + * @throws \InvalidArgumentException + * Throws an exception if the key is not in the array. + */ + public static function moveKeyToEnd(array &$array, mixed $key): void { + if (!isset($array[$key])) { + throw new \InvalidArgumentException('Key not found in array.'); + } + + $value = $array[$key]; + + unset($array[$key]); + + $array[$key] = $value; + } + +} From a16b02f5d3cf92d95582e935ab3d95b0710677f9 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 24 Jan 2026 09:18:44 +0000 Subject: [PATCH 110/144] Added collect task for the analysis timestamp. Fixes #118. --- .../DrupalCodeBuilderCompiledContainer.php | 892 +++++++++--------- Task/Collect.php | 15 +- Task/Collect/MetadataCollector.php | 45 + 3 files changed, 526 insertions(+), 426 deletions(-) create mode 100644 Task/Collect/MetadataCollector.php diff --git a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php index b7740d0c..f4e38b35 100644 --- a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php +++ b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php @@ -45,170 +45,176 @@ class DrupalCodeBuilderCompiledContainer extends DI\CompiledContainer{ 'subEntry17' => 'get39', 'Collect\\HooksCollector9' => 'get40', 'subEntry18' => 'get41', - 'Collect\\MethodCollector' => 'get42', - 'Collect\\PluginTypesCollector' => 'get43', - 'subEntry19' => 'get44', - 'subEntry20' => 'get45', - 'subEntry21' => 'get46', - 'subEntry22' => 'get47', - 'Collect\\ServiceTagTypesCollector' => 'get48', - 'subEntry23' => 'get49', - 'subEntry24' => 'get50', - 'subEntry25' => 'get51', - 'Collect\\ServicesCollector' => 'get52', - 'subEntry26' => 'get53', - 'subEntry27' => 'get54', - 'subEntry28' => 'get55', - 'Collect5' => 'get56', - 'subEntry29' => 'get57', - 'subEntry30' => 'get58', - 'Collect6' => 'get59', - 'subEntry31' => 'get60', - 'subEntry32' => 'get61', - 'Collect7' => 'get62', - 'subEntry33' => 'get63', - 'subEntry34' => 'get64', - 'Configuration' => 'get65', - 'subEntry35' => 'get66', - 'subEntry36' => 'get67', - 'Generate\\ComponentClassHandler' => 'get68', - 'subEntry37' => 'get69', - 'subEntry38' => 'get70', - 'Generate\\ComponentCollector' => 'get71', - 'subEntry39' => 'get72', - 'subEntry40' => 'get73', - 'Generate\\FileAssembler' => 'get74', - 'ReportAdminRoutes' => 'get75', - 'subEntry41' => 'get76', - 'ReportDataTypes' => 'get77', - 'subEntry42' => 'get78', - 'ReportElementTypes' => 'get79', - 'subEntry43' => 'get80', - 'ReportEntityTypes' => 'get81', - 'subEntry44' => 'get82', - 'ReportEventNames' => 'get83', - 'subEntry45' => 'get84', - 'ReportFieldTypes' => 'get85', - 'subEntry46' => 'get86', - 'ReportHookClassMethodData' => 'get87', - 'subEntry47' => 'get88', - 'ReportHookData' => 'get89', - 'subEntry48' => 'get90', - 'ReportHookDataFolder' => 'get91', - 'subEntry49' => 'get92', - 'ReportHookGroups' => 'get93', - 'subEntry50' => 'get94', - 'ReportHookPresets' => 'get95', - 'subEntry51' => 'get96', - 'ReportPluginData' => 'get97', - 'subEntry52' => 'get98', - 'ReportServiceData' => 'get99', - 'subEntry53' => 'get100', - 'ReportServiceTags' => 'get101', - 'subEntry54' => 'get102', - 'ReportSummary' => 'get103', - 'subEntry55' => 'get104', - 'subEntry56' => 'get105', - 'subEntry57' => 'get106', - 'subEntry58' => 'get107', - 'subEntry59' => 'get108', - 'subEntry60' => 'get109', - 'subEntry61' => 'get110', - 'subEntry62' => 'get111', - 'subEntry63' => 'get112', - 'subEntry64' => 'get113', - 'subEntry65' => 'get114', - 'subEntry66' => 'get115', - 'subEntry67' => 'get116', - 'subEntry68' => 'get117', - 'Testing\\CollectTesting10' => 'get118', - 'subEntry69' => 'get119', - 'subEntry70' => 'get120', - 'subEntry71' => 'get121', - 'subEntry72' => 'get122', - 'subEntry73' => 'get123', - 'subEntry74' => 'get124', - 'subEntry75' => 'get125', - 'subEntry76' => 'get126', - 'subEntry77' => 'get127', - 'subEntry78' => 'get128', - 'subEntry79' => 'get129', - 'subEntry80' => 'get130', - 'subEntry81' => 'get131', - 'Testing\\CollectTesting11' => 'get132', + 'Collect\\MetadataCollector' => 'get42', + 'Collect\\MethodCollector' => 'get43', + 'Collect\\PluginTypesCollector' => 'get44', + 'subEntry19' => 'get45', + 'subEntry20' => 'get46', + 'subEntry21' => 'get47', + 'subEntry22' => 'get48', + 'Collect\\ServiceTagTypesCollector' => 'get49', + 'subEntry23' => 'get50', + 'subEntry24' => 'get51', + 'subEntry25' => 'get52', + 'Collect\\ServicesCollector' => 'get53', + 'subEntry26' => 'get54', + 'subEntry27' => 'get55', + 'subEntry28' => 'get56', + 'Collect5' => 'get57', + 'subEntry29' => 'get58', + 'subEntry30' => 'get59', + 'Collect6' => 'get60', + 'subEntry31' => 'get61', + 'subEntry32' => 'get62', + 'Collect7' => 'get63', + 'subEntry33' => 'get64', + 'subEntry34' => 'get65', + 'Configuration' => 'get66', + 'subEntry35' => 'get67', + 'subEntry36' => 'get68', + 'Generate\\ComponentClassHandler' => 'get69', + 'subEntry37' => 'get70', + 'subEntry38' => 'get71', + 'Generate\\ComponentCollector' => 'get72', + 'subEntry39' => 'get73', + 'subEntry40' => 'get74', + 'Generate\\FileAssembler' => 'get75', + 'ReportAdminRoutes' => 'get76', + 'subEntry41' => 'get77', + 'ReportDataTypes' => 'get78', + 'subEntry42' => 'get79', + 'ReportElementTypes' => 'get80', + 'subEntry43' => 'get81', + 'ReportEntityTypes' => 'get82', + 'subEntry44' => 'get83', + 'ReportEventNames' => 'get84', + 'subEntry45' => 'get85', + 'ReportFieldTypes' => 'get86', + 'subEntry46' => 'get87', + 'ReportHookClassMethodData' => 'get88', + 'subEntry47' => 'get89', + 'ReportHookData' => 'get90', + 'subEntry48' => 'get91', + 'ReportHookDataFolder' => 'get92', + 'subEntry49' => 'get93', + 'ReportHookGroups' => 'get94', + 'subEntry50' => 'get95', + 'ReportHookPresets' => 'get96', + 'subEntry51' => 'get97', + 'ReportPluginData' => 'get98', + 'subEntry52' => 'get99', + 'ReportServiceData' => 'get100', + 'subEntry53' => 'get101', + 'ReportServiceTags' => 'get102', + 'subEntry54' => 'get103', + 'ReportSummary' => 'get104', + 'subEntry55' => 'get105', + 'subEntry56' => 'get106', + 'subEntry57' => 'get107', + 'subEntry58' => 'get108', + 'subEntry59' => 'get109', + 'subEntry60' => 'get110', + 'subEntry61' => 'get111', + 'subEntry62' => 'get112', + 'subEntry63' => 'get113', + 'subEntry64' => 'get114', + 'subEntry65' => 'get115', + 'subEntry66' => 'get116', + 'subEntry67' => 'get117', + 'subEntry68' => 'get118', + 'Testing\\CollectTesting10' => 'get119', + 'subEntry69' => 'get120', + 'subEntry70' => 'get121', + 'subEntry71' => 'get122', + 'subEntry72' => 'get123', + 'subEntry73' => 'get124', + 'subEntry74' => 'get125', + 'subEntry75' => 'get126', + 'subEntry76' => 'get127', + 'subEntry77' => 'get128', + 'subEntry78' => 'get129', + 'subEntry79' => 'get130', + 'subEntry80' => 'get131', + 'subEntry81' => 'get132', 'subEntry82' => 'get133', - 'subEntry83' => 'get134', - 'subEntry84' => 'get135', - 'subEntry85' => 'get136', - 'subEntry86' => 'get137', - 'subEntry87' => 'get138', - 'subEntry88' => 'get139', - 'subEntry89' => 'get140', - 'subEntry90' => 'get141', - 'subEntry91' => 'get142', - 'subEntry92' => 'get143', - 'subEntry93' => 'get144', - 'subEntry94' => 'get145', - 'Testing\\CollectTesting7' => 'get146', + 'Testing\\CollectTesting11' => 'get134', + 'subEntry83' => 'get135', + 'subEntry84' => 'get136', + 'subEntry85' => 'get137', + 'subEntry86' => 'get138', + 'subEntry87' => 'get139', + 'subEntry88' => 'get140', + 'subEntry89' => 'get141', + 'subEntry90' => 'get142', + 'subEntry91' => 'get143', + 'subEntry92' => 'get144', + 'subEntry93' => 'get145', + 'subEntry94' => 'get146', 'subEntry95' => 'get147', 'subEntry96' => 'get148', - 'Testing\\CollectTesting8' => 'get149', + 'Testing\\CollectTesting7' => 'get149', 'subEntry97' => 'get150', 'subEntry98' => 'get151', - 'subEntry99' => 'get152', - 'subEntry100' => 'get153', - 'subEntry101' => 'get154', - 'subEntry102' => 'get155', - 'subEntry103' => 'get156', - 'subEntry104' => 'get157', - 'subEntry105' => 'get158', - 'subEntry106' => 'get159', - 'subEntry107' => 'get160', - 'subEntry108' => 'get161', - 'subEntry109' => 'get162', - 'Testing\\CollectTesting9' => 'get163', + 'Testing\\CollectTesting8' => 'get152', + 'subEntry99' => 'get153', + 'subEntry100' => 'get154', + 'subEntry101' => 'get155', + 'subEntry102' => 'get156', + 'subEntry103' => 'get157', + 'subEntry104' => 'get158', + 'subEntry105' => 'get159', + 'subEntry106' => 'get160', + 'subEntry107' => 'get161', + 'subEntry108' => 'get162', + 'subEntry109' => 'get163', 'subEntry110' => 'get164', 'subEntry111' => 'get165', 'subEntry112' => 'get166', - 'subEntry113' => 'get167', - 'subEntry114' => 'get168', - 'subEntry115' => 'get169', - 'subEntry116' => 'get170', - 'subEntry117' => 'get171', - 'subEntry118' => 'get172', - 'subEntry119' => 'get173', - 'subEntry120' => 'get174', - 'subEntry121' => 'get175', - 'subEntry122' => 'get176', - 'Generate|module' => 'get177', - 'Generate|profile' => 'get178', - 'Collect\\HooksCollector' => 'get179', - 'Collect' => 'get180', - 'Collect.unversioned' => 'get181', - 'subEntry123' => 'get182', - 'subEntry124' => 'get183', - 'subEntry125' => 'get184', - 'subEntry126' => 'get185', - 'subEntry127' => 'get186', - 'subEntry128' => 'get187', - 'subEntry129' => 'get188', - 'subEntry130' => 'get189', - 'subEntry131' => 'get190', - 'subEntry132' => 'get191', - 'subEntry133' => 'get192', - 'subEntry134' => 'get193', - 'subEntry135' => 'get194', - 'Testing\\CollectTesting' => 'get195', - 'DrupalCodeBuilder\\Task\\Collect\\HooksCollector' => 'get196', - 'DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler' => 'get197', - 'subEntry136' => 'get198', - 'subEntry137' => 'get199', - 'DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter' => 'get200', - 'DrupalCodeBuilder\\Task\\Collect\\MethodCollector' => 'get201', - 'DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser' => 'get202', - 'subEntry138' => 'get203', - 'DrupalCodeBuilder\\Task\\ReportHookData' => 'get204', - 'subEntry139' => 'get205', + 'Testing\\CollectTesting9' => 'get167', + 'subEntry113' => 'get168', + 'subEntry114' => 'get169', + 'subEntry115' => 'get170', + 'subEntry116' => 'get171', + 'subEntry117' => 'get172', + 'subEntry118' => 'get173', + 'subEntry119' => 'get174', + 'subEntry120' => 'get175', + 'subEntry121' => 'get176', + 'subEntry122' => 'get177', + 'subEntry123' => 'get178', + 'subEntry124' => 'get179', + 'subEntry125' => 'get180', + 'subEntry126' => 'get181', + 'Generate|module' => 'get182', + 'Generate|profile' => 'get183', + 'Collect\\HooksCollector' => 'get184', + 'Collect' => 'get185', + 'Collect.unversioned' => 'get186', + 'subEntry127' => 'get187', + 'subEntry128' => 'get188', + 'subEntry129' => 'get189', + 'subEntry130' => 'get190', + 'subEntry131' => 'get191', + 'subEntry132' => 'get192', + 'subEntry133' => 'get193', + 'subEntry134' => 'get194', + 'subEntry135' => 'get195', + 'subEntry136' => 'get196', + 'subEntry137' => 'get197', + 'subEntry138' => 'get198', + 'subEntry139' => 'get199', + 'subEntry140' => 'get200', + 'Testing\\CollectTesting' => 'get201', + 'DrupalCodeBuilder\\Task\\Collect\\HooksCollector' => 'get202', + 'DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler' => 'get203', + 'subEntry141' => 'get204', + 'subEntry142' => 'get205', + 'DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter' => 'get206', + 'DrupalCodeBuilder\\Task\\Collect\\MethodCollector' => 'get207', + 'DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser' => 'get208', + 'subEntry143' => 'get209', + 'DrupalCodeBuilder\\Task\\ReportHookData' => 'get210', + 'subEntry144' => 'get211', ); protected function get1() @@ -482,778 +488,808 @@ protected function get40() } protected function get42() + { + $object = new \DrupalCodeBuilder\Task\Collect\MetadataCollector(); + return $object; + } + + protected function get43() { $object = new \DrupalCodeBuilder\Task\Collect\MethodCollector(); return $object; } - protected function get44() + protected function get45() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get45() + protected function get46() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter'); } - protected function get46() + protected function get47() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\MethodCollector'); } - protected function get47() + protected function get48() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser'); } - protected function get43() + protected function get44() { - $object = new \DrupalCodeBuilder\Task\Collect\PluginTypesCollector($this->get44(), $this->get45(), $this->get46(), $this->get47()); + $object = new \DrupalCodeBuilder\Task\Collect\PluginTypesCollector($this->get45(), $this->get46(), $this->get47(), $this->get48()); return $object; } - protected function get49() + protected function get50() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get50() + protected function get51() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter'); } - protected function get51() + protected function get52() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\MethodCollector'); } - protected function get48() + protected function get49() { - $object = new \DrupalCodeBuilder\Task\Collect\ServiceTagTypesCollector($this->get49(), $this->get50(), $this->get51()); + $object = new \DrupalCodeBuilder\Task\Collect\ServiceTagTypesCollector($this->get50(), $this->get51(), $this->get52()); return $object; } - protected function get53() + protected function get54() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get54() + protected function get55() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\ContainerBuilderGetter'); } - protected function get55() + protected function get56() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\CodeAnalyser'); } - protected function get52() + protected function get53() { - $object = new \DrupalCodeBuilder\Task\Collect\ServicesCollector($this->get53(), $this->get54(), $this->get55()); + $object = new \DrupalCodeBuilder\Task\Collect\ServicesCollector($this->get54(), $this->get55(), $this->get56()); return $object; } - protected function get57() + protected function get58() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get58() + protected function get59() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\HooksCollector'); } - protected function get56() + protected function get57() { - $object = new \DrupalCodeBuilder\Task\Collect5($this->get57(), $this->get58()); + $object = new \DrupalCodeBuilder\Task\Collect5($this->get58(), $this->get59()); return $object; } - protected function get60() + protected function get61() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get61() + protected function get62() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\HooksCollector'); } - protected function get59() + protected function get60() { - $object = new \DrupalCodeBuilder\Task\Collect6($this->get60(), $this->get61()); + $object = new \DrupalCodeBuilder\Task\Collect6($this->get61(), $this->get62()); return $object; } - protected function get63() + protected function get64() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get64() + protected function get65() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\HooksCollector'); } - protected function get62() + protected function get63() { - $object = new \DrupalCodeBuilder\Task\Collect7($this->get63(), $this->get64()); + $object = new \DrupalCodeBuilder\Task\Collect7($this->get64(), $this->get65()); return $object; } - protected function get66() + protected function get67() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get67() + protected function get68() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler'); } - protected function get65() + protected function get66() { - $object = new \DrupalCodeBuilder\Task\Configuration($this->get66(), $this->get67()); + $object = new \DrupalCodeBuilder\Task\Configuration($this->get67(), $this->get68()); return $object; } - protected function get69() + protected function get70() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get70() + protected function get71() { return $this->delegateContainer->get('generator_classmap'); } - protected function get68() + protected function get69() { - $object = new \DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get69(), $this->get70()); + $object = new \DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get70(), $this->get71()); return $object; } - protected function get72() + protected function get73() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get73() + protected function get74() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Generate\\ComponentClassHandler'); } - protected function get71() + protected function get72() { - $object = new \DrupalCodeBuilder\Task\Generate\ComponentCollector($this->get72(), $this->get73()); + $object = new \DrupalCodeBuilder\Task\Generate\ComponentCollector($this->get73(), $this->get74()); return $object; } - protected function get74() + protected function get75() { $object = new \DrupalCodeBuilder\Task\Generate\FileAssembler(); return $object; } - protected function get76() + protected function get77() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get75() + protected function get76() { - $object = new \DrupalCodeBuilder\Task\ReportAdminRoutes($this->get76()); + $object = new \DrupalCodeBuilder\Task\ReportAdminRoutes($this->get77()); return $object; } - protected function get78() + protected function get79() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get77() + protected function get78() { - $object = new \DrupalCodeBuilder\Task\ReportDataTypes($this->get78()); + $object = new \DrupalCodeBuilder\Task\ReportDataTypes($this->get79()); return $object; } - protected function get80() + protected function get81() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get79() + protected function get80() { - $object = new \DrupalCodeBuilder\Task\ReportElementTypes($this->get80()); + $object = new \DrupalCodeBuilder\Task\ReportElementTypes($this->get81()); return $object; } - protected function get82() + protected function get83() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get81() + protected function get82() { - $object = new \DrupalCodeBuilder\Task\ReportEntityTypes($this->get82()); + $object = new \DrupalCodeBuilder\Task\ReportEntityTypes($this->get83()); return $object; } - protected function get84() + protected function get85() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get83() + protected function get84() { - $object = new \DrupalCodeBuilder\Task\ReportEventNames($this->get84()); + $object = new \DrupalCodeBuilder\Task\ReportEventNames($this->get85()); return $object; } - protected function get86() + protected function get87() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get85() + protected function get86() { - $object = new \DrupalCodeBuilder\Task\ReportFieldTypes($this->get86()); + $object = new \DrupalCodeBuilder\Task\ReportFieldTypes($this->get87()); return $object; } - protected function get88() + protected function get89() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get87() + protected function get88() { - $object = new \DrupalCodeBuilder\Task\ReportHookClassMethodData($this->get88()); + $object = new \DrupalCodeBuilder\Task\ReportHookClassMethodData($this->get89()); return $object; } - protected function get90() + protected function get91() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get89() + protected function get90() { - $object = new \DrupalCodeBuilder\Task\ReportHookData($this->get90()); + $object = new \DrupalCodeBuilder\Task\ReportHookData($this->get91()); return $object; } - protected function get92() + protected function get93() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get91() + protected function get92() { - $object = new \DrupalCodeBuilder\Task\ReportHookDataFolder($this->get92()); + $object = new \DrupalCodeBuilder\Task\ReportHookDataFolder($this->get93()); return $object; } - protected function get94() + protected function get95() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\ReportHookData'); } - protected function get93() + protected function get94() { - $object = new \DrupalCodeBuilder\Task\ReportHookGroups($this->get94()); + $object = new \DrupalCodeBuilder\Task\ReportHookGroups($this->get95()); return $object; } - protected function get96() + protected function get97() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get95() + protected function get96() { - $object = new \DrupalCodeBuilder\Task\ReportHookPresets($this->get96()); + $object = new \DrupalCodeBuilder\Task\ReportHookPresets($this->get97()); return $object; } - protected function get98() + protected function get99() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get97() + protected function get98() { - $object = new \DrupalCodeBuilder\Task\ReportPluginData($this->get98()); + $object = new \DrupalCodeBuilder\Task\ReportPluginData($this->get99()); return $object; } - protected function get100() + protected function get101() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get99() + protected function get100() { - $object = new \DrupalCodeBuilder\Task\ReportServiceData($this->get100()); + $object = new \DrupalCodeBuilder\Task\ReportServiceData($this->get101()); return $object; } - protected function get102() + protected function get103() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get101() + protected function get102() { - $object = new \DrupalCodeBuilder\Task\ReportServiceTags($this->get102()); + $object = new \DrupalCodeBuilder\Task\ReportServiceTags($this->get103()); return $object; } - protected function get104() + protected function get105() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get106() + protected function get107() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get107() + protected function get108() { return $this->delegateContainer->get('ReportAdminRoutes'); } - protected function get108() + protected function get109() { return $this->delegateContainer->get('ReportDataTypes'); } - protected function get109() + protected function get110() { return $this->delegateContainer->get('ReportElementTypes'); } - protected function get110() + protected function get111() { return $this->delegateContainer->get('ReportEntityTypes'); } - protected function get111() + protected function get112() { return $this->delegateContainer->get('ReportEventNames'); } - protected function get112() + protected function get113() { return $this->delegateContainer->get('ReportFieldTypes'); } - protected function get113() + protected function get114() { return $this->delegateContainer->get('ReportHookClassMethodData'); } - protected function get114() + protected function get115() { return $this->delegateContainer->get('ReportHookData'); } - protected function get115() + protected function get116() { return $this->delegateContainer->get('ReportPluginData'); } - protected function get116() + protected function get117() { return $this->delegateContainer->get('ReportServiceData'); } - protected function get117() + protected function get118() { return $this->delegateContainer->get('ReportServiceTags'); } - protected function get105() + protected function get106() { return [ - 'Analyse\\TestTraits' => $this->get106(), - 'ReportAdminRoutes' => $this->get107(), - 'ReportDataTypes' => $this->get108(), - 'ReportElementTypes' => $this->get109(), - 'ReportEntityTypes' => $this->get110(), - 'ReportEventNames' => $this->get111(), - 'ReportFieldTypes' => $this->get112(), - 'ReportHookClassMethodData' => $this->get113(), - 'ReportHookData' => $this->get114(), - 'ReportPluginData' => $this->get115(), - 'ReportServiceData' => $this->get116(), - 'ReportServiceTags' => $this->get117(), + 'Analyse\\TestTraits' => $this->get107(), + 'ReportAdminRoutes' => $this->get108(), + 'ReportDataTypes' => $this->get109(), + 'ReportElementTypes' => $this->get110(), + 'ReportEntityTypes' => $this->get111(), + 'ReportEventNames' => $this->get112(), + 'ReportFieldTypes' => $this->get113(), + 'ReportHookClassMethodData' => $this->get114(), + 'ReportHookData' => $this->get115(), + 'ReportPluginData' => $this->get116(), + 'ReportServiceData' => $this->get117(), + 'ReportServiceTags' => $this->get118(), ]; } - protected function get103() + protected function get104() { - $object = new \DrupalCodeBuilder\Task\ReportSummary($this->get104()); - $object->setReportHelpers($this->get105()); + $object = new \DrupalCodeBuilder\Task\ReportSummary($this->get105()); + $object->setReportHelpers($this->get106()); return $object; } - protected function get119() + protected function get120() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get121() + protected function get122() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get122() + protected function get123() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get123() + protected function get124() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get124() + protected function get125() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get125() + protected function get126() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get126() + protected function get127() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get127() + protected function get128() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get128() + protected function get129() + { + return $this->delegateContainer->get('Collect\\MetadataCollector'); + } + + protected function get130() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get129() + protected function get131() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get130() + protected function get132() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get131() + protected function get133() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get120() + protected function get121() { return [ - 'Analyse\\TestTraits' => $this->get121(), - 'Collect\\AdminRoutesCollector' => $this->get122(), - 'Collect\\DataTypesCollector' => $this->get123(), - 'Collect\\ElementTypesCollector' => $this->get124(), - 'Collect\\EntityTypesCollector' => $this->get125(), - 'Collect\\EventNamesCollector' => $this->get126(), - 'Collect\\FieldTypesCollector' => $this->get127(), - 'Collect\\PluginTypesCollector' => $this->get128(), - 'Collect\\ServiceTagTypesCollector' => $this->get129(), - 'Collect\\ServicesCollector' => $this->get130(), - 'Collect\\HooksCollector' => $this->get131(), + 'Analyse\\TestTraits' => $this->get122(), + 'Collect\\AdminRoutesCollector' => $this->get123(), + 'Collect\\DataTypesCollector' => $this->get124(), + 'Collect\\ElementTypesCollector' => $this->get125(), + 'Collect\\EntityTypesCollector' => $this->get126(), + 'Collect\\EventNamesCollector' => $this->get127(), + 'Collect\\FieldTypesCollector' => $this->get128(), + 'Collect\\MetadataCollector' => $this->get129(), + 'Collect\\PluginTypesCollector' => $this->get130(), + 'Collect\\ServiceTagTypesCollector' => $this->get131(), + 'Collect\\ServicesCollector' => $this->get132(), + 'Collect\\HooksCollector' => $this->get133(), ]; } - protected function get118() + protected function get119() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting10($this->get119()); - $object->setCollectors($this->get120()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting10($this->get120()); + $object->setCollectors($this->get121()); return $object; } - protected function get133() + protected function get135() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get135() + protected function get137() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get136() + protected function get138() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get137() + protected function get139() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get138() + protected function get140() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get139() + protected function get141() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get140() + protected function get142() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get141() + protected function get143() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get142() + protected function get144() + { + return $this->delegateContainer->get('Collect\\MetadataCollector'); + } + + protected function get145() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get143() + protected function get146() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get144() + protected function get147() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get145() + protected function get148() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get134() + protected function get136() { return [ - 'Analyse\\TestTraits' => $this->get135(), - 'Collect\\AdminRoutesCollector' => $this->get136(), - 'Collect\\DataTypesCollector' => $this->get137(), - 'Collect\\ElementTypesCollector' => $this->get138(), - 'Collect\\EntityTypesCollector' => $this->get139(), - 'Collect\\EventNamesCollector' => $this->get140(), - 'Collect\\FieldTypesCollector' => $this->get141(), - 'Collect\\PluginTypesCollector' => $this->get142(), - 'Collect\\ServiceTagTypesCollector' => $this->get143(), - 'Collect\\ServicesCollector' => $this->get144(), - 'Collect\\HooksCollector' => $this->get145(), + 'Analyse\\TestTraits' => $this->get137(), + 'Collect\\AdminRoutesCollector' => $this->get138(), + 'Collect\\DataTypesCollector' => $this->get139(), + 'Collect\\ElementTypesCollector' => $this->get140(), + 'Collect\\EntityTypesCollector' => $this->get141(), + 'Collect\\EventNamesCollector' => $this->get142(), + 'Collect\\FieldTypesCollector' => $this->get143(), + 'Collect\\MetadataCollector' => $this->get144(), + 'Collect\\PluginTypesCollector' => $this->get145(), + 'Collect\\ServiceTagTypesCollector' => $this->get146(), + 'Collect\\ServicesCollector' => $this->get147(), + 'Collect\\HooksCollector' => $this->get148(), ]; } - protected function get132() + protected function get134() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting11($this->get133()); - $object->setCollectors($this->get134()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting11($this->get135()); + $object->setCollectors($this->get136()); return $object; } - protected function get147() + protected function get150() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get148() + protected function get151() { return $this->delegateContainer->get('DrupalCodeBuilder\\Task\\Collect\\HooksCollector'); } - protected function get146() + protected function get149() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting7($this->get147(), $this->get148()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting7($this->get150(), $this->get151()); return $object; } - protected function get150() + protected function get153() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get152() + protected function get155() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get153() + protected function get156() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get154() + protected function get157() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get155() + protected function get158() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get156() + protected function get159() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get157() + protected function get160() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get158() + protected function get161() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get159() + protected function get162() + { + return $this->delegateContainer->get('Collect\\MetadataCollector'); + } + + protected function get163() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get160() + protected function get164() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get161() + protected function get165() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get162() + protected function get166() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get151() + protected function get154() { return [ - 'Analyse\\TestTraits' => $this->get152(), - 'Collect\\AdminRoutesCollector' => $this->get153(), - 'Collect\\DataTypesCollector' => $this->get154(), - 'Collect\\ElementTypesCollector' => $this->get155(), - 'Collect\\EntityTypesCollector' => $this->get156(), - 'Collect\\EventNamesCollector' => $this->get157(), - 'Collect\\FieldTypesCollector' => $this->get158(), - 'Collect\\PluginTypesCollector' => $this->get159(), - 'Collect\\ServiceTagTypesCollector' => $this->get160(), - 'Collect\\ServicesCollector' => $this->get161(), - 'Collect\\HooksCollector' => $this->get162(), + 'Analyse\\TestTraits' => $this->get155(), + 'Collect\\AdminRoutesCollector' => $this->get156(), + 'Collect\\DataTypesCollector' => $this->get157(), + 'Collect\\ElementTypesCollector' => $this->get158(), + 'Collect\\EntityTypesCollector' => $this->get159(), + 'Collect\\EventNamesCollector' => $this->get160(), + 'Collect\\FieldTypesCollector' => $this->get161(), + 'Collect\\MetadataCollector' => $this->get162(), + 'Collect\\PluginTypesCollector' => $this->get163(), + 'Collect\\ServiceTagTypesCollector' => $this->get164(), + 'Collect\\ServicesCollector' => $this->get165(), + 'Collect\\HooksCollector' => $this->get166(), ]; } - protected function get149() + protected function get152() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting8($this->get150()); - $object->setCollectors($this->get151()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting8($this->get153()); + $object->setCollectors($this->get154()); return $object; } - protected function get164() + protected function get168() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get166() + protected function get170() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get167() + protected function get171() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get168() + protected function get172() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get169() + protected function get173() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get170() + protected function get174() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get171() + protected function get175() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get172() + protected function get176() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get173() + protected function get177() + { + return $this->delegateContainer->get('Collect\\MetadataCollector'); + } + + protected function get178() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get174() + protected function get179() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get175() + protected function get180() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get176() + protected function get181() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get165() + protected function get169() { return [ - 'Analyse\\TestTraits' => $this->get166(), - 'Collect\\AdminRoutesCollector' => $this->get167(), - 'Collect\\DataTypesCollector' => $this->get168(), - 'Collect\\ElementTypesCollector' => $this->get169(), - 'Collect\\EntityTypesCollector' => $this->get170(), - 'Collect\\EventNamesCollector' => $this->get171(), - 'Collect\\FieldTypesCollector' => $this->get172(), - 'Collect\\PluginTypesCollector' => $this->get173(), - 'Collect\\ServiceTagTypesCollector' => $this->get174(), - 'Collect\\ServicesCollector' => $this->get175(), - 'Collect\\HooksCollector' => $this->get176(), + 'Analyse\\TestTraits' => $this->get170(), + 'Collect\\AdminRoutesCollector' => $this->get171(), + 'Collect\\DataTypesCollector' => $this->get172(), + 'Collect\\ElementTypesCollector' => $this->get173(), + 'Collect\\EntityTypesCollector' => $this->get174(), + 'Collect\\EventNamesCollector' => $this->get175(), + 'Collect\\FieldTypesCollector' => $this->get176(), + 'Collect\\MetadataCollector' => $this->get177(), + 'Collect\\PluginTypesCollector' => $this->get178(), + 'Collect\\ServiceTagTypesCollector' => $this->get179(), + 'Collect\\ServicesCollector' => $this->get180(), + 'Collect\\HooksCollector' => $this->get181(), ]; } - protected function get163() + protected function get167() { - $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting9($this->get164()); - $object->setCollectors($this->get165()); + $object = new \DrupalCodeBuilder\Task\Testing\CollectTesting9($this->get168()); + $object->setCollectors($this->get169()); return $object; } - protected function get177() + protected function get182() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1263,7 +1299,7 @@ protected function get177() ]); } - protected function get178() + protected function get183() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1273,7 +1309,7 @@ protected function get178() ]); } - protected function get179() + protected function get184() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1281,7 +1317,7 @@ protected function get179() ], 'Collect\\HooksCollector'); } - protected function get180() + protected function get185() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1289,91 +1325,97 @@ protected function get180() ], 'Collect'); } - protected function get182() + protected function get187() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get184() + protected function get189() { return $this->delegateContainer->get('Analyse\\TestTraits'); } - protected function get185() + protected function get190() { return $this->delegateContainer->get('Collect\\AdminRoutesCollector'); } - protected function get186() + protected function get191() { return $this->delegateContainer->get('Collect\\DataTypesCollector'); } - protected function get187() + protected function get192() { return $this->delegateContainer->get('Collect\\ElementTypesCollector'); } - protected function get188() + protected function get193() { return $this->delegateContainer->get('Collect\\EntityTypesCollector'); } - protected function get189() + protected function get194() { return $this->delegateContainer->get('Collect\\EventNamesCollector'); } - protected function get190() + protected function get195() { return $this->delegateContainer->get('Collect\\FieldTypesCollector'); } - protected function get191() + protected function get196() + { + return $this->delegateContainer->get('Collect\\MetadataCollector'); + } + + protected function get197() { return $this->delegateContainer->get('Collect\\PluginTypesCollector'); } - protected function get192() + protected function get198() { return $this->delegateContainer->get('Collect\\ServiceTagTypesCollector'); } - protected function get193() + protected function get199() { return $this->delegateContainer->get('Collect\\ServicesCollector'); } - protected function get194() + protected function get200() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get183() + protected function get188() { return [ - 'Analyse\\TestTraits' => $this->get184(), - 'Collect\\AdminRoutesCollector' => $this->get185(), - 'Collect\\DataTypesCollector' => $this->get186(), - 'Collect\\ElementTypesCollector' => $this->get187(), - 'Collect\\EntityTypesCollector' => $this->get188(), - 'Collect\\EventNamesCollector' => $this->get189(), - 'Collect\\FieldTypesCollector' => $this->get190(), - 'Collect\\PluginTypesCollector' => $this->get191(), - 'Collect\\ServiceTagTypesCollector' => $this->get192(), - 'Collect\\ServicesCollector' => $this->get193(), - 'Collect\\HooksCollector' => $this->get194(), + 'Analyse\\TestTraits' => $this->get189(), + 'Collect\\AdminRoutesCollector' => $this->get190(), + 'Collect\\DataTypesCollector' => $this->get191(), + 'Collect\\ElementTypesCollector' => $this->get192(), + 'Collect\\EntityTypesCollector' => $this->get193(), + 'Collect\\EventNamesCollector' => $this->get194(), + 'Collect\\FieldTypesCollector' => $this->get195(), + 'Collect\\MetadataCollector' => $this->get196(), + 'Collect\\PluginTypesCollector' => $this->get197(), + 'Collect\\ServiceTagTypesCollector' => $this->get198(), + 'Collect\\ServicesCollector' => $this->get199(), + 'Collect\\HooksCollector' => $this->get200(), ]; } - protected function get181() + protected function get186() { - $object = new \DrupalCodeBuilder\Task\Collect($this->get182()); - $object->setCollectors($this->get183()); + $object = new \DrupalCodeBuilder\Task\Collect($this->get187()); + $object->setCollectors($this->get188()); return $object; } - protected function get195() + protected function get201() { return $this->resolveFactory([ 0 => 'DrupalCodeBuilder\\DependencyInjection\\ServiceFactories', @@ -1381,58 +1423,58 @@ protected function get195() ], 'Testing\\CollectTesting'); } - protected function get196() + protected function get202() { return $this->delegateContainer->get('Collect\\HooksCollector'); } - protected function get198() + protected function get204() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get199() + protected function get205() { return $this->delegateContainer->get('generator_classmap'); } - protected function get197() + protected function get203() { - $object = new DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get198(), $this->get199()); + $object = new DrupalCodeBuilder\Task\Generate\ComponentClassHandler($this->get204(), $this->get205()); return $object; } - protected function get200() + protected function get206() { $object = new DrupalCodeBuilder\Task\Collect\ContainerBuilderGetter(); return $object; } - protected function get201() + protected function get207() { $object = new DrupalCodeBuilder\Task\Collect\MethodCollector(); return $object; } - protected function get203() + protected function get209() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get202() + protected function get208() { - $object = new DrupalCodeBuilder\Task\Collect\CodeAnalyser($this->get203()); + $object = new DrupalCodeBuilder\Task\Collect\CodeAnalyser($this->get209()); return $object; } - protected function get205() + protected function get211() { return $this->delegateContainer->get('DrupalCodeBuilder\\Environment\\EnvironmentInterface'); } - protected function get204() + protected function get210() { - $object = new DrupalCodeBuilder\Task\ReportHookData($this->get205()); + $object = new DrupalCodeBuilder\Task\ReportHookData($this->get211()); return $object; } diff --git a/Task/Collect.php b/Task/Collect.php index 4dcdcb07..4bb34804 100644 --- a/Task/Collect.php +++ b/Task/Collect.php @@ -8,8 +8,10 @@ namespace DrupalCodeBuilder\Task; use DrupalCodeBuilder\Attribute\InjectImplementations; +use DrupalCodeBuilder\Environment\EnvironmentInterface; use DrupalCodeBuilder\Task\Collect\CollectorInterface; - +use DrupalCodeBuilder\Task\Collect\MetadataCollector; +use DrupalCodeBuilder\Utility\ArrayOrder; /** * Task handler for collecting and processing definitions for Drupal components. * @@ -51,6 +53,17 @@ class Collect extends Base { */ #[InjectImplementations(CollectorInterface::class)] public function setCollectors(array $collectors) { + // Append the metadata controller so it runs last. + // This is faffy, but the faff is small and contained. The alternatives + // would be: a weight system for interface-based injection + // (over-engineered), or injecting this collector separately (but then it + // can't implement the interface or use the base class, which requires more + // code). + // WARNING: We rely on the array of collectors being keyed by the service + // name, which is only the case because of a bug in PHP-DI which doesn't + // allow us to use the splat operator for the collectors parameter! + ArrayOrder::moveKeyToEnd($collectors, 'Collect\\MetadataCollector'); + $this->collectors = $collectors; } diff --git a/Task/Collect/MetadataCollector.php b/Task/Collect/MetadataCollector.php new file mode 100644 index 00000000..825fa9b1 --- /dev/null +++ b/Task/Collect/MetadataCollector.php @@ -0,0 +1,45 @@ + time(), + ]; + } + +} From cddbc9134243aa4cd101edf1b0b07cde1d0b2a6a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 21 Nov 2025 19:42:36 +0000 Subject: [PATCH 111/144] Added lastUpdatedDate() to ReportSummary using metadata, overriding method in parent class. --- Task/ReportSummary.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Task/ReportSummary.php b/Task/ReportSummary.php index 28a8cd80..1a8df762 100644 --- a/Task/ReportSummary.php +++ b/Task/ReportSummary.php @@ -45,6 +45,18 @@ public function setReportHelpers(array $helper_services) { $this->helperServices = $helper_services; } + /** + * Gets the timestamp of the last data analysis. + * + * @return + * A unix timestamp, or NULL if the data analysis has never been done. + */ + public function lastUpdatedDate(): ?int { + $metadata = $this->environment->getStorage()->retrieve('metadata'); + + return $metadata['timestamp'] ?? NULL; + } + /** * Returns a listing of all stored data, with counts. * From f2ee79c037c2164966955900743270609178f3e0 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 21 Nov 2025 20:09:12 +0000 Subject: [PATCH 112/144] Deprecated ReportHookDataFolder::lastUpdatedDate(). Fixes #418. --- Task/ReportHookDataFolder.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Task/ReportHookDataFolder.php b/Task/ReportHookDataFolder.php index d6e2bd2b..37d62db2 100644 --- a/Task/ReportHookDataFolder.php +++ b/Task/ReportHookDataFolder.php @@ -27,6 +27,9 @@ class ReportHookDataFolder extends Base { * * @return * A unix timestamp, or NULL if the hooks have never been collected. + * + * @deprecated Use \DrupalCodeBuilder\Task\ReportSummary::lastUpdatedDate() + * instead. */ public function lastUpdatedDate() { $directory = $this->environment->getDataDirectory(); From 4ebde3b2cbe127ad02e27630be20dbef4d18ed62 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 24 Jan 2026 17:52:12 +0000 Subject: [PATCH 113/144] Fixed services whose name is a class-like producing broken variable and property names. Fixes #415. --- Task/Collect/ServicesCollector.php | 3 ++- ...e10Test.php => ComponentService11Test.php} | 24 +++++++++++++++++-- .../11/services_processed.php | 10 ++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) rename Test/Unit/{ComponentService10Test.php => ComponentService11Test.php} (98%) diff --git a/Task/Collect/ServicesCollector.php b/Task/Collect/ServicesCollector.php index ae9cc9ba..5cce5e56 100644 --- a/Task/Collect/ServicesCollector.php +++ b/Task/Collect/ServicesCollector.php @@ -31,6 +31,7 @@ class ServicesCollector extends CollectorBase { 'module_handler' => TRUE, 'cache.discovery' => TRUE, 'storage:node' => TRUE, + 'Drupal\Core\DefaultContent\Importer' => TRUE, ]; /** @@ -251,7 +252,7 @@ protected function getAllServices(): array { $service_class = '\\' . $service_class; } - if (substr_count($service_id, '.') == 0) { + if (!str_contains($service_id, '.') && !str_contains($service_id, '\\')) { // If the service name does not contain any dots, in particular, // 'current_user', then use that, as it's usually clearer than the // class name. diff --git a/Test/Unit/ComponentService10Test.php b/Test/Unit/ComponentService11Test.php similarity index 98% rename from Test/Unit/ComponentService10Test.php rename to Test/Unit/ComponentService11Test.php index 603c9add..87a34133 100644 --- a/Test/Unit/ComponentService10Test.php +++ b/Test/Unit/ComponentService11Test.php @@ -11,14 +11,14 @@ * * @group yaml */ -class ComponentService10Test extends TestBase { +class ComponentService11Test extends TestBase { /** * The Drupal core major version to set up for this test. * * @var int */ - protected $drupalMajorVersion = 10; + protected $drupalMajorVersion = 11; /** * Test generating a module with a service. @@ -366,6 +366,26 @@ public static function providerServiceGenerationWithServices() { ], ], ], + // Class name service. + 'class-named' => [ + 'injected_services' => [ + 'Drupal\Core\DefaultContent\Importer', + ], + 'property_promotion' => FALSE, + 'yaml_arguments' => [ + '@Drupal\Core\DefaultContent\Importer', + ], + 'assert_injected_services' => [ + [ + // Eh this typehint is a whole other problem with this particular + // service. + 'typehint' => 'Psr\Log\LoggerAwareInterface', + 'service_name' => 'importer', + 'property_name' => 'importer', + 'parameter_name' => 'importer', + ], + ], + ], // Pseudoservice with the real service also present as a parameter. 'pseudo-with-real' => [ 'injected_services' => [ diff --git a/Test/sample_hook_definitions/11/services_processed.php b/Test/sample_hook_definitions/11/services_processed.php index d6ac5137..7b1496a1 100644 --- a/Test/sample_hook_definitions/11/services_processed.php +++ b/Test/sample_hook_definitions/11/services_processed.php @@ -32,6 +32,16 @@ ), 'all' => array ( + 'Drupal\\Core\\DefaultContent\\Importer' => + array ( + 'id' => 'Drupal\\Core\\DefaultContent\\Importer', + 'label' => 'Importer', + 'static_method' => '', + 'class' => '\\Drupal\\Core\\DefaultContent\\Importer', + 'interface' => '\\Psr\\Log\\LoggerAwareInterface', + 'description' => 'The importer service', + 'variable_name' => 'importer', + ), 'cache.discovery' => array ( 'id' => 'cache.discovery', From 87c234de13614ccd4573873de5ecffbd8af5279e Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 25 Jan 2026 13:14:48 +0000 Subject: [PATCH 114/144] Added DI to plugin deriver classes. Fixes #410. --- Generator/PluginClassDiscovery.php | 11 ++- Generator/PluginDeriver.php | 77 +++++++++++++++++++ Generator/PluginYamlDiscovery.php | 11 ++- Test/Unit/ComponentPluginsAnnotated9Test.php | 2 +- Test/Unit/ComponentPluginsAttribute11Test.php | 34 +++++++- Test/Unit/ComponentPluginsYAML10Test.php | 2 +- 6 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 Generator/PluginDeriver.php diff --git a/Generator/PluginClassDiscovery.php b/Generator/PluginClassDiscovery.php index c2a5bcb7..e8bb3f01 100644 --- a/Generator/PluginClassDiscovery.php +++ b/Generator/PluginClassDiscovery.php @@ -91,6 +91,11 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio 'deriver' => PropertyDefinition::create('boolean') ->setLabel('Use deriver') ->setDescription("Adds a deriver class to dynamically derive plugins from a template."), + 'deriver_injected_services' => PropertyDefinition::create('string') + ->setLabel('Deriver injected services') + ->setDescription("Services to inject into the deriver class.") + ->setMultiple(TRUE) + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')), 'deriver_plain_class_name' => PropertyDefinition::create('string') ->setInternal(TRUE) ->setDefault(DefaultDefinition::create() @@ -236,16 +241,14 @@ public function requiredComponents(): array { if (!empty($this->component_data->deriver->value)) { $components['deriver'] = [ - 'component_type' => 'PHPClassFile', + 'component_type' => 'PluginDeriver', 'class_docblock_lines' => [ 'Plugin deriver for ' . $this->component_data->plugin_name->value . '.', ], 'plain_class_name' => $this->component_data->deriver_plain_class_name->value, 'relative_namespace' => 'Plugin\Derivative', 'parent_class_name' => '\Drupal\Component\Plugin\Derivative\DeriverBase', - 'interfaces' => [ - '\Drupal\Core\Plugin\Discovery\ContainerDeriverInterface', - ], + 'injected_services' => $this->component_data->deriver_injected_services->values(), ]; $components['getDerivativeDefinitions'] = [ diff --git a/Generator/PluginDeriver.php b/Generator/PluginDeriver.php new file mode 100644 index 00000000..1d38bc8d --- /dev/null +++ b/Generator/PluginDeriver.php @@ -0,0 +1,77 @@ + 'base_plugin_id', + 'description' => 'The base plugin ID.', + 'typehint' => 'string', + ], + ]; + + /** + * {@inheritdoc} + */ + public static function addToGeneratorDefinition(PropertyListInterface $definition) { + parent::addToGeneratorDefinition($definition); + + $definition->getProperty('use_static_factory_method') + ->setLiteralDefault(TRUE); + } + + /** + * Produces the class declaration. + */ + function classDeclaration() { + if (!$this->needsDiInterface()) { + // Numeric key will clobber, so make something up! + // TODO: fix! + $this->component_data->interfaces->add(['CLASS_NO_DI_INTERFACE' => static::CLASS_NO_DI_INTERFACE]); + } + + return parent::classDeclaration(); + } + + /** + * {@inheritdoc} + */ + protected function getConstructBaseParameters() { + // Deriver classes do not pass on the $base_plugin_id create() parameter to + // the constructor. + return []; + } + + /** + * {@inheritdoc} + */ + protected function getCreateParameters() { + return static::STANDARD_FIXED_PARAMS; + } + +} diff --git a/Generator/PluginYamlDiscovery.php b/Generator/PluginYamlDiscovery.php index 1bc3d92c..35351351 100644 --- a/Generator/PluginYamlDiscovery.php +++ b/Generator/PluginYamlDiscovery.php @@ -33,6 +33,11 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio 'deriver' => PropertyDefinition::create('boolean') ->setLabel('Use deriver') ->setDescription("Adds a deriver class to dynamically derive plugins from a template."), + 'deriver_injected_services' => PropertyDefinition::create('string') + ->setLabel('Deriver injected services') + ->setDescription("Services to inject into the deriver class.") + ->setMultiple(TRUE) + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')), 'deriver_plain_class_name' => PropertyDefinition::create('string') ->setInternal(TRUE) ->setDefault(DefaultDefinition::create() @@ -190,16 +195,14 @@ public function requiredComponents(): array { if (!empty($this->component_data->deriver->value)) { $components['deriver'] = [ - 'component_type' => 'PHPClassFile', + 'component_type' => 'PluginDeriver', 'class_docblock_lines' => [ 'Plugin deriver for ' . $this->component_data->plugin_name->value . '.', ], 'plain_class_name' => $this->component_data->deriver_plain_class_name->value, 'relative_namespace' => 'Plugin\Derivative', 'parent_class_name' => '\Drupal\Component\Plugin\Derivative\DeriverBase', - 'interfaces' => [ - '\Drupal\Core\Plugin\Discovery\ContainerDeriverInterface', - ], + 'injected_services' => $this->component_data->deriver_injected_services->values(), ]; $components['getDerivativeDefinitions'] = [ diff --git a/Test/Unit/ComponentPluginsAnnotated9Test.php b/Test/Unit/ComponentPluginsAnnotated9Test.php index c8f4d5a0..75019945 100644 --- a/Test/Unit/ComponentPluginsAnnotated9Test.php +++ b/Test/Unit/ComponentPluginsAnnotated9Test.php @@ -213,7 +213,7 @@ function testPluginsGenerationDeriver() { $deriver = $files['src/Plugin/Derivative/AlphaBlockDeriver.php']; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $deriver); $php_tester->assertClassHasParent('Drupal\Component\Plugin\Derivative\DeriverBase'); - $php_tester->assertClassHasInterfaces(['Drupal\Core\Plugin\Discovery\ContainerDeriverInterface']); + $php_tester->assertClassHasInterfaces(['Drupal\Component\Plugin\Derivative\DeriverInterface']); $php_tester->assertHasMethod('getDerivativeDefinitions'); // Check the plugin file declares the deriver. diff --git a/Test/Unit/ComponentPluginsAttribute11Test.php b/Test/Unit/ComponentPluginsAttribute11Test.php index a6bb4691..0077276d 100644 --- a/Test/Unit/ComponentPluginsAttribute11Test.php +++ b/Test/Unit/ComponentPluginsAttribute11Test.php @@ -264,6 +264,8 @@ public static function providerPluginsGenerationNamePrefixing() { /** * Tests plugin derivers. + * + * @group di */ function testPluginsGenerationDeriver() { // Create a module. @@ -293,7 +295,7 @@ function testPluginsGenerationDeriver() { $deriver = $files['src/Plugin/Derivative/AlphaFieldFormatterDeriver.php']; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $deriver); $php_tester->assertClassHasParent('Drupal\Component\Plugin\Derivative\DeriverBase'); - $php_tester->assertClassHasInterfaces(['Drupal\Core\Plugin\Discovery\ContainerDeriverInterface']); + $php_tester->assertClassHasInterfaces(['Drupal\Component\Plugin\Derivative\DeriverInterface']); $php_tester->assertHasMethod('getDerivativeDefinitions'); // Check the plugin file declares the deriver. @@ -301,6 +303,36 @@ function testPluginsGenerationDeriver() { $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $plugin_file); $php_tester->assertImportsClassLike(['Drupal\test_module\Plugin\Derivative\AlphaFieldFormatterDeriver']); $php_tester->assertClassAttributeHasNamedParameterValue('deriver', 'AlphaFieldFormatterDeriver::class', 'FieldFormatter'); + + // Add DI to the deriver class. + $module_data['plugins'][0]['deriver_injected_services'] = [ + 'current_user', + 'entity_type.manager', + ]; + + $files = $this->generateModuleFiles($module_data); + + $this->assertArrayHasKey('src/Plugin/Derivative/AlphaFieldFormatterDeriver.php', $files); + $deriver = $files['src/Plugin/Derivative/AlphaFieldFormatterDeriver.php']; + + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $deriver); + $php_tester->assertClassHasParent('Drupal\Component\Plugin\Derivative\DeriverBase'); + $php_tester->assertClassHasInterfaces(['Drupal\Core\Plugin\Discovery\ContainerDeriverInterface']); + // Check service injection. + $php_tester->assertInjectedServicesWithFactory([ + [ + 'typehint' => 'Drupal\Core\Session\AccountProxyInterface', + 'service_name' => 'current_user', + 'property_name' => 'currentUser', + 'parameter_name' => 'current_user', + ], + [ + 'typehint' => 'Drupal\Core\Entity\EntityTypeManagerInterface', + 'service_name' => 'entity_type.manager', + 'property_name' => 'entityTypeManager', + 'parameter_name' => 'entity_type_manager', + ], + ]); } /** diff --git a/Test/Unit/ComponentPluginsYAML10Test.php b/Test/Unit/ComponentPluginsYAML10Test.php index 1eca1f6c..71d661be 100644 --- a/Test/Unit/ComponentPluginsYAML10Test.php +++ b/Test/Unit/ComponentPluginsYAML10Test.php @@ -91,7 +91,7 @@ function testYamlPluginsGenerationDeriver() { $deriver = $files['src/Plugin/Derivative/AlphaMenuLinkDeriver.php']; $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $deriver); $php_tester->assertClassHasParent('Drupal\Component\Plugin\Derivative\DeriverBase'); - $php_tester->assertClassHasInterfaces(['Drupal\Core\Plugin\Discovery\ContainerDeriverInterface']); + $php_tester->assertClassHasInterfaces(['Drupal\Component\Plugin\Derivative\DeriverInterface']); $php_tester->assertHasMethod('getDerivativeDefinitions'); // Check the plugin YAML file declares the deriver. From 89f6f45f1d661cae0d7b6ad6b4a198bf4bd66840 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 25 Jan 2026 15:55:59 +0000 Subject: [PATCH 115/144] Added dependent values system to data properties. --- Definition/PropertyDefinition.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Definition/PropertyDefinition.php b/Definition/PropertyDefinition.php index 1bf4f97d..3635fc01 100644 --- a/Definition/PropertyDefinition.php +++ b/Definition/PropertyDefinition.php @@ -18,6 +18,7 @@ * - Presets * - Processing * - Auto-acquisition + * - Dependency values */ class PropertyDefinition extends BasePropertyDefinition implements PropertyListInterface, \ArrayAccess { @@ -36,6 +37,8 @@ class PropertyDefinition extends BasePropertyDefinition implements PropertyListI protected $autoAcquired = FALSE; + protected ?array $dependentValue = NULL; + /** * {@inheritdoc} */ @@ -322,6 +325,27 @@ public function loadLazyProperties() { } } + /** + * Sets dependent values. + * + * UIs can use these to determine whether to show a property. + * + * @param array $dependent_value + * An array of dependencies which this property may require to be shown. + * Keys are relative addresses. Values are either the target value, or for + * string data, TRUE to represent that the target property must be filled. + * + * @return self + */ + public function setDependencyValue(array $dependent_value): self { + $this->dependentValue = $dependent_value; + return $this; + } + + public function getDependencyValue(): ?array { + return $this->dependentValue; + } + public function offsetExists(mixed $offset): bool { dump($this); throw new \Exception("Accessing definition $this->name as array with offsetExists $offset."); From e62e33bf7b3ea45ee7062bf7c95f105ec962e579 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 25 Jan 2026 15:56:27 +0000 Subject: [PATCH 116/144] Added dependencies on plugin deriver injected services, and parent plugin replacement. --- Generator/PluginClassDiscovery.php | 10 ++++++++-- Generator/PluginYamlDiscovery.php | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Generator/PluginClassDiscovery.php b/Generator/PluginClassDiscovery.php index e8bb3f01..be663289 100644 --- a/Generator/PluginClassDiscovery.php +++ b/Generator/PluginClassDiscovery.php @@ -95,7 +95,10 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLabel('Deriver injected services') ->setDescription("Services to inject into the deriver class.") ->setMultiple(TRUE) - ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')), + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')) + ->setDependencyValue([ + '..:deriver' => TRUE, + ]), 'deriver_plain_class_name' => PropertyDefinition::create('string') ->setInternal(TRUE) ->setDefault(DefaultDefinition::create() @@ -126,7 +129,10 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ), 'replace_parent_plugin' => PropertyDefinition::create('boolean') ->setLabel('Replace parent plugin') - ->setDescription("Replace the parent plugin's class with the generated class, rather than define a new plugin."), + ->setDescription("Replace the parent plugin's class with the generated class, rather than define a new plugin.") + ->setDependencyValue([ + '..:parent_plugin_id' => TRUE, + ]), 'class_docblock_lines' => PropertyDefinition::create('mapping') ->setInternal(TRUE) ->setDefault( diff --git a/Generator/PluginYamlDiscovery.php b/Generator/PluginYamlDiscovery.php index 35351351..ea152412 100644 --- a/Generator/PluginYamlDiscovery.php +++ b/Generator/PluginYamlDiscovery.php @@ -37,7 +37,10 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLabel('Deriver injected services') ->setDescription("Services to inject into the deriver class.") ->setMultiple(TRUE) - ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')), + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')) + ->setDependencyValue([ + '..:deriver' => TRUE, + ]), 'deriver_plain_class_name' => PropertyDefinition::create('string') ->setInternal(TRUE) ->setDefault(DefaultDefinition::create() @@ -96,7 +99,10 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLabel('Injected services for custom class') ->setDescription("Services to inject if using a custom plugin class.") ->setMultiple(TRUE) - ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')), + ->setOptionSetDefinition(\DrupalCodeBuilder\Factory::getTask('ReportServiceData')) + ->setDependencyValue([ + '..:plugin_custom_class' => TRUE, + ]), 'prefix_name' => PropertyDefinition::create('boolean') ->setInternal(TRUE) ->setLiteralDefault(TRUE), From 6121ffe5a26dd543a71043145edbc4d5f9e0808f Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 25 Jan 2026 15:56:43 +0000 Subject: [PATCH 117/144] Added detail to label. --- Generator/RouterItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/RouterItem.php b/Generator/RouterItem.php index c2d2ceee..08913b27 100644 --- a/Generator/RouterItem.php +++ b/Generator/RouterItem.php @@ -56,7 +56,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLabel('The title for the menu tab') ->setLiteralDefault('My Page'), 'base_route' => PropertyDefinition::create('string') - ->setLabel('Route that this tab shows on') + ->setLabel('Base route that this tab shows on') ]), // TODO: remove this if possible? Probably need to allow PHPClassFile From 37b6b450945cc5078ab662e09eb63e417c1a32f3 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 25 Jan 2026 16:07:50 +0000 Subject: [PATCH 118/144] Changed preprocess_ hooks to be available for OO. Fixes #417. --- Task/Collect/HooksCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Task/Collect/HooksCollector.php b/Task/Collect/HooksCollector.php index 14ebecce..d1c99244 100644 --- a/Task/Collect/HooksCollector.php +++ b/Task/Collect/HooksCollector.php @@ -279,7 +279,7 @@ protected function processHookData($hook_file_data) { $procedural = ( in_array($short_name, $obligate_procedural_hooks) || - preg_match('/^(post_update_|preprocess_|process_|update_\d+$)/', $short_name) + preg_match('/^(post_update_|process_|update_\d+$)/', $short_name) ); // Because we're working through the raw data array, we keep the incoming From 3555886337c2bd5abd924faae74a714dbc00a403 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 26 Jan 2026 09:43:56 +0000 Subject: [PATCH 119/144] Updated tests for new class name match sniff. --- Test/Unit/Parsing/PHPTester.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index ea4b5818..e5916d43 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -126,8 +126,9 @@ public function assertDrupalCodingStandards(array $excluded_sniffs = []) { $excluded_sniffs[] = 'SlevomatCodingStandard.Commenting.ForbiddenComments'; if (empty($this->phpCodeFilePath)) { - // Exclude this sniff if we don't have access to the file name. + // Exclude the class names sniff if we don't have access to the file name. $excluded_sniffs[] = 'Squiz.Classes.ClassFileName.NoMatch'; + $excluded_sniffs[] = 'Drupal.Classes.ClassFileName.NoMatch'; } if ($this->drupalMajorVersion <= 7) { @@ -137,6 +138,7 @@ public function assertDrupalCodingStandards(array $excluded_sniffs = []) { // Drupal 7 typically has classes in .inc files, which do not match the // class name. $excluded_sniffs[] = 'Squiz.Classes.ClassFileName.NoMatch'; + $excluded_sniffs[] = 'Drupal.Classes.ClassFileName.NoMatch'; } $constraint = new CodeAdheresToCodingStandards($this->drupalMajorVersion, $excluded_sniffs, $this->phpCodeFilePath); From 7c5d2942f0463990075346e196cfa0105cf3fda7 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Wed, 28 Jan 2026 17:36:21 +0000 Subject: [PATCH 120/144] Fixed implicit nullable param deprecation. --- Definition/OptionDefinition.php | 2 +- Definition/PresetDefinition.php | 2 +- Generator/DrushCommand.php | 2 +- Generator/PHPFunction.php | 2 +- Task/Generate.php | 2 +- Task/Generate/ComponentClassHandler.php | 2 +- Task/Generate/ComponentCollector.php | 2 +- Task/Generate/FileAssembler.php | 4 ++-- .../modules/test_analyze_9/src/Plugin/Action/Alpha.php | 2 +- Test/Unit/Parsing/FormBuilderTester.php | 2 +- Test/Unit/Parsing/PHPTester.php | 2 +- Test/Unit/TestBase.php | 4 ++-- Utility/NestedArray.php | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Definition/OptionDefinition.php b/Definition/OptionDefinition.php index b01ce8e4..c6c45f74 100644 --- a/Definition/OptionDefinition.php +++ b/Definition/OptionDefinition.php @@ -37,7 +37,7 @@ public function __construct( * * @return static */ - public static function create($value, string $label, string $description = NULL, int $weight = 0, ?string $api_url = NULL): self { + public static function create($value, string $label, ?string $description = NULL, int $weight = 0, ?string $api_url = NULL): self { return new static($value, $label, $description, $weight, $api_url); } diff --git a/Definition/PresetDefinition.php b/Definition/PresetDefinition.php index c2a0e211..e93eb58e 100644 --- a/Definition/PresetDefinition.php +++ b/Definition/PresetDefinition.php @@ -57,7 +57,7 @@ public function __construct($value, $label, $description = NULL) { * * @return static */ - public static function create($value, string $label, string $description = NULL): self { + public static function create($value, string $label, ?string $description = NULL): self { return new static($value, $label, $description); } diff --git a/Generator/DrushCommand.php b/Generator/DrushCommand.php index c81a4b03..c15a0a75 100644 --- a/Generator/DrushCommand.php +++ b/Generator/DrushCommand.php @@ -309,7 +309,7 @@ protected function getFunctionAttributes(): array { /** * {@inheritdoc} */ - protected function buildMethodDeclaration($name, $parameters = [], $options = [], string $return_type = NULL): array { + protected function buildMethodDeclaration($name, $parameters = [], $options = [], ?string $return_type = NULL): array { $parameters = []; // Add command parameters. diff --git a/Generator/PHPFunction.php b/Generator/PHPFunction.php index a335d78a..f6cfb071 100644 --- a/Generator/PHPFunction.php +++ b/Generator/PHPFunction.php @@ -406,7 +406,7 @@ protected function getFunctionAttributes(): array { * @return array * An array of code lines. */ - protected function buildMethodDeclaration($name, $parameters = [], $options = [], string $return_type = NULL): array { + protected function buildMethodDeclaration($name, $parameters = [], $options = [], ?string $return_type = NULL): array { $options += [ 'prefixes' => [], 'break_declaration' => FALSE, diff --git a/Task/Generate.php b/Task/Generate.php index 5330815f..d33d4161 100644 --- a/Task/Generate.php +++ b/Task/Generate.php @@ -111,7 +111,7 @@ public function getRootComponentData($component_type = 'module') { * @throws \DrupalCodeBuilder\Exception\InvalidInputException * Throws an exception if the given data is invalid. */ - public function generateComponent(DataItem $component_data, $existing_module_files = [], DataItem $configuration = NULL, DrupalExtension $existing_extension = NULL) { + public function generateComponent(DataItem $component_data, $existing_module_files = [], ?DataItem $configuration = NULL, ?DrupalExtension $existing_extension = NULL) { // Validate to ensure defaults are filled in. $component_data->validate(); diff --git a/Task/Generate/ComponentClassHandler.php b/Task/Generate/ComponentClassHandler.php index b616d2a5..1e909dbe 100644 --- a/Task/Generate/ComponentClassHandler.php +++ b/Task/Generate/ComponentClassHandler.php @@ -54,7 +54,7 @@ public function __construct( * @throws \InvalidArgumentException * Throws an exception if there is no class found for the component type. */ - public function getStandaloneComponentPropertyDefinition(string $component_type, string $machine_name = NULL): PropertyDefinition { + public function getStandaloneComponentPropertyDefinition(string $component_type, ?string $machine_name = NULL): PropertyDefinition { $definition = MergingGeneratorDefinition::createFromGeneratorType($component_type); if (!$definition->getName()) { diff --git a/Task/Generate/ComponentCollector.php b/Task/Generate/ComponentCollector.php index 3b89546a..cbf5f68f 100644 --- a/Task/Generate/ComponentCollector.php +++ b/Task/Generate/ComponentCollector.php @@ -174,7 +174,7 @@ public function __construct( * @return \DrupalCodeBuilder\Generator\Collection\ComponentCollection * The collection of components. */ - public function assembleComponentList(DataItem $component_data, DrupalExtension $extension = NULL): ComponentCollection { + public function assembleComponentList(DataItem $component_data, ?DrupalExtension $extension = NULL): ComponentCollection { // Reset all class properties. We don't normally run this twice, but // probably needed for tests. $this->requested_data_record = []; diff --git a/Task/Generate/FileAssembler.php b/Task/Generate/FileAssembler.php index f232274d..c2cb4b1d 100644 --- a/Task/Generate/FileAssembler.php +++ b/Task/Generate/FileAssembler.php @@ -23,7 +23,7 @@ class FileAssembler { * are filepaths relative to the module folder (eg, 'foo.module', * 'tests/module.test'). */ - public function generateFiles($component_data, ComponentCollection $component_collection, DrupalExtension $existing_extension = NULL) { + public function generateFiles($component_data, ComponentCollection $component_collection, ?DrupalExtension $existing_extension = NULL) { $component_list = $component_collection->getComponents(); // Let each file component in the tree gather data from its own children. @@ -73,7 +73,7 @@ protected function collectFileContents(ComponentCollection $component_collection * are filepaths relative to the module folder (eg, 'foo.module', * 'tests/module.test'). */ - protected function collectFiles(ComponentCollection $component_collection, DrupalExtension $existing_extension = NULL): array { + protected function collectFiles(ComponentCollection $component_collection, ?DrupalExtension $existing_extension = NULL): array { $code_files = []; // Components which provide a file should have registered themselves as diff --git a/Test/Fixtures/modules/test_analyze_9/src/Plugin/Action/Alpha.php b/Test/Fixtures/modules/test_analyze_9/src/Plugin/Action/Alpha.php index 42573a50..5e441986 100644 --- a/Test/Fixtures/modules/test_analyze_9/src/Plugin/Action/Alpha.php +++ b/Test/Fixtures/modules/test_analyze_9/src/Plugin/Action/Alpha.php @@ -76,7 +76,7 @@ public function executeMultiple(array $objects) { /** * {@inheritdoc} */ - public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) { // Checks object access. } diff --git a/Test/Unit/Parsing/FormBuilderTester.php b/Test/Unit/Parsing/FormBuilderTester.php index 973f409f..d0b483f5 100644 --- a/Test/Unit/Parsing/FormBuilderTester.php +++ b/Test/Unit/Parsing/FormBuilderTester.php @@ -272,7 +272,7 @@ public function assertElementIsRequired($element_name, $message = NULL) { * @param string $message * (optional) The assertion message. */ - public function assertElementHasAttribute(string $attribute_name, string $element_name, string $message = NULL) { + public function assertElementHasAttribute(string $attribute_name, string $element_name, ?string $message = NULL) { $message = $message ?? "The form's '{$element_name}' element has the attribute '{$attribute_name}'."; $this->assertHasElementName($element_name); diff --git a/Test/Unit/Parsing/PHPTester.php b/Test/Unit/Parsing/PHPTester.php index e5916d43..4c38e9a6 100644 --- a/Test/Unit/Parsing/PHPTester.php +++ b/Test/Unit/Parsing/PHPTester.php @@ -776,7 +776,7 @@ public function assertNotClassHasInterfaces($not_expected_interface_names) { * @param string $message * (optional) The assertion message. */ - public function assertClassHasConstant(string $name, string $message = NULL) { + public function assertClassHasConstant(string $name, ?string $message = NULL) { $message ??= "The class has the constant '$name'."; Assert::assertArrayHasKey($name, $this->parser_nodes['constants'], $message); diff --git a/Test/Unit/TestBase.php b/Test/Unit/TestBase.php index 6d6f77d7..41573c0c 100644 --- a/Test/Unit/TestBase.php +++ b/Test/Unit/TestBase.php @@ -121,7 +121,7 @@ protected function getMockedExtension(string $type, array $files = []) { * @param * An array of files. */ - protected function generateComponentFilesFromData(DataItem $component_data, DrupalExtension $extension = NULL) { + protected function generateComponentFilesFromData(DataItem $component_data, ?DrupalExtension $extension = NULL) { $violations = $component_data->validate(); if ($violations) { @@ -146,7 +146,7 @@ protected function generateComponentFilesFromData(DataItem $component_data, Drup * @param * An array of files. */ - protected function generateModuleFiles(array $module_data, DrupalExtension $extension = NULL) { + protected function generateModuleFiles(array $module_data, ?DrupalExtension $extension = NULL) { $component_data = $this->getRootComponentBlankData('module'); $component_data->set($module_data); diff --git a/Utility/NestedArray.php b/Utility/NestedArray.php index 0dddc84f..f008a3bc 100644 --- a/Utility/NestedArray.php +++ b/Utility/NestedArray.php @@ -361,7 +361,7 @@ public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FA * @return array * The filtered array. */ - public static function filter(array $array, callable $callable = NULL) { + public static function filter(array $array, ?callable $callable = NULL) { $array = is_callable($callable) ? array_filter($array, $callable) : array_filter($array); foreach ($array as &$element) { if (is_array($element)) { From 7ec94fa47cb704aa9ce5c2efff275bf672e5ba4e Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 7 Feb 2026 17:41:33 +0000 Subject: [PATCH 121/144] Added content entity properties for revisions UI. Fixes #402. --- .../DrupalCodeBuilderCompiledContainer.php | 4 ++ Generator/ContentEntityType.php | 45 ++++++++++++--- Generator/ContentEntityType9AndLower.php | 24 ++++++++ .../Unit/ComponentContentEntityType10Test.php | 41 +++++++++++++ Test/Unit/ComponentContentEntityType9Test.php | 57 +++++++++++++++++++ 5 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 Generator/ContentEntityType9AndLower.php create mode 100644 Test/Unit/ComponentContentEntityType9Test.php diff --git a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php index f4e38b35..8063a636 100644 --- a/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php +++ b/DependencyInjection/cache/DrupalCodeBuilderCompiledContainer.php @@ -233,6 +233,10 @@ protected function get3() return [ 'AdminSettingsForm' => [ 7 => 'AdminSettingsForm7', + ], + 'ContentEntityType' => [ + 9 => 'ContentEntityType9AndLower', + 8 => 'ContentEntityType9AndLower', ], 'DrushCommand' => [ 11 => 'DrushCommand', diff --git a/Generator/ContentEntityType.php b/Generator/ContentEntityType.php index 488aa02a..82dac940 100644 --- a/Generator/ContentEntityType.php +++ b/Generator/ContentEntityType.php @@ -560,13 +560,18 @@ public function requiredComponents(): array { protected function getAnnotationData() { $annotation_data = parent::getAnnotationData(); + $revisionable = in_array('revisionable', $this->component_data['functionality']); + $translatable = in_array('translatable', $this->component_data['functionality']); + $ui = !empty($this->component_data['entity_ui']); + // Add further annotation properties. // Use the entity type ID as the base table. $annotation_data['base_table'] = $this->component_data['entity_type_id']; - if (!empty($this->component_data['entity_ui'])) { + if ($ui) { $annotation_data['links'] = []; $entity_path_component = $this->component_data['entity_type_id']; + $entity_path_placeholder = "{{$entity_path_component}}"; // The structure of the add UI depends on whether there is a bundle // entity. @@ -584,12 +589,10 @@ protected function getAnnotationData() { $annotation_data['links']["add-form"] = "/$entity_path_component/add"; } - $annotation_data['links']["canonical"] = "/$entity_path_component/{{$entity_path_component}}"; + $annotation_data['links']["canonical"] = "/$entity_path_component/$entity_path_placeholder"; $annotation_data['links']["collection"] = "/admin/content/$entity_path_component"; - $annotation_data['links']["delete-form"] = "/$entity_path_component/{{$entity_path_component}}/delete"; - $annotation_data['links']["edit-form"] = "/$entity_path_component/{{$entity_path_component}}/edit"; - // TODO: revision link template. - // $annotation_data['links']["revision"] = "/$entity_path_component/{}/revisions/{media_revision}/view"; + $annotation_data['links']["delete-form"] = "/$entity_path_component/$entity_path_placeholder/delete"; + $annotation_data['links']["edit-form"] = "/$entity_path_component/$entity_path_placeholder/edit"; } if (!$this->component_data->bundle_entity->isEmpty()) { @@ -597,9 +600,6 @@ protected function getAnnotationData() { $annotation_data['bundle_label'] = ClassAnnotation::Translation($this->component_data['bundle_label']); } - $revisionable = in_array('revisionable', $this->component_data['functionality']); - $translatable = in_array('translatable', $this->component_data['functionality']); - if ($this->component_data->field_ui_base_route->value) { $annotation_data['field_ui_base_route'] = $this->component_data->field_ui_base_route->value; } @@ -617,7 +617,34 @@ protected function getAnnotationData() { $annotation_data['revision_data_table'] = "{$annotation_data['base_table']}_field_revision"; } + if ($ui && $revisionable) { + $this->addRevisionUiAnnotationData($annotation_data); + } + return $annotation_data; } + /** + * Adds annotation data for revisions UI. + * + * @param array &$annotation_data + * The annotation data. + */ + protected function addRevisionUiAnnotationData(&$annotation_data) { + $entity_path_component = $this->component_data->entity_type_id->value; + $entity_path_placeholder = "{{$entity_path_component}}"; + $entity_revision_path_placeholder = "{{$entity_path_component}_revision}"; + + $annotation_data['handlers']['route_provider']['revision'] = 'Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider'; + + $annotation_data['handlers']['form']['revision-delete'] = 'Drupal\Core\Entity\Form\RevisionDeleteForm'; + $annotation_data['handlers']['form']['revision-revert'] = 'Drupal\Core\Entity\Form\RevisionRevertForm'; + + $annotation_data['links']['version-history'] = "/$entity_path_component/$entity_path_placeholder/revisions"; + $annotation_data['links']['revision'] = "/$entity_path_component/$entity_path_placeholder/revisions/$entity_revision_path_placeholder/view"; + $annotation_data['links']['revision-delete-form'] = "/$entity_path_component/$entity_path_placeholder/revisions/$entity_revision_path_placeholder/view"; + $annotation_data['links']['revision-revert-form'] = "/$entity_path_component/$entity_path_placeholder/revisions/$entity_revision_path_placeholder/revert"; + $annotation_data['links']['version-history'] = "/$entity_path_component/$entity_path_placeholder/revisions"; + } + } diff --git a/Generator/ContentEntityType9AndLower.php b/Generator/ContentEntityType9AndLower.php new file mode 100644 index 00000000..12a57446 --- /dev/null +++ b/Generator/ContentEntityType9AndLower.php @@ -0,0 +1,24 @@ +assertHasLine('$form_state->setRedirectUrl($this->entity->toUrl(\'canonical\'));'); } + /** + * Tests creating a content entity with a revision UI. + * + * @group entity_ui + * @group form + */ + public function testContentEntityTypeWithRevisionEntityUI() { + $module_name = 'test_module'; + $module_data = [ + 'base' => 'module', + 'root_name' => $module_name, + 'readable_name' => 'Test module', + 'content_entity_types' => [ + 0 => [ + 'entity_type_id' => 'kitty_cat', + 'functionality' => [ + 'revisionable', + ], + 'entity_ui' => 'admin', + ], + ], + 'readme' => FALSE, + ]; + + $files = $this->generateModuleFiles($module_data); + + $entity_class_file = $files['src/Entity/KittyCat.php']; + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $entity_class_file); + $annotation_tester = $php_tester->getAnnotationTesterForClass(); + + $annotation_tester->assertPropertyHasValue(['handlers', 'route_provider', 'revision'], 'Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider'); + + $annotation_tester->assertPropertyHasValue(['handlers', 'form', 'revision-delete'], 'Drupal\Core\Entity\Form\RevisionDeleteForm'); + $annotation_tester->assertPropertyHasValue(['handlers', 'form', 'revision-revert'], 'Drupal\Core\Entity\Form\RevisionRevertForm'); + + $annotation_tester->assertPropertyHasValue(['links', 'revision'], '/kitty_cat/{kitty_cat}/revisions/{kitty_cat_revision}/view'); + $annotation_tester->assertPropertyHasValue(['links', 'revision-delete-form'], '/kitty_cat/{kitty_cat}/revisions/{kitty_cat_revision}/view'); + $annotation_tester->assertPropertyHasValue(['links', 'revision-revert-form'], '/kitty_cat/{kitty_cat}/revisions/{kitty_cat_revision}/revert'); + $annotation_tester->assertPropertyHasValue(['links', 'version-history'], '/kitty_cat/{kitty_cat}/revisions'); + } + /** * Tests creating a content entity with a bundle entity UI. * diff --git a/Test/Unit/ComponentContentEntityType9Test.php b/Test/Unit/ComponentContentEntityType9Test.php new file mode 100644 index 00000000..b9017571 --- /dev/null +++ b/Test/Unit/ComponentContentEntityType9Test.php @@ -0,0 +1,57 @@ + 'module', + 'root_name' => $module_name, + 'readable_name' => 'Test module', + 'content_entity_types' => [ + 0 => [ + 'entity_type_id' => 'kitty_cat', + 'functionality' => [ + 'revisionable', + ], + 'entity_ui' => 'admin', + ], + ], + 'readme' => FALSE, + ]; + + $files = $this->generateModuleFiles($module_data); + + $entity_class_file = $files['src/Entity/KittyCat.php']; + $php_tester = PHPTester::fromCodeFile($this->drupalMajorVersion, $entity_class_file); + $annotation_tester = $php_tester->getAnnotationTesterForClass(); + + $annotation_tester->assertNotHasProperty(['handlers', 'route_provider', 'revision']); + } + +} From 52d036d7e3930892bcfb2075b91d094c6f52cda5 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sat, 7 Feb 2026 17:41:55 +0000 Subject: [PATCH 122/144] Fixed missing revision metadata keys on content entities using revisions. --- Generator/ContentEntityType.php | 7 +++++++ Test/Unit/ComponentContentEntityType10Test.php | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/Generator/ContentEntityType.php b/Generator/ContentEntityType.php index 82dac940..00aa42cf 100644 --- a/Generator/ContentEntityType.php +++ b/Generator/ContentEntityType.php @@ -41,6 +41,7 @@ class ContentEntityType extends EntityTypeBase { 'handlers', 'admin_permission', 'entity_keys', + 'revision_metadata_keys', 'bundle_entity_type', 'field_ui_base_route', 'links', @@ -606,6 +607,12 @@ protected function getAnnotationData() { if ($revisionable) { $annotation_data['revision_table'] = "{$annotation_data['base_table']}_revision"; + + $annotation_data['revision_metadata_keys'] = [ + 'revision_user' => 'revision_uid', + 'revision_created' => 'revision_timestamp', + 'revision_log_message' => 'revision_log' + ]; } if ($translatable) { diff --git a/Test/Unit/ComponentContentEntityType10Test.php b/Test/Unit/ComponentContentEntityType10Test.php index 6c6cfb44..5f2b6ea5 100644 --- a/Test/Unit/ComponentContentEntityType10Test.php +++ b/Test/Unit/ComponentContentEntityType10Test.php @@ -594,6 +594,7 @@ public function testEntityTypeWithRevisions() { 'handlers', 'admin_permission', 'entity_keys', + 'revision_metadata_keys', 'field_ui_base_route', ]); $annotation_tester->assertPropertyHasValue('base_table', 'kitty_cat'); @@ -606,6 +607,10 @@ public function testEntityTypeWithRevisions() { ], 'entity_keys'); $annotation_tester->assertPropertyHasValue(['entity_keys', 'id'], 'kitty_cat_id'); $annotation_tester->assertPropertyHasValue(['entity_keys', 'revision'], 'revision_id'); + + $annotation_tester->assertPropertyHasValue(['revision_metadata_keys', 'revision_user'], 'revision_uid'); + $annotation_tester->assertPropertyHasValue(['revision_metadata_keys', 'revision_created'], 'revision_timestamp'); + $annotation_tester->assertPropertyHasValue(['revision_metadata_keys', 'revision_log_message'], 'revision_log'); } /** From 0bc257f1330bbbb48f02e8bef419280ba58fcf44 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 8 Feb 2026 10:25:16 +0000 Subject: [PATCH 123/144] Added default container injection interface to base DI class. --- Generator/Controller.php | 5 ----- Generator/DrushCommandsClass.php | 5 +++++ Generator/DynamicRouteProvider.php | 5 ----- Generator/Form.php | 5 ----- Generator/PHPClassFileWithInjection.php | 2 +- Generator/Service.php | 5 +++++ 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Generator/Controller.php b/Generator/Controller.php index ffb886ad..835219e3 100644 --- a/Generator/Controller.php +++ b/Generator/Controller.php @@ -9,11 +9,6 @@ */ class Controller extends PHPClassFileWithInjection { - /** - * {@inheritdoc} - */ - protected const CLASS_DI_INTERFACE = '\Drupal\Core\DependencyInjection\ContainerInjectionInterface'; - /** * {@inheritdoc} */ diff --git a/Generator/DrushCommandsClass.php b/Generator/DrushCommandsClass.php index 233bec4b..87e7e769 100644 --- a/Generator/DrushCommandsClass.php +++ b/Generator/DrushCommandsClass.php @@ -9,6 +9,11 @@ */ class DrushCommandsClass extends PHPClassFileWithInjection { + /** + * {@inheritdoc} + */ + protected const CLASS_DI_INTERFACE = NULL; + /** * {@inheritdoc} */ diff --git a/Generator/DynamicRouteProvider.php b/Generator/DynamicRouteProvider.php index c17248b8..d4d46d15 100644 --- a/Generator/DynamicRouteProvider.php +++ b/Generator/DynamicRouteProvider.php @@ -12,11 +12,6 @@ */ class DynamicRouteProvider extends PHPClassFileWithInjection { - /** - * {@inheritdoc} - */ - protected const CLASS_DI_INTERFACE = '\Drupal\Core\DependencyInjection\ContainerInjectionInterface'; - /** * {@inheritdoc} */ diff --git a/Generator/Form.php b/Generator/Form.php index 7aa1099d..c18c933b 100644 --- a/Generator/Form.php +++ b/Generator/Form.php @@ -19,11 +19,6 @@ */ class Form extends PHPClassFileWithInjection implements AdoptableInterface { - /** - * {@inheritdoc} - */ - protected const CLASS_DI_INTERFACE = '\Drupal\Core\DependencyInjection\ContainerInjectionInterface'; - /** * {@inheritdoc} */ diff --git a/Generator/PHPClassFileWithInjection.php b/Generator/PHPClassFileWithInjection.php index 25936d60..6ca53ed1 100644 --- a/Generator/PHPClassFileWithInjection.php +++ b/Generator/PHPClassFileWithInjection.php @@ -18,7 +18,7 @@ class PHPClassFileWithInjection extends PHPClassFile { * * @var string|null */ - protected const CLASS_DI_INTERFACE = NULL; + protected const CLASS_DI_INTERFACE = '\Drupal\Core\DependencyInjection\ContainerInjectionInterface'; /** * The interface to use for the static create() method's container parameter. diff --git a/Generator/Service.php b/Generator/Service.php index c159c687..7c5b704e 100644 --- a/Generator/Service.php +++ b/Generator/Service.php @@ -16,6 +16,11 @@ */ class Service extends PHPClassFileWithInjection implements AdoptableInterface { + /** + * {@inheritdoc} + */ + protected const CLASS_DI_INTERFACE = NULL; + use NameFormattingTrait; /** From e2a4902f6c5eff88dbc9625636702ecf31e5eb8a Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 8 Feb 2026 11:59:56 +0000 Subject: [PATCH 124/144] Fixed DI code on constraint plugin going in the plugin rather than the validator class. Fixes #381. --- Generator/PluginValidationConstraint.php | 12 ++++++++++-- Test/Unit/ComponentPluginsAttribute11Test.php | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Generator/PluginValidationConstraint.php b/Generator/PluginValidationConstraint.php index 768e0b75..99c7d824 100644 --- a/Generator/PluginValidationConstraint.php +++ b/Generator/PluginValidationConstraint.php @@ -17,11 +17,13 @@ class PluginValidationConstraint extends PluginClassDiscoveryHybrid { * Return an array of subcomponent types. */ public function requiredComponents(): array { - $components = parent::requiredComponents(); + $components = []; $components['validator'] = [ - 'component_type' => 'PHPClassFile', + 'component_type' => 'PHPClassFileWithInjection', 'plain_class_name' => $this->component_data['plain_class_name'] . 'Validator', + 'injected_services' => $this->component_data->injected_services->values(), + 'use_static_factory_method' => TRUE, 'relative_namespace' => $this->component_data['relative_namespace'], 'parent_class_name' => '\Symfony\Component\Validator\ConstraintValidator', 'docblock_first_line' => "Validates the {$this->component_data['plain_class_name']} constraint.", @@ -30,6 +32,12 @@ public function requiredComponents(): array { // See https://github.com/drupal-code-builder/drupal-code-builder/issues/134 ]; + // Zap the injected services set here, as we don't want the plugin class to + // have any DI. + $this->component_data->injected_services = []; + + $components += parent::requiredComponents(); + $components['validator_validate'] = [ 'component_type' => 'PHPFunction', 'function_name' => 'validate', diff --git a/Test/Unit/ComponentPluginsAttribute11Test.php b/Test/Unit/ComponentPluginsAttribute11Test.php index 0077276d..09bbbe32 100644 --- a/Test/Unit/ComponentPluginsAttribute11Test.php +++ b/Test/Unit/ComponentPluginsAttribute11Test.php @@ -598,6 +598,8 @@ function testPluginsGenerationWithOtherSchema() { /** * Test the validation constraint plugin variant. + * + * @group di */ public function testPluginValidationConstraint(): void { // Create a module. @@ -611,6 +613,9 @@ public function testPluginValidationConstraint(): void { 0 => [ 'plugin_type' => 'validation.constraint', 'plugin_name' => 'alpha', + 'injected_services' => [ + 'entity_type.manager', + ], ], ], 'readme' => FALSE, @@ -639,6 +644,20 @@ public function testPluginValidationConstraint(): void { $php_tester->assertHasClass('Drupal\test_module\Plugin\Validation\Constraint\AlphaValidator'); $php_tester->assertClassHasParent('Symfony\Component\Validator\ConstraintValidator'); $php_tester->assertHasMethod('validate'); + + // Check service injection. + $php_tester->assertClassHasInterfaces([ + 'Drupal\Core\DependencyInjection\ContainerInjectionInterface', + ]); + $php_tester->assertInjectedServicesWithFactory([ + [ + 'typehint' => 'Drupal\Core\Entity\EntityTypeManagerInterface', + 'service_name' => 'entity_type.manager', + 'property_name' => 'entityTypeManager', + 'parameter_name' => 'entity_type_manager', + ], + ]); + } } From e4ce6a5e3a821c3721fa3c34d679b1179b7c3974 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 9 Feb 2026 13:50:07 +0000 Subject: [PATCH 125/144] Fixed (temporarily!) crash caused by analysis of BlockContentCreationTrait. --- Task/Analyse/TestTraits.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Task/Analyse/TestTraits.php b/Task/Analyse/TestTraits.php index 4d67c093..9448767f 100644 --- a/Task/Analyse/TestTraits.php +++ b/Task/Analyse/TestTraits.php @@ -120,6 +120,12 @@ public function collect($job_list) { $short_trait_name = $file->getFilenameWithoutExtension(); + // Temporary workaround. + // See https://github.com/drupal-code-builder/drupal-code-builder/issues/420. + if ($short_trait_name == 'BlockContentCreationTrait') { + continue; + } + // Files in test folders aren't in the regular Composer autoloader, so // include the file so we can use reflection on the class. include_once($relative_pathname); From 28a9c0e7429135152e7d03891e55e772596afa97 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 9 Feb 2026 13:54:35 +0000 Subject: [PATCH 126/144] Fixed NULL offset. --- Task/ReportPluginData.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Task/ReportPluginData.php b/Task/ReportPluginData.php index d7b711e3..da305b85 100644 --- a/Task/ReportPluginData.php +++ b/Task/ReportPluginData.php @@ -74,17 +74,21 @@ public function getVariantMapping(): array { * The processed plugin data. * * @see \DrupalCodeBuilder\Task\Collect::gatherPluginTypeInfo() + * + * @todo Split this method into two. */ function listPluginData($discovery_type = NULL) { - // We may come here several times, so cache this. - // TODO: look into finer-grained caching higher up. - if (isset($this->cache[$discovery_type])) { - return $this->cache[$discovery_type]; + if ($discovery_type) { + // We may come here several times, so cache a filtered result. + // TODO: look into finer-grained caching higher up. + if (isset($this->cache[$discovery_type])) { + return $this->cache[$discovery_type]; + } } $plugin_data = $this->environment->getStorage()->retrieve('plugins'); - // Filter the plugins by the discovery type. + // Filter the plugins if there's a requested discovery type. if ($discovery_type) { $plugin_data = array_filter($plugin_data, function($item) use ($discovery_type) { $discovery_pieces = explode('\\', $item['discovery']); @@ -92,9 +96,9 @@ function listPluginData($discovery_type = NULL) { return ($discovery_short_name == $discovery_type); }); - } - $this->cache[$discovery_type] = $plugin_data; + $this->cache[$discovery_type] = $plugin_data; + } return $plugin_data; } From b95830b765db0e12a38846ea9e77761a7b6f84bb Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 9 Feb 2026 13:55:12 +0000 Subject: [PATCH 127/144] Fixed generator type for generator classes with version attributes. Fixes #384. --- Generator/BaseGenerator.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Generator/BaseGenerator.php b/Generator/BaseGenerator.php index c3aed17a..51b67d01 100644 --- a/Generator/BaseGenerator.php +++ b/Generator/BaseGenerator.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Generator; +use DrupalCodeBuilder\Attribute\RelatedBaseClass; use MutableTypedData\Definition\PropertyListInterface; use DrupalCodeBuilder\Generator\Collection\ComponentCollection; use DrupalCodeBuilder\Definition\PropertyDefinition; @@ -194,9 +195,16 @@ function __construct(DataItem $component_data) { // Set the type. This is the short class name without the numeric version // suffix. $class = get_class($this); - $class_pieces = explode('\\', $class); - $short_class = array_pop($class_pieces); - $this->type = preg_replace('@\d+$@', '', $short_class); + + $reflector = new \ReflectionClass($class); + if ($base_class_attributes = $reflector->getAttributes(RelatedBaseClass::class)) { + $this->type = $base_class_attributes[0]->newInstance()->base_class; + } + else { + $class_pieces = explode('\\', $class); + $short_class = array_pop($class_pieces); + $this->type = preg_replace('@\d+$@', '', $short_class); + } } /** From 1f5be93d8b98378ca21e177a8066e471ba1db9a7 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 23 Feb 2026 15:20:32 +0000 Subject: [PATCH 128/144] Fixed naive loading of test trait files not working for trait inheritance and non-installed modules. Fixes #420. --- Task/Analyse/TestTraits.php | 44 +++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/Task/Analyse/TestTraits.php b/Task/Analyse/TestTraits.php index 9448767f..0af1fa3a 100644 --- a/Task/Analyse/TestTraits.php +++ b/Task/Analyse/TestTraits.php @@ -2,12 +2,14 @@ namespace DrupalCodeBuilder\Task\Analyse; -use MutableTypedData\Definition\OptionSetDefininitionInterface; +use Composer\Autoload\ClassLoader; use DrupalCodeBuilder\Environment\EnvironmentInterface; use DrupalCodeBuilder\Task\Collect\CollectorBase; use DrupalCodeBuilder\Task\Report\SectionReportInterface; use DrupalCodeBuilder\Task\SectionReportSimpleCountTrait; use DrupalCodeBuilder\Definition\OptionDefinition; +use Drupal\Core\Extension\ExtensionDiscovery; +use MutableTypedData\Definition\OptionSetDefininitionInterface; /** * Task helper for analysing and reporting on traits intended for use in tests. @@ -74,6 +76,36 @@ public function getJobList() { * {@inheritdoc} */ public function collect($job_list) { + // Set up an autoloader, as test code is not included in Composer's project + // autoloader. Traits may extend other traits, so we can't just include + // files naively. + $loader = new ClassLoader(); + + $drupal_test_dir = $this->environment->getRoot() . '/core/tests'; + + $loader->add('Drupal\\BuildTests', $drupal_test_dir); + $loader->add('Drupal\\Tests', $drupal_test_dir); + $loader->add('Drupal\\TestSite', $drupal_test_dir); + $loader->add('Drupal\\KernelTests', $drupal_test_dir); + $loader->add('Drupal\\FunctionalTests', $drupal_test_dir); + $loader->add('Drupal\\FunctionalJavascriptTests', $drupal_test_dir); + $loader->add('Drupal\\TestTools', $drupal_test_dir); + + $root = $this->environment->getRoot() . '/'; + + // We need to include all discovered modules, not just enabled ones, as our + // Finder looks at all code, and installed modules may have traits which + // inherit from non-installed modules. + // Safe to use this as ExtensionDiscovery was introduced in 8.0.0. + $discovery = new ExtensionDiscovery($root); + $discovery->setProfileDirectories([]); + $discovered_modules = $discovery->scan('module'); + foreach ($discovered_modules as $module_name => $module_extension) { + $loader->addPsr4('Drupal\\Tests\\' . $module_name . '\\', $root . $module_extension->getPath() . '/tests/src'); + } + + $loader->register(); + $finder = new \Symfony\Component\Finder\Finder(); $finder ->in($this->environment->getRoot()) @@ -120,16 +152,6 @@ public function collect($job_list) { $short_trait_name = $file->getFilenameWithoutExtension(); - // Temporary workaround. - // See https://github.com/drupal-code-builder/drupal-code-builder/issues/420. - if ($short_trait_name == 'BlockContentCreationTrait') { - continue; - } - - // Files in test folders aren't in the regular Composer autoloader, so - // include the file so we can use reflection on the class. - include_once($relative_pathname); - $class_reflection = new \ReflectionClass($classname); $docblock = $class_reflection->getDocComment(); From 855e80f8a8c89fa5fa52e2106241fd8f6982269b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 23 Feb 2026 15:33:20 +0000 Subject: [PATCH 129/144] Updated for deprecated setAccessible(). --- Task/Collect/ContainerBuilderGetter.php | 4 +- Task/Collect/PluginTypesCollector.php | 43 +++++++++++++++---- .../CollectPluginInfoDummyModulesTest.php | 5 ++- .../Collection/CollectPluginInfoTest.php | 5 ++- .../Installation/InstallationTestBase.php | 5 ++- Test/Unit/ComponentPHPFile10Test.php | 5 ++- Test/Unit/ContainerCollectionTest.php | 5 ++- Test/Unit/ContainerTest.php | 20 +++++++-- 8 files changed, 73 insertions(+), 19 deletions(-) diff --git a/Task/Collect/ContainerBuilderGetter.php b/Task/Collect/ContainerBuilderGetter.php index d4c5e5ed..17f552a8 100644 --- a/Task/Collect/ContainerBuilderGetter.php +++ b/Task/Collect/ContainerBuilderGetter.php @@ -27,7 +27,9 @@ public function getContainerBuilder() { $kernel_R = new \ReflectionClass($kernel); $compileContainer_R = $kernel_R->getMethod('compileContainer'); - $compileContainer_R->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $compileContainer_R->setAccessible(TRUE); + } $this->containerBuilder = $compileContainer_R->invoke($kernel); } diff --git a/Task/Collect/PluginTypesCollector.php b/Task/Collect/PluginTypesCollector.php index 53997371..3ef1c592 100644 --- a/Task/Collect/PluginTypesCollector.php +++ b/Task/Collect/PluginTypesCollector.php @@ -366,7 +366,9 @@ protected function addPluginTypeServiceData(&$data) { // Determine the alter hook name. if ($service_reflection->hasProperty('alterHook')) { $property_alter_hook = $service_reflection->getProperty('alterHook'); - $property_alter_hook->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $property_alter_hook->setAccessible(TRUE); + } $alter_hook_name = $property_alter_hook->getValue($service); if (!empty($alter_hook_name)) { $data['alter_hook_name'] = $alter_hook_name . '_alter'; @@ -376,7 +378,9 @@ protected function addPluginTypeServiceData(&$data) { // Determine the plugin discovery type. // Get the discovery object from the plugin manager. $method_getDiscovery = $service_reflection->getMethod('getDiscovery'); - $method_getDiscovery->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $method_getDiscovery->setAccessible(TRUE); + } $discovery = $method_getDiscovery->invoke($service); $reflection_discovery = new \ReflectionClass($discovery); @@ -396,7 +400,10 @@ protected function addPluginTypeServiceData(&$data) { break; } - $property_decorated->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $property_decorated->setAccessible(TRUE); + } + $decorated_discovery = $property_decorated->getValue($discovery); // We don't go in to a decorated class that's not in a Plugin component. @@ -525,7 +532,10 @@ protected function addPluginTypeServiceDataAttribute(&$data, $service, $discover } $property = $reflection->getProperty($property_name); - $property->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $property->setAccessible(TRUE); + } + $data[$data_key] = $property->getValue($service) ?? ''; } @@ -570,7 +580,10 @@ protected function addPluginTypeServiceDataAnnotated(&$data, $service, $discover } $property = $reflection->getProperty($property_name); - $property->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $property->setAccessible(TRUE); + } + $data[$data_key] = $property->getValue($service) ?? ''; } @@ -595,7 +608,10 @@ protected function addPluginTypeServiceDataAnnotated(&$data, $service, $discover protected function addPluginTypeServiceDataYaml(&$data, $service, $discovery) { $service_reflection = new \ReflectionClass($service); $property = $service_reflection->getProperty('defaults'); - $property->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $property->setAccessible(TRUE); + } + $defaults = $property->getValue($service); // YAML plugins don't specify their ID; it's generated automatically. @@ -608,12 +624,18 @@ protected function addPluginTypeServiceDataYaml(&$data, $service, $discovery) { // when recursively getting the decorated discovery. $discovery_reflection = new \ReflectionClass($discovery); $property = $discovery_reflection->getProperty('discovery'); - $property->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $property->setAccessible(TRUE); + } + $wrapped_discovery = $property->getValue($discovery); $wrapped_discovery_reflection = new \ReflectionClass($wrapped_discovery); $property = $wrapped_discovery_reflection->getProperty('name'); - $property->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $property->setAccessible(TRUE); + } + $name = $property->getValue($wrapped_discovery); $data['yaml_file_suffix'] = $name; @@ -1492,7 +1514,10 @@ protected function addPluginModuleData(&$plugin_type_data) { // Unfortunately, there's no accessor for this, so some reflection hackery // is required until https://www.drupal.org/node/2907862 is fixed. $reflection = new \ReflectionProperty(\Drupal\plugin\PluginType\PluginType::class, 'pluginManagerServiceId'); - $reflection->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $reflection->setAccessible(TRUE); + } + foreach ($plugin_types as $plugin_type) { // Get the service ID from the reflection, and then our ID. diff --git a/Test/Integration/Collection/CollectPluginInfoDummyModulesTest.php b/Test/Integration/Collection/CollectPluginInfoDummyModulesTest.php index 3d89e430..3bb61a4d 100644 --- a/Test/Integration/Collection/CollectPluginInfoDummyModulesTest.php +++ b/Test/Integration/Collection/CollectPluginInfoDummyModulesTest.php @@ -35,7 +35,10 @@ protected function setUp(): void { // of plugin manager service IDs. $class = new \ReflectionObject($this->pluginTypesCollector); $this->gatherPluginTypeInfoMethod = $class->getMethod('gatherPluginTypeInfo'); - $this->gatherPluginTypeInfoMethod->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $this->gatherPluginTypeInfoMethod->setAccessible(TRUE); + } + } protected function getPluginTypeInfoFromCollector($job) { diff --git a/Test/Integration/Collection/CollectPluginInfoTest.php b/Test/Integration/Collection/CollectPluginInfoTest.php index aada724a..c98ca755 100644 --- a/Test/Integration/Collection/CollectPluginInfoTest.php +++ b/Test/Integration/Collection/CollectPluginInfoTest.php @@ -31,7 +31,10 @@ protected function setUp(): void { // of plugin manager service IDs. $class = new \ReflectionObject($this->pluginTypesCollector); $this->gatherPluginTypeInfoMethod = $class->getMethod('gatherPluginTypeInfo'); - $this->gatherPluginTypeInfoMethod->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $this->gatherPluginTypeInfoMethod->setAccessible(TRUE); + } + } /** diff --git a/Test/Integration/Installation/InstallationTestBase.php b/Test/Integration/Installation/InstallationTestBase.php index e9d16a78..3e9d05cb 100644 --- a/Test/Integration/Installation/InstallationTestBase.php +++ b/Test/Integration/Installation/InstallationTestBase.php @@ -153,7 +153,10 @@ protected function installModule(string $module_name) { // ExtensionDiscovery keeps a cache of found files in a static property that // can only be cleared by hacking it with reflection. $reflection_property = new \ReflectionProperty(\Drupal\Core\Extension\ExtensionDiscovery::class, 'files'); - $reflection_property->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $reflection_property->setAccessible(TRUE); + } + $reflection_property->setValue(NULL, []); $result = $this->container->get('module_installer')->install([$module_name]); diff --git a/Test/Unit/ComponentPHPFile10Test.php b/Test/Unit/ComponentPHPFile10Test.php index 1667450a..677d0755 100644 --- a/Test/Unit/ComponentPHPFile10Test.php +++ b/Test/Unit/ComponentPHPFile10Test.php @@ -67,7 +67,10 @@ public static function setUpMockedComponent() { public function testQualifiedClassNameExtraction($code, $expected_changed_code, $expected_qualified_class_names) { // Make the protected method we're testing callable. $method = new \ReflectionMethod(PHPFile::class, 'extractFullyQualifiedClasses'); - $method->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $method->setAccessible(TRUE); + } + // Pass a component data item to PHPFile. Prophecy won't work as // PHPFile::extractFullyQualifiedClasses() accesses a property, so we create diff --git a/Test/Unit/ContainerCollectionTest.php b/Test/Unit/ContainerCollectionTest.php index 5ae4f418..62025d6a 100644 --- a/Test/Unit/ContainerCollectionTest.php +++ b/Test/Unit/ContainerCollectionTest.php @@ -18,7 +18,10 @@ class ContainerCollectionTest extends TestBase { public function testCollectorServices() { $collect = \DrupalCodeBuilder\Factory::getTask('Collect'); $reflection = new \ReflectionProperty($collect, 'collectors'); - $reflection->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $reflection->setAccessible(TRUE); + } + $collectors = $reflection->getValue($collect); $collector_classes = []; diff --git a/Test/Unit/ContainerTest.php b/Test/Unit/ContainerTest.php index be10e555..524059fd 100644 --- a/Test/Unit/ContainerTest.php +++ b/Test/Unit/ContainerTest.php @@ -34,7 +34,10 @@ public function testContainer7() { $collect_task = $container->get('Collect'); $collect_reflection = new \ReflectionObject($collect_task); $p = $collect_reflection->getProperty('collectors'); - $p->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $p->setAccessible(TRUE); + } + $collectors = $p->getValue($collect_task); $this->assertCount(1, $collectors); $this->assertArrayHasKey('Collect\HooksCollector', $p->getValue($collect_task)); @@ -61,7 +64,10 @@ public function testContainer8() { $collect_task = $container->get('Collect'); $collect_reflection = new \ReflectionObject($collect_task); $p = $collect_reflection->getProperty('collectors'); - $p->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $p->setAccessible(TRUE); + } + $collectors = $p->getValue($collect_task); $this->assertNotCount(1, $collectors); @@ -70,7 +76,10 @@ public function testContainer8() { $r = new \ReflectionObject($generate_module); $p = $r->getProperty('base'); - $p->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $p->setAccessible(TRUE); + } + $this->assertEquals('module', $p->getValue($generate_module)); $generate_profile = $container->get('Generate|profile'); @@ -78,7 +87,10 @@ public function testContainer8() { $r = new \ReflectionObject($generate_profile); $p = $r->getProperty('base'); - $p->setAccessible(TRUE); + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $p->setAccessible(TRUE); + } + $this->assertEquals('profile', $p->getValue($generate_profile)); } From f448e58ab56c4a24a1135ae35b7f82b393b4ed14 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Fri, 27 Feb 2026 14:23:51 +0000 Subject: [PATCH 130/144] Fixed warning when Drupal's container namespaces contains array items. --- Task/Collect/CodeAnalyser.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Task/Collect/CodeAnalyser.php b/Task/Collect/CodeAnalyser.php index ea385042..49502898 100644 --- a/Task/Collect/CodeAnalyser.php +++ b/Task/Collect/CodeAnalyser.php @@ -113,20 +113,22 @@ protected function setupScript() { // This code is taken from DrupalKernel::attachSynthetic(). $container = $this->environment->getContainer(); $namespaces = $container->getParameter('container.namespaces'); + + // Build a list of data to pass to the script on STDIN. $psr4 = []; foreach ($namespaces as $prefix => $paths) { if (is_array($paths)) { foreach ($paths as $key => $value) { - $paths[$key] = $drupal_root . '/' . $value; + $path = $drupal_root . '/' . $value; + + $psr4[] = $prefix . '\\' . '::' . $path; } } elseif (is_string($paths)) { $paths = $drupal_root . '/' . $paths; - } - // Build a list of data to pass to the script on STDIN. - // $paths is never an array, AFAICT. - $psr4[] = $prefix . '\\' . '::' . $paths; + $psr4[] = $prefix . '\\' . '::' . $paths; + } } // Debug option for the script. From 4425a12c5bd62e5eb02511b704a629cb1fcb2f29 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Wed, 11 Mar 2026 21:01:09 +0000 Subject: [PATCH 131/144] Added docs. --- Task/Collect/CollectorInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Task/Collect/CollectorInterface.php b/Task/Collect/CollectorInterface.php index 0acced7e..8c85fccc 100644 --- a/Task/Collect/CollectorInterface.php +++ b/Task/Collect/CollectorInterface.php @@ -4,6 +4,9 @@ /** * Interface for collector task helpers. + * + * Task classes that implement this interface are automatically gathered and + * injected into the Collect task. */ interface CollectorInterface { From f3258546536f472fe18816df797b501b491c64c1 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Wed, 11 Mar 2026 21:01:27 +0000 Subject: [PATCH 132/144] Added checking interface exists. --- Attribute/InjectImplementations.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Attribute/InjectImplementations.php b/Attribute/InjectImplementations.php index f9088a1e..69049b8f 100644 --- a/Attribute/InjectImplementations.php +++ b/Attribute/InjectImplementations.php @@ -27,7 +27,11 @@ #[Attribute(Attribute::TARGET_METHOD)] final class InjectImplementations { - public function __construct(private string $interface) {} + public function __construct(private string $interface) { + if (!interface_exists($interface)) { + throw new \InvalidArgumentException("The interface '{$interface}' used in an InjectImplementations attribute does not exist."); + } + } /** * Gets the name of the interface the marked method collects. From 2e9af4221813fdbbc9cd1cca67930fa0de4c77ab Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 10:06:18 +0000 Subject: [PATCH 133/144] Fixed Drush command option description using wrong variable. --- Generator/DrushCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/DrushCommand.php b/Generator/DrushCommand.php index c15a0a75..11678554 100644 --- a/Generator/DrushCommand.php +++ b/Generator/DrushCommand.php @@ -291,7 +291,7 @@ protected function getFunctionAttributes(): array { [$option_name, ] = explode(':', $option); $attributes[] = PhpAttributes::method('\Drush\Attributes\Option', [ 'name' => $option_name, - 'description' => "TODO: description of {$parameter} option.", + 'description' => "TODO: description of {$option_name} option.", ]); } From 671f1cf6c77fa8960d75fdf5cb5b57dfd582942b Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 13:13:33 +0000 Subject: [PATCH 134/144] Fixed failing tests. --- .../Unit/ComponentContentEntityType10Test.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Test/Unit/ComponentContentEntityType10Test.php b/Test/Unit/ComponentContentEntityType10Test.php index 5f2b6ea5..6c0d3932 100644 --- a/Test/Unit/ComponentContentEntityType10Test.php +++ b/Test/Unit/ComponentContentEntityType10Test.php @@ -1436,9 +1436,18 @@ public function testContentEntityTypeWithUI() { 'label_plural', 'label_count', 'base_table', + // The next 4 lines are here only as a workaround for an MTD bug: see + // https://github.com/joachim-n/mutable-typed-data/issues/22. + 'data_table', + 'revision_table', + 'revision_data_table', + 'translatable', 'handlers', 'admin_permission', 'entity_keys', + // Next 2 lines same as above. + 'revision_metadata_keys', + 'field_ui_base_route', 'links', ]); $annotation_tester->assertPropertyHasValue(['handlers', 'form', 'default'], 'Drupal\test_module\Form\KittyCatForm'); @@ -1692,10 +1701,20 @@ public function testEntityTypeWithUIAndBundleEntity() { 'label_count', 'bundle_label', 'base_table', + // The next 4 lines are here only as a workaround for an MTD bug: see + // https://github.com/joachim-n/mutable-typed-data/issues/22. + 'data_table', + 'revision_table', + 'revision_data_table', + 'translatable', 'handlers', 'admin_permission', 'entity_keys', + // Next line same as above. + 'revision_metadata_keys', 'bundle_entity_type', + // Next line same as above. + 'field_ui_base_route', 'links', ]); $annotation_tester->assertPropertyHasValue('bundle_label', "Kitty Cat Type"); From d166c53390782b74468008d3aeb0e02a896e75a6 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 13:16:47 +0000 Subject: [PATCH 135/144] Fixed surplus 'none' option appearing in UIs for entity handlers. --- Generator/EntityTypeBase.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Generator/EntityTypeBase.php b/Generator/EntityTypeBase.php index 52c0ac3c..496f5dbb 100644 --- a/Generator/EntityTypeBase.php +++ b/Generator/EntityTypeBase.php @@ -138,28 +138,34 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio // builder handler. $handler_property = PropertyDefinition::create('string') ->setLabel(ucfirst("{$handler_type_info['label']} handler")) + ->setRequired(TRUE) ->setOptionsArray([ + // We use an explicit empty option and make this required, so we + // can control the label used for this in the UI. 'none' => 'Do not use a handler', 'core' => 'Use the core handler class', 'custom' => 'Provide a custom handler class', - ]); + ]) + ->setLiteralDefault('none'); break; case 'custom_default': $default_handler_type = $handler_type_info['default_type']; $handler_property = PropertyDefinition::create('string') ->setLabel(ucfirst("{$handler_type_info['label']} handler")) + ->setRequired(TRUE) ->setOptionsArray([ 'none' => 'Do not use a handler', 'default' => "Use the '{$default_handler_type}' handler class (forces '{$default_handler_type}' to use the default if not set)", 'custom' => "Provide a custom handler class (forces '{$default_handler_type}' to use the default if not set)", ]) + ->setLiteralDefault('none') // Force the default type to at least be specified if it isn't // already. // TODO: this assumes the mode of the default handler type is // 'core_none'. ->setProcessing(function(DataItem $component_data) use ($default_handler_type) { - if ($component_data->isEmpty() || $component_data->value == 'none') { + if ($component_data->value == 'none') { // Nothing to do; this isn't set to use anything. return; } From 0af1b93e724516bf272bf684cd791bf50f8dbbde Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 13:30:08 +0000 Subject: [PATCH 136/144] Fixed ordering of admin route handler option; converted entity handler options to use OptionDefinition objects. --- Generator/EntityTypeBase.php | 37 ++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/Generator/EntityTypeBase.php b/Generator/EntityTypeBase.php index 496f5dbb..50b032a6 100644 --- a/Generator/EntityTypeBase.php +++ b/Generator/EntityTypeBase.php @@ -139,13 +139,15 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $handler_property = PropertyDefinition::create('string') ->setLabel(ucfirst("{$handler_type_info['label']} handler")) ->setRequired(TRUE) - ->setOptionsArray([ + ->setOptions( // We use an explicit empty option and make this required, so we // can control the label used for this in the UI. - 'none' => 'Do not use a handler', - 'core' => 'Use the core handler class', - 'custom' => 'Provide a custom handler class', - ]) + // Set weights and leave gaps so that static::getHandlerTypes() + // can insert options in between. + new OptionDefinition('none', 'Do not use a handler', weight: 0), + new OptionDefinition('core', 'Use the core handler class.', weight: 10), + new OptionDefinition('custom', 'Provide a custom handler class.', weight: 20), + ) ->setLiteralDefault('none'); break; @@ -154,11 +156,13 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $handler_property = PropertyDefinition::create('string') ->setLabel(ucfirst("{$handler_type_info['label']} handler")) ->setRequired(TRUE) - ->setOptionsArray([ - 'none' => 'Do not use a handler', - 'default' => "Use the '{$default_handler_type}' handler class (forces '{$default_handler_type}' to use the default if not set)", - 'custom' => "Provide a custom handler class (forces '{$default_handler_type}' to use the default if not set)", - ]) + ->setOptions( + // We use an explicit empty option and make this required, so we + // can control the label used for this in the UI. + new OptionDefinition('none', 'Do not use a handler', weight: 0), + new OptionDefinition('default', "Use the '{$default_handler_type}' handler class (forces '{$default_handler_type}' to use the default if not set)", weight: 10), + new OptionDefinition('custom', "Provide a custom handler class (forces '{$default_handler_type}' to use the default if not set)", weight: 20), + ) ->setLiteralDefault('none') // Force the default type to at least be specified if it isn't // already. @@ -186,8 +190,8 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio // Add extra options specific to the handler type. if (isset($handler_type_info['options'])) { - foreach ($handler_type_info['options'] as $option_value => $option_label) { - $handler_property->addOption(new OptionDefinition($option_value, $option_label)); + foreach ($handler_type_info['options'] as $option) { + $handler_property->addOption($option); } } @@ -287,8 +291,9 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio * - 'custom_default': No handler is provided, but handler for another * type can be used. The option is whether to use that, or create a * custom handler. The 'default_type' property must also be given. - * - 'options': An array of additional options for the handler property. - * These are added to the options provided by the mode. + * - 'options': A array of additional OptionDefinition objects for the + * handler property. These are added to the options provided by the mode. + * Array keys are ignored. * - 'property_path': (optional) The path to set this into the annotation * beneath the 'handlers' key. Only required if this is not simply the * handler type key. @@ -315,8 +320,8 @@ protected static function getHandlerTypes() { 'options' => [ // Overwrite the label for the 'core' option which the mode provides. // This is OK because addOption() replaces an existing option. - 'core' => 'Default core route provider', - 'admin' => 'Admin route provider', + new OptionDefinition('core', 'Default core route provider', weight: 10), + new OptionDefinition('admin', 'Admin route provider', weight: 15), ], 'options_classes' => [ 'default' => '\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider', From 3b5bd261cdb6896e2c5cc65d9da7eba202029801 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 13:30:15 +0000 Subject: [PATCH 137/144] Fixed typo. --- Generator/ConfigEntityType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/ConfigEntityType.php b/Generator/ConfigEntityType.php index 59c00789..66274106 100644 --- a/Generator/ConfigEntityType.php +++ b/Generator/ConfigEntityType.php @@ -112,7 +112,7 @@ protected static function getHandlerTypes() { $handler_types[$form_handler_type]['base_class'] = '\Drupal\Core\Entity\EntityForm'; $handler_types[$form_handler_type]['handler_properties'] = [ - // Config entity formss redirect to the collection page. + // Config entity forms redirect to the collection page. 'redirect_link_template' => 'collection', ]; } From 77df4c763aee6c644bcb62bc27e92d2546c761db Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 13:36:56 +0000 Subject: [PATCH 138/144] Changed option label to be clearer. --- Generator/EntityTypeBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/EntityTypeBase.php b/Generator/EntityTypeBase.php index 50b032a6..829b8b00 100644 --- a/Generator/EntityTypeBase.php +++ b/Generator/EntityTypeBase.php @@ -321,7 +321,7 @@ protected static function getHandlerTypes() { // Overwrite the label for the 'core' option which the mode provides. // This is OK because addOption() replaces an existing option. new OptionDefinition('core', 'Default core route provider', weight: 10), - new OptionDefinition('admin', 'Admin route provider', weight: 15), + new OptionDefinition('admin', 'Core admin route provider', weight: 15), ], 'options_classes' => [ 'default' => '\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider', From f173204d8044f7eba4747d89da69685e5fddbc85 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 13:37:25 +0000 Subject: [PATCH 139/144] Changed UI text about forcing handler classes to be on the option set description rather than repeated on each option. --- Generator/EntityTypeBase.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Generator/EntityTypeBase.php b/Generator/EntityTypeBase.php index 829b8b00..d272792f 100644 --- a/Generator/EntityTypeBase.php +++ b/Generator/EntityTypeBase.php @@ -155,13 +155,14 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio $default_handler_type = $handler_type_info['default_type']; $handler_property = PropertyDefinition::create('string') ->setLabel(ucfirst("{$handler_type_info['label']} handler")) + ->setDescription("Setting a handler class here will force the '{$default_handler_type}' handler to be set to use the default handler class, if it is not set.'") ->setRequired(TRUE) ->setOptions( // We use an explicit empty option and make this required, so we // can control the label used for this in the UI. new OptionDefinition('none', 'Do not use a handler', weight: 0), - new OptionDefinition('default', "Use the '{$default_handler_type}' handler class (forces '{$default_handler_type}' to use the default if not set)", weight: 10), - new OptionDefinition('custom', "Provide a custom handler class (forces '{$default_handler_type}' to use the default if not set)", weight: 20), + new OptionDefinition('default', "Use the '{$default_handler_type}' handler class", weight: 10), + new OptionDefinition('custom', "Provide a custom handler class", weight: 20), ) ->setLiteralDefault('none') // Force the default type to at least be specified if it isn't @@ -340,7 +341,7 @@ protected static function getHandlerTypes() { ], 'form_add' => [ 'label' => 'add form', - 'description' => "The entity form class for the 'add' operation.", + 'description' => "The entity form class for the 'add' operation. Setting a handler class here will force the 'default form' handler to be set to use the default handler class, if it is not set.", 'component_type' => 'EntityForm', 'property_path' => ['form', 'add'], 'class_name_suffix' => 'AddForm', @@ -350,7 +351,7 @@ protected static function getHandlerTypes() { ], 'form_edit' => [ 'label' => 'edit form', - 'description' => "The entity form class for the 'edit' operation.", + 'description' => "The entity form class for the 'edit' operation. Setting a handler class here will force the 'default form' handler to be set to use the default handler class, if it is not set.", 'component_type' => 'EntityForm', 'property_path' => ['form', 'edit'], 'class_name_suffix' => 'EditForm', From 6de57050adebf597537dfb355ab61ffdc4af9d73 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 13:47:35 +0000 Subject: [PATCH 140/144] Changed order of plugin types so deprecated annotation type is last. --- Generator/PluginType.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Generator/PluginType.php b/Generator/PluginType.php index a414ec79..c8fb1cf3 100644 --- a/Generator/PluginType.php +++ b/Generator/PluginType.php @@ -32,11 +32,6 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ->setLabel('Plugin discovery type') ->setDescription("The way in which plugins of this type are formed.") ->setOptions( - OptionDefinition::create( - 'annotation', - 'Annotation plugin', - "Each plugin is a class with an annotation to declare the plugin data. WARNING: This plugin discovery type will soon be deprecated in Drupal core." - ), OptionDefinition::create( 'attribute', 'Attribute plugin', @@ -46,7 +41,12 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio 'yaml', 'YAML plugin', "Plugins are declared in a single YAML file, and usually share the same class." - ) + ), + OptionDefinition::create( + 'annotation', + 'Annotation plugin', + "Each plugin is a class with an annotation to declare the plugin data. WARNING: This plugin discovery type will soon be deprecated in Drupal core." + ), ) ]) ->setVariants([ From d90803da384d43ead34986995c2b8b9d78cd1dac Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Mar 2026 15:05:48 +0000 Subject: [PATCH 141/144] Fixed typo. --- Generator/PHPUnitTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/PHPUnitTest.php b/Generator/PHPUnitTest.php index 67d87b94..f8b0cf51 100644 --- a/Generator/PHPUnitTest.php +++ b/Generator/PHPUnitTest.php @@ -99,7 +99,7 @@ public static function addToGeneratorDefinition(PropertyListInterface $definitio ], ], 'javascript' => [ - 'label' => 'Javascript test', + 'label' => 'JavaScript test', 'data' => [ 'force' => [ 'relative_namespace' => [ From f2ba73618334f172cfdbcd9eee9aadd84d112ecb Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Wed, 18 Mar 2026 11:37:25 +0000 Subject: [PATCH 142/144] Fixed test dataset labels the wrong way round. --- Test/Unit/ComponentRouterItem10Test.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Test/Unit/ComponentRouterItem10Test.php b/Test/Unit/ComponentRouterItem10Test.php index bfa8afba..59543515 100644 --- a/Test/Unit/ComponentRouterItem10Test.php +++ b/Test/Unit/ComponentRouterItem10Test.php @@ -345,12 +345,9 @@ public static function dataRouteAccessTypes() { ], '_custom_access', '\Drupal\test_module\Controller\MyPathControllerController::access', - [ - 'controller_type' => 'controller', - ], + [], [ 'Controller\MyPathControllerController' => [ - 'content', 'access', ], ], @@ -365,9 +362,12 @@ public static function dataRouteAccessTypes() { ], '_custom_access', '\Drupal\test_module\Controller\MyPathControllerController::access', - [], + [ + 'controller_type' => 'controller', + ], [ 'Controller\MyPathControllerController' => [ + 'content', 'access', ], ], From 47cc4e177391c647cff856f575d77c044f9b8f0d Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Wed, 18 Mar 2026 11:37:38 +0000 Subject: [PATCH 143/144] Added future parameter from MTD. --- MutableTypedData/Data/MappingData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MutableTypedData/Data/MappingData.php b/MutableTypedData/Data/MappingData.php index 8b787036..2d044247 100644 --- a/MutableTypedData/Data/MappingData.php +++ b/MutableTypedData/Data/MappingData.php @@ -34,7 +34,7 @@ public function items(): array { /** * {@inheritdoc} */ - public function validate(): array { + public function validate(?bool $include_internal = FALSE): array { return []; } From d78c29ecbe3d4e8cd11466c0bc0cd03e23ac02fc Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Sun, 5 Apr 2026 17:43:49 +0100 Subject: [PATCH 144/144] Converted PHPUnit annotations to attributes. --- Test/Unit/ComponentAPI10Test.php | 4 +- Test/Unit/ComponentAdminSettings10Test.php | 6 +-- Test/Unit/ComponentConfigEntityType10Test.php | 11 +++-- .../Unit/ComponentContentEntityType10Test.php | 38 +++++++--------- Test/Unit/ComponentContentEntityType9Test.php | 13 +++--- Test/Unit/ComponentCssLibrary10Test.php | 4 +- Test/Unit/ComponentDrushCommand11Test.php | 7 ++- Test/Unit/ComponentDrushCommand8Test.php | 7 ++- .../ComponentDynamicRouteProvider11Test.php | 4 +- Test/Unit/ComponentForm10Test.php | 13 +++--- Test/Unit/ComponentHooks11Test.php | 4 +- Test/Unit/ComponentHooks7Test.php | 4 +- Test/Unit/ComponentHooks8Test.php | 10 ++--- Test/Unit/ComponentInfo10Test.php | 6 +-- Test/Unit/ComponentInfo9Test.php | 12 +++--- Test/Unit/ComponentModule10Test.php | 7 ++- Test/Unit/ComponentModule7Test.php | 4 +- Test/Unit/ComponentPHPFile10Test.php | 4 +- Test/Unit/ComponentPermissions10Test.php | 4 +- Test/Unit/ComponentPermissions7Test.php | 4 +- Test/Unit/ComponentPluginType10Test.php | 9 ++-- Test/Unit/ComponentPluginsAnnotated9Test.php | 19 ++++---- Test/Unit/ComponentPluginsAttribute11Test.php | 19 ++++---- Test/Unit/ComponentPluginsYAML10Test.php | 9 ++-- Test/Unit/ComponentProfile10Test.php | 4 +- Test/Unit/ComponentReadme10Test.php | 4 +- Test/Unit/ComponentRouterItem10Test.php | 20 ++++----- Test/Unit/ComponentRouterItem7Test.php | 4 +- Test/Unit/ComponentService11Test.php | 43 ++++++++----------- .../ComponentServiceEventSubscriber10Test.php | 4 +- Test/Unit/ComponentTestsPHPUnit10Test.php | 29 ++++++------- Test/Unit/ComponentTestsPHPUnit9Test.php | 30 ++++++------- Test/Unit/ComponentThemeHook10Test.php | 3 +- Test/Unit/ComponentThemeHook11Test.php | 3 +- Test/Unit/ContainerCollectionTest.php | 4 +- Test/Unit/ContainerRebuildTest.php | 3 +- Test/Unit/ContainerTest.php | 3 +- Test/Unit/DrupalCodeBuilderAssertionsTest.php | 13 +++--- .../GenerateHelperComponentCollectorTest.php | 11 +++-- Test/Unit/ParserDocBlockTest.php | 4 +- Test/Unit/ParserPHPTest.php | 14 +++--- Test/Unit/ParserYamlTest.php | 7 ++- Test/Unit/UnitDataItemMergeTest.php | 13 +++--- .../UnitDependencyInjectionAnalysisTest.php | 4 +- Test/Unit/UnitMethodCollectorTest.php | 4 +- Test/Unit/UnitValidatorTest.php | 13 +++--- 46 files changed, 202 insertions(+), 257 deletions(-) diff --git a/Test/Unit/ComponentAPI10Test.php b/Test/Unit/ComponentAPI10Test.php index 1edd6eb8..0e2a2880 100644 --- a/Test/Unit/ComponentAPI10Test.php +++ b/Test/Unit/ComponentAPI10Test.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Task\AnalyzeModule; use DrupalCodeBuilder\Test\Fixtures\File\MockableExtension; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; @@ -62,9 +63,8 @@ public function testModuleGenerationApiFile() { /** * Tests with an existing api file. - * - * @group existing */ + #[Group('existing')] public function testExistingAPIFile() { // Assemble module data. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentAdminSettings10Test.php b/Test/Unit/ComponentAdminSettings10Test.php index cd362c9a..69e2b0b8 100644 --- a/Test/Unit/ComponentAdminSettings10Test.php +++ b/Test/Unit/ComponentAdminSettings10Test.php @@ -2,15 +2,15 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests the AdminSettingsForm generator class. - * - * @group form - * @group yaml */ +#[Group('form')] +#[Group('yaml')] class ComponentAdminSettings10Test extends TestBase { /** diff --git a/Test/Unit/ComponentConfigEntityType10Test.php b/Test/Unit/ComponentConfigEntityType10Test.php index d945785d..f61edd01 100644 --- a/Test/Unit/ComponentConfigEntityType10Test.php +++ b/Test/Unit/ComponentConfigEntityType10Test.php @@ -2,15 +2,15 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests the config entity type generator class. - * - * @group yaml - * @group entity */ +#[Group('yaml')] +#[Group('entity')] class ComponentConfigEntityType10Test extends TestBase { /** @@ -340,10 +340,9 @@ public function testConfigEntityTypeWithHandlers() { /** * Test creating a config entity type with a UI. - * - * @group entity_ui - * @group form */ + #[Group('entity_ui')] + #[Group('form')] public function testConfigEntityTypeWithUI() { // Create a module. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentContentEntityType10Test.php b/Test/Unit/ComponentContentEntityType10Test.php index 6c0d3932..ddc9cd2d 100644 --- a/Test/Unit/ComponentContentEntityType10Test.php +++ b/Test/Unit/ComponentContentEntityType10Test.php @@ -2,16 +2,17 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests the entity type generator class. - * - * @group yaml - * @group annotation - * @group entity */ +#[Group('yaml')] +#[Group('annotation')] +#[Group('entity')] class ComponentContentEntityType10Test extends TestBase { /** @@ -173,9 +174,8 @@ public function testBasicContentEntityType() { * Tests the parent interfaces. * * This covers the different combinations of options. - * - * @dataProvider providerContentEntityTypeFunctionalityOptions */ + #[DataProvider('providerContentEntityTypeFunctionalityOptions')] public function testContentEntityTypeFunctionalityOptions( $interface_option, $expected_parent_interfaces, @@ -780,9 +780,8 @@ public function testEntityTypeWithBundleEntityNoUI() { * Tests creating a content entity type with handlers. * * This covers the different combinations of options. - * - * @dataProvider providerHandlers */ + #[DataProvider('providerHandlers')] public function testContentEntityTypeHandlers($handler_properties, $expected_handlers_annotation, $expected_files_base_classes) { // Create a module. $module_name = 'test_module'; @@ -1321,9 +1320,8 @@ public function testContentEntityTypeCustomHandlers() { /** * Tests the handler namespace configuration setting. - * - * @group config */ + #[Group('config')] public function testContentEntityTypeHandlerNamespaceConfiguration() { // Create a module. $module_name = 'test_module'; @@ -1396,10 +1394,9 @@ public function testContentEntityTypeHandlerNamespaceConfiguration() { /** * Tests creating a content entity with a UI. - * - * @group entity_ui - * @group form */ + #[Group('entity_ui')] + #[Group('form')] public function testContentEntityTypeWithUI() { $module_name = 'test_module'; $module_data = [ @@ -1515,10 +1512,9 @@ public function testContentEntityTypeWithUI() { /** * Tests creating a content entity with a revision UI. - * - * @group entity_ui - * @group form */ + #[Group('entity_ui')] + #[Group('form')] public function testContentEntityTypeWithRevisionEntityUI() { $module_name = 'test_module'; $module_data = [ @@ -1556,10 +1552,9 @@ public function testContentEntityTypeWithRevisionEntityUI() { /** * Tests creating a content entity with a bundle entity UI. - * - * @group entity_ui - * @group form */ + #[Group('entity_ui')] + #[Group('form')] public function testContentEntityTypeWithBundleEntityUI() { $module_name = 'test_module'; $module_data = [ @@ -1631,10 +1626,9 @@ public function testContentEntityTypeWithBundleEntityUI() { /** * Test creating a content entity type with a bundle entity and UI for both. - * - * @group entity_ui - * @group form */ + #[Group('entity_ui')] + #[Group('form')] public function testEntityTypeWithUIAndBundleEntity() { // Create a module. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentContentEntityType9Test.php b/Test/Unit/ComponentContentEntityType9Test.php index b9017571..6e634044 100644 --- a/Test/Unit/ComponentContentEntityType9Test.php +++ b/Test/Unit/ComponentContentEntityType9Test.php @@ -2,16 +2,16 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests the D8/9 entity type generator class. - * - * @group yaml - * @group annotation - * @group entity */ +#[Group('yaml')] +#[Group('annotation')] +#[Group('entity')] class ComponentContentEntityType9Test extends TestBase { /** @@ -23,10 +23,9 @@ class ComponentContentEntityType9Test extends TestBase { /** * Tests creating a content entity with a revision UI. - * - * @group entity_ui - * @group form */ + #[Group('entity_ui')] + #[Group('form')] public function testContentEntityTypeWithRevisionEntityUI() { $module_name = 'test_module'; $module_data = [ diff --git a/Test/Unit/ComponentCssLibrary10Test.php b/Test/Unit/ComponentCssLibrary10Test.php index fdcb1d33..8c8a8ecd 100644 --- a/Test/Unit/ComponentCssLibrary10Test.php +++ b/Test/Unit/ComponentCssLibrary10Test.php @@ -2,13 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for library generation. - * - * @group yaml */ +#[Group('yaml')] class ComponentCssLibrary10Test extends TestBase { /** diff --git a/Test/Unit/ComponentDrushCommand11Test.php b/Test/Unit/ComponentDrushCommand11Test.php index 2f8ec428..31d50473 100644 --- a/Test/Unit/ComponentDrushCommand11Test.php +++ b/Test/Unit/ComponentDrushCommand11Test.php @@ -2,13 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests for Drush command component. - * - * @group yaml */ +#[Group('yaml')] class ComponentDrushCommand11Test extends TestBase { /** @@ -152,9 +152,8 @@ function testCommandGenerationWithParameters() { /** * Test a command with with injected services. - * - * @group di */ + #[Group('di')] function testCommandGenerationWithServices() { // Assemble module data. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentDrushCommand8Test.php b/Test/Unit/ComponentDrushCommand8Test.php index 873312fa..dc941115 100644 --- a/Test/Unit/ComponentDrushCommand8Test.php +++ b/Test/Unit/ComponentDrushCommand8Test.php @@ -2,14 +2,14 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for Drush command component. - * - * @group yaml */ +#[Group('yaml')] class ComponentDrushCommand8Test extends TestBase { /** @@ -173,9 +173,8 @@ function testCommandGenerationWithParameters() { /** * Test a command with with injected services. - * - * @group di */ + #[Group('di')] function testCommandGenerationWithServices() { // Assemble module data. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentDynamicRouteProvider11Test.php b/Test/Unit/ComponentDynamicRouteProvider11Test.php index 4cfb970e..a01edf05 100644 --- a/Test/Unit/ComponentDynamicRouteProvider11Test.php +++ b/Test/Unit/ComponentDynamicRouteProvider11Test.php @@ -2,14 +2,14 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for dynamic route providers. - * - * @group yaml */ +#[Group('yaml')] class ComponentDynamicRouteProvider11Test extends TestBase { /** diff --git a/Test/Unit/ComponentForm10Test.php b/Test/Unit/ComponentForm10Test.php index f8d8c0b5..8125e507 100644 --- a/Test/Unit/ComponentForm10Test.php +++ b/Test/Unit/ComponentForm10Test.php @@ -2,14 +2,15 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for Form component. - * - * @group form */ +#[Group('form')] class ComponentForm10Test extends TestBase { /** @@ -129,9 +130,8 @@ public function testFormGenerationWithCustomElements() { /** * Test Form component with injected services. - * - * @group di */ + #[Group('di')] function testFormGenerationWithServices() { // Assemble module data. $module_name = 'test_module'; @@ -255,10 +255,9 @@ public static function dataAdoptionMerge() { * Whether a generated component exists to have the adopted component merged * with. * - * @group adopt - * - * @dataProvider dataAdoptionMerge */ + #[Group('adopt')] + #[DataProvider('dataAdoptionMerge')] public function testExistingFormAdoption(bool $merge) { // First pass: generate the files we'll mock as existing. $module_name = 'existing'; diff --git a/Test/Unit/ComponentHooks11Test.php b/Test/Unit/ComponentHooks11Test.php index 83e87436..3076cb21 100644 --- a/Test/Unit/ComponentHooks11Test.php +++ b/Test/Unit/ComponentHooks11Test.php @@ -2,14 +2,14 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for Hooks component on Drupal 11. - * - * @group hooks */ +#[Group('hooks')] class ComponentHooks11Test extends TestBase { /** diff --git a/Test/Unit/ComponentHooks7Test.php b/Test/Unit/ComponentHooks7Test.php index 024c2565..1ac3d596 100644 --- a/Test/Unit/ComponentHooks7Test.php +++ b/Test/Unit/ComponentHooks7Test.php @@ -2,13 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests for Hooks component. - * - * @group hooks */ +#[Group('hooks')] class ComponentHooks7Test extends TestBase { /** diff --git a/Test/Unit/ComponentHooks8Test.php b/Test/Unit/ComponentHooks8Test.php index 10ce5b5d..7f3adfca 100644 --- a/Test/Unit/ComponentHooks8Test.php +++ b/Test/Unit/ComponentHooks8Test.php @@ -2,15 +2,15 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Fixtures\File\MockableExtension; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for Hooks component. - * - * @group hooks */ +#[Group('hooks')] class ComponentHooks8Test extends TestBase { /** @@ -173,9 +173,8 @@ public function testModuleGenerationHooks() { /** * Tests with an existing code file. - * - * @group existing */ + #[Group('existing')] public function testHooksWithExistingFunctions() { // Assemble module data. $module_name = 'test_module'; @@ -255,9 +254,8 @@ function test_module_element_info_alter(array &$info) { /** * Tests hook_update_N() existing implementations. - * - * @group existing */ + #[Group('existing')] public function testHooksWithExistingHookInstall() { // Assemble module data. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentInfo10Test.php b/Test/Unit/ComponentInfo10Test.php index 26cff7e3..6c039954 100644 --- a/Test/Unit/ComponentInfo10Test.php +++ b/Test/Unit/ComponentInfo10Test.php @@ -2,14 +2,14 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for Info component. - * - * @group yaml - * @group info */ +#[Group('yaml')] +#[Group('info')] class ComponentInfo10Test extends TestBase { /** diff --git a/Test/Unit/ComponentInfo9Test.php b/Test/Unit/ComponentInfo9Test.php index d47d6d41..41e80d51 100644 --- a/Test/Unit/ComponentInfo9Test.php +++ b/Test/Unit/ComponentInfo9Test.php @@ -2,16 +2,17 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Fixtures\File\MockableExtension; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; use Symfony\Component\Yaml\Yaml; /** * Tests for Info component. - * - * @group yaml - * @group info */ +#[Group('yaml')] +#[Group('info')] class ComponentInfo9Test extends TestBase { /** @@ -121,10 +122,9 @@ public static function dataExistingInfoFile() { /** * Tests with an existing info file. * - * @group existing - * - * @dataProvider dataExistingInfoFile */ + #[Group('existing')] + #[DataProvider('dataExistingInfoFile')] public function testExistingInfoFile($existing_dependencies, $generated_dependencies, $resulting_dependencies) { $module_name = 'test_module'; $module_data = [ diff --git a/Test/Unit/ComponentModule10Test.php b/Test/Unit/ComponentModule10Test.php index 92ac3077..77943069 100644 --- a/Test/Unit/ComponentModule10Test.php +++ b/Test/Unit/ComponentModule10Test.php @@ -2,14 +2,14 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use MutableTypedData\Data\DataItem; /** * Tests basic module generation. - * - * @group hooks */ +#[Group('hooks')] class ComponentModule10Test extends TestBase { /** @@ -69,9 +69,8 @@ protected function simulateUiWalk(DataItem $data_item) { /** * Tests getting module configuration data. - * - * @group config */ + #[Group('config')] public function testConfiguration() { $config_data = \DrupalCodeBuilder\Factory::getTask('Configuration')->getConfigurationData('module'); $properties = $config_data->getProperties(); diff --git a/Test/Unit/ComponentModule7Test.php b/Test/Unit/ComponentModule7Test.php index a7a1cf1b..db52c5b0 100644 --- a/Test/Unit/ComponentModule7Test.php +++ b/Test/Unit/ComponentModule7Test.php @@ -2,13 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests basic module generation. - * - * @group hooks */ +#[Group('hooks')] class ComponentModule7Test extends TestBase { /** diff --git a/Test/Unit/ComponentPHPFile10Test.php b/Test/Unit/ComponentPHPFile10Test.php index 677d0755..960e34ac 100644 --- a/Test/Unit/ComponentPHPFile10Test.php +++ b/Test/Unit/ComponentPHPFile10Test.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use DrupalCodeBuilder\File\CodeFile; use DrupalCodeBuilder\Generator\PHPFile as RealPHPFile; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; @@ -49,8 +50,6 @@ public static function setUpMockedComponent() { /** * Test the qualified class name extraction. * - * @dataProvider providerQualifiedClassNameExtraction - * * @param string $code * The code to extract class names from. * @param string $expected_changed_code @@ -64,6 +63,7 @@ public static function setUpMockedComponent() { * extracted. * - NULL if we do not expect extraction. */ + #[DataProvider('providerQualifiedClassNameExtraction')] public function testQualifiedClassNameExtraction($code, $expected_changed_code, $expected_qualified_class_names) { // Make the protected method we're testing callable. $method = new \ReflectionMethod(PHPFile::class, 'extractFullyQualifiedClasses'); diff --git a/Test/Unit/ComponentPermissions10Test.php b/Test/Unit/ComponentPermissions10Test.php index dddc9acb..9f4c319c 100644 --- a/Test/Unit/ComponentPermissions10Test.php +++ b/Test/Unit/ComponentPermissions10Test.php @@ -2,13 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests the Permissions generator class. - * - * @group yaml */ +#[Group('yaml')] class ComponentPermissions10Test extends TestBase { /** diff --git a/Test/Unit/ComponentPermissions7Test.php b/Test/Unit/ComponentPermissions7Test.php index 7571ff06..42507ba8 100644 --- a/Test/Unit/ComponentPermissions7Test.php +++ b/Test/Unit/ComponentPermissions7Test.php @@ -2,13 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests the Permissions generator class. - * - * @group hooks */ +#[Group('hooks')] class ComponentPermissions7Test extends TestBase { /** diff --git a/Test/Unit/ComponentPluginType10Test.php b/Test/Unit/ComponentPluginType10Test.php index d6145031..0a428fea 100644 --- a/Test/Unit/ComponentPluginType10Test.php +++ b/Test/Unit/ComponentPluginType10Test.php @@ -2,15 +2,15 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests generation of plugin types. - * - * @group yaml - * @group di */ +#[Group('yaml')] +#[Group('di')] class ComponentPluginType10Test extends TestBase { /** @@ -586,9 +586,8 @@ function testMultiplePluginTypes() { /** * Test Plugin Type with DI in the plugin base class. - * - * @group di */ + #[Group('di')] function testAttributePluginTypeWithServices() { $module_data = [ 'base' => 'module', diff --git a/Test/Unit/ComponentPluginsAnnotated9Test.php b/Test/Unit/ComponentPluginsAnnotated9Test.php index 75019945..8f1f4394 100644 --- a/Test/Unit/ComponentPluginsAnnotated9Test.php +++ b/Test/Unit/ComponentPluginsAnnotated9Test.php @@ -2,6 +2,8 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; use MutableTypedData\Exception\InvalidInputException; @@ -15,10 +17,9 @@ * types left. * * TODO: Restore this to Drupal 10 if I CBA. - * - * @group yaml - * @group plugin */ +#[Group('yaml')] +#[Group('plugin')] class ComponentPluginsAnnotated9Test extends TestBase { /** @@ -128,9 +129,8 @@ function testPluginsGenerationClassName() { * Tests special cases where prefixing of the plugin name should be skipped. * * This also tests that derivative plugin IDs are handled correctly. - * - * @dataProvider providerPluginsGenerationNamePrefixing */ + #[DataProvider('providerPluginsGenerationNamePrefixing')] public function testPluginsGenerationNamePrefixing(string $plugin_id, string $filename) { // Create a module. $module_name = 'test_module'; @@ -496,9 +496,8 @@ function testPluginsGenerationBadPluginType() { /** * Test Plugins component with injected services. - * - * @group di */ + #[Group('di')] function testPluginsGenerationWithServices() { // Create a module. $module_name = 'test_module'; @@ -573,9 +572,8 @@ function testPluginsGenerationWithServices() { /** * Test Plugins component with a plugin base class with an existing create(). - * - * @group di */ + #[Group('di')] function testPluginsGenerationWithExistingCreate() { // Create a module. $module_name = 'test_module'; @@ -658,9 +656,8 @@ function testPluginsGenerationWithExistingCreate() { /** * Tests a plugin base class with nonstandard fixed constructor parameters. - * - * @group di */ + #[Group('di')] function testPluginsGenerationWithNonstandardFixedParameters() { // Create a module. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentPluginsAttribute11Test.php b/Test/Unit/ComponentPluginsAttribute11Test.php index 09bbbe32..e48b586a 100644 --- a/Test/Unit/ComponentPluginsAttribute11Test.php +++ b/Test/Unit/ComponentPluginsAttribute11Test.php @@ -2,6 +2,8 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; use MutableTypedData\Exception\InvalidInputException; @@ -12,10 +14,9 @@ * Tests generation of attribute plugins. * * TODO: This is missing testing of the actual attributes! - * - * @group yaml - * @group plugin */ +#[Group('yaml')] +#[Group('plugin')] class ComponentPluginsAttribute11Test extends TestBase { /** @@ -206,9 +207,8 @@ function testPluginsGenerationClassName() { * Tests special cases where prefixing of the plugin name should be skipped. * * This also tests that derivative plugin IDs are handled correctly. - * - * @dataProvider providerPluginsGenerationNamePrefixing */ + #[DataProvider('providerPluginsGenerationNamePrefixing')] public function testPluginsGenerationNamePrefixing(string $plugin_id, string $filename) { // Create a module. $module_name = 'test_module'; @@ -264,9 +264,8 @@ public static function providerPluginsGenerationNamePrefixing() { /** * Tests plugin derivers. - * - * @group di */ + #[Group('di')] function testPluginsGenerationDeriver() { // Create a module. $module_name = 'test_module'; @@ -465,9 +464,8 @@ function testPluginsGenerationBadPluginType() { /** * Test Plugins component with injected services. - * - * @group di */ + #[Group('di')] function testPluginsGenerationWithServices() { // Create a module. $module_name = 'test_module'; @@ -598,9 +596,8 @@ function testPluginsGenerationWithOtherSchema() { /** * Test the validation constraint plugin variant. - * - * @group di */ + #[Group('di')] public function testPluginValidationConstraint(): void { // Create a module. $module_name = 'test_module'; diff --git a/Test/Unit/ComponentPluginsYAML10Test.php b/Test/Unit/ComponentPluginsYAML10Test.php index 71d661be..4e977181 100644 --- a/Test/Unit/ComponentPluginsYAML10Test.php +++ b/Test/Unit/ComponentPluginsYAML10Test.php @@ -2,15 +2,15 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests the PluginYAMLDiscovery generator class. - * - * @group yaml - * @group plugin */ +#[Group('yaml')] +#[Group('plugin')] class ComponentPluginsYAML10Test extends TestBase { /** @@ -104,9 +104,8 @@ function testYamlPluginsGenerationDeriver() { /** * Tests a custom plugin class with DI. - * - * @group di */ + #[Group('di')] public function testCustomPluginClass(): void { // Create a module. $module_data = [ diff --git a/Test/Unit/ComponentProfile10Test.php b/Test/Unit/ComponentProfile10Test.php index 14923f87..7c37fcd5 100644 --- a/Test/Unit/ComponentProfile10Test.php +++ b/Test/Unit/ComponentProfile10Test.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; use MutableTypedData\Data\DataItem; @@ -67,9 +68,8 @@ protected function simulateUiWalk(DataItem $data_item) { /** * Tests getting module configuration data. - * - * @group config */ + #[Group('config')] public function testConfiguration() { $config_data = \DrupalCodeBuilder\Factory::getTask('Configuration')->getConfigurationData('profile'); $properties = $config_data->getProperties(); diff --git a/Test/Unit/ComponentReadme10Test.php b/Test/Unit/ComponentReadme10Test.php index 62183bc6..77270648 100644 --- a/Test/Unit/ComponentReadme10Test.php +++ b/Test/Unit/ComponentReadme10Test.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; /** * Tests the README file. @@ -143,8 +144,6 @@ public static function dataReadmeWithDependencies() { /** * Tests the README file with dependencies. * - * @dataProvider dataReadmeWithDependencies - * * @param boolean $readme * The value for the readme property in the module data. * @param array $dependencies @@ -156,6 +155,7 @@ public static function dataReadmeWithDependencies() { * @param boolean $expect_requirements_dependencies * Whether to expect a list of dependencies in the Requirements section. */ + #[DataProvider('dataReadmeWithDependencies')] function testReadmeWithDependencies(bool $readme, array $dependencies, bool $expect_readme, bool $expect_requirements_section, bool $expect_requirements_dependencies) { // Create a module. $module_data = [ diff --git a/Test/Unit/ComponentRouterItem10Test.php b/Test/Unit/ComponentRouterItem10Test.php index 59543515..f6e11cfb 100644 --- a/Test/Unit/ComponentRouterItem10Test.php +++ b/Test/Unit/ComponentRouterItem10Test.php @@ -2,14 +2,15 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for Router item component. - * - * @group yaml */ +#[Group('yaml')] class ComponentRouterItem10Test extends TestBase { /** @@ -227,9 +228,8 @@ public function testRouteControllerTypes() { * @param array $class_names_and_methods * (optional) An array of data about expected classes and their methods. * Keys are the relative class names. Values are arrays of method names. - * - * @dataProvider dataRouteAccessTypes */ + #[DataProvider('dataRouteAccessTypes')] public function testRouteAccessTypes( array $access, string $yaml_property, @@ -514,9 +514,8 @@ public function testRouteControllerClass() { /** * Test options for the controller class - * - * @group di */ + #[Group('di')] public function testRouteControllerClassWithDI() { // Assemble module data. $module_name = 'test_module'; @@ -634,9 +633,8 @@ public function testRouteForm() { /** * Test generating a route with a menu link. - * - * @group plugin */ + #[Group('plugin')] public function testRouteGenerationWithMenuLink() { // Assemble module data. $module_name = 'test_module'; @@ -692,9 +690,8 @@ public function testRouteGenerationWithMenuLink() { /** * Test generating a route with a menu tab. - * - * @group plugin */ + #[Group('plugin')] public function testRouteGenerationWithMenuTab() { // Assemble module data. $module_name = 'test_module'; @@ -742,9 +739,8 @@ public function testRouteGenerationWithMenuTab() { /** * Tests adoption of existing form. - * - * @group adopt */ + #[Group('adopt')] public function testExistingRouterItemAdoption() { // First pass: generate the files we'll mock as existing. $module_name = 'existing'; diff --git a/Test/Unit/ComponentRouterItem7Test.php b/Test/Unit/ComponentRouterItem7Test.php index 388ff874..7b8a8b73 100644 --- a/Test/Unit/ComponentRouterItem7Test.php +++ b/Test/Unit/ComponentRouterItem7Test.php @@ -2,13 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests for Router item component. - * - * @group hooks */ +#[Group('hooks')] class ComponentRouterItem7Test extends TestBase { /** diff --git a/Test/Unit/ComponentService11Test.php b/Test/Unit/ComponentService11Test.php index 87a34133..6015bfd0 100644 --- a/Test/Unit/ComponentService11Test.php +++ b/Test/Unit/ComponentService11Test.php @@ -2,15 +2,16 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Fixtures\File\MockableExtension; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for Service component. - * - * @group yaml */ +#[Group('yaml')] class ComponentService11Test extends TestBase { /** @@ -70,9 +71,8 @@ public function testBasicServiceGeneration() { /** * Test namespace configuration for service generation. - * - * @group config */ + #[Group('config')] public function testServiceGenerationNamespaceConfiguration() { // Assemble module data. $module_name = 'test_module'; @@ -152,9 +152,8 @@ public function testServiceGenerationNamespaceConfiguration() { /** * Test YAML linebreaks configuration for service generation. - * - * @group config */ + #[Group('config')] public function testServiceGenerationYamlLinebreaksConfiguration() { $module_data = [ 'base' => 'module', @@ -192,9 +191,8 @@ public function testServiceGenerationYamlLinebreaksConfiguration() { /** * Test service parameter expansion configuration. - * - * @group config */ + #[Group('config')] public function testServiceGenerationYamlParameterLinebreaksConfiguration() { $module_data = [ 'base' => 'module', @@ -236,9 +234,8 @@ public function testServiceGenerationYamlParameterLinebreaksConfiguration() { /** * Test generating a module with a service using a preset. - * - * @group presets */ + #[Group('presets')] public function testServiceGenerationFromPreset() { // Assemble module data. $module_name = 'test_module'; @@ -484,10 +481,9 @@ public static function providerServiceGenerationWithServices() { /** * Test a service with with injected services. * - * @group di - * - * @dataProvider providerServiceGenerationWithServices */ + #[Group('di')] + #[DataProvider('providerServiceGenerationWithServices')] function testServiceGenerationWithServices($injected_services, bool $property_promotion, $yaml_arguments, $assert_injected_services) { // Assemble module data. $module_name = 'test_module'; @@ -539,9 +535,8 @@ function testServiceGenerationWithServices($injected_services, bool $property_pr /** * Test several services injecting the same services. - * - * @group di */ + #[Group('di')] function testServiceGenerationRepeatedInjectedServies() { $module_name = 'test_module'; $module_data = [ @@ -827,9 +822,8 @@ class: Drupal\existing\Alpha /** * Test a service that decorates another. - * - * @group di */ + #[Group('di')] function testServiceGenerationWithDecoration() { // Assemble module data: decoration without any injected services. $module_data = [ @@ -941,11 +935,10 @@ function testServiceGenerationWithDecoration() { * The expected YAML defining the list of existing services, without the * initial 'services' key. NULL if no file is expected to be generated. * - * @group existing - * - * @dataProvider dataExistingServicesYamlFile * */ + #[Group('existing')] + #[DataProvider('dataExistingServicesYamlFile')] public function testExistingServicesYamlFile(?string $existing, ?array $generated, ?string $resulting) { $module_name = 'existing'; $module_data = [ @@ -1079,9 +1072,6 @@ public static function dataExistingServiceMerge() { /** * Test merging of injected services in an existing service. * - * @group existing - * - * @dataProvider dataExistingServiceMerge * * @param array $existing_service_data * The module data for a single service to generate as the mocked existing @@ -1092,6 +1082,8 @@ public static function dataExistingServiceMerge() { * The expected injected services, in the format expected by * assertInjectedServices(). */ + #[Group('existing')] + #[DataProvider('dataExistingServiceMerge')] public function testExistingServiceMerge($existing_service_data, $generated_service_data, $expected_injected_services) { // First pass: generate the files we'll mock as existing. $module_name = 'existing'; @@ -1233,10 +1225,9 @@ public static function dataExistingServicesAdoption() { * @param string $adopted_relative_class * The relative class name of the adopted service. * - * @group adopt - * - * @dataProvider dataExistingServicesAdoption */ + #[Group('adopt')] + #[DataProvider('dataExistingServicesAdoption')] public function testExistingServicesAdoption(array $data_services, string $adopted_relative_class) { // Mock an existing module. $extension = new MockableExtension('module', __DIR__ . '/../Fixtures/modules/existing/'); diff --git a/Test/Unit/ComponentServiceEventSubscriber10Test.php b/Test/Unit/ComponentServiceEventSubscriber10Test.php index 130560b1..b8efd469 100644 --- a/Test/Unit/ComponentServiceEventSubscriber10Test.php +++ b/Test/Unit/ComponentServiceEventSubscriber10Test.php @@ -2,14 +2,14 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; /** * Tests for event subscriber component. - * - * @group yaml */ +#[Group('yaml')] class ComponentServiceEventSubscriber10Test extends TestBase { /** diff --git a/Test/Unit/ComponentTestsPHPUnit10Test.php b/Test/Unit/ComponentTestsPHPUnit10Test.php index 8a8b44e9..7194dbdb 100644 --- a/Test/Unit/ComponentTestsPHPUnit10Test.php +++ b/Test/Unit/ComponentTestsPHPUnit10Test.php @@ -2,6 +2,8 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Fixtures\File\MockableExtension; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; @@ -271,10 +273,9 @@ public static function dataModuleGenerationTestsWithServices(): array { /** * Create a test class with services. * - * @group di - * - * @dataProvider dataModuleGenerationTestsWithServices */ + #[Group('di')] + #[DataProvider('dataModuleGenerationTestsWithServices')] function testModuleGenerationTestsWithServices(bool $container_services = FALSE, bool $mocked_services = FALSE) { // Create a module. $module_name = 'test_module'; @@ -340,9 +341,8 @@ function testModuleGenerationTestsWithServices(bool $container_services = FALSE, /** * Create a test class with a test module. - * - * @group test */ + #[Group('test')] function testModuleGenerationTestsWithBasicTestModule() { // Create a module. $module_name = 'generated_module'; @@ -375,9 +375,8 @@ function testModuleGenerationTestsWithBasicTestModule() { /** * Create a test class with a test module and module components. - * - * @group test */ + #[Group('test')] function testModuleGenerationTestsWithTestModuleComponents() { // Create a module. $module_name = 'generated_module'; @@ -554,11 +553,6 @@ public static function dataTestModuleWithExistingFunctions() { * This generates a hook in either the main or the test module, with an * existing function is either the main or test module file. * - * @group existing - * @group test - * - * @dataProvider dataTestModuleWithExistingFunctions - * * @param string $generated * Where the generated hook goes. One of: * - 'main': The main generated module. @@ -568,6 +562,9 @@ public static function dataTestModuleWithExistingFunctions() { * - 'main': The main generated module. * - 'test': The test module. */ + #[Group('existing')] + #[Group('test')] + #[DataProvider('dataTestModuleWithExistingFunctions')] public function testTestModuleWithExistingFunctions(string $generated, string $existing_code) { // Create a module. $module_data = [ @@ -680,11 +677,6 @@ function $existing_function_name() { * This generates a service in either the main or the test module, with an * existing service is either the main or test module. * - * @group existing - * @group test - * - * @dataProvider dataTestModuleWithExistingFunctions - * * @param string $generated * Where the generated service goes. One of: * - 'main': The main generated module. @@ -694,6 +686,9 @@ function $existing_function_name() { * - 'main': The main generated module. * - 'test': The test module. */ + #[Group('existing')] + #[Group('test')] + #[DataProvider('dataTestModuleWithExistingFunctions')] public function testTestModuleWithExistingServices(string $generated, string $existing_code) { $services_value = [ [ diff --git a/Test/Unit/ComponentTestsPHPUnit9Test.php b/Test/Unit/ComponentTestsPHPUnit9Test.php index 3155128d..c73a8475 100644 --- a/Test/Unit/ComponentTestsPHPUnit9Test.php +++ b/Test/Unit/ComponentTestsPHPUnit9Test.php @@ -2,6 +2,8 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Fixtures\File\MockableExtension; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; use DrupalCodeBuilder\Test\Unit\Parsing\YamlTester; @@ -163,9 +165,8 @@ function testModuleGenerationKernelTest() { /** * Create a kernel test with creation traits. - * - * @group test */ + #[Group('test')] function testKernelTestWithTraits() { // Create a module. $module_name = 'test_module'; @@ -204,9 +205,8 @@ function testKernelTestWithTraits() { /** * Create a test class with services. - * - * @group di */ + #[Group('di')] function testModuleGenerationTestsWithServices() { // Create a module. $module_name = 'test_module'; @@ -261,9 +261,8 @@ function testModuleGenerationTestsWithServices() { /** * Create a test class with a test module. - * - * @group test */ + #[Group('test')] function testModuleGenerationTestsWithBasicTestModule() { // Create a module. $module_name = 'generated_module'; @@ -296,9 +295,8 @@ function testModuleGenerationTestsWithBasicTestModule() { /** * Create a test class with a test module and module components. - * - * @group test */ + #[Group('test')] function testModuleGenerationTestsWithTestModuleComponents() { // TODO: Figure out why this works on 8 but not on 9! $this->markTestSkipped(); @@ -478,11 +476,6 @@ public static function dataTestModuleWithExistingFunctions() { * This generates a hook in either the main or the test module, with an * existing function is either the main or test module file. * - * @group existing - * @group test - * - * @dataProvider dataTestModuleWithExistingFunctions - * * @param string $generated * Where the generated hook goes. One of: * - 'main': The main generated module. @@ -492,6 +485,9 @@ public static function dataTestModuleWithExistingFunctions() { * - 'main': The main generated module. * - 'test': The test module. */ + #[Group('existing')] + #[Group('test')] + #[DataProvider('dataTestModuleWithExistingFunctions')] public function testTestModuleWithExistingFunctions(string $generated, string $existing_code) { // Create a module. $module_data = [ @@ -604,11 +600,6 @@ function $existing_function_name() { * This generates a service in either the main or the test module, with an * existing service is either the main or test module. * - * @group existing - * @group test - * - * @dataProvider dataTestModuleWithExistingFunctions - * * @param string $generated * Where the generated service goes. One of: * - 'main': The main generated module. @@ -618,6 +609,9 @@ function $existing_function_name() { * - 'main': The main generated module. * - 'test': The test module. */ + #[Group('existing')] + #[Group('test')] + #[DataProvider('dataTestModuleWithExistingFunctions')] public function testTestModuleWithExistingServices(string $generated, string $existing_code) { $services_value = [ [ diff --git a/Test/Unit/ComponentThemeHook10Test.php b/Test/Unit/ComponentThemeHook10Test.php index 11552953..2d8635ee 100644 --- a/Test/Unit/ComponentThemeHook10Test.php +++ b/Test/Unit/ComponentThemeHook10Test.php @@ -2,12 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests the theme hook generator class. - * @group hooks */ +#[Group('hooks')] class ComponentThemeHook10Test extends TestBase { /** diff --git a/Test/Unit/ComponentThemeHook11Test.php b/Test/Unit/ComponentThemeHook11Test.php index 39b69c62..16767eb1 100644 --- a/Test/Unit/ComponentThemeHook11Test.php +++ b/Test/Unit/ComponentThemeHook11Test.php @@ -2,12 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; /** * Tests the theme hook generator class. - * @group hooks */ +#[Group('hooks')] class ComponentThemeHook11Test extends TestBase { /** diff --git a/Test/Unit/ContainerCollectionTest.php b/Test/Unit/ContainerCollectionTest.php index 62025d6a..1a8b036e 100644 --- a/Test/Unit/ContainerCollectionTest.php +++ b/Test/Unit/ContainerCollectionTest.php @@ -2,12 +2,12 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; /** * Tests the container collection. - * - * @group container */ +#[Group('container')] class ContainerCollectionTest extends TestBase { protected $drupalMajorVersion = 9; diff --git a/Test/Unit/ContainerRebuildTest.php b/Test/Unit/ContainerRebuildTest.php index 36d4eeae..f7ef77eb 100644 --- a/Test/Unit/ContainerRebuildTest.php +++ b/Test/Unit/ContainerRebuildTest.php @@ -2,12 +2,13 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** * Tests rebuilding the cached container. - * @group container */ +#[Group('container')] class ContainerRebuildTest extends TestCase { /** diff --git a/Test/Unit/ContainerTest.php b/Test/Unit/ContainerTest.php index 524059fd..d88b22a5 100644 --- a/Test/Unit/ContainerTest.php +++ b/Test/Unit/ContainerTest.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; /** @@ -10,8 +11,8 @@ * In particular, this needs to test that two separate test cases with different * core major versions set on the environment don't cross-pollute the * container's versionned services. - * @group container */ +#[Group('container')] class ContainerTest extends TestCase { /** diff --git a/Test/Unit/DrupalCodeBuilderAssertionsTest.php b/Test/Unit/DrupalCodeBuilderAssertionsTest.php index 4ca90cdd..f3cb7e9b 100644 --- a/Test/Unit/DrupalCodeBuilderAssertionsTest.php +++ b/Test/Unit/DrupalCodeBuilderAssertionsTest.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; @@ -15,13 +16,12 @@ class DrupalCodeBuilderAssertionsTest extends TestCase { /** * Tests the assertNoTrailingWhitespace() assertion. * - * @dataProvider providerAssertNoTrailingWhitespace - * * @param $code * The code to test with the assertion. * @param $pass * Whether the assertion is expected to pass (TRUE) or fail (FALSE). */ + #[DataProvider('providerAssertNoTrailingWhitespace')] public function testAssertNoTrailingWhitespace($code, $pass) { try { TestBase::assertNoTrailingWhitespace($code); @@ -54,13 +54,12 @@ public static function providerAssertNoTrailingWhitespace() { /** * Tests the assertFunctionParameter() assertion. * - * @dataProvider providerAssertFunctionParameter - * * @param $code * The code to test with the assertion. * @param $pass * Whether the assertion is expected to pass (TRUE) or fail (FALSE). */ + #[DataProvider('providerAssertFunctionParameter')] public function testAssertFunctionParameter($code, $pass) { // TODO: adapt these to cover PHPTester. $this->markTestSkipped(); @@ -102,8 +101,6 @@ public static function providerAssertFunctionParameter() { /** * Tests the assertDocBlock() assertion. * - * @dataProvider providerAssertDocBlock - * * @param $lines * The docblock lines to test with the assertion. * @param $code @@ -111,6 +108,7 @@ public static function providerAssertFunctionParameter() { * @param $pass * Whether the assertion is expected to pass (TRUE) or fail (FALSE). */ + #[DataProvider('providerAssertDocBlock')] public function testAssertDocBlock($lines, $code, $indent, $pass) { // TODO: adapt these to cover PHPTester. $this->markTestSkipped(); @@ -251,13 +249,12 @@ function myMethod { /** * Tests the assertFunction() assertion. * - * @dataProvider providerAssertFunction - * * @param $code * The code to test with the assertion. * @param $pass * Whether the assertion is expected to pass (TRUE) or fail (FALSE). */ + #[DataProvider('providerAssertFunction')] public function testAssertFunction($code, $pass) { // TODO: adapt these to cover PHPTester. $this->markTestSkipped(); diff --git a/Test/Unit/GenerateHelperComponentCollectorTest.php b/Test/Unit/GenerateHelperComponentCollectorTest.php index 69a97792..42748436 100644 --- a/Test/Unit/GenerateHelperComponentCollectorTest.php +++ b/Test/Unit/GenerateHelperComponentCollectorTest.php @@ -2,6 +2,8 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\DataProvider; use DrupalCodeBuilder\Definition\MergingGeneratorDefinition; use DrupalCodeBuilder\Definition\PropertyDefinition; use DrupalCodeBuilder\Definition\DeferredGeneratorDefinition; @@ -180,9 +182,8 @@ public static function providerGeneratorChildNoRequests() { /** * Request with nested component properties. - * - * @dataProvider providerGeneratorChildNoRequests */ + #[DataProvider('providerGeneratorChildNoRequests')] public function testGeneratorChildNoRequests($data_value, $expected_paths) { $definition = MergingGeneratorDefinition::createFromGeneratorType('my_root') ->setName('my_root') @@ -256,9 +257,8 @@ public function testMultipleStringGeneratorChildNoRequests() { /** * Request with only the root generator, with a single-value preset. - * - * @group presets */ + #[Group('presets')] public function testSingleGeneratorSinglePresetsNoRequirements() { $definition = MergingGeneratorDefinition::createFromGeneratorType('my_root') ->setProperties([ @@ -343,9 +343,8 @@ public function testSingleGeneratorSinglePresetsNoRequirements() { /** * Request with only the root generator, with a multi-valued preset. - * - * @group presets */ + #[Group('presets')] public function testSingleGeneratorMultiPresetsNoRequirements() { $definition = MergingGeneratorDefinition::createFromGeneratorType('my_root') ->setProperties([ diff --git a/Test/Unit/ParserDocBlockTest.php b/Test/Unit/ParserDocBlockTest.php index f781e63c..c81aaa2f 100644 --- a/Test/Unit/ParserDocBlockTest.php +++ b/Test/Unit/ParserDocBlockTest.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; use DrupalCodeBuilder\Test\Unit\Parsing\DocBlockTester; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\ExpectationFailedException; @@ -10,9 +11,8 @@ /** * Unit tests for the DocBlockTester test helper. - * - * @group php_tester_docblocks */ +#[Group('php_tester_docblocks')] class ParserDocBlockTest extends TestCase { /** diff --git a/Test/Unit/ParserPHPTest.php b/Test/Unit/ParserPHPTest.php index abfa9e1e..9b3f0dd2 100644 --- a/Test/Unit/ParserPHPTest.php +++ b/Test/Unit/ParserPHPTest.php @@ -2,6 +2,8 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\ExpectationFailedException; use DrupalCodeBuilder\Test\Unit\Parsing\PHPTester; @@ -17,9 +19,8 @@ class ParserPHPTest extends TestCase { * * This is fairly simple for now, and is just to sanity check that Coder * and PHP Codersniffer run ok. - * - * @dataProvider providerAssertDrupalCodingStandards */ + #[DataProvider('providerAssertDrupalCodingStandards')] public function testAssertDrupalCodingStandards($code, $pass) { $php_tester = new PHPTester(8, $code); @@ -180,9 +181,8 @@ class Foo implements Plain, WithSpace { /** * Tests the assertClassHasInterfaces() assertion. - * - * @dataProvider providerAssertClassInterfaces */ + #[DataProvider('providerAssertClassInterfaces')] public function testAssertClassInterfaces($expected_interfaces, $pass_has, $pass_has_not) { $php = <<setMultiple(TRUE); @@ -147,9 +147,8 @@ public static function dataMultipleSimpleData() { /** * Tests single-valued complex data. - * - * @dataProvider dataSingleComplexData */ + #[DataProvider('dataSingleComplexData')] public function testSingleComplexData(array $original_values, array $other_values, ?array $end_values, ?bool $expected_result, bool $expect_exception) { $definition = PropertyDefinition::create('complex') ->setProperties([ @@ -219,9 +218,8 @@ public static function dataSingleComplexData() { /** * Tests multi-valued complex data. - * - * @dataProvider dataMultipleComplexData */ + #[DataProvider('dataMultipleComplexData')] public function testMultipleComplexData(array $original_values, array $other_values, ?array $end_values, ?bool $expected_result) { $definition = PropertyDefinition::create('complex') ->setMultiple(TRUE) @@ -353,9 +351,8 @@ public static function dataMultipleComplexData() { * Tests merging mapping data. * * We only cover single-valued as mapping data is rarely if ever multiple. - * - * @dataProvider dataMappingData */ + #[DataProvider('dataMappingData')] public function testMappingData(array $original_values, array $other_values, ?array $end_values, ?bool $expected_result, bool $expect_exception) { $definition = PropertyDefinition::create('mapping'); diff --git a/Test/Unit/UnitDependencyInjectionAnalysisTest.php b/Test/Unit/UnitDependencyInjectionAnalysisTest.php index 817e5d40..f8f1f8c6 100644 --- a/Test/Unit/UnitDependencyInjectionAnalysisTest.php +++ b/Test/Unit/UnitDependencyInjectionAnalysisTest.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** @@ -12,14 +13,13 @@ class UnitDependencyInjectionAnalysisTest extends TestCase { /** * Tests the method analysis. * - * @dataProvider dataDependencyInjection - * * @param object $class_object * An instance of an anonymous class to analyse. This is an object because * passing the class name of anonymous class is fiddly. * @param array $result * The expected result. */ + #[DataProvider('dataDependencyInjection')] public function testDependencyInjection($class_object, $result) { $parent_construction_parameters = \DrupalCodeBuilder\Utility\CodeAnalysis\DependencyInjection::getInjectedParameters($class_object::class, 0); diff --git a/Test/Unit/UnitMethodCollectorTest.php b/Test/Unit/UnitMethodCollectorTest.php index 1937c2a4..4eb9cead 100644 --- a/Test/Unit/UnitMethodCollectorTest.php +++ b/Test/Unit/UnitMethodCollectorTest.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use DrupalCodeBuilder\Task\Collect\MethodCollector; use DrupalCodeBuilder\Test\Fixtures\MethodCollectorInterface; @@ -13,9 +14,8 @@ class UnitMethodCollectorTest extends TestCase { /** * Tests the method analysis. - * - * @dataProvider providerMethodCollector */ + #[DataProvider('providerMethodCollector')] public function testMethodCollector($method_name, $declaration_result) { $method_collector = new MethodCollector(); diff --git a/Test/Unit/UnitValidatorTest.php b/Test/Unit/UnitValidatorTest.php index 82e97501..637f2fbf 100644 --- a/Test/Unit/UnitValidatorTest.php +++ b/Test/Unit/UnitValidatorTest.php @@ -2,6 +2,7 @@ namespace DrupalCodeBuilder\Test\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use MutableTypedData\Definition\DataDefinition; use PHPUnit\Framework\TestCase; @@ -33,9 +34,8 @@ public static function providerClassNameValidator() { /** * Tests the class name validator. - * - * @dataProvider providerClassNameValidator */ + #[DataProvider('providerClassNameValidator')] public function testClassNameValidator($value, $expected_pass) { $validator = new \DrupalCodeBuilder\MutableTypedData\Validator\ClassName(); @@ -84,9 +84,8 @@ public static function providerMachineNameValidator() { /** * Tests the machine name validator. - * - * @dataProvider providerMachineNameValidator */ + #[DataProvider('providerMachineNameValidator')] public function testMachineNameValidator($value, $expected_pass) { $validator = new \DrupalCodeBuilder\MutableTypedData\Validator\MachineName(); @@ -166,9 +165,8 @@ public static function providerPluginNameValidator() { /** * Tests the plugin name validator. - * - * @dataProvider providerPluginNameValidator */ + #[DataProvider('providerPluginNameValidator')] public function testPluginNameValidator($value, $expected_pass) { $validator = new \DrupalCodeBuilder\MutableTypedData\Validator\PluginName(); @@ -233,9 +231,8 @@ public static function providerYamlPluginNameValidator() { /** * Tests the YAML plugin name validator. - * - * @dataProvider providerYamlPluginNameValidator */ + #[DataProvider('providerYamlPluginNameValidator')] public function testYamlPluginNameValidator($value, $expected_pass) { $validator = new \DrupalCodeBuilder\MutableTypedData\Validator\YamlPluginName();