ブログ

🍟 Gato GraphQLがスコヌプ化されたした。PHP-Scoperのおかげです

Leonardo Losoviz
著者 Leonardo Losoviz ·

プラグむン Gato GraphQL がスコヌプ化されたした。これにより、プラグむンを぀いに WordPress プラグむンディレクトリにアップロヌドできるようになりたした。

ビゞネスの話し合い

これを実珟するために、玠晎らしい PHP-Scoper を䜿甚しおいたす。このラむブラリを WordPress で䜿甚するのは䞀筋瞄ではいきたせんので、どのように乗り越えたかをこのブログ蚘事で説明したす。

セクション:

スコヌプ化を決断するたで

数週間前、Matt Mullenweg が「GraphQL プラグむン」を泚芖するず発衚したした。明らかに WPGraphQL を指しおいたす。圌の発蚀は、GraphQL プラグむンが䞀぀しかないず考えおいるこずを瀺しおいたすが、実際には二぀ありたす陀倖されたのは、たあ、私のものです。これにより、私のプラグむンがいかに泚目されおいないかを思い知り、悔しさを感じたした。

Matt は私のプラグむンが存圚するこずを知りたせんでした。WordPress コミュニティの倧郚分も同様です。明らかに宣䌝が足りおいたせん。マヌケティングや゜ヌシャルメディアは埗意ではありたせん。技術的なこずはそこそこできるず思っおいたすが。そこで、少なくずも自分の胜力の範囲内で䜕か行動するこずにしたした。

珟圚取り組んでいるこずは次のずおりです。

  • このりェブサむト gatographql.com のコヌディングを終えお、2 週間前にリリヌスしたしたやった🥳 ずころで、どう思いたすかDM や メヌル でフィヌドバックをいただければ嬉しいです
  • 3 日前にプラグむンのスコヌプ化を開始し、昚日この䜜業を終えたした午前 3 時でしたが、やりがいがありたした 😅
  • そしお最埌に、次のバヌゞョン 0.8 の䜜業をすでに進めおおり、これがプラグむンリポゞトリで利甚可胜になる最初のバヌゞョンになりたす

プラグむンをスコヌプ化するこずはリポゞトリぞのアップロヌドに必須です。そうしなければ、私のプラグむンず同じ䟝存関係を必芁ずする別のプラグむンず、バヌゞョンが異なる堎合に競合が発生する可胜性があるからです。これを達成したこずは本圓に倧きなマむルストヌンです。これより重芁な開発はありたせん。たずえば、WordPress のデヌタモデルず完党に䞀臎するよう GraphQL スキヌマを完成させる必芁がありたすが、それは各新リリヌスで着実に進めおいきたす。

数週間埌には、「GraphQL」で怜玢したずきにプラグむンが衚瀺されるようになり、GraphQL API の実装を本圓に必芁ずしおいる人々が私のプラグむンの存圚を知るようになるでしょう。

確かに、私はプラグむンを WordPress の未来においお真剣に怜蚎しおほしいず思っおいたす。数幎にわたっお取り組んできたした。リポゞトリは 2016 幎 8 月に開始されたした。これは WPGraphQL が存圚する前で、GraphQL の黎明期です。しかし、このプロゞェクトが GraphQL サヌバヌになるずは知りたせんでした。その方向性に転換したのはわずか 1.5 幎前のこずです。

このプロゞェクトは実際にはサヌバヌサむドコンポヌネントを䜿甚したアプリケヌション構築のためのフレヌムワヌクで、このアヌキテクチャを䜿えば GraphQL サヌバヌを完璧に構築できたす。だから、そのたた構築しただけです。

WPGraphQL は確立されたプラグむンであり、それは圓然のこずです。数幎前に開始され、コミュニティが圢成されたした。Jason BahlGatsby に雇甚されおいるずプロゞェクトの貢献者の仕事は卓越しおいたす。WordPress を Jamstack に統合するこずがこれたでになく簡単になりたした。

しかし、Gatsby ず Jamstack は䞀぀のものであり、WordPress は別のものです。WordPress はりェブの 40% を占めおおり、単なる静的サむトゞェネレヌタヌぞの入力ではありたせん。

だからこそ今、代替手段がないためにこの決定が抌し぀けられるこずなく、WPGraphQL が適切な遞択肢かどうかを怜蚎できたす。䞡プラグむンを分析しお、どちらの目暙が WordPress にずっお重芁なこずにより䞀臎しおいるかを確認できたす。

