├── .github └── workflows │ ├── composer-json-lint.yml │ └── php-cs-fixer.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── LICENSE ├── README.md ├── composer.json └── src └── PhpCsFixer └── Rules.php /.github/workflows/composer-json-lint.yml: -------------------------------------------------------------------------------- 1 | name: "Lint composer.json" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | composer-lint: 9 | name: "Lint composer.json" 10 | 11 | runs-on: ${{ matrix.operating-system }} 12 | 13 | strategy: 14 | matrix: 15 | php-version: 16 | - "8.3" 17 | operating-system: 18 | - "ubuntu-latest" 19 | 20 | steps: 21 | - name: "Checkout" 22 | uses: "actions/checkout@v2" 23 | 24 | - name: "Install PHP" 25 | uses: "shivammathur/setup-php@v2" 26 | with: 27 | coverage: "none" 28 | php-version: "${{ matrix.php-version }}" 29 | ini-values: memory_limit=-1 30 | tools: composer:v2, composer-normalize, composer-require-checker 31 | 32 | - name: Get composer cache directory 33 | id: composer-cache 34 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 35 | 36 | - name: "Cache dependencies" 37 | uses: "actions/cache@v2" 38 | with: 39 | path: ${{ steps.composer-cache.outputs.dir }} 40 | key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}" 41 | restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-" 42 | 43 | - name: "Install dependencies" 44 | run: "composer install --no-interaction --no-progress" 45 | 46 | - name: "Validate composer.json" 47 | run: "composer validate --strict" 48 | 49 | - name: "Normalize composer.json" 50 | run: "composer-normalize --dry-run" 51 | 52 | - name: "Check composer.json explicit dependencies" 53 | run: "composer-require-checker check" 54 | -------------------------------------------------------------------------------- /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: "PHP-CS-Fixer checks" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | php-cs-fixer: 9 | name: "PHP-CS-Fixer" 10 | 11 | runs-on: ${{ matrix.operating-system }} 12 | 13 | strategy: 14 | matrix: 15 | php-version: 16 | - "8.0" 17 | - "8.1" 18 | - "8.2" 19 | - "8.3" 20 | operating-system: 21 | - "ubuntu-latest" 22 | 23 | steps: 24 | - name: "Checkout" 25 | uses: "actions/checkout@v2" 26 | 27 | - name: "Install PHP" 28 | uses: "shivammathur/setup-php@v2" 29 | with: 30 | coverage: "none" 31 | php-version: "${{ matrix.php-version }}" 32 | ini-values: memory_limit=-1 33 | tools: composer:v2, cs2pr 34 | 35 | - name: Get composer cache directory 36 | id: composer-cache 37 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 38 | 39 | - name: "Cache dependencies" 40 | uses: "actions/cache@v2" 41 | with: 42 | path: ${{ steps.composer-cache.outputs.dir }} 43 | key: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}" 44 | restore-keys: "php-${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-" 45 | 46 | - name: "Install dependencies" 47 | run: "composer install --no-interaction --no-progress" 48 | 49 | 50 | - name: "Lint PHP files" 51 | run: "find src -name *.php | xargs -n 1 php -l" 52 | 53 | - name: "Check coding standards" 54 | run: "vendor/bin/php-cs-fixer fix -v --dry-run" 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .php_cs.cache 3 | .php-cs-fixer.cache 4 | 5 | # Libraries should ignore the lock file 6 | composer.lock 7 | 8 | vendor/ 9 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in(__DIR__); 11 | 12 | $overrides = [ 13 | 'declare_strict_types' => true, 14 | ]; 15 | 16 | return (new Config()) 17 | ->setFinder($finder) 18 | ->setRiskyAllowed(true) 19 | ->setRules(Rules::getForPhp83($overrides)); 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Mollie B.V. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mollie PHP Coding Standards 2 | Contains PHP coding standards like rules for PHP-CS-Fixer that serves for purpose of standardization. 3 | 4 | ## Installation 5 | ```bash 6 | composer require --dev mollie/php-coding-standards 7 | ``` 8 | 9 | ## Usage 10 | This package makes use of PHP-CS-Fixer. 11 | 12 | ### Already familiar with PHP-CS-Fixer 13 | 14 | This package provides default rules to be used with PHP-CS-Fixer. 15 | 16 | You can find them in `Mollie\PhpCodingStandards\PhpCsFixer\Rules` which has methods specific to php version, 17 | which you can directly use in the `->setRules()` part of your config. For example, assuming PHP version 8.3: 18 | 19 | ```php 20 | use Mollie\PhpCodingStandards\PhpCsFixer\Rules; 21 | 22 | $config->setRules(Rules::getForPhp83()); 23 | ``` 24 | 25 | ### New to PHP-CS-Fixer 26 | 27 | Place a file named `.php-cs-fixer.dist.php` that has the following content in your project's root directory. 28 | 29 | ```php 30 | in(__DIR__); 38 | 39 | return (new Config()) 40 | ->setFinder($finder) 41 | ->setRiskyAllowed(true) 42 | // use specific rules for your php version e.g.: getForPhp74, getForPhp82, getForPhp83 43 | ->setRules(Rules::getForPhp74()); 44 | ``` 45 | 46 | ### Manual Triggering 47 | Run following command in your project directory, that will run fixer for every `.php` file. 48 | ```bash 49 | vendor/bin/php-cs-fixer fix 50 | ``` 51 | 52 | ### Use via PhpStorm file watcher 53 | Please follow [official PhpStorm documentation](https://www.jetbrains.com/help/phpstorm/using-php-cs-fixer.html#f21a70ca) 54 | 55 | ### Use via pre-commit git hook 56 | Place a file with the content of the following bash script into `.git/hooks` directory called pre-commit and make it executable 57 | you can find more details about git hooks on [official git manual](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks). 58 | 59 | ```bash 60 | #!/usr/bin/env bash 61 | 62 | EXCLUDE_DIRECTORIES_REGEX='^(directory_one/(sub_directory_one|sub_directory_two)|directory_two)/.*' 63 | git diff --diff-filter=ACMRTUXB --name-only --staged | grep -E '\.php(_cs\.dist)?$' | grep -vE $EXCLUDE_DIRECTORIES_REGEX | while read FILE; do 64 | STATUS="$(git status --porcelain ${FILE} | cut -c 1-2)" 65 | vendor/bin/php-cs-fixer fix --using-cache=no --quiet --dry-run ${FILE} 66 | 67 | # Not 0 means php-cs-fixer either errored or wanted to make changes 68 | if [ $? != 0 ] 69 | then 70 | # MM = staged & non-staged modification in same file 71 | if [ ${STATUS} = "MM" ] 72 | then 73 | echo -e "\033[31m┌────────────────────────────────────────────────────────────────────────────────┐" 74 | echo -e "│ Failure:\033[39m Codestyle violation(s) in file with both staged and unstaged changes. \033[31m│" 75 | echo -e "├────────────────────────────────────────────────────────────────────────────────┘" 76 | echo -e "└\033[33m File:\033[39m $FILE" 77 | exit 1 78 | fi 79 | 80 | vendor/bin/php-cs-fixer fix --using-cache=no --quiet ${FILE} 81 | git add ${FILE} 82 | fi 83 | done 84 | ``` 85 | 86 | ## Working at Mollie 87 | Mollie is always looking for new talent to join our teams. We’re looking for inquisitive minds with good ideas and 88 | strong opinions, and, most importantly, who know how to ship great products. Want to join the future of payments? 89 | [Check out our vacancies](https://jobs.mollie.com). 90 | 91 | ## License 92 | [BSD (Berkeley Software Distribution) License](https://opensource.org/licenses/bsd-license.php). 93 | Copyright (c) 2025, Mollie B.V. 94 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mollie/php-coding-standards", 3 | "description": "Contains PHP coding standards like rules for PHP-CS-Fixer that serves for purpose of standardization.", 4 | "license": "BSD-2-Clause", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "Mollie B.V.", 9 | "email": "info@mollie.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.1.3 || ^8.0", 14 | "friendsofphp/php-cs-fixer": "^3.68" 15 | }, 16 | "minimum-stability": "stable", 17 | "autoload": { 18 | "psr-4": { 19 | "Mollie\\PhpCodingStandards\\": "src/" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/PhpCsFixer/Rules.php: -------------------------------------------------------------------------------- 1 | true, 39 | 'method_argument_space' => [ 40 | 'after_heredoc' => true, 41 | 'on_multiline' => 'ensure_fully_multiline', 42 | ], 43 | 'no_whitespace_before_comma_in_array' => [ 44 | 'after_heredoc' => true, 45 | ], 46 | ]; 47 | 48 | return array_merge(self::getForPhp72($specific73Rules), $overriddenRules); 49 | } 50 | 51 | public static function getForPhp74(array $overriddenRules = []): array 52 | { 53 | $specific74Rules = [ 54 | // At the moment there are no specific 7.4 rules or configurations 55 | ]; 56 | 57 | return array_merge(self::getForPhp73($specific74Rules), $overriddenRules); 58 | } 59 | 60 | public static function getForPhp80(array $overriddenRules = []): array 61 | { 62 | $specific80Rules = [ 63 | // At the moment there are no specific 8.0 rules or configurations 64 | ]; 65 | 66 | return array_merge(self::getForPhp74($specific80Rules), $overriddenRules); 67 | } 68 | 69 | public static function getForPhp81(array $overriddenRules = []): array 70 | { 71 | $specific81Rules = [ 72 | 'declare_strict_types' => true, 73 | ]; 74 | 75 | return array_merge(self::getForPhp80($specific81Rules), $overriddenRules); 76 | } 77 | 78 | public static function getForPhp82(array $overriddenRules = []): array 79 | { 80 | $specific82Rules = [ 81 | // At the moment there are no specific 8.2 rules or configurations 82 | ]; 83 | 84 | return array_merge(self::getForPhp81($specific82Rules), $overriddenRules); 85 | } 86 | 87 | public static function getForPhp83(array $overriddenRules = []): array 88 | { 89 | $specific83Rules = [ 90 | // At the moment there are no specific 8.3 rules or configurations 91 | ]; 92 | 93 | return array_merge(self::getForPhp82($specific83Rules), $overriddenRules); 94 | } 95 | 96 | private static function getBaseRules(): array 97 | { 98 | return [ 99 | '@Symfony' => true, 100 | 'align_multiline_comment' => [ 101 | 'comment_type' => 'all_multiline', 102 | ], 103 | 'array_indentation' => true, 104 | 'blank_line_before_statement' => [ 105 | 'statements' => [ 106 | 'break', 'continue', 'case', 'declare', 'default', 'do', 'for', 'foreach', 107 | 'if', 'return', 'switch', 'throw', 'try', 'while', 108 | ], 109 | ], 110 | 'cast_spaces' => [ 111 | 'space' => 'none', 112 | ], 113 | 'combine_consecutive_issets' => true, 114 | 'combine_consecutive_unsets' => true, 115 | 'combine_nested_dirname' => true, 116 | 'compact_nullable_type_declaration' => true, 117 | 'concat_space' => [ 118 | 'spacing' => 'one', 119 | ], 120 | 'dir_constant' => true, 121 | 'string_implicit_backslashes' => true, 122 | 'explicit_indirect_variable' => true, 123 | 'explicit_string_variable' => true, 124 | 'fully_qualified_strict_types' => true, 125 | 'function_to_constant' => true, 126 | 'global_namespace_import' => false, 127 | // 'header_comment' => [ // Has too many issues atm 128 | // 'header' => '', 129 | // ], 130 | 'increment_style' => [ 131 | 'style' => 'post', 132 | ], 133 | 'list_syntax' => [ 134 | 'syntax' => 'short', 135 | ], 136 | 'magic_method_casing' => true, 137 | 'method_argument_space' => [ 138 | 'on_multiline' => 'ensure_fully_multiline', 139 | ], 140 | 'method_chaining_indentation' => true, 141 | 'modernize_types_casting' => true, 142 | 'multiline_comment_opening_closing' => true, 143 | 'multiline_whitespace_before_semicolons' => true, 144 | 'native_type_declaration_casing' => true, 145 | 'no_alias_functions' => true, 146 | 'no_alternative_syntax' => true, 147 | 'no_extra_blank_lines' => [ 148 | 'tokens' => [ 149 | 'break', 'case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 150 | 'return', 'square_brace_block', 'throw', 151 | ], 152 | ], 153 | 'class_attributes_separation' => [ 154 | 'elements' => [ 155 | 'trait_import' => 'none', 156 | ], 157 | ], 158 | 'no_null_property_initialization' => true, 159 | 'no_superfluous_elseif' => true, 160 | 'no_superfluous_phpdoc_tags' => true, 161 | 'no_unset_cast' => true, 162 | 'no_unset_on_property' => false, // It's purposely used for the side effect :( 163 | 'no_useless_else' => true, 164 | 'no_useless_return' => true, 165 | 'nullable_type_declaration_for_default_null_value' => true, 166 | 'ordered_class_elements' => [ 167 | 'order' => [ 168 | 'use_trait', 169 | 'constant_public', 'constant_protected', 'constant_private', 170 | 'property_public', 'property_protected', 'property_private', 171 | // Not shuffling methods. I'd love to do it but I think it's too much for most people. 172 | ], 173 | ], 174 | 'ordered_imports' => [ 175 | 'imports_order' => ['class', 'function', 'const'], 176 | ], 177 | 'phpdoc_line_span' => [ 178 | 'const' => 'single', 179 | 'method' => 'multi', 180 | 'property' => 'single', 181 | ], 182 | 'phpdoc_no_empty_return' => true, 183 | 'php_unit_construct' => true, 184 | 'php_unit_dedicate_assert_internal_type' => true, 185 | 'php_unit_method_casing' => true, 186 | 'php_unit_test_case_static_method_calls' => [ 187 | 'call_type' => 'self', 188 | ], 189 | 'phpdoc_align' => ['align' => 'left'], // Prevent modifying multiple lines when changing one param in a docblock 190 | 'phpdoc_annotation_without_dot' => false, // Sometimes comments have a good reason to end with a dot. Leave this up to the engineer. 191 | 'phpdoc_no_alias_tag' => [ 192 | 'replacements' => [ 193 | 'link' => 'see', 194 | 'type' => 'var', 195 | ], 196 | ], 197 | 'phpdoc_order' => true, 198 | 'phpdoc_summary' => false, // Sometimes comments have a good reason not to end with a dot. Leave this up to the engineer. 199 | 'phpdoc_to_comment' => false, // We use more annotations than only structural elements (f.e. @lang JSON) 200 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 201 | 'phpdoc_types_order' => [ 202 | 'null_adjustment' => 'always_last', 203 | ], 204 | 'phpdoc_var_annotation_correct_order' => true, 205 | 'pow_to_exponentiation' => true, 206 | 'return_assignment' => true, 207 | 'simple_to_complex_string_variable' => true, 208 | 'simplified_null_return' => false, // Too many old code places that become implicit, also ignores doc blocks. 209 | 'single_class_element_per_statement' => true, 210 | 'single_line_throw' => false, 211 | 'single_quote' => false, 212 | 'single_trait_insert_per_statement' => true, 213 | 'space_after_semicolon' => [ 214 | 'remove_in_empty_for_expressions' => true, 215 | ], 216 | 'ternary_to_null_coalescing' => true, 217 | 'types_spaces' => [ 218 | 'space' => 'single', // Added to keep previous behaviour with the cs-fixer 3.1.0 upgrade. 219 | ], 220 | 'visibility_required' => [ 221 | 'elements' => [ 222 | 'const', 'method', 'property', 223 | ], 224 | ], 225 | 'yoda_style' => [ 226 | 'equal' => false, 227 | 'identical' => false, 228 | 'less_and_greater' => false, 229 | ], 230 | 'trailing_comma_in_multiline' => [ 231 | 'after_heredoc' => true, 232 | 'elements' => [ 233 | 'arguments', 234 | 'array_destructuring', 235 | 'arrays', 236 | 'match', 237 | 'parameters', 238 | ], 239 | ], 240 | ]; 241 | } 242 | } 243 | --------------------------------------------------------------------------------