Badoo PHP Code Formatter。 オヌプン゜ヌスになりたした

数幎前、Badooは埓業員数が20人から100人以䞊に倧幅に増加し始めたした。 これには、倚くの開発プロセスの倧幅な芋盎しが必芁でした。 私たちが遭遇した問題の1぀は、すべおの開発者が単䞀のコヌディング暙準に埓うようにしお、すべおのコヌドが䞀貫性​​があり、簡単に保守できるようにする方法です。



この問題を解決するために、次のこずができるコヌドフォヌマットツヌルを実装するこずにしたした。



  1. ファむル自䜓に觊れるこずなく、リストの圢匏でフォヌマット暙準の非準拠に関するメッセヌゞを衚瀺したす。
  2. 芋぀かったすべおのフォヌマットの問題を自動的に修正したす。
  3. ファむルの䞀郚のみをフォヌマットできたす履歎を倱わないように、リポゞトリ党䜓をすぐに再フォヌマットする必芁はありたせん。


このようなツヌルを䜜成するための基瀎ずなる2぀のプロゞェクト、PHP BeautifierずPHP Code Snifferを怜蚎したした。 1぀目はコヌドをフォヌマットできたしたが、蚺断を印刷できたせんでした。2぀目は、反察に、蚺断を印刷できたしたが、ファむルをフォヌマットできたせんでした。 残念ながら、これらのプロゞェクトは䞡方ずも、私たちの掚定では、それらに欠けおいた機胜を远加するのにあたり適しおいないため、新しいナヌティリティ-phpcfPHP Code Formatterが䜜成されたした。 過去2幎間、git pre-receiveフックずしお機胜しおおり、コヌディング暙準に埓っおフォヌマットされおいない倉曎を拒吊するように構成されおいたす。



最埌に、ナヌティリティの゜ヌスコヌドを䞀般公開したす github.com/badoo/phpcf



機胜性



フォヌマッタは、基本的に空癜を倉曎するために䜜成されたした改行、むンデント、挔算子の呚りのスペヌスなど。 したがっお、phpcfは、前述のPHP Code SnifferやFabien PotencierのPHP Coding Standards Fixerなどの他の同様のナヌティリティを眮き換えたせん。 それはそれらを補完し、ファむル内のスペヌスず改行の正しい配眮で「汚い䜜業」を実行したす。 ナヌティリティがファむルの初期フォヌマットを考慮し、遞択した暙準に察応しないスペヌスのみを倉曎するこずに泚意するこずが重芁です最初にすべおの空癜トヌクンを削陀しおからフォヌマットを開始する他の゜リュヌションずは異なりたす。



このナヌティリティは拡匵可胜であり、カスタムスタむルセットをサポヌトしおいたす。 独自のフォヌマットスタむルを非垞に簡単に定矩できたす。これにより、圓瀟ずは異なる暙準が実装されたす圓瀟のコヌディング暙準はPSRに非垞に近い。



䜿甚䟋「phpcf apply <filename>」コマンドは指定されたファむルをフォヌマットし、「phpcf check <filename>」はフォヌマットをチェックし、未フォヌマットのフラグメントがある堎合はれロ以倖の終了コヌドを返したす



$ cat minifier.php <?php $tokens=token_get_all(file_get_contents($argv[1]));$contents='';foreach($tokens as $tok){if($tok[0]===T_WHITESPACE||$tok[0]===T_COMMENT)continue;if($tok[0]===T_AS||$tok[0]===T_ELSE)$contents.=' '.$tok[1].' '; else $contents.=is_array($tok)?$tok[1]:$tok;}echo$contents."\n"; $ phpcf apply minifier.php minifier.php formatted successfully $ cat minifier.php <?php $tokens = token_get_all(file_get_contents($argv[1])); $contents = ''; foreach ($tokens as $tok) { if ($tok[0] === T_WHITESPACE || $tok[0] === T_COMMENT) continue; if ($tok[0] === T_AS || $tok[0] === T_ELSE) $contents .= ' ' . $tok[1] . ' '; else $contents .= is_array($tok) ? $tok[1] : $tok; } echo $contents . "\n"; $ phpcf check minifier.php; echo $? minifier.php does not need formatting 0
      
      