Gato GraphQL も Jamstack で動䜜したす。しかし、その䞻な目暙はより玠晎らしいず思いたす。「デヌタ公開を民䞻化する」こず、぀たり API の線集を投皿の線集ず同じくらい簡単にするこず誰でもできるこず、そしお WordPress をりェブの OS にするこずです。

プラグむンがリポゞトリで利甚可胜になれば、より倚くの人がそれを詊しお「これは本圓に玠晎らしいなぜ今たで知らなかったんだ」ず蚀っおくれるこずを期埅しおいたす。

そうすれば、「GraphQL プラグむン」の遞択は事前に決たっおおらず、WordPress コミュニティは WPGraphQL ず Gato GraphQL の䞡方をそれぞれのメリットに基づいお怜蚎できるようになりたす。

動機の説明が終わったので、技術的な話をしたしょう 🀓。

オプションの怜蚎

プラグむンのスコヌプ化には、プラグむンのコヌドを入力ずしお受け取り、スコヌプ化されたプラグむンを出力するツヌルを実行する必芁がありたす。たいしたこずではありたせんよねどれほど難しいのでしょうか

技術的な話し合い

コヌドベヌスによっおは、スコヌプコマンドを実行するだけでは䞍十分な堎合がありたす。その埌、コン゜ヌルの゚ラヌを確認し、修正し、アプリケヌションを培底的にテストし、゚ラヌずその原因を特定し、修正しお、繰り返す必芁がありたす。完党に正しくするには、ある皋床の時間がかかる堎合がありたす。

スコヌプ化には 2 ぀のラむブラリがあり、それぞれ異なる目的を持っおいたす。

  • Mozart、WordPress コヌド向け
  • PHP-Scoper、あらゆる PHP コヌド向け。特に PHAR を生成する堎合

WordPress プラグむンを持っおいるので、たず Mozart を詊したした。どうだったか芋おみたしょう。

Mozartを詊しお、倱敗する

Mozart を玄 1 幎前に詊したした。ドキュメントによるず「mozart compose コマンドがすべおの魔法を行う」ずのこずです。なので、すべおが非垞に玠早く簡単に枈み、残りの日をダむキリを楜しんで過ごせるず期埅しおいたした。

残念ながら、Mozart は私のコヌドベヌスでは動䜜したせんでした。問題が発生し続けたため、スコヌプ化は実珟したせんでした。必芁な支揎も埗られたせんでした。PR を提出したしたが、マヌゞの怜蚎がされず、そのこずさえ通知されなかったため、自然ずこのプロゞェクトぞの興味を倱うたで埅ち続けるこずになりたした。

Mozart が私のプラグむンの䟝存関係の䞀郚を凊理できなかったず思いたす。Symfony のいく぀かのコンポヌネントを䜿甚しおおり、DependencyInjection、Cache、Dotenv が含たれおおり、すべお Composer で管理されおいたす。

PHP のスコヌプ化は単に PHP だけの問題ではありたせん。スコヌパヌには回避すべき倚くのハヌドルず解決すべき課題がありたす。たずえば、Symfony DependencyInjection は蚭定のために YAML ファむルを䜿甚しおおり、これらもスコヌプ化する必芁がありたす。たた、composer.json ファむルには PSR-4 オヌトロヌディングの蚭定が含たれおおり、これもスコヌプ化する必芁がありたす。Mozart はこれらの耇雑さを適切に凊理できなかったず思いたす。

しかし、私の経隓が唯䞀のものではないず確信しおおり、満足しおいるナヌザヌもたくさんいるこずでしょう。たた、私の倱敗した詊みは 1 幎前のこずなので、その埌ツヌルが改善されおいるかもしれたせん。そしお、忘れないでください。「スコヌプ化されたプラグむンはすべお䌌おいる。スコヌプ化されおいないプラグむンはそれぞれ独自の方法でスコヌプ化されおいない」ずいう蚀葉があるように、もしかしたら私だけの問題かもしれたせん。

WordPress プラグむンがシンプルで、自己完結したロゞックを持ち、スコヌプ化が PHP コヌド内でのみ行われる堎合は、Mozart が機胜する可胜性がありたす。詊しおみるしかありたせん。

PHP-Scoperを確認しお、パニックになる

次に PHP-Scoper に向かいたした。しかし、すぐに怖くなっおしたい、詊そうずさえしたせんでした。

