WordPress のソースコードを読んで
REST API の処理を追いかけてみる

WordPress

WordPress の REST API を使ってやりたいことがあったので公式ドキュメントを読んだり検索をしていたけれど、なかなか欲しい情報にたどり着けず。するとふと「ソースコードを読んでみようかな」という考えが浮かんだので実践してみました。

なかなか楽しかったので、ざーっとしたまとめを掲載します。

なお WordPress 始動からの流れは以下がとても参考になります。特に前者は、被引用回数が多く各所で紹介されている有名記事といった印象です。

  1. WordPressの実行フローを視覚化してみる | Simple Colors
  2. Rewrite APIその1 「Rewriteとパーマリンク」(WordPressプラグイン開発のバイブルのボツ原稿から) – Shinichi Nishikawa's
  3. クエリ概要 – WordPress Codex 日本語版

準備

今回読んだのは WordPress 4.9.1 のソースコード。リリースアーカイブのページから zip をゲットです。

あとは展開してまるっと Sublime Text で開けたら、端から順に愚直に読んでいきましょう。

千里の道も /index.php から

WordPress は “全ての道は/index.phpへ通ず” なので、/index.phpさえ開ければあとは芋づる式です。なお/index.phpへたどり着くまでの流れは冒頭で紹介した西川さんのブログに明るいです。

私のメモ書きママですが、/index.phpからの流れをざーっと書くと…。

