Find the ultimate configurations to automate code quality checks using GrumpPHP in Magento 2
TLDR; this article is a tutorial setting up GrumPHP task runner for quality checks in Magento 2. An earlier video tutorial and presentation by me is already there. Check out: https://youtu.be/tq-DPi9wMss and the presentation https://slides.com/milindsingh/virtual-magento-meetup/
-
why automate?
-
developers are (a little) lazy
- running 6-7 tools manually is what I will try to skip few times ( and expect it would pass in deployment pipelines )
- we are overburdened (most of the times)
-
development pipelines will fail
- suppose we miss a space at the end of the file, and phpcs fails while running the automated pipelines on pull request merge.
- fix the change and again push just to add a simple line at the end of the file (time taking)
-
automation can be enforced
- grumphp can be configured to listen to git commit commands and will not allow until all quality checks passed.
-
-
what to automate?
-
PHP Code Sniffer
- https://github.com/squizlabs/PHP_CodeSniffer
- https://github.com/magento/magento-coding-standard
- tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard
-
PHP Code Beautifier
- automatically correct coding standard violations
-
PHP Coding Standards Fixer
- https://github.com/FriendsOfPHP/PHP-CS-Fixer
- automatically correct coding standard violations (more rules available, not needed to validate)
-
PHP Mess Detector
- https://phpmd.org/
- auto-detects cyclomatic complex in the code
-
PHP Stan
- https://github.com/phpstan/phpstan
- https://devdocs.magento.com/guides/v2.4/test/testing.html
- <static analysis tool integrated into Magento 2 by default>
-
Keywords
- print_r("test")
- die("hi")
- ObjectManager::getInstance()
-
Custom
- Create your own automation!
- You can check out a small task created by me to validate swagger documentation before every commit. https://github.com/milindsingh/grumphp-swagger
-
-
how to automate?
-
GrumPHP
GrumPHP has a set of common tasks built-in.
GrumPHP will run some tests on the committed code. If the tests fail, you won't be able to commit your changes.
This handy tool will not only improve your codebase, it will also teach your co-workers to write better code following the best practices you've determined as a team.
-
Installation
Install GrumPHP by running
composer require --dev phpro/grumphp
(in current project only) or,composer global require --dev phpro/grumphp
(globally recommended)
-
Dependencies
-
PHPCS:
-
Install PHP CodeSniffer (skip if already installed) :
composer global require --dev "squizlabs/php_codesniffer=*"
(globally recommended)composer require --dev "squizlabs/php_codesniffer=*"
(at project level, need to addproject-root/vendor/bin
to PATH for direct cli use)
-
Goto Magento2 root run following commands to install Magento2 coding standard :
composer require --dev magento/magento-coding-standard
-
Set Magento2 Standard in PHP CodeSniffer available standards:
phpcs --config-set installed_paths ../../magento/magento-coding-standard/
-
-
PHPCS Fixer 2
Install PHP Coding Standards Fixer (skip if already installed) :
composer global require --dev friendsofphp/php-cs-fixer
(globally recommended)composer require --dev friendsofphp/php-cs-fixer
(at project level, need to addproject-root/vendor/bin
to PATH for direct cli use)
-
PHPStan
composer global require --dev phpstan/phpstan
(globally recommended)composer require --dev phpstan/phpstan
(at project level, need to addproject-root/vendor/bin
to PATH for direct cli use)
-
PHPMD
composer global require --dev phpmd/phpmd
(globally recommended)composer require --dev phpmd/phpmd
(at project level, need to addproject-root/vendor/bin
to PATH for direct cli use)
-
PHPLint
composer global require --dev php-parallel-lint/php-parallel-lint
(globally recommended)composer require --dev php-parallel-lint/php-parallel-lint
(at project level, need to addproject-root/vendor/bin
to PATH for direct cli use)
-
-
Setup
GrumPHP can monitor each git repository push action by initializing it in the repository. GrumPHP can be configured at 2 levels:
-
Project Level Setup
-
Goto the
magento-2-root
and run:php vendor/bin/grumphp git:init
orgrumphp git:init
(recommended)
- Create a grumphp.yml file in
magento-2-root
and copy all content as below code. - Though GrumPHP auto detect
git commit
command but you can manually test by runningphp vendor/bin/grumphp
run orgrumphp run
inside inmagento-2-root
.
# Project level GrumPHP configuration for Magento 2 grumphp: hide_circumvention_tip: true process_timeout: 120 stop_on_failure: false ignore_unstaged_changes: false tasks: jsonlint: detect_key_conflicts: true metadata: priority: 100 xmllint: ignore_patterns: - "#test/(.*).xml#" metadata: priority: 100 phplint: triggered_by: ['php', 'phtml'] metadata: priority: 200 yamllint: ignore_patterns: - "#test/(.*).yml#" - "#charts/.*#" metadata: priority: 100 composer: file: ./composer.json no_check_all: true no_check_lock: false no_check_publish: false with_dependencies: false strict: false metadata: priority: 80 # validate git commit message git_commit_message: allow_empty_message: false enforce_capitalized_subject: false enforce_no_subject_punctuations: false enforce_no_subject_trailing_period: true enforce_single_lined_subject: true type_scope_conventions: [] max_body_width: 80 max_subject_width: 80 matchers: "Commit message must contain issue topic and number": /^\[(HOTFIX|BUGFIX|FEATURE|INFRA|MERGE|RELEASE)]\sICRSICRP-\d+\s::\s.*\s\[(COMPLETED|WIP)]/ case_insensitive: true multiline: false additional_modifiers: '' # validate git branch names git_branch_name: whitelist: # allowed branch names: 'feature-1', 'feature-new', 'feature-new1', 'task-1', etc - "/(hotfix|bugfix|feature|release|task)-([a-z|0-9]+)$/" blacklist: - "development" - "production" - "staging" - "master" - "infra" allow_detached_head: true # catch not allowed keywords git_blacklist: keywords: - "\\.dev" - "\\.local" - "\\.test" - "<<<<<<<" - "=======" - "DebuggerUtility" - "ObjectManager::getInstance" - "_GET\\[" - "_POST\\[" - "_REQUEST\\[" - "console.log(" - "die(" - "die;" - "exit(" - "exit;" - "fileadmin" - "localhost" - "phpinfo" - "phpinfo(" - "print_r(" - "var_dump(" - "_objectManager" - "ObjectManagerInterface" triggered_by: ['php', 'js', 'html', 'phtml'] metadata: priority: 90 # https://devdocs.magento.com/guides/v2.4/coding-standards/code-standard-php.html phpcs: standard: Magento2 tab_width: 4 severity: 10 # can remove this to dis allow all level of severity. error_severity: 10 warning_severity: ~ report: full triggered_by: [phtml, php] metadata: priority: 70 phpcsfixer2: allow_risky: false config: '.php_cs.dist' triggered_by: ['php', 'phtml'] using_cache: true cache_file: './.php_cs.cache' config_contains_finder: true verbose: true phpmd: ruleset: ['./dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml'] triggered_by: ['php'] exclude: - "./app/code/Magento/" - "./app/code/*/*/Setup/" metadata: priority: 70 # uncomment to skip modules using whitelist patterns # whitelist_patterns: # - /^app\/code\/MyVendor\/MyModuleToSkip\/(.*)/ # https://devdocs.magento.com/guides/v2.4/test/testing.html#phpstan phpstan: autoload_file: ~ configuration: './dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/phpstan.neon' level: 8 triggered_by: ['php'] force_patterns: [] ignore_patterns: [] memory_limit: "-1" metadata: priority: 90 phpversion: project: '7.3'
-
-
Module Level Setup
-
Goto the module directory i.e.
magento-2-root/app/code/MyVendor/MyModule
and run:php vendor/bin/grumphp git:init
orgrumphp git:init
(recommended)
- Create a grumphp.yml file in
magento-2-root/app/code/MyVendor/MyModule
and copy all content as below code. -
Add bin path
magento2-root/vendor/bin
to your modulecomposer.json
. Refer to composer.json.sample.{ "config": { "bin-dir": "../../../../vendor/bin" } }
- Same as project level setup, GrumPHP auto detect
git commit
command but you can manually test by runningphp ../../../../vendor/bin/grumphp
run orgrumphp run
inside module.
# Module level GrumPHP configuration for Magento 2 grumphp: hide_circumvention_tip: true process_timeout: 120 stop_on_failure: false ignore_unstaged_changes: false tasks: jsonlint: detect_key_conflicts: true metadata: priority: 100 xmllint: ignore_patterns: - "#test/(.*).xml#" metadata: priority: 100 phplint: triggered_by: ['php', 'phtml'] metadata: priority: 200 yamllint: ignore_patterns: - "#test/(.*).yml#" - "#charts/.*#" metadata: priority: 100 composer: file: ./composer.json no_check_all: true no_check_lock: false no_check_publish: false with_dependencies: false strict: false metadata: priority: 80 # validate git commit message git_commit_message: allow_empty_message: false enforce_capitalized_subject: false enforce_no_subject_punctuations: false enforce_no_subject_trailing_period: true enforce_single_lined_subject: true type_scope_conventions: [] max_body_width: 80 max_subject_width: 80 matchers: "Commit message must contain issue topic and number": /^\[(HOTFIX|BUGFIX|FEATURE|INFRA|MERGE|RELEASE)]\sICRSICRP-\d+\s::\s.*\s\[(COMPLETED|WIP)]/ case_insensitive: true multiline: false additional_modifiers: '' # validate git branch names git_branch_name: whitelist: # allowed branch names: 'feature-1', 'feature-new', 'feature-new1', 'task-1', etc - "/(hotfix|bugfix|feature|release|task)-([a-z|0-9]+)$/" blacklist: - "development" - "production" - "staging" - "master" - "infra" allow_detached_head: true # catch not allowed keywords git_blacklist: keywords: - "\\.dev" - "\\.local" - "\\.test" - "<<<<<<<" - "=======" - "DebuggerUtility" - "ObjectManager::getInstance" - "_GET\\[" - "_POST\\[" - "_REQUEST\\[" - "console.log(" - "die(" - "die;" - "exit(" - "exit;" - "fileadmin" - "localhost" - "phpinfo" - "phpinfo(" - "print_r(" - "var_dump(" - "_objectManager" - "ObjectManagerInterface" triggered_by: ['php', 'js', 'html', 'phtml'] metadata: priority: 90 # https://devdocs.magento.com/guides/v2.4/coding-standards/code-standard-php.html phpcs: standard: Magento2 tab_width: 4 severity: 10 # can remove this to dis allow all level of severity. error_severity: 10 warning_severity: ~ report: full triggered_by: [phtml, php] metadata: priority: 70 phpcsfixer2: allow_risky: false config: '../../../../.php_cs.dist' triggered_by: ['php', 'phtml'] using_cache: true cache_file: './.php_cs.cache' # config_contains_finder: false - to skip fixing all Magento 2 directory config_contains_finder: false verbose: true phpmd: ruleset: ['../../../../dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml'] triggered_by: ['php'] exclude: - "./app/code/Magento/" - "./app/code/*/*/Setup/" metadata: priority: 70 # uncomment to skip modules using whitelist patterns # whitelist_patterns: # - /^app\/code\/MyVendor\/MyModuleToSkip\/(.*)/ # https://devdocs.magento.com/guides/v2.4/test/testing.html#phpstan phpstan: autoload_file: ~ configuration: '../../../../dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/phpstan.neon' level: 8 triggered_by: ['php'] force_patterns: [] ignore_patterns: [] memory_limit: "-1" metadata: priority: 90 phpversion: project: '7.3'
-
-
-
-
References