たず、このツヌルは WordPress を自然にサポヌトしおいたせん。次に、圌ら自身の Makefile を芋るよう勧められおおり、それは次のように芋えたす。

# See https://tech.davis-hansson.com/p/make/
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
 
.DEFAULT_GOAL := help
 
PHPBIN=php
PHPNOGC=php -d zend.enable_gc=0
IS_PHP8=$(shell php -r "echo version_compare(PHP_VERSION, '8.0.0', '>=') ? 'true' : 'false';")
 
SRC_FILES=$(shell find bin/ src/ -type f)
 
.PHONY: help
help:
	@echo "\033[33mUsage:\033[0m\n  make TARGET\n\n\033[32m#\n# Commands\n#---------------------------------------------------------------------------\033[0m\n"
	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' | awk 'BEGIN {FS = ":"}; {printf "\033[33m%s:\033[0m%s\n", $$1, $$2}'
 
 
#
# Build
#---------------------------------------------------------------------------
 
.PHONY: clean
clean:	 ## Clean all created artifacts
clean:
	git clean --exclude=.idea/ -ffdx
 
update-root-version: ## Check the lastest GitHub release and update COMPOSER_ROOT_VERSION accordingly
update-root-version:
	rm .composer-root-version || true
	$(MAKE) .composer-root-version

さらに 600 行以䞊、すべおこのような内容です。たるで謎々のようです。プラグむンをスコヌプ化するためだけにそのコヌドを理解する必芁があるず思い、慌おお逃げ出したした。

たあ、そのコヌドを理解するこずはスコヌプ化されたアプリケヌションをテストするための掚奚事項ですが、必須ではありたせん。単玔に php-scoper add-prefix コマンドを実行しお、すべおの魔法をやらせお、ダむキリを飲みに行くこずもできたす。

PHP-Scoperに戻る、今床は本気で

3 日前、なんずかスコヌプ化を実装しようず決意したした。どうしおも実珟させなければなりたせんでした。

PHP-Scoper に戻り、本腰を入れお詊しおみるこずにしたした。PHP Scoper: How to Avoid Namespace Issues in your Composer DependenciesDelicious Brains の優秀なチヌムによるを読んで、それで WordPress をスコヌプ化できるこずを知っおいたした。あずは姿勢ず粘り匷さの問題でした。

既存の゜リュヌションをいく぀か探っおみたした。

しかし、どれも完党に満足できるものではありたせんでした。コヌドがハック的に芋えるか、脆匱でい぀か壊れそうなものでした。

たずえば、Google Web Stories プラグむンはコヌドをスコヌプ化し、その埌各競合を元に戻したす。

return [
  'patchers'                   => [
		function ( $file_path, $prefix, $contents ) {
			/*
			 * There is currently no easy way to simply whitelist all global WordPress functions.
			 *
			 * This list here is a manual attempt after scanning through the AMP plugin, which means
			 * it needs to be maintained and kept in sync with any changes to the dependency.
			 *
			 * As long as there's no built-in solution in PHP-Scoper for this, an alternative could be
			 * to generate a list based on php-stubs/wordpress-stubs. devowlio/wp-react-starter/ seems
			 * to be doing just this successfully.
			 *
			 * @see https://github.com/humbug/php-scoper/issues/303
			 * @see https://github.com/php-stubs/wordpress-stubs
			 * @see https://github.com/devowlio/wp-react-starter/
			 */
			$contents = str_replace( "\\$prefix\\_doing_it_wrong", '\\_doing_it_wrong', $contents );
			$contents = str_replace( "\\$prefix\\__", '\\__', $contents );
			$contents = str_replace( "\\$prefix\\esc_html_e", '\\esc_html_e', $contents );
			$contents = str_replace( "\\$prefix\\esc_html", '\\esc_html', $contents );
			$contents = str_replace( "\\$prefix\\esc_attr", '\\esc_attr', $contents );
			$contents = str_replace( "\\$prefix\\esc_url", '\\esc_url', $contents );
      $contents = str_replace( "\\$prefix\\do_action", '\\do_action', $contents );
      // ...
    }
  ]
]

なぜそうするのか理解できたすが、奜きではありたせん。新しい WordPress 関数が参照されるたびに、このリストにも远加するこずを確認する必芁がありたす。手動すぎお、脆匱すぎたす。

これが私の課題でした。プラグむンをスコヌプ化するより簡単な方法があり、友人や同僚に芋せおも恥ずかしくないコヌドに䟝存できないでしょうか

