0
1

More than 1 year has passed since last update.

GitLab CI/CD × php-cs-fixerを利用してPHPコードを自動フォーマットする

Posted at

概要

やろうと思ったきっかけとしてはこちら↓

php-cs-fixerを利用してコードの自動成形を行いたいが、個々のIDE上でフォーマットを行ったつもりでもかかっていなかったり、独自の設定を行ってしまい、結局バラバラになってしまうこともありました。
設定が一定であるサーバ上でフォーマットをかけて、フォーマットがかかっていないコードのコミットだった場合CI側で自動でフォーマットをかけてコミットを行うことで確実にフォーマットがかかったような状態を目指します。
(git hookでフォーマットをかける方法もありますが、これも環境依存となってしまうため、GitLab CI/CDで行う方法を選択しました。)

前提

  • ソースはphpを対象とします
  • 差分が大変なことになってしまうので、ある一定のタイミング(プロジェクトが落ち着いているタイミング)で自動整形を行う前に一斉に成形を行うことをおすすめします。
  • git flowで開発を行い、マージリクエスト時に自動フォーマットが動くことを想定しています

詳細

GitLab側の事前準備

当記事内ではGitLab Runnerの設定が完了している前提で記載しますが、設定していない場合は下記の手順を参考にRunnerの設定を行います。

設定 > リポジトリ から Deploy Keysの登録を行います。

2022-12-22_12h56_54.png

設定 > CI/CD からジョブで使用する変数を定義します。

2022-12-22_13h00_01.png

gitlab-ci.yml記述内容

scriptの箇所は下記の記事を参照させていただきました。

ジョブの実行条件としてdevelopブランチをターゲットとしてマージリクエストが発行されたときとします。

code_format:
  image: php:7.4-alpine
  tags:
    - docker-runner
  script:
    - apk --update add git openssh-client curl
    - eval $(ssh-agent -s)
    - printenv DEPLOY_SSH_PRIVATE_KEY | ssh-add -
    - git config --global user.name "codeFormatBot"
    - git config --global user.email "test@example.jp"
    - git remote set-url --push origin git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git
    - git fetch
    - git checkout $CI_COMMIT_REF_NAME
    - git pull
    - curl -s https://getcomposer.org/installer | php
    - alias composer='php composer.phar'
    - composer require --dev friendsofphp/php-cs-fixer
    - ./vendor/bin/php-cs-fixer fix ./
    - git add -u
    - if [ -n $ret ];then
    -   git commit -m '[ci skip] CodeFormat, Push by GitLab runner'
    -   git -c core.sshCommand="ssh -oStrictHostKeyChecking=no" push origin
    - fi
  only:
    refs:
        - merge_requests
    variables:
        - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"
ちょっと解説
    - eval $(ssh-agent -s)
    - printenv DEPLOY_SSH_PRIVATE_KEY | ssh-add -

で ssh-agent の開始を行い、事前にGitLab設定画面から設定していたCI用の変数DEPLOY_SSH_PRIVATE_KEYからSSHプライベートキーをssh-agentに追加

    - git config --global user.name "codeFormatBot"
    - git config --global user.email "test@example.jp"
    - git remote set-url --push origin git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git

でgitの設定

    - git fetch
    - git checkout $CI_COMMIT_REF_NAME
    - git pull

でコミットが行われたブランチのチェックアウト、プル

    - curl -s https://getcomposer.org/installer | php
    - alias composer='php composer.phar'
    - composer require --dev friendsofphp/php-cs-fixer

でcomposer自体のインストール

    - ./vendor/bin/php-cs-fixer fix ./src
    - git add -u
    - if [ -n $ret ];then
    -   git commit -m '[ci skip] CodeFormat, Push by GitLab runner'
    -   git -c core.sshCommand="ssh -oStrictHostKeyChecking=no" push origin
    - fi

で srcディレクトリを対象にphp-cs-fixerを実行し、変更があったものをgitステージング、
ステージング対象があれば'[ci skip] CodeFormat, Push by GitLab runner'というメッセージでコミット、プッシュを行う

コミットメッセージに[ci skip]のようなキーワードを含めることでジョブをスキップすることができます。

↓こちらの記事が参考になりました。
CIサービスの自動ビルドをスキップする方法まとめ

  only:
    refs:
        - merge_requests
    variables:
        - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"

でdevelopブランチに対するマージリクエストが行われたとき(また、マージリクエスト後に同じブランチに対してコミットが行われたとき)という条件にしています。

.php-cs-fixer.php 例

設定例(長いので折りたたみ)

下記の設定例は自分の関わった比較的古いプロジェクトを元に記載していますので、
https://mlocati.github.io/php-cs-fixer-configurator
等でプロジェクトに合う設定を行ってください

<?php
/*
 * This document has been generated with
 * https://mlocati.github.io/php-cs-fixer-configurator/#version:2.15.5|configurator
 * you can change this configuration by importing this file.
 */
