Coding style,
Static code analysis and PHP
Outline
-
About me
-
What's Coding style?
-
PSR-2與PSR-12程式碼風格標準。
-
What's static code analysis?
-
PHPStan
-
Psalm
-
Phan
-
-
CI/CD examples
-
Laravel framework integration
About me
- Peter
- GitHub
- Active open source contributor
-
An associate engineer
- DevOps
- Back-end
- System Architecture Researching
- Web Application Security
- PHP, Python and JavaScript
- Smart Grid Technology (2017~2021)
- Database, Data platform architecture (2021~)
What's coding style?
AKA Programming style
PHP有Coding style嗎?
PHP有Coding style嗎?
Coding style
-
Founded by PHP-FIG
-
PHP Framework Interop Group
-
-
PSR-1
-
PSR-2
-
PSR-12
-
More standard docs
-
-
https://github.com/php-fig/fig-standards/tree/master/accepted
PSR-1 Overview
-
Files MUST use only <?php and <?= tags.
-
Files MUST use only UTF-8 without BOM for PHP code.
-
Files SHOULD either declare symbols (classes, functions, constants, etc.) or cause side-effects (e.g. generate output, change .ini settings, etc.) but SHOULD NOT do both.
-
Namespaces and classes MUST follow an "autoloading" PSR: [PSR-0, PSR-4].
-
Class names MUST be declared in StudlyCaps.
-
Class constants MUST be declared in all upper case with underscore separators.
-
Method names MUST be declared in camelCase.
PSR-2 Overview(Deprecated)
-
Code MUST follow a "coding style guide" PSR [PSR-1].
-
Code MUST use 4 spaces for indenting, not tabs.
-
There MUST NOT be a hard limit on line length; the soft limit MUST be 120 characters; lines SHOULD be 80 characters or less.
-
There MUST be one blank line after the namespace declaration, and there MUST be one blank line after the block of use declarations.
-
Opening braces for classes MUST go on the next line, and closing braces MUST go on the next line after the body.
-
Opening braces for methods MUST go on the next line, and closing braces MUST go on the next line after the body.
-
Visibility MUST be declared on all properties and methods; abstract and final MUST be declared before the visibility; static MUST be declared after the visibility.
-
Control structure keywords MUST have one space after them; method and function calls MUST NOT.
-
Opening braces for control structures MUST go on the same line, and closing braces MUST go on the next line after the body.
-
Opening parentheses for control structures MUST NOT have a space after them, and closing parentheses for control structures MUST NOT have a space before.
- This specification extends, expands and replaces PSR-2, the coding style guide and requires adherence to PSR-1, the basic coding standard.
規則太多要檢查,有沒有檢查工具?
PHP_CodeSniffer
- curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
- chmod +x phpcs.phar
- mv phpcs.phar phpcs
- phpcs --help
- phpcs --standard=PSR2 src/ tests/
- curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar
- chmod +x phpcbf.phar
- mv phpcbf.phar phpcbf
- phpcbf --help
- phpcbf --standard=PSR2 src/ tests/
phpcs --standard=PSR2
FILE: ...n-source-contributions/localized/src/Validation/LtValidation.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
31 | ERROR | [x] Use single instead of double quotes for simple
| | strings.
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
FILE: ...is/build/open-source-contributions/localized/tests/bootstrap.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
15 | ERROR | [x] Use single instead of double quotes for simple
| | strings.
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
FILE: ...n-source-contributions/localized/src/Validation/BrValidation.php
----------------------------------------------------------------------
FOUND 2 ERRORS AFFECTING 1 LINE
----------------------------------------------------------------------
196 | ERROR | [x] Use single instead of double quotes for simple
| | strings.
196 | ERROR | [x] Use single instead of double quotes for simple
| | strings.
----------------------------------------------------------------------
PHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
phpcs --standard=PSR2
phpcbf --standard=PSR2
phpcs.xml
<?xml version="1.0"?>
<ruleset name="Coding Standard">
<arg name="basepath" value="."/>
<arg name="colors"/>
<arg value="sp"/>
<config name="ignore_warnings_on_exit" value="1"/>
<file>./src</file>
<file>./tests</file>
<rule ref="PSR2"></rule>
<!-- <rule ref="PSR12"></rule> -->
<rule ref="Squiz.Commenting.ClassComment">
<exclude name="Squiz.Commenting.ClassComment.TagNotAllowed"/>
<type>warning</type>
<exclude-pattern>*/tests/</exclude-pattern>
</rule>
<rule ref="Squiz.Commenting.ClassComment.Missing">
<type>warning</type>
</rule>
<rule ref="Squiz.Commenting.FunctionComment.Missing">
<type>warning</type>
<exclude-pattern>*/config/</exclude-pattern>
</rule>
<rule ref="Squiz.Commenting.FunctionComment.MissingParamTag">
<type>warning</type>
</rule>
<rule ref="Squiz.Commenting.FunctionComment.MissingParamComment">
<type>warning</type>
</rule>
<rule ref="Squiz.Commenting.FunctionComment.ParamCommentNotCapital">
<type>warning</type>
</rule>
<rule ref="Generic.Metrics.CyclomaticComplexity">
<properties>
<property name="absoluteComplexity" value="50"/>
</properties>
</rule>
<rule ref="Generic.Metrics.NestingLevel">
<properties>
<property name="nestingLevel" value="2"/>
<property name="absoluteNestingLevel" value="4"/>
</properties>
</rule>
</ruleset>
PHP-CS-Fixer
-
curl -OL https://cs.symfony.com/download/php-cs-fixer-v2.phar
-
php php-cs-fixer-v2.phar fix --dry-run --format=txt --verbose --diff --diff-format=udiff --config=.cs.php
-
curl -OL https://cs.symfony.com/download/php-cs-fixer-v3.phar
-
php php-cs-fixer-v3.phar fix --dry-run --format=txt --verbose --diff --diff-format=udiff --config=.cs.php
.cs.php
<?php
return PhpCsFixer\Config::create()
->setUsingCache(false)
->setRiskyAllowed(true)
//->setCacheFile(__DIR__ . '/.php_cs.cache')
->setRules([
'@PSR1' => true,
'@PSR2' => true,
'@Symfony' => true,
'psr4' => true,
'yoda_style' => false,
'array_syntax' => ['syntax' => 'short'],
'list_syntax' => ['syntax' => 'short'],
'concat_space' => ['spacing' => 'one'],
'cast_spaces' => ['space' => 'none'],
'compact_nullable_typehint' => true,
'increment_style' => ['style' => 'post'],
'declare_equal_normalize' => ['space' => 'single'],
'no_short_echo_tag' => true,
'protected_to_private' => false,
'phpdoc_align' => false,
'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
'phpdoc_order' => true, // psr-5
'phpdoc_no_empty_return' => false,
'align_multiline_comment' => true, // psr-5
'general_phpdoc_annotation_remove' => [
'annotations' => [
'author',
'package',
],
],
])
->setFinder(PhpCsFixer\Finder::create()
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests')
->name('*.php')
->ignoreDotFiles(true)
->ignoreVCS(true));
What's static code analysis?
Static Code Analysis
-
It's the analysis of computer software that is performed without actually executing programs.
-
Dynamic code analysis is the analysis of computer software that is performed by executing programs.
-
Unit tests, integration tests, system tests and acceptance tests use dynamic testing.
-
Static Code Analysis for PHP
-
Psalm
-
PHPStan
-
Phan→The PHP Father recommended
Installation
Installation
-
composer require phpstan/phpstan:0.* --dev
-
composer require vimeo/psalm:4.* --dev
-
composer require phan/phan:5.* --dev
Standard Checks
-
there are no syntax errors;
-
all the classes, methods, functions and constants exist;
-
the variables exist;
-
the hints in PHPDoc correspond to reality;
-
there are no arguments or variables unused.
Avoid copy-caste code errors and careless
Data type checks
-
Most analyzers allow to configure the level of strictness of checking and imitate strict_types:
-
they check that String or Boolean aren’t passed to this function.
-
Union types
-
Most analyzers allow to configure the level of strictness of checking and imitate strict_types:
-
they check that String or Boolean aren’t passed to this function.
-
/**
* @var string|int|bool $yes_or_no
*/
function isYes($yes_or_no) :bool
{
if (is_numeric($yes_or_no)) {
return $yes_or_no > 0;
} else {
return strtoupper($yes_or_no) == 'YES';
}
}
False type
-
Most analyzers allow to configure the level of strictness of checking and imitate strict_types:
-
they check that String or Boolean aren’t passed to this function.
-
/** @return int|bool */
function fwrite(...) {
…
}
False type Error
<?php
/** @return resource|bool */
function open_file() {
$fp = fopen('./composer.json', 'r');
if($fp === false) {
return false;
}
return fwrite($fp, "some string");
}
lee@lee-VirtualBox:~/phpstan-example$ vendor/bin/phpstan analyse ./false_type.php --level=max -c phpstan.neon --no-progress --ansi
------ --------------------------------------------------------------------------------------------
Line false_type.php
------ --------------------------------------------------------------------------------------------
4 Function open_file() never returns resource so it can be removed from the return typehint.
10 Function open_file() should return bool|resource but returns int|false.
------ --------------------------------------------------------------------------------------------
False type Error Fix
<?php
/** @return int|false */
function open_file() {
$fp = fopen('./composer.json', 'r');
if($fp === false) {
return false;
}
return fwrite($fp, "some string");
}
lee@lee-VirtualBox:~/phpstan-example$ vendor/bin/phpstan analyse ./false_type.php \
--level=max -c phpstan.neon --no-progress --ansi
[OK] No errors
Array shapes
<?php
/** @return array */
function array_func(array $arr) {
return $arr;
}
lee@lee-VirtualBox:~/phpstan-example$ vendor/bin/phpstan analyse ./array_example.php \
--level=max -c phpstan.neon --no-progress --ansi
------ -----------------------------------------------------------------------------------------------
Line array_example.php
------ -----------------------------------------------------------------------------------------------
4 Function array_func() has parameter $arr with no value type specified in iterable type array.
💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
4 Function array_func() return type has no value type specified in iterable type array.
💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
------ -----------------------------------------------------------------------------------------------
[ERROR] Found 2 errors
Array shapes fix
<?php
/**
@param array<string> $arr
@return array<string>
*/
function array_func($arr) {
return $arr;
}
Overview of static code analysis tools
PHPStan
-
Developed by Ondřej Mirtes
-
Install it (the simplest way is via Composer)
-
Configure it (optional)
-
Run it
lee@lee-VirtualBox:~/phpstan-example$ vendor/bin/phpstan analyse ./array_example.php
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
[OK] No errors
💡 Tip of the Day:
PHPStan is performing only the most basic checks.
You can pass a higher rule level through the --level option
(the default and current level is 0) to analyse code more thoroughly.
lee@lee-VirtualBox:~/phpstan-example$
PHPStan Key Features
-
PHPStan will try to autoload unknown classes.
-
If some classes are not autoloaded, it will not be able to find them and will return an error.
-
If using magical methods via __call, __get, or __set, it can write a plug-in for PHPStan.
-
In actual fact, PHPStan doesn’t only perform autoload in the case of unknown classes, but it also does so for all classes.
-
Using neon-format for configuration.
-
No support for its PHPDoc tags @phpstan-var, @phpstan-return etc.
-
PhpStan has a playground website https://phpstan.org.
Phan
-
Developed by the Etsy company. First commits by Rasmus Lerdorf.
-
Requiring the php-ast extension.
-
Plugin example is available here.
-
Creating a .phan/config.php file.
-
Playground website is available.
lee@lee-VirtualBox:~/phpstan-example$ php vendor/bin/phan array_example.php
analyze ████████████████████████████████████████████████████████████ 100.0% 29MB/29MB
lee@lee-VirtualBox:~/phpstan-example$ php vendor/bin/phan array_example.php
analyze ████████████████████████████████████████████████████████████ 100.0% 28MB/31MB
array_example.php:9 PhanSyntaxError syntax error, unexpected '}', expecting ';' (at column 1)
Psalm
-
Developed by the Vimeo company
-
Annotations code
-
XML format file about configuration
-
Type aliases
-
array
-
closure
-
union type (for example, several classes or a class and other types)
-
enum
-
psalm.xml
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>
vendor/bin/psalm
░░░░░░░E░░░░E░E░░░EE░░░░░░░░░░░E░░░░E░░░░░E░E░░
ERROR: ParamNameMismatch - src/Element/Element.php:131:54 - Argument 2 of Innmind\Xml\Element\Element::replaceChild has wrong name $node, expecting $child as defined by Innmind\Xml\Node::replaceChild (see https://psalm.dev/230)
public function replaceChild(int $position, Node $node): Node
ERROR: ParamNameMismatch - src/Element/SelfClosingElement.php:36:54 - Argument 2 of Innmind\Xml\Element\SelfClosingElement::replaceChild has wrong name $node, expecting $child as defined by Innmind\Xml\Node::replaceChild (see https://psalm.dev/230)
public function replaceChild(int $position, Node $node): Node
ERROR: ParamNameMismatch - src/Node/CharacterData.php:43:54 - Argument 2 of Innmind\Xml\Node\CharacterData::replaceChild has wrong name $node, expecting $child as defined by Innmind\Xml\Node::replaceChild (see https://psalm.dev/230)
public function replaceChild(int $position, Node $node): Node
ERROR: ParamNameMismatch - src/Node/Comment.php:43:54 - Argument 2 of Innmind\Xml\Node\Comment::replaceChild has wrong name $node, expecting $child as defined by Innmind\Xml\Node::replaceChild (see https://psalm.dev/230)
public function replaceChild(int $position, Node $node): Node
ERROR: ParamNameMismatch - src/Node/Document.php:86:54 - Argument 2 of Innmind\Xml\Node\Document::replaceChild has wrong name $node, expecting $child as defined by Innmind\Xml\Node::replaceChild (see https://psalm.dev/230)
public function replaceChild(int $position, Node $node): Node
CI/CD examples
GitHub Workflow examples
-
Using Composer to install required development dependencies.
- GithubAction for PHP-CS-Fixer.
- PHP Static Analysis in Github Actions.
composer install
.......
psalm:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['7.4', '8.0']
name: 'Psalm'
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl
- name: Get Composer Cache Directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Dependencies
run: composer install
- name: Psalm
run: vendor/bin/psalm --shepherd
.......
GithubAction for PHP-CS-Fixer
PHP Static Analysis in Github Actions
Laravel framework integration
Psalm plugin for Laravel
nunomaduro/larastan
參考資料
Thanks!
Coding style, Static code analysis and PHP
By peter279k
Coding style, Static code analysis and PHP
LaravelConf Taiwan 2021
- 745