/index.php
	/wp-blog-header.php
		/wp-load.php
			/wp-config.php
				/wp-settings.php
					/wp-includes/load.php
					/wp-includes/default-constants.php
					/wp-includes/plugin.php
					/wp-includes/version.php
					wp_initial_constants()
					wp_check_php_mysql_versions()
					wp_unregister_GLOBALS()
					wp_fix_server_vars()
					wp_favicon_request()
					wp_maintenance()
					timer_start()
					wp_debug_mode()
					/wp-includes/compat.php
					/wp-includes/class-wp-list-util.php
					/wp-includes/functions.php
					/wp-includes/class-wp-matchesmapregex.php
					/wp-includes/class-wp.php
					/wp-includes/class-wp-error.php
					/wp-includes/pomo/mo.php
					require_wp_db()
					wp_set_wpdb_vars()
					wp_start_object_cache()
					/wp-includes/default-filters.php
					/wp-includes/l10n.php
					/wp-includes/class-wp-locale.php
					/wp-includes/class-wp-locale-switcher.php
					/wp-includes/class-wp-walker.php
					/wp-includes/class-wp-ajax-response.php
					/wp-includes/formatting.php
					/wp-includes/capabilities.php
					/wp-includes/class-wp-roles.php
					/wp-includes/class-wp-role.php
					/wp-includes/class-wp-user.php
					/wp-includes/class-wp-query.php
					/wp-includes/query.php
					/wp-includes/date.php
					/wp-includes/theme.php
					/wp-includes/class-wp-theme.php
					/wp-includes/template.php
					/wp-includes/user.php
					/wp-includes/class-wp-user-query.php
					/wp-includes/class-wp-session-tokens.php
					/wp-includes/class-wp-user-meta-session-tokens.php
					/wp-includes/meta.php
					/wp-includes/class-wp-meta-query.php
					/wp-includes/class-wp-metadata-lazyloader.php
					/wp-includes/general-template.php
					/wp-includes/link-template.php
					/wp-includes/author-template.php
					/wp-includes/post.php
					/wp-includes/class-walker-page.php
					/wp-includes/class-walker-page-dropdown.php
					/wp-includes/class-wp-post-type.php
					/wp-includes/class-wp-post.php
					/wp-includes/post-template.php
					/wp-includes/revision.php
					/wp-includes/post-formats.php
					/wp-includes/post-thumbnail-template.php
					/wp-includes/category.php
					/wp-includes/class-walker-category.php
					/wp-includes/class-walker-category-dropdown.php
					/wp-includes/category-template.php
					/wp-includes/comment.php
					/wp-includes/class-wp-comment.php
					/wp-includes/class-wp-comment-query.php
					/wp-includes/class-walker-comment.php
					/wp-includes/comment-template.php
					/wp-includes/rewrite.php
					/wp-includes/class-wp-rewrite.php
					/wp-includes/feed.php
					/wp-includes/bookmark.php
					/wp-includes/bookmark-template.php
					/wp-includes/kses.php
					/wp-includes/cron.php
					/wp-includes/deprecated.php
					/wp-includes/script-loader.php
					/wp-includes/taxonomy.php
					/wp-includes/class-wp-taxonomy.php
					/wp-includes/class-wp-term.php
					/wp-includes/class-wp-term-query.php
					/wp-includes/class-wp-tax-query.php
					/wp-includes/update.php
					/wp-includes/canonical.php
					/wp-includes/shortcodes.php
					/wp-includes/embed.php
					/wp-includes/class-wp-embed.php
					/wp-includes/class-oembed.php
					/wp-includes/class-wp-oembed-controller.php
					/wp-includes/media.php
					/wp-includes/http.php
					/wp-includes/class-http.php
					/wp-includes/class-wp-http-streams.php
					/wp-includes/class-wp-http-curl.php
					/wp-includes/class-wp-http-proxy.php
					/wp-includes/class-wp-http-cookie.php
					/wp-includes/class-wp-http-encoding.php
					/wp-includes/class-wp-http-response.php
					/wp-includes/class-wp-http-requests-response.php
					/wp-includes/class-wp-http-requests-hooks.php
					/wp-includes/widgets.php
					/wp-includes/class-wp-widget.php
					/wp-includes/class-wp-widget-factory.php
					/wp-includes/nav-menu.php
					/wp-includes/nav-menu-template.php
					/wp-includes/admin-bar.php
					/wp-includes/rest-api.php
					/wp-includes/rest-api/class-wp-rest-server.php
					/wp-includes/rest-api/class-wp-rest-response.php
					/wp-includes/rest-api/class-wp-rest-request.php
					/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
					/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
					/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
					/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php
					/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php
					/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php
					/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php
					wp_plugin_directory_constants()
					do_action( 'muplugins_loaded' )
					wp_cookie_constants()
					wp_ssl_constants()
					/wp-includes/vars.php
					create_initial_taxonomies()
					create_initial_post_types()
					wp_start_scraping_edited_file_errors()
					register_theme_directory( get_theme_root() )
					wp_register_plugin_realpath( $plugin )
					include_once( $plugin )
					/wp-includes/pluggable.php
					/wp-includes/pluggable-deprecated.php
					wp_set_internal_encoding()
					do_action( 'plugins_loaded' )
					wp_functionality_constants()
					wp_magic_quotes()
					do_action( 'sanitize_comment_cookies' )
					$GLOBALS['wp_the_query'] = new WP_Query()
					$GLOBALS['wp_query'] = $GLOBALS['wp_the_query']
					$GLOBALS['wp_rewrite'] = new WP_Rewrite()
					$GLOBALS['wp'] = new WP()
					$GLOBALS['wp_widget_factory'] = new WP_Widget_Factory()
					$GLOBALS['wp_roles'] = new WP_Roles()
					do_action( 'setup_theme' )
					wp_templating_constants(  )
					load_default_textdomain()
					$GLOBALS['wp_locale'] = new WP_Locale()
					$GLOBALS['wp_locale_switcher'] = new WP_Locale_Switcher()
					$GLOBALS['wp_locale_switcher']->init()
					do_action( 'after_setup_theme' )
					$GLOBALS['wp']->init()
					do_action( 'init' )
					do_action( 'wp_loaded' )
		wp() 
		/wp-includes/template-loader.php

とにかく目を引くのが/wp-settings.php。およそ100のファイルを読み込んでいます。途中にこんなコメントがあってちょっと笑ってしまいました。

// Load most of WordPress.