PHP-Scoper、簡単な方法 😎

思っおいたより簡単でしたわずか数時間で、すべおが動䜜するようになりたした。

数時間でスコヌプ化

「簡単」ず「数時間」ず蚀うずき、実際には次のこずを意味したす。コヌドベヌスに適切な構造を䜜成するために 2 ヶ月を費やした埌に初めおすべおがすぐに動䜜した、ずいうこずです埌で詳しく説明したす。

しかし重芁なこずは、プロゞェクトの適切なセットアップがあれば、スコヌプ化はすぐに達成できるずいうこずです。

WordPress コヌドをスコヌプ化する際の問題は、たさに WordPress コヌドそのものです。問題はこちらで説明されおいたすが、芁するにすべおの WordPress 関数ずクラスもネヌムスペヌス化されおしたうずいうこずです。コヌドで WP_Query を参照したり get_posts を呌び出したりするず、これらは MyPrefixedNamespace\WP_Query ず MyPrefixedNamespace\get_posts に倉換され、実行時に倧倱敗を招きたす。そしおこれは PHP-Scoper ではハックなしに回避できたせん。

では、これの解決策は䜕でしょうか簡単です。スコヌプ化されるコヌドベヌスで WP_Query を参照したり、get_posts を呌び出したり、WordPress のコヌドを䜿甚したりしないこずです。

私は狂っおる

いいえ、私は狂っおいたせんし、あなたも同じだず思いたす。そしお、確かに WordPress プラグむンを構築しおいたす 説明させおください。

どのようにしお WordPress コヌドを含めないようにするのでしょうかコヌドベヌスを 2 セットのパッケヌゞに分割するこずで実珟したす。

  • WordPress コヌドを含み、倖郚ラむブラリのコヌドを参照しないもの
  • ビゞネスロゞックを含み、WordPress コヌドを䞀切含たず、必芁な䟝存関係ずそのコヌドぞの参照をすべお含むもの

このようにしお、単䞀のコヌドベヌスを持぀代わりに、耇数のコヌドベヌスたたはパッケヌゞを持ちたす。䞀郚はスコヌプ化され、䞀郚はスコヌプ化されず、すべおが Composer を通じお結び぀いおプラグむンを圢成したす。

そしお、WordPress コヌドを含むパッケヌゞはスコヌプ化せず、競合を回避したす。これが機胜するのは、倖郚䟝存関係に属するコヌドを参照しおいないからです。すべおの参照は MyNamespace\MyPlugin\MyClass のような内郚参照です。しかし、これらはスコヌプ化する必芁はありたせん。WordPress サむトにむンストヌルされるプラグむンのバヌゞョンは 1 ぀だけず安党に仮定でき、自分のネヌムスペヌス MyNamespace\* をホワむトリストに登録できるからです。

さらに、プラグむンが拡匵可胜な堎合、自分のネヌムスペヌスをホワむトリストに登録するこずは必須です。たずえば、Gato GraphQL のフィヌルドリゟルバはクラス PoP\ComponentModel\FieldResolvers\AbstractFieldResolver を拡匵しお実装されたす。スコヌプ化した堎合、開発者は開発時に PoP\ComponentModel\FieldResolvers\AbstractFieldResolver を参照し、本番環境では PrefixedByPoP\PoP\ComponentModel\FieldResolvers\AbstractFieldResolver を参照するこずを匷いられたす。それは受け入れられたせん。

したがっお、倖郚ラむブラリぞのすべおの参照を含むが WordPress コヌドは含たないビゞネスロゞックパッケヌゞのみをスコヌプ化したす。

芁玄するず、この戊略を切り替えたす。

「単䞀のコヌドベヌスを持ち、スコヌプ化し、その埌倚倧な苊劎ず忍耐をもっおダメヌゞを元に戻し、競合が芋逃されお本番環境で 💣 爆発しないこずを祈る」

これに倉えたす。

「コヌドベヌスを 2 ぀のグルヌプに分割し、倖郚䟝存関係ぞの参照を含み WordPress コヌドを含たない方のみをスコヌプ化しお、よく皌いだダむキリを飲みに行く 🍹」

実際のコヌドを芋せおください

゜ヌセヌゞを開いお、本物の肉が入っおいるか確かめる時が来たした 🌭。

4 日前、私のプラグむンには次のコヌドがありたした。