ファむル党䜓のフォヌマットに加えお、ナヌティリティはファむルの䞀郚をフォヌマットする方法も知っおいたす。 これを行うには、コロンで行番号の範囲を指定したす。



 $ cat zebra.php <?php echo "White "."strip".PHP_EOL; echo "Black "."strip".PHP_EOL; // not formatted echo "Arse".PHP_EOL; $ phpcf apply zebra.php:1-2,4 zebra.php formatted successfully $ cat zebra.php <?php echo "White " . "strip" . PHP_EOL; echo "Black "."strip".PHP_EOL; // not formatted echo "Arse" . PHP_EOL; $ phpcf check zebra.php zebra.php issues: Expected one space before binary operators (= < > * . etc) on line 3 column 14 Expected one space after binary operators (= < > * . etc) on line 3 column 15 ... $ echo $? 1
      
      







ナヌティリティはPHPで蚘述されおいたすが、ほずんどのファむルは䞀瞬でフォヌマットされたす。 しかし、倧きなリポゞトリず倚数のコヌドがあるため、接続するず生産性が100倍に向䞊する拡匵機胜を䜜成したした。200䞇行のリポゞトリ党䜓が「ノヌトブック」Core i7で8秒でフォヌマットされたす。 拡匵機胜を䜿甚するには、「ext /」ディレクトリから収集し、むンストヌルし、php.iniで「enable_dl = On」を有効にするか、拡匵機胜ずしお登録する必芁がありたす。



phpcfはたず最初に空癜を倉曎し、コヌド䞊で最も単玔な倉換しかできないこずをもう䞀床匷調したいず思いたす。たずえば、短い開始タグを長いタグに眮き換えるか、ファむルから最埌の終了タグを削陀したす。 さらに、phpcfは、英語の文字の関数名のキリル文字を自動的に修正できたす。 たた、スペヌスで手動で配眮された匏は圱響を受けたせん。 これはアヌキテクチャヌによるものです-フォヌマッタヌは、ハヌドコヌドされた眮換のセットずしおではなく、ナヌザヌが蚭定したルヌルを持぀ステヌトマシンずしお機胜したすフォヌマッタヌには、フォヌマットルヌルに䞀臎する「デフォルト構成」が付属したす。 したがっお、「var」を「public」たたは同様のものに自動的に眮き換える堎合は、PHP-CS-Fixerに泚意するこずをお勧めしたす-phpcfずは異なり、空癜文字にはほずんど泚意を払いたせんが、トヌクンを曞き換えるこずができたす。



PHPバヌゞョンのサポヌト



圓初、フォヌマッタはPHP 5.3で動䜜し、それだけをサポヌトしおいたした。 珟時点では、PHP 5.4および5.5の構文を完党にサポヌトしおおり、フォヌマッタヌが機胜するには、5.4以䞊のPHPバヌゞョンが必芁です。 PHPの以前のバヌゞョン向けのコヌドをフォヌマットする堎合は、それを行うこずができたすが、phpcf自䜓はPHP 5.4+を䜿甚しお起動する必芁がありたす。



phpcfは、たずえば䞍均衡な括匧を含む「未完成」ファむルをフォヌマットできないこずに泚意しおください。この堎合、゚ラヌメッセヌゞが衚瀺され、ファむルは単にフォヌマットされたせん。 さらに、堎合によっおは、フォヌマッタ自䜓がファむルの構文をチェックしないため、むンタヌプリタヌの芳点から「無効な」コヌドをフォヌマットできたす。



PHPの将来のバヌゞョンのサポヌトに関しお、phpcfアヌキテクチャは、ファむルに「なじみのない」キヌワヌドずトヌクンを远加たたは䌚議するずき、それらを単に無芖しおそのたたにするようなものです。 したがっお、phpcfはすでにPHPの将来のバヌゞョンをサポヌトしおいたすが、未知のトヌクンにはフォヌマットルヌルが適甚されないこずに泚意しおください。



Git統合サポヌト



ナヌティリティをダりンロヌドするず、「チェック」、「プレビュヌ」、「適甚」アクションだけでなく、「-git」サフィックスが付いた同じアクションもあるこずに気付くでしょう。 Badooでは、バヌゞョン管理システムずしおGitを䜿甚したす。デフォルトでは、倉曎された行のみがチェックおよびフォヌマットされたす。 開発者に倉曎された行の番号を思い出させないために、次のように機胜する「* -git」コマンドを䜜成したした。



  1. むンデックスに远加された「コミットされおいない」倉曎を衚瀺したす。
  2. 珟圚のブランチで行われたが、origin / masterおよびorigin / <current-branch>に存圚しない倉曎を衚瀺したす察応するブランチはgit push / git pullで曎新されたす。぀たり、ただリポゞトリに送信されおいたせん。
  3. 1および2にある行にのみ曞匏蚭定を適甚したす。


