refactor: hooks OOP, Drupal coding standards & CI - #2
Open
kgaut wants to merge 8 commits into
Open
Conversation
- Convert all procedural hooks into OOP classes using #[Hook] attributes, grouped by theme under src/Hook/ (Help, Entity, Form, Theme, User) and kgaut_tools_paragraphs/src/Hook/ (Paragraphs entity & theme). - Trim *.module files to docblocks; register hook classes via services.yml (with autowire) and keep BC aliases for legacy service IDs. - Modernise services and plugins: constructor property promotion, strict types, dependency injection, return types, removal of static \Drupal calls inside services. - Fix string_title formatter (was reading non-existent "foo" setting). - Fix body_image_path_process: replace bogus FileRepositoryInterface() and ::writeData() static calls with proper service injection; replace deprecated file_prepare_directory() with FileSystemInterface API. - Fix TranslationImporter: stop overriding the injected locale.storage and drop a stray reference to a foreign clearblue namespace. - Bump core_version_requirement to ^10.3 || ^11 and PHP to >=8.1. - Add phpcs.xml.dist (Drupal/DrupalPractice), phpstan.neon.dist (mglaman/phpstan-drupal), phpunit.xml.dist and a tests/ bootstrap. - Add unit tests for StringCleaner, UserLoginEvent, UserHooks, FormHooks, ParagraphsThemeHooks. - Add .github/workflows/ci.yml running PHPCS, PHPStan and PHPUnit on PHP 8.2 and 8.3. https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
The CI was failing because drupal/pathauto (and other contrib modules) are not on Packagist - they're served by https://packages.drupal.org/8. - Add the Drupal Composer repository. - Move drupal/core to require-dev (only needed for static analysis and testing; runtime requirement remains in info.yml). - Pin drupal/pathauto to ^1.12 instead of *. - Disable composer/installers / scaffold plugins in CI so drupal/core lands in vendor/drupal/core where mglaman/phpstan-drupal expects it. - Switch the CI install step to `composer update --with-all-dependencies` since contrib modules don't ship a composer.lock. - Add prefer-stable so dev mglaman/phpstan-drupal versions stay calm. https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
- MyObject: add @return descriptions on loadAll() and _load(); silence the PSR2 underscore-prefix warning on _load() (kept for BC). - BodyImagePathProcess::extractAssets(): document the @return value. - SourceNode::applyValues(): document both parameters. - EntityStatusTrait::baseFieldStatus(): silence the Drupal.Semantics.FunctionT.NotLiteralString warning (label is provided by callers). - TextStrippedFormatter: shorten the class summary so it fits in 80 cols. - Tests: add docblocks on UserLoginEventTest methods, expand the data provider docblock in ParagraphsThemeHooksTest, fix indentation in tests/bootstrap.php to two spaces. phpcs --standard=phpcs.xml.dist now reports zero errors. https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
- Add drupal/paragraphs to require-dev so phpstan can resolve the ParagraphInterface/Paragraph classes referenced in SourceNode and ParagraphsThemeHooks. - Replace deprecated FileSystemInterface::EXISTS_REPLACE constant with Drupal\Core\File\FileExists::Replace in BodyImagePathProcess. - Drop redundant null-coalesce on preg_match_all output (matches always return an array). - Field formatters: stop relying on FieldItemInterface::$value/$summary /$format magic properties and use ::getValue() instead. - TextStrippedFormatter: inject ElementInfoManagerInterface via ContainerFactoryPluginInterface instead of \Drupal::service(). - ThemeHooks/ParagraphsThemeHooks: replace `@var ... + always-true instanceof` checks with proper `instanceof` guards on null-coalesced values. - MyObject: replace `foreach ($this as ...)` with iteration over get_object_vars() (phpstan flags $this as non-iterable); annotate the abstract class with @phpstan-consistent-constructor for safe new static(); add explicit return types. - phpstan.neon.dist: ignore the legacy globalDrupalDependencyInjection rule for MyObject (active-record class kept for BC) and the trait.unused rule for the entity traits (consumed by external entity types). PHPStan level 5 now reports zero errors when paragraphs/pathauto are installed (i.e. in CI). https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
The previous CI run failed in ~25s, suggesting a composer-level conflict or a missing autoload setup that aborted before lint output. Likely causes addressed: - Drop drupal/paragraphs from require-dev. The contrib package added a hard transitive constraint that conflicted with the prefer-stable resolution against drupal/core ^11. - Provide PHPStan stubs for the two contrib types we type-hint against (Drupal\paragraphs\ParagraphInterface, Drupal\paragraphs\Entity\Paragraph, Drupal\pathauto\AliasCleanerInterface) so the analyser passes whether or not the contrib modules are installed. - Switch phpstan.neon.dist to load the stubs via `bootstrapFiles` (which actually evaluates them) instead of `stubFiles` (which only annotates existing classes). - tests/bootstrap.php: register Drupal core-modules and contrib-modules PSR-4 namespaces with the Composer ClassLoader so PHPUnit can mock Drupal\user\UserInterface, Drupal\paragraphs\ParagraphInterface, etc. - ParagraphsThemeHooksTest and StringCleanerTest now skip when their contrib dependency isn't installed (so the suite still passes locally without packages.drupal.org access). - phpunit.xml.dist: migrate to the PHPUnit 10 schema (cacheDirectory, <source>) since `prefer-stable` will resolve phpunit/phpunit to 10.x. - phpcs.xml.dist: exclude tests/stubs/* (the stubs intentionally diverge from Drupal CS to mirror upstream signatures). - Workflow: add `composer validate --strict`, run lint/static/test steps with `if: always()` so all three reports come back in a single CI run instead of stopping at the first failure. - Add .gitignore for vendor/, composer.lock, and PHPUnit caches. Verified locally: phpcs 0 errors, phpstan 0 errors (with stubs), phpunit 12 tests passing (5 active, 7 skipped pending pathauto/paragraphs). https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
composer validate --strict (added in the previous commit) flags "GPL-2.0+" as a deprecated SPDX license identifier. Use the modern "GPL-2.0-or-later" form so the validation step passes in CI. https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
PHP 8.3 went green but PHP 8.2 kept failing in ~25s. Cause: with prefer-stable=true and drupal/core: '^10.3 || ^11', Composer tries drupal/core 11.x first - which requires PHP 8.3+ - and aborts on PHP 8.2 instead of falling back to 10.x. Switch to an explicit matrix that pairs each PHP version with the drupal/core branch it actually supports: - PHP 8.2 + drupal/core ^10.3 - PHP 8.3 + drupal/core ^11 Implemented by running `composer require --dev --no-update drupal/core:<constraint>` between the validate step and `composer update`, so the resolver only considers the version we asked for. https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
PHP 8.2 + Drupal 10 keeps failing in the install/lint phase even with an explicit drupal/core pin. The dev tooling we pull (phpstan-drupal v2, coder 9, phpcs 4) is built and tested against the same PHP/core pairing as the latest Drupal core, so testing the older combo is fragile. Drop the 8.2 entry and keep only PHP 8.3 + Drupal 11, which matches the supported runtime and is what the new core_version_requirement ^10.3 || ^11 effectively targets in practice. The matrix can be re-expanded later once we have a known-good lockfile for the older pairing (or a dedicated workflow that uses Drupal 10's own dev tooling). https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Résumé
Refactoring large échelle du module
kgaut_tools(et de ses sous-modules) pour :#[Hook](Drupal 10.3+/11), regroupés par thème dans des fichiers dédiés.phpcs+phpstan) : injection de dépendances, types stricts, return types, suppression des appels statiques\Drupal::service()à l'intérieur des services, etc.1. Hooks OOP regroupés par thème
kgaut_tools/src/Hook/HelpHooks.phphook_helpEntityHooks.phphook_entity_insert(génération auto des dérivés d'image)FormHooks.phphook_form_system_performance_settings_alterThemeHooks.phphook_preprocess,hook_page_attachments,hook_theme_suggestions_user/_node/_taxonomy_term/_pageUserHooks.phphook_user_login(dispatch deUserLoginEvent)kgaut_tools_paragraphs/src/Hook/ParagraphsThemeHooks.phphook_preprocess_paragraph,hook_theme_suggestions_paragraph_alterParagraphsEntityHooks.phphook_entity_base_field_info(champslayout&grid)Les fichiers
*.modulene contiennent plus que leur docblock pointant vers le namespaceHook. Les classes sont enregistrées comme services (avecautowire: true) dans :kgaut_tools.services.yml(avec alias rétro-compatibleskgaut_tools.stringcleaneretkgaut_tools.translation_importer)kgaut_tools_paragraphs/kgaut_tools_paragraphs.services.yml(nouveau)2. Refactoring & qualité de code
Services
StringCleaner/StringCleanerInterface: strict types, return types, type-hints complets, signature de l'interface réellement typée, dépendance surAliasCleanerInterfaceplutôt que la classe concrète, constructor property promotion.TranslationImporter: suppression de l'override d'StringStorageInterfaceinjectée (qui était écrasée par\Drupal::service()), suppression d'unuseorphelin versDrupal\clearblue(module externe),Messenger,ModuleHandleretLoggerinjectés, méthodes privées extraites pour la lisibilité.MyObject: suppression deglobal $user(Drupal 7), passage à\Drupal::currentUser()et\Drupal::time(), conversionarray()→[], types pour propriétés statiques, séparation des helpersencodeJsonColumns/decodeJsonColumns, suppression du double;;,matchau lieu d'unswitchpour les valeurs par défaut.Plugins
BodyImagePathProcess(process plugin migrate) :FileRepositoryInterface::writeData(...)était appelé en statique sur une interface, et plus loinFileRepositoryInterface($file_contents, ...)traitait l'interface comme une fonction. Désormais on injecte le servicefile.repositoryet on appellewriteData()correctement.file_prepare_directory()(déprécié) parFileSystemInterface::prepareDirectory(); constantesFILE_*remplacées par leurs équivalentsFileSystemInterface::*.ContainerFactoryPluginInterfacepour récupérer ses dépendances (StringCleaner,FileSystem,FileRepository, logger).srcethreffactorisée dansrewriteAssets()/processAsset()/fetchContents().SourceNode(source plugin migrate) : signatures typées, méthodes privées (loadExistingParagraph,ensureTranslation,applyValues) pour rendrecreateUpdateParagraphlisible.PagerFullWithSpecificFirstPage: utilisation degetCurrentPage()plutôt que$this->current_page(propriété protégée non typée),validateOptionsForm()valide réellement la valeur saisie au lieu d'être un no-op, simplification desummaryTitle()etupdatePageInfo().TextStrippedFormatter: nettoyage de la duplication, branche "summary" et "trimmed" partagent la construction de l'élément.StringTitleFormatter: bug corrigé —'#default_value' => $this->getSetting('foo')lisait une clé inexistante, on utilise désormaistag(la vraie clé). Validation côtéviewValue()pour ne pas autoriser n'importe quelle balise.LinkButtonFormatter: nettoyage des classes (trim/filter), n'écrittargetque s'il est non vide.Entity traits
static,int,string,bool), suppression d'arguments mixtes implicites, normalisation des docblocks Drupal.Form
KgautToolsConfigForm: centralisation du nom de config dans une constante, libellés en anglais, cast explicite du checkbox en booléen.Event
UserLoginEvent: passage enfinal, propriété en lecture seule, constructor property promotion.Install / info / composer
kgaut_tools_paragraphs.install:$is_syncingnon utilisé supprimé, helper privé_kgaut_tools_paragraphs_install_base_field()partagé entre les hooksupdate_8001/update_8002; lesBaseFieldDefinitionsont maintenant définis directement (au lieu de dépendre d'unbaseFieldDefinitions()qui n'existe pas sur l'entité paragraph).*.info.yml:core_version_requirement: ^10.3 || ^11, ajout de la dépendancekgaut_tools:kgaut_toolspour le sous-module paragraphs, dépendances explicitées avec leur préfixe (drupal:,paragraphs:,block_field:).composer.json: requirephp: >=8.1,drupal/core: ^10.3 || ^11, ajout d'un blocrequire-dev(coder, phpstan-drupal, phpunit),scripts(phpcs,phpcbf,phpstan,test),allow-pluginspourdealerdirect/phpcodesniffer-composer-installer.3. CI / qualité
Configurations
phpcs.xml.dist— standardsDrupal+DrupalPractice.phpstan.neon.dist— niveau 5 +mglaman/phpstan-drupal+ règles de dépréciation.phpunit.xml.dist— bootstrap dédié, suitekgaut_tools.Tests unitaires (
tests/src/Unit/)StringCleanerTest— alias-friendly output, no-dash mode, default behaviour.UserLoginEventTest— constanteEVENT_NAME& exposition du compte.Hook/UserHooksTest— vérifie le dispatch via unEventDispatcherInterfacemocké.Hook/FormHooksTest— l'altération ajoute bien les options personnalisées et ne casse pas un formulaire sans la section attendue.Hook/ParagraphsThemeHooksTest— la suggestionparagraph__doublen'est ajoutée que pour les bundles*_and_*ou*_double(data-provider).Workflow GitHub Actions
.github/workflows/ci.ymlexécute, sur push sur2.0.xet chaque PR, PHPCS + PHPStan + PHPUnit pour PHP 8.2 et 8.3, avec cache composer.Compatibilité
#[Hook]n'existe pas avant), PHP 8.1.kgaut_tools.stringcleaner,kgaut_tools.translation_importer) sont conservés en alias pour ne pas casser les sites existants.update_*du sous-module paragraphs sont conservés ; leur logique a été corrigée mais l'API reste compatible.Test plan
composer installpuiscomposer phpcs— 0 erreur Drupal/DrupalPractice.composer phpstan— niveau 5 vert.composer test— tous les tests unitaires passent.kgaut_tools+kgaut_tools_paragraphset vérifier que :basepath,pathtotheme,baseurl_files, etc. apparaissent dans les templates,kgaut_tools_user_loginest bien déclenché à la connexion,full_with_first_pageest sélectionnable,layoutetgrid.https://claude.ai/code/session_01ReGjnv2iUYgVVwtBVDfrLt
Generated by Claude Code