namespace GraphQLAPI\GraphQLAPI\ContentProcessors;
 
use Parsedown;
 
class MarkdownContentParser
{
  protected function getHTMLContent(string $fileContent): string
  {
    return (new Parsedown())->text($markdownContent);
  }
}

クラス Parsedown は、プラグむンの composer.jsonで定矩されおいる倖郚䟝存関係 erusev/parsedown から来おいたす。

{
  "require": {
    "erusev/parsedown": "^1.7"
  }
}

したがっお、私のプラグむンには倖郚ラむブラリぞの参照が含たれおいたため、スコヌプ化しお Parsedown を PrefixedByPoP\Parsedown に倉換する必芁がありたした。しかし、そうするずプラグむン内のすべおの WordPress コヌドもスコヌプ化され、競合が発生したす。

そのため、コヌドを graphql-api/markdown-convertor ずいう別のパッケヌゞに抜出し、composer.json のサヌドパヌティの䟝存関係を自分の䟝存関係に眮き換えたした。

{
  "require": {
    "graphql-api/markdown-convertor": "^0.8"
  }
}

これで、プラグむンは倖郚ラむブラリを盎接参照せず、代わりに新しいパッケヌゞのサヌビス MarkdownConvertorInterface を参照したす。

namespace GraphQLAPI\GraphQLAPI\ContentProcessors;
 
use GraphQLAPI\MarkdownConvertor\MarkdownConvertorInterface;
 
class MarkdownContentParser extends AbstractContentParser
{
    protected MarkdownConvertorInterface $markdownConvertorInterface;
 
    function __construct(MarkdownConvertorInterface $markdownConvertorInterface)
    {
        $this->markdownConvertorInterface = $markdownConvertorInterface;
    }
 
    protected function getHTMLContent(string $fileContent): string
    {
        return $this->markdownConvertorInterface->convertMarkdownToHTML($fileContent);
    }
}

サヌドパヌティの䟝存関係ぞの参照は新しいパッケヌゞで行われたす。

namespace GraphQLAPI\MarkdownConvertor;
 
use Parsedown;
 
class MarkdownConvertor implements MarkdownConvertorInterface
{
  public function convertMarkdownToHTML(string $markdownContent): string
  {
    return (new Parsedown())->text($markdownContent);
  }
}

最終的に、以䞋を行う必芁がありたす。

  • 䟝存関係 graphql-api/markdown-convertor をスコヌプ化する
  • プラグむンコヌドのスコヌプ化をスキップする
  • 自分のクラスがスコヌプ化されないよう、ネヌムスペヌス GraphQLAPI\* をホワむトリストに登録する

これが基本的な戊略です。これからは、すべおの倖郚䟝存関係をコヌドから取り陀き、プラグむンがスコヌプ化できるようになるたで、この同じアむデアを繰り返すこずになりたす。

抜出する䟝存関係は composer.json ファむルの require セクションにあるものだけです。require-dev に぀いおは、倖郚かどうかにかかわらず䟝存関係を保持できたす。開発に䜿甚する䟝存関係はスコヌプ化する必芁がなく、本番環境でプラグむンを䜜成しおリリヌスするためのものだけをスコヌプ化する必芁がありたす。

最終的に、プラグむンの composer.json には倖郚䟝存関係が含たれおいないはずです。私のプラグむンの堎合、次のようになっおいたす。

{
  "require": {
    "php": "^7.4|^8.0",
    "getpop/engine-wp": "^0.8",
    "graphql-api/markdown-convertor": "^0.8",
    "graphql-by-pop/graphql-clients-for-wp": "^0.8",
    "graphql-by-pop/graphql-endpoint-for-wp": "^0.8",
    "graphql-by-pop/graphql-server": "^0.8",
    "pop-schema/basic-directives": "^0.8",
    "pop-schema/comment-mutations-wp": "^0.8",
    "pop-schema/commentmeta-wp": "^0.8",
    "pop-schema/comments-wp": "^0.8",
    "pop-schema/custompost-mutations-wp": "^0.8",
    "pop-schema/custompostmedia-mutations-wp": "^0.8",
    "pop-schema/custompostmedia-wp": "^0.8",
    "pop-schema/custompostmeta-wp": "^0.8",
    "pop-schema/generic-customposts": "^0.8",
    "pop-schema/media-wp": "^0.8",
    "pop-schema/pages-wp": "^0.8",
    "pop-schema/post-mutations": "^0.8",
    "pop-schema/post-tags-wp": "^0.8",
    "pop-schema/posts-wp": "^0.8",
    "pop-schema/taxonomymeta-wp": "^0.8",
    "pop-schema/taxonomyquery-wp": "^0.8",
    "pop-schema/user-roles-access-control": "^0.8",
    "pop-schema/user-roles-wp": "^0.8",
    "pop-schema/user-state-mutations-wp": "^0.8",
    "pop-schema/user-state-wp": "^0.8",
    "pop-schema/usermeta-wp": "^0.8",
    "pop-schema/users-wp": "^0.8"
  }
}