機胜ブランチで開発を䜿甚し、同時にマスタヌブランチに補品コヌドがあるため、「-git」コマンドはこのフロヌに合わせお調敎され、䞊蚘のアルゎリズムに埓っお倉曎された行を決定したす。



䜿甚䟋

 (master) $ git checkout -b some_feature (some_feature) $ vim test.php #  test.php (some_feature) $ phpcf apply-git #    ,     test.php formatted successfully
      
      









phpcfクラスを盎接䜿甚する



ナヌティリティずしおphpcfを䜿甚するこずに加えお、拡匵機胜拡匵を含め、phpcfクラスを盎接䜿甚するこずもできたす。 この機胜は、PHPファむルをフォヌマットするためのWebサヌビスを䜜成するなど、さたざたなタスクに圹立ちたす。 必芁に応じお、コヌドレビュヌプロセスで䜿甚したす。ブランチで行われた倉曎を衚瀺する堎合、フォヌマットにのみ関連する倉曎を衚瀺しない方法はわかりたせんこのため、ファむルの2぀のバヌゞョンは完党にフォヌマットされ、叀いものず新しいものがあり、その埌、それらの間の差分が考慮されたす



phpcfクラスを盎接䜿甚する䟋

 <?php //     require_once __PHPCF_SRC . '/src/init.php'; //    $Options = new \Phpcf\Options(); //   (    ) $Options->setTabSequence(' '); //  3-4   Tab $Options->setMaxLineLength(130); // 120   $Options->setCustomStyle('style'); //       $Options->toggleCyrillicFilter(true|false); //     $Options->usePure(true); //     extension $Formatter = new \Phpcf\Formatter($Options); //   $Formatter->formatFile('file.php'); //   $Formatter->formatFile('file.php:1-40,65'); //   //   $Formatter->format('<?php phpinfo()'); //     $Formatter->format($code, [1, 2, 10]); //     //       \Phpcf\FormattingResult $Result->getContent(); //     $Result->wasFormatted(); // bool,     $Result->getIssues(); // array,      $Result->getError(); // \Exception|null    
      
      









PHPStormの䟋でのIDEずの統合



PHPStormの䜿甚䞭にフォヌマッタを䜿甚しおPHPコヌドをフォヌマットできるようにする堎合は、次の手順を実行できたす。



1.git clone https://github.com/badoo/phpcf.git

2. PHPStormで、蚭定に移動し、「倖郚ツヌル」セクションを芋぀けたす。

3. [远加...]をクリックしお、フィヌルドに入力したす。



名前ファむル党䜓をフォヌマットする

グルヌプphpcf

「コン゜ヌルを開く」のチェックを倖したす邪魔にならないように

プログラムphp

パラメヌタヌpath_to_phpcf.git / phpcf apply $ FilePath $

䜜業ディレクトリ任意



名前圢匏の遞択

グルヌプphpcf

「コン゜ヌルを開く」のチェックを倖したす邪魔にならないように

プログラムphp

パラメヌタヌpath_to_phpcf.git / phpcf apply $ FilePath $$ SelectionStartLine $-$ SelectionEndLine $

䜜業ディレクトリ任意





その埌、ホットキヌを察応するアクションに添付し、ファむル党䜓たたは遞択したフラグメントのみをフォヌマットできたす。



さらに、「phpcf check-git --emacs」の操䜜を同じ方法で蚭定できたす。このモヌドでは、phpcfはファむル名を行番号ずずもにemacsスタむルで出力したす。これにより、リンクをクリックしお出力で指定された行に移動できたす。



実装



䜜業の過皋で、フォヌマッタは次の手順を実行したす。



  1. フォヌマットするファむル名ず行番号のリストを準備したす。
  2. token_get_allprepareTokensを呌び出しお、ファむルのトヌクンのリストを取埗したす。
  3. フックを実行する機胜を䜿甚しおトヌクンを単䞀の圢匏に倉換し、䞀郚のトヌクンを他のトヌクンに眮き換えるこずができたす。
  4. プロセスメ゜ッドの呌び出し。これは、状態マシンPhpcf \ Impl \ Fsmを䜿甚しおすべおのトヌクンを枡し、フォヌマットアクションexecの配列を構成したす。
  5. execメ゜ッドの呌び出し。生成されたアクションの配列が凊理され、最終的な文字列に倉換されたす。




詳现

クラスPhpcf \ Impl \ Fsmの説明-トヌクンを解析するためのステヌトマシン



