

皆さんこんにちは。
今回はWordpressの公開画面において他のPHPフレームワークのようにMVC風のフロントルーティングを適用したいと思い独自にカスタマイズをしたのでご紹介したいと思います。
もしswitch文で静的ルーティングしか実装できていない方やもっと動的ルーティングだったりフルルーティングをしたいといった方にかなりお勧めです。
静的パスはもちろん、動的パスにも対応した仕組みとなっているので汎用性的にも便利かなと思います!
ルーティングの処理を実装
今回はルーティングの処理を2つの用途に分けて実装していきます。
まずはルートを処理するファイルです。
※Route.phpというファイル名で作成しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
<?php class Route { protected static $instance; protected $routes = []; protected $idparam = ':'; protected static function get_instance() { if(!self::$instance instanceof Route) { self::$instance = new self(); } return self::$instance; } public static function get() { return self::get_instance(); } public function add(string $route, callable $fn) { $this->routes[$route] = [ 'fn' => $fn, 'args' => [] ]; return $this; } public function where(string $route, string $param, string $pattern) { if(isset($this->routes[$route])) { $this->routes[$route][$param] = $pattern; } return $this; } public function route(string $uri) { $route = $this->search_route($uri); if($route) { // マッチしたルートがあれば関数を呼び出す $route['fn'](...$route['args']); } } protected function search_route(string $uri) { $target = null; // uriパスを分割 $uripaths = []; if(strpos($uri, '/') !== false) { $uripaths = explode('/', $uri); } else { $uripaths[] = $uri; } $uripathcount = count($uripaths); // 指定ルートのパスを配列で取得 $routes = array_keys($this->routes); foreach ($routes as $route) { $routepaths = []; if(strpos($route, '/') !== false) { $routepaths = explode('/', $route); } else { $routepaths[] = $route; } // ルートパスとuriパスの数が一致しているか確認 if(count($routepaths) === $uripathcount) { // マッチするルートの存在を捜査 $match = true; for($i = 0; $i < $uripathcount; $i++) { if(strpos($routepaths[$i], $this->idparam) === false) { if($routepaths[$i] !== $uripaths[$i]) { $match = false; } } } if(!$match) continue; // ルートの動的パスを取得 $route_filters = array_filter($routepaths, function($path) { return strpos($path, $this->idparam) !== false; }); if(!empty($route_filters)) { // ルートの動的パスが存在する場合は条件に一致しているか確認する $route_filter_keys = array_keys($route_filters); $filters_uri = []; foreach ($route_filter_keys as $f_key) { $filters_uri[$f_key] = $uripaths[$f_key]; } // routeの動的パスの数と一致する場合 if(count($route_filters) === count($filters_uri)) { $pattern_match = true; $this->routes[$route]['args'] = []; foreach ($route_filters as $route_filter_key => $filter) { $arg = str_replace($this->idparam, '', $filter); if(isset($this->routes[$route][$arg])) { $pattern = $this->routes[$route][$arg]; if(!preg_match($pattern, $filters_uri[$route_filter_key], $filter_matches)) { $pattern_match = false; } else { // uriの動的queryを格納 $this->routes[$route]['args'][$arg] = $filter_matches[0]; } } else { // uriの動的queryを格納 $this->routes[$route]['args'][$arg] = $filters_uri[$route_filter_key]; } } if(!$pattern_match) { $this->routes[$route]['args'] = []; continue; } } } $target = $this->routes[$route]; } } return $target; } public function identify(string $param) { $this->idparam = $param; return $this; } } |
メインとなるルーティングの処理を作成しました。
基本的な使い方は”add”メソッドを呼び出しルートと関数を設定する仕組みになります。
続いてルートを追加し対象のルートに対して関数を実行するファイルを作成していきます。
※Routing.phpというファイル名で作成しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php class Routing { public function route() { global $wp_query; $uri = isset($wp_query->query_vars['my_action']) ? $wp_query->query_vars['my_action'] : null; if($uri) { $route = $this->routing(Route::get()); $route->route($uri); } } // ルーティングの設定 protected function routing(Route $route) { $route->add('login', function() { // ログイン処理関連の実装 })->add('register', function() { // 会員作成処理関連の実装 }); $route->add('topics', function() { // お知らせ関連の実装 }); $route->add('topics/:topic_id', function($topic_id) { // お知らせの詳細に関連する実装 })->where('topics/:topic_id', 'topic_id', "/^[0-9]+$/"); return $route; } } |
こちらは主にルートを追加しそのルートへのアクセスがあった場合に実行する関数を設定します。
静的パラメーターはそのままURLのパスを指定し呼び出す関数を設定します。
動的パラメーターの場合は”Route.php”の設定にもよりますが、ご紹介したものだと頭に”:”コロンを追記して動的に対応できるようにします。
動的化したいパスがある場合は”where”メソッドを別途呼び出し、対象のルートを第1引数に指定し第2引数に”:”コロンを除外した文字列を指定し第3引数にパターンにマッチさせたい条件を指定します。
マッチした値がリクエストのパスに存在していると対象の関数が呼び出され可変変数で引数として受け取れるようになっています。
まさにMVC風なルーティングの設定方法となっていますよね。
次は実際にこのルーティング設定が使えるようフックを追加していきます。
WordPressに必要なフックを追加する
まずはWordpressに必要なアクションフックやフィルターフックを追加していきます。
ご自身の各テーマ内にある”function.php”に以下を追記していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// rewriteルールを追加 function my_rewrite_rule() { add_rewrite_rule('^path/to/(.+)/?', 'index.php?my_action=$matches[1]', 'top'); flush_rewrite_rules(true); } add_action('init', 'my_rewrite_rule'); // queryの許可 add_filter('query_vars', function($query_vars) { $query_vars[] = 'my_action' return $query_vars; }); // templateの制御 add_action('template_redirect', function() { $routing = new Routing(); $routing->route(); }); |
フックに関する説明は省きますが、追加したコード内を解説します。
rewrite ルールの追加
add_rewrite_rule()という関数を使って独自のURLに沿ったrewriteルールを設定します。
今回追加したrewriteルールは”path/to/”以降のURLに対してパターンに沿ったリクエストが来た場合”my_action”というパラメーターでマッチした値を受け取ることができます。
“my_action”はお好みに合わせて設定を変更してください。
queryの許可
WordPressのデフォルトでは独自に追加したqueryはグローバル変数の”query_vars”に許可がされないため先ほど追加した”my_action”というパラメーターも取得できなくなってしまいます。
取得できるように”my_action”というパラメーターを許可しておきます。
templateの制御
WordPressのデフォルトでは受け取ったリクエストに対してテーマ内に存在するファイルを優先順位をもとにテンプレートが読み込まれ出力されます。
リクエストを受け取りテンプレートが出力するまでの中間処理を追加するためにtemplateの制御を設けます。
※Routingクラスは先ほど作成したRouting.phpを読み込ませる必要があります。
ルーティングがうまく動かない時
ここまでご紹介した設定を追加してもうまく動作しない場合はWordpressのパーマリンク設定を見直してみてください。
パーマリンクの設定を”基本”に設定している場合そもそもhtaccessのrewrite_ruleが記述されません。
今回のルーティング方法を活用するためには少なくとも”基本”以外のパーマリンクに設定する必要があります。
最後に
いかがでしたでしょうか。
この記事を書く前に最初はswitch文を使ったルーティングをしていたんですが、やはり動的にも対応したいなと思いわりと使い勝手も良かったので今回ご紹介させていただきました。
フレームワークに慣れている方だと感覚的にもわかりやすいものになっているのでぜひ試してみてください。