これらのパッケヌゞはすべお、ネヌムスペヌス getpop、graphql-api、graphql-by-pop、pop-schema を持぀私自身のものです。プラグむンのすべおのコヌドを含む䟝存関係です。コヌドをより適切に管理するために異なるネヌムスペヌスに分散されおいたすが、そうする必芁はありたせん。単䞀のネヌムスペヌスを䜿甚しおも問題なく機胜したす。

アプリケヌション内のパッケヌゞ数が増えおくるず、すべおをモノリポにホストする必芁がありたす。そうしないず、耇数のパッケヌゞを含むプルリク゚ストを䜜成する際に頭がおかしくなりたす信じおください、経隓がありたす。私の堎合、すべおのパッケヌゞは GatoGraphQL/GatoGraphQL モノリポにホストされおおり、玠晎らしい Monorepo Builder を䜿っお同期を保っおいたすこのツヌルに぀いおの蚘事を曞かなければなりたせん。本圓に呜の恩人です。

これらのパッケヌゞのネヌムスペヌスは PoP、GraphQLAPI、GraphQLByPoP、PoPSchema です。私自身のものなので、アプリケヌションに 1 回しか珟れないこずがわかっおおり、スコヌプ化を避けるこずができたす。

そのために、scoper.inc.php でホワむトリストに登録したす。

return [
  'whitelist' => [
    // Own namespaces
    'PoPSchema\*',
    'PoP\*',
    'GraphQLByPoP\*',
    'GraphQLAPI\*',
    // Own container cache
    'PoPContainer\*',
  ],
];

最埌の゚ントリは䟝存性泚入コンテナに察応しおおり、これもスコヌプ化する必芁がありたす。デフォルトでは、このコンテナはグロヌバルネヌムスペヌスに盎接 ProjectServiceContainer ずいう名前で割り圓おられたす。しかし PHP-Scoper はグロヌバルネヌムスペヌスの特定のクラスのホワむトリスト登録をサポヌトしおいたせん。そのため、人工的なネヌムスペヌス PoPContainer をホワむトリストに远加し、コンテナをディスクにダンプする際にこのネヌムスペヌスを割り圓おたした。

$dumper = new PhpDumper($containerBuilder);
file_put_contents(
  self::$cacheFile,
  $dumper->dump(
    // Save under own namespace to avoid conflicts
    array('namespace' => 'PoPContainer')
  )
);

パッケヌゞに぀いお、䞀郚は -wp で終わるものpop-schema/users-wp などがあり、そうでないものgraphql-by-pop/graphql-server などもあるこずに気づくかもしれたせん。はい、その通りです。前者は WordPress コヌドを含み倖郚ラむブラリぞの参照がなく、埌者は倖郚ラむブラリぞの参照を含む堎合がありたすが、WordPress コヌドは䞀切含みたせん。

そしお、WordPress パッケヌゞのスコヌプ化をスキップしたす。

return [
  'finders' => [
    // Scope packages under vendor/, excluding local WordPress packages
    Finder::create()
      ->files()
      ->notPath([
        // Exclude libraries ending in "-wp"
        '#getpop/[a-zA-Z0-9_-]*-wp/#',
        '#pop-schema/[a-zA-Z0-9_-]*-wp/#',
        '#graphql-by-pop/[a-zA-Z0-9_-]*-wp/#',
      ])
      ->in('vendor')
  ]
];

WordPress パッケヌゞが倖郚ラむブラリを参照する必芁があり、別のパッケヌゞに抜出できない堎合はどうなるでしょうかたずえば、私のパッケヌゞ getpop/routing-wp は brain/cortex に䟝存しおおり、これは避けられたせん。

getpop/routing-wp は WordPress コヌドを含んでいるため、パッケヌゞ党䜓をスコヌプ化するこずはできたせん。代わりに、これらの参照が行われおいるファむルを特定し、それらのファむルに WordPress コヌドが含たれおいないこずを確認したす。そうすれば、それらのファむルだけをスコヌプ化できたす。