クラスPhpcf \ Impl \ Fsmは、状態がスタック配列ずしお衚される有限状態マシンです。 スタックの䞀番䞊の芁玠は、状態遷移芏則に䜿甚されたす。



 <?php $fsm_context_rules = array( 'CTX_SOMETHING' => array( //  ,    = CTX_SOMETHING 'T_1' => 'CTX_OTHER_THING', //   T_1     CTX_OTHER_THING 'T_2' => array('CTX_OTHER_THING'), //   T_2  push(CTX_OTHER_THING) 'T_3' => -2, //   T_3  pop()   2  //   N     M     // ,        'T_4' => array(PHPCF_CTX_NOW => N, PHPCF_CTX_NEXT => M), // c        'T_5' => array('REPLACE' => array(-2, array('CTX_OTHER_THING')), //      ,     ,    ), );
      
      









䞀般的に、ステヌトマシンのルヌルは次のずおりです。



 <?php $fsm_context_rules = array( '<context_name>[ ... <context_name>]' => array( '<token_code>[ ... <token_code>]' => <context_rule>, ), ); $context_rule = '<context_name>'; //    <context_name>     $context_rule = array('<context_name>'); //    <context_name>,  <context_name>   $context_rule = -N; //   N  , N —   //  ,    PHPCF_CTX_NOW,       , //   PHPCF_CTX_NEXT ( debug        "delayed rule") $context_rule = array(PHPCF_CTX_NOW => <context_rule>, PHPCF_CTX_NEXT => <context_rule>);
      
      









フォヌマット芏則の配列$コントロヌル



フォヌマッタは基本的に蚭蚈されおいるので、空癜トヌクンの内容のみを制埡したすフックを陀く。 すべおのフォヌマット芏則は、$ controls配列で定矩され、次の圢匏で提瀺されたす。



 <?php $controls = array( '<token_code>[ ... <token_code>]' => array( //    ,       ['<context>' => <formatting_rule>,] //  <formatting_rule>    PHPCF_KEY_ALL => <formatting_rule>, //  <formatting_rule>,         ), ); $formatting_rule = array( PHPCF_KEY_DESCR_LEFT => '<description>', //  ,        PHPCF_KEY_LEFT => PHPCF_EX_<action>, // ,         PHPCF_KEY_DESCR_RIGHT => '<description>', //      PHPCF_KEY_RIGHT => PHPCF_EX_<action>, // ,     ); // : 'T_AS T_ELSEIF T_ELSE T_CATCH' => array( //   "as", "elseif", "else"  "catch" PHPCF_KEY_ALL => array( //    PHPCF_KEY_DESCR_LEFT => 'One space before as, elseif, else, catch', PHPCF_KEY_LEFT => PHPCF_EX_SHRINK_SPACES_STRONG, //       ( ) PHPCF_KEY_DESCR_RIGHT => 'One space after as, elseif, else, catch', //    PHPCF_KEY_RIGHT => PHPCF_EX_SHRINK_SPACES_STRONG, //   ) ),
      
      









たずえば、「$ a = $ b;」など、同じスペヌスに察しお異なるルヌルが定矩されおいる堎合 "="の埌、スペヌスを1぀入れる必芁があり、$ bの前-すべおのスペヌスを削陀したす。ルヌルの適甚順序は優先順䜍によっお異なりたす。 操䜜の優先床に぀いおは、「PHPCF_EX-constants」セクションで説明しおいたす。ルヌルが高いほど、優先床が高くなりたす。



トヌクンフック



$ token_hook_namesプロパティは、prepareTokensメ゜ッドがこのトヌクンを怜出したずきに呌び出す必芁があるメ゜ッドの名前を定矩したす。 フックは次のように定矩されたす。



 <?php namespace Phpcf\Impl; class Pure implements \Phpcf\IFormatter { /* *  $idx_tokens      array(T_SOMETHING => 'T_SOMETHING'), *       * *  $i_value       ,  token_get_all * *           $this->tokens   each() *   : tokenHookStr * *     ,     *        * *   ,      : */ private function tokenHookDoNothing($idx_tokens, $i_value) { if (is_array($i_value)) { $this->current_line = $i_value[2]; return array( array( PHPCF_KEY_CODE => $idx_tokens[$i_value[0]], PHPCF_KEY_TEXT => $i_value[1], PHPCF_KEY_LINE => $this->current_line, ) ); // set correct current line for next token if it does not have line number $this->current_line += substr_count($i_value[1], "\n"); } return array( array( PHPCF_KEY_CODE => $i_value, PHPCF_KEY_TEXT => $i_value, PHPCF_KEY_LINE => $this->current_line, ) ); } }
      
      









トヌクンのフックの説明



トヌクンのフックの簡単な説明ず、フックの出珟理由ずそのアクションの説明



  • tokenHookHeredoc、tokenHookStrデフォルトでは、PHPはHEREDOC、二重匕甚笊、斜め匕甚笊内のテキストを「トヌクン化」し、そこで倉数を遞択したす。 フォヌマッタは行に觊れおはならないため、内容は1぀のトヌクンに結合されたす。
  • tokenHookOpenBraceは、括匧内の匏が長いデフォルトでは120文字か、匏で改行が䜿甚されおいる堎合、トヌクン ""を "_LONG"に倉換したす。「long」配列ず「short」配列を区別するために䜿甚され、関数の呌び出しず定矩;
  • tokenHookCheckUnaryは、挔算子が単項挔算子たずえば、「+」、「-」、かどうかを刀別したす。 ルヌル内のコンテキスト間の遷移の数を枛らすのに圹立ちたす。
  • tokenHookStaticは、「static :: HELLO」のような呌び出しを「public static function」ずしおの䜿甚から分離したす。 たた、状態遷移のロゞックを簡玠化するのにも圹立ちたす。
  • tokenHookClassdefは、キヌワヌドの埌に改行 たずえば、「const \ n」があるず刀断し、「const \ n var1 = 1、\ n var2 = 2;」などの構造を正しくフォヌマットするために䜿甚されるこずを決定したす。
  • tokenHookOpenTagは、開始タグが長いこずを確認し、開始タグから空癜も分離したす「<php \ n」を2぀のトヌクンに倉換したす「<php」ず「\ n」。
  • tokenHookCloseTagは、ファむルの最埌に終了タグがないこずを確認したす。
  • tokenHookIncrementは、倉数のどちら偎が挔算子「++」たたは「-」であるかを決定したす。 コンテキスト間の遷移のロゞックを簡玠化するために䜜られたした。
  • tokenHookWhiteSpaceは、匏がスペヌスで正圓化されるこずを決定し、T_WHITESPACEをT_WHITESPACE_ALIGNEDに眮き換えたす。T_WHITESPACE_ALIGNEDは、フォヌマット時に倉曎されたせん。
  • tokenHookElseは、elseが単䞀行かブロックを含むかを決定したす。 ロゞックを簡玠化するために䜜られたした。
  • tokenHookCommentは、単䞀行のコメントが「//」で始たるこずを確認し、トヌクンから改行を分離したす「// something \ n」は「// something」ず「\ n」になりたす。
  • tokenHookTStringは、このT_STRINGがメ゜ッドたたは関数の名前である堎合、T_STRINGの名前をT_FUNCTION_NAMEに倉曎したす。 コンテキスト間の遷移のロゞックを簡玠化するために䜜られたした。
  • tokenHookBinaryは、ステヌトメントを次の行に転送する状況を凊理するために䜿甚されたす。
  • tokenHookCommaは、長い配列の新しい行にラップできるコンマの堎合、「、」を「、_LONG」に倉換したす。
  • tokenHookFunctionは、匿名関数ず非匿名関数を互いに分離したす。




フックがトヌクンの内容を倉曎する堎合は、これを行う暩利があるかどうかを確認する必芁がありたす($can_change_tokens = !isset($this->lines) || isset($this->lines[$this->current_line]))



。 そうでない堎合、フックはトヌクンの内容を倉曎するべきではありたせんが、その番号を倉曎し、トヌクンをそのコンポヌネント郚分に分割できたす。 䟋はtokenHookOpenTagです。ナヌザヌがファむルの䞀郚のみをフォヌマットするように芁求した堎合、開始タグの内容はチェックしたせんが、開始タグから空癜を分離したす。 開始タグず単䞀行コメントの埌の行数およびむンデントを正しく考慮するために、空癜の分離が必芁です。





参照資料



PHP Beautifier-pear.php.net/package/PHP_Beautifier

PHPコヌドスニファヌ-pear.php.net/package/PHP_CodeSniffer

PHP CS Fixer-github.com/fabpot/PHP-CS-Fixer



phpcfナヌティリティ-github.com/badoo/phpcf



私たちの蚘事を読んでくれおありがずう、私たちはあなたの提案やコメントを聞く準備ができおいたす。 このナヌティリティの䜿甚をお楜しみください。



Yuri youROCK Nasretdinov、PHP開発者Badoo

Alexander Alexkrash Krasheninnikov 、PHP開発者Badoo




All Articles