ファイル名の通りセッティングが主で、後半も後半で思い出したようにインスタンス生成を連発。そして最後にdo_action('init')を実行しています。見慣れたアクション名を発見した時はなんだかホッとしました。

/wp-settings.phpまでは正直ほとんど何もしていないので、次のwp()が WordPress の肝だろうと予想。途中 rest-api の文字が散見されたのでそれらも合わせてさらに読み進めます。

WP REST API 簡単まとめ

相変わらず前置きが長い、話が長いので、今回の目的の REST API に直接関係しそうな箇所だけのまとめに向かいましょう。

ポイントは2つ。

add_action( 'init', 'rest_api_init' )
add_action( 'parse_request', 'rest_api_loaded' )

いずれも/wp-includes/default-filters.phpに書かれています。

それでは見てみましょう。

/wp-settings.php
	/wp-includes/default-filters.php
		add_action( 'init',          'rest_api_init' )
		add_action( 'rest_api_init', 'rest_api_default_filters',   10, 1 )
		add_action( 'rest_api_init', 'register_initial_settings',  10 )
		add_action( 'rest_api_init', 'create_initial_rest_routes', 99 )
		add_action( 'parse_request', 'rest_api_loaded' )
		add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 )
	$GLOBALS['wp_the_query'] = new WP_Query()
	$GLOBALS['wp_query'] = $GLOBALS['wp_the_query']
	$GLOBALS['wp_rewrite'] = new WP_Rewrite()
	$GLOBALS['wp'] = new WP()
	do_action( 'init' )
		rest_api_init()
			rest_api_register_rewrites()
			$wp->add_query_var( 'rest_route' )
wp()
	$wp->main()
		$wp->init()
		$wp->parse_request($query_args)
			do_action_ref_array( 'parse_request', array( &$this ) )
				rest_api_loaded()
					$server = rest_get_server()
						do_action( 'rest_api_init', $wp_rest_server )
							rest_api_default_filters()
							register_initial_settings()
							create_initial_rest_routes()
						return new WP_REST_Server()
					$server->serve_request( $route )
						$request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path )
							return new WP_REST_Response( $response )
						$result = $server->check_authentication()
							return apply_filters( 'rest_authentication_errors', null )
								rest_cookie_check_errors()
						$result = $server->dispatch( $request )
						$result = rest_ensure_response( $result )
							return new WP_REST_Response( $result )
						$result = $server->response_to_data( $result, isset( $_GET['_embed'] ) )
						$result = wp_json_encode( $result )
						echo $result
						return null
					die()

先にあげた2つのadd_action()によって WordPress コアの流れに介入していることがわかります。逆に言えば、これがなければコアに影響しないわけで、REST API が WordPress 4.4 まではプラグインとして提供されていた様子が伺いしれます。

まずinitのアクションに引っ掛けて API のエンドポイントを追加。add_rewrite_rule()を使っています。

ちなみにデフォルトのエンドポイントには/wp-json/という接頭語がつきますよね。これがなんとなくモヤモヤしていたのですが、apply_filters( 'rest_url_prefix', 'wp-json' )という箇所があったので、簡単に加工できそうです。

ここまでは/wp-settings.php

続けてwp()の内部に入ります。REST API はparse_requestのアクションで介入しています。このアクションはWP::parse_request()の最後の最後で実行されるので、REST API が動きだす時点で、URLを解析してクエリ変数をセットするところまで完了しています。このへんの話は日本語codexに明るいです。

ついに REST API の本丸に辿り着いたように思います。長い旅も終わりが見えてきました。終わりが見えてきましたが、残念ながら終わる前に終わりです。ここまで読んでくれた方には申し訳ないですが、私と皆さんが乗った船は泥舟だったようです。

parse_requestのアクションに引っ掛けたあとの3つのクラス、WP_REST_Server, WP_REST_Response, WP_REST_Requestは、私には越えられない嵐で沈没してしまいました。

なんか色々したあとWP_REST_Server::dispatch()して、echo $resultするんですよ、きっと。

ごめんなさい。

おわり。