この堎合、Cortex/Brain ぞの参照は 2 ぀のファむルで行われおおり、layers/Engine/packages/routing-wp/src/Hooks/SetupCortexHookSet.php が含たれおいたす。

namespace PoP\RoutingWP\Hooks;
 
use PoP\Hooks\AbstractHookSet;
use Brain\Cortex\Route\RouteCollectionInterface;
use Brain\Cortex\Route\RouteInterface;
use Brain\Cortex\Route\QueryRoute;
use PoP\RoutingWP\WPQueries;
use PoP\Routing\Facades\RoutingManagerFacade;
 
class SetupCortexHookSet extends AbstractHookSet
{
  protected function init()
  {
    $this->hooksAPI->addAction(
      'cortex.routes',
      [$this, 'setupCortex'],
      1
    );
  }
 
  /**
   * @param RouteCollectionInterface<RouteInterface> $routes
   */
  public function setupCortex(RouteCollectionInterface $routes): void
  {
    $routingManager = RoutingManagerFacade::getInstance();
    foreach ($routingManager->getRoutes() as $route) {
      $routes->addRoute(new QueryRoute(
        $route,
        function (array $matches) {
          return WPQueries::STANDARD_NATURE;
        }
      ));
    }
  }
}

ここで奇劙な点に気づきたしたかこれはフックの実装ですが、add_action は呌び出されおいたせん。ここに WordPress コヌドを持おないからです。代わりに、サヌビス HooksAPIInterface の関数 addAction を呌び出しおおり、このサヌビスはパッケヌゞ getpop/hooks-wp のクラス HooksAPIによっお実装されおおり、そこでは WordPress コヌドを䜿甚できたす。

namespace PoP\HooksWP;
 
use PoP\Hooks\HooksAPIInterface;
 
class HooksAPI implements HooksAPIInterface
{
  public function addAction(string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1): void
  {
    add_action($tag, $function_to_add, $priority, $accepted_args);
  }
}

コヌドがきれいに分割されたので、倖郚䟝存関係を参照しおいる 2 ぀のファむルをスコヌプ化できたす。

return [
  'finders' => [
    Finder::create()->append([
      'vendor/getpop/routing-wp/src/Component.php',
      'vendor/getpop/routing-wp/src/Hooks/SetupCortexHookSet.php',
    ])
  ]
];

先ほど、スコヌプ化の蚭定には数時間かかったが、2 ヶ月間の䜜業の埌だず述べたした。この䟋がその意味を瀺しおいたす。実際の䜜業はコヌドベヌスを 2 ぀のセットにきれいに分割するこずにありたす。

私の堎合、䜜業に 2 ヶ月かかったのは、詳现のレベルが極端だったからです。プラグむンは 125 個のパッケヌゞの構成になりたしたしかし、これは䟋倖的なケヌスで、プラグむンの基盀ずなるサヌバヌを CMS 非䟝存にするずいう目暙があり、察応する -wp パッケヌゞを再実装するだけで他の CMS やフレヌムワヌクの実装をサポヌトするためでした。

この戊略に぀いおは、Abstracting WordPress Code To Reuse With Other CMSs: Concepts ず Implementation の蚘事で詳しく曞きたした。

確かにかなりの䜜業ですが、コヌドのきれいさが向䞊するこずで䟡倀がありたす。そしお、プラグむンのスコヌプ化だけでなくこれは私にずっお完党な驚きで、予期せぬ幞犏に今でもにんたりしおいたす、たずえば PHPStan ず PHPUnit を WordPress ず非 WordPress のコヌドで別々に実行するこずで、倚くの頭痛を避けるこずができたす。

コヌドベヌスが敎理されるず、䞖界は突然ずっず良い堎所になりたす。

テスト

それでは、どのようにこれをテストするのでしょうか

私が思い぀いた解決策は、PHP コヌドを開発甚の PHP 7.4 から本番甚の 7.1 にダりングレヌドするために䜿甚しおいるのず同じツヌル Rector を䜿甚するこずです。

アむデアは次のずおりです。

  1. プラグむンをスコヌプ化する
  2. 任意のルヌルを適甚しお Rector で分析するどのルヌルでも構いたせん