return PhpCsFixer\Config::create()
    ->setRules([
        'align_multiline_comment' => true,
        'array_indentation' => true,
        'array_syntax' => ['syntax'=>'short'],
        'backtick_to_shell_exec' => true,
        'binary_operator_spaces' => true,
        'blank_line_after_namespace' => true,
        'blank_line_after_opening_tag' => true,
        'blank_line_before_statement' => true,
        'braces' => true,
        'cast_spaces' => true,
        'class_attributes_separation' => true,
        'class_definition' => true,
        'combine_consecutive_issets' => true,
        'combine_consecutive_unsets' => true,
        'compact_nullable_typehint' => true,
        'concat_space' => true,
        'declare_equal_normalize' => true,
        'elseif' => true,
        'encoding' => true,
        'escape_implicit_backslashes' => true,
        'explicit_string_variable' => true,
        'full_opening_tag' => true,
        'fully_qualified_strict_types' => true,
        'function_declaration' => true,
        'function_typehint_space' => true,
        'heredoc_to_nowdoc' => true,
        'include' => true,
        'indentation_type' => true,
        'line_ending' => true,
        'linebreak_after_opening_tag' => true,
        'lowercase_cast' => true,
        'lowercase_constants' => true,
        'lowercase_keywords' => true,
        'lowercase_static_reference' => true,
        'magic_constant_casing' => true,
        'magic_method_casing' => true,
        'method_argument_space' => true,
        'method_chaining_indentation' => true,
        'multiline_comment_opening_closing' => true,
        'multiline_whitespace_before_semicolons' => true,
        'native_function_casing' => true,
        'native_function_type_declaration_casing' => true,
        'new_with_braces' => true,
        'no_alternative_syntax' => true,
        'no_binary_string' => true,
        'no_blank_lines_after_class_opening' => true,
        'no_blank_lines_after_phpdoc' => true,
        'no_break_comment' => true,
        'no_closing_tag' => true,
        'no_empty_comment' => true,
        'no_empty_phpdoc' => true,
        'no_empty_statement' => true,
        'no_extra_blank_lines' => true,
        'no_leading_import_slash' => true,
        'no_leading_namespace_whitespace' => true,
        'no_mixed_echo_print' => true,
        'no_multiline_whitespace_around_double_arrow' => true,
        'no_null_property_initialization' => true,
        'no_short_bool_cast' => true,
        'no_singleline_whitespace_before_semicolons' => true,
        'no_spaces_after_function_name' => true,
        'no_spaces_around_offset' => true,
        'no_spaces_inside_parenthesis' => true,
        'no_superfluous_elseif' => true,
        'no_trailing_comma_in_list_call' => true,
        'no_trailing_comma_in_singleline_array' => true,
        'no_trailing_whitespace' => true,
        'no_trailing_whitespace_in_comment' => true,
        'no_unneeded_control_parentheses' => true,
        'no_unneeded_curly_braces' => true,
        'no_unneeded_final_method' => true,
        'no_unset_cast' => true,
        'no_unused_imports' => true,
        'no_useless_else' => true,
        'no_useless_return' => true,
        'no_whitespace_before_comma_in_array' => true,
        'no_whitespace_in_blank_line' => true,
        'normalize_index_brace' => true,
        'object_operator_without_whitespace' => true,
        'ordered_class_elements' => true,
        'ordered_imports' => true,
        'php_unit_fqcn_annotation' => true,
        'phpdoc_add_missing_param_annotation' => true,
        'phpdoc_align' => true,
        'phpdoc_annotation_without_dot' => true,
        'phpdoc_indent' => true,
        'phpdoc_inline_tag' => true,
        'phpdoc_no_access' => true,
        'phpdoc_no_empty_return' => true,
        'phpdoc_no_package' => true,
        'phpdoc_order' => true,
        'phpdoc_return_self_reference' => true,
        'phpdoc_scalar' => true,
        'phpdoc_separation' => true,
        'phpdoc_single_line_var_spacing' => true,
        'phpdoc_summary' => true,
        'phpdoc_to_comment' => true,
        'phpdoc_trim' => true,
        'phpdoc_trim_consecutive_blank_line_separation' => true,
        'phpdoc_types' => true,
        'phpdoc_types_order' => true,
        'phpdoc_var_annotation_correct_order' => true,
        'phpdoc_var_without_name' => true,
        'return_type_declaration' => true,
        'semicolon_after_instruction' => true,
        'short_scalar_cast' => true,
        'simplified_null_return' => true,
        'single_blank_line_at_eof' => true,
        'single_blank_line_before_namespace' => true,
        'single_class_element_per_statement' => true,
        'single_import_per_statement' => true,
        'single_line_after_imports' => true,
        'single_line_comment_style' => true,
        'single_quote' => true,
        'space_after_semicolon' => true,
        'standardize_not_equals' => true,
        'switch_case_semicolon_to_colon' => true,
        'switch_case_space' => true,
        'ternary_operator_spaces' => true,
        'trailing_comma_in_multiline_array' => true,
        'trim_array_spaces' => true,
        'unary_operator_spaces' => true,
        'whitespace_after_comma_in_array' => true,
    ])
    ->setFinder(PhpCsFixer\Finder::create()
        ->exclude('vendor')
        ->in(__DIR__)
    )
;

最後に->exclude('vendor')としているのはVSCodeを使用したリモート開発を想定した設定で、(自分の場合はVSCode上でファイルの保存を行うときにフォーマットをかけるようにしていますが、)コマンドラインからphp-CS-fixerをディレクトリに対して実行したときcomposerで落としてきているソースまでフォーマットをかけてしまうためexcludeとしています。
(実際にはgitignoreでvendor配下のソースは管理しないようにしていると思いますが、無駄に時間がかかってしまうので排除しています。)

実行時のGitLab側の画面

マージリクエスト > パイプライン画面からどのようにジョブが動いているか確認できます。
(一時的にCIによるコミットログが発生しますが、squashオプションを付けてマージを完了させることでログからは見えない状態となります。)

image.png

終わりに

まだしっかりとした運用にすることはできていませんが、自動化することでphp-cs-fixerでできる範囲↓

  • インデントがずれている
  • インデントにスペースとタブが入り乱れている
  • シングルクォーテーションとダブルクオーテーションが入り乱れている
  • etc...

のようなコードを読むのに集中力の切れてしまう要因を少なくすることができるようになったのではないかと思います。

また、他の拡張子のファイルについても同じく自動フォーマットが行えるツールを探して適用していこうと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1