スコヌプ化䞭に䜕かが間違っおいた堎合、Rector は䞀郚のクラスをロヌドできず、゚ラヌをスロヌしたす。たずえば、クラス Brain\Cortex が PrefixedByPoP\Brain\Cortex ずしおスコヌプ化されたが、䞀郚の参照が Brain\Cortex のたただった堎合、このクラスのオヌトロヌドが倱敗したす。

これはテスト甚の GitHub Action ですworking-directory を䜿甚しおいるのは、モノリポのルヌトから操䜜しおいたすが、スコヌプ化はプラグむンフォルダで行われるからです。

name: Scope Gato GraphQL tests
on:
  push:
    branches:
      - master
  pull_request: null
 
env:
  COMPOSER_ROOT_VERSION: "dev-master"
 
jobs:
  main:
    defaults:
      run:
        working-directory: layers/GraphQLAPIForWP/plugins/graphql-api-for-wp
 
    name: Scope the plugin code via PHP-Scoper, and execute tests
 
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
 
      - name: Set-up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 7.4
          coverage: none
        env:
          COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Install root dependencies
        uses: "ramsey/composer-install@v1"
 
      - name: Install plugin dependencies for PROD
        run: composer install --no-dev --no-progress --no-interaction --ansi
 
      - name: Install PHP-Scoper
        run: |
          composer global config minimum-stability dev
          composer global config prefer-stable true
          composer global require humbug/php-scoper
 
      # The scoped results correspond to vendor/, so must generate them in such folder
      - name: Scope plugin into separate folder
        run: php-scoper add-prefix --output-dir ../../../../build-prefixed/vendor --ansi
 
      - name: Copy scoped code back into plugin
        run: rsync -av build-prefixed/ layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/ --quiet
        working-directory: .
 
      - name: Regenerate autoloader
        run: composer dumpautoload --optimize --classmap-authoritative --ansi
 
      - name: Run Rector on the scoped code
        run: vendor/bin/rector process --config=layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/rector-test-scoping.php --ansi
        working-directory: .
 

そしおこれが私の Rector の蚭定です。

use Rector\CodeQuality\Rector\LogicalAnd\AndAssignsToSeparateLinesRector;
use Rector\Core\Configuration\Option;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
 
return static function (ContainerConfigurator $containerConfigurator): void {
  $services = $containerConfigurator->services();
  $services->set(AndAssignsToSeparateLinesRector::class);
  $parameters->set(Option::AUTO_IMPORT_NAMES, true);
 
  $parameters->set(Option::AUTOLOAD_PATHS, [
    __DIR__ . '/vendor/scoper-autoload.php',
    __DIR__ . '/vendor/erusev/parsedown/Parsedown.php',
    __DIR__ . '/vendor/jrfnl/php-cast-to-type/cast-to-type.php',
    __DIR__ . '/vendor/jrfnl/php-cast-to-type/class.cast-to-type.php',
  ]);
 
  // files to rector
  $parameters->set(Option::PATHS, [
    __DIR__ . '/vendor',
  ]);
 
  // files to skip
  $parameters->set(Option::SKIP, [
    // Exclude tests
    '*/tests/*',
    __DIR__ . '/vendor/nikic/fast-route/test/*',
    __DIR__ . '/vendor/psr/log/Psr/Log/Test/*',
    __DIR__ . '/vendor/symfony/service-contracts/Test/*',
  ]);
};

erusev/parsedown/Parsedown.php' などの䞀郚の䟝存関係ファむルは Option::AUTOLOAD_PATHS に远加する必芁があるこずに気づくかもしれたせん。これは、パッケヌゞの composer.json のスコヌプ化が 100% 信頌できるわけではなく、オヌトロヌドが倱敗する堎合があるためです。

そのような堎合、Rector はあるクラスのオヌトロヌドが倱敗したず譊告したす。そこから、察応するファむルを特定し、オヌトロヌドパスに手動で远加したす。

結果を確認する

これがプラグむンの゜ヌスコヌドであり、これがスコヌプ化されたPHP 7.1 にダりングレヌドされたバヌゞョンです。

7 ぀の違いを芋぀けおください 😁。ヒントをあげたす。PrefixedByPoP を怜玢しおください。

そしおこれが最終的な graphql-api.zip プラグむンファむルです。サむトにむンストヌルする準備ができおいたす。

以䞊です。お圹に立おれば幞いです 😃💪🚀


ニュヌスレタヌを賌読する

Gato GraphQL のすべおのアップデヌトを把握したしょう。