エンジニアリング部エンジニアリング課のひよっこエンジニアKと申します。最近私はwordpressについて勉強しているのですが、ふとその中で$query_stringという変数に出会いました。今回の記事では、$query_stringがどんな変数なのかと、それを知るにあたって私が調べた、$query_stringを使って記事を取得するコードを書いてから、データベースから$query_stringで指定された記事が引っ張ってこられて表示されるまでの三千里(嘘)の道のりをご紹介しようと思います。
今回$query_stringを使ったコード
今回私が$query_stringのコードを使ったページはarchive.phpのページでした。
| parse_str($query_string,$args); $args['posts_per_page']=5; $args['order']='DESC'; $query=newWP_Query($args); |
$query_stringをここでvar_dumpすると、
var_dump($query_string);
出力は以下の通りになりました。
[そのページがカテゴリーのアーカイブページの場合]
string(75)"category_name=%25e3%2582%25ab%25e3%2583%2586%25e3%2582%25b4%25e3%2583%25aa1"
[そのページが月別アーカイブページの場合]
string(21)"year=2019&monthnum=09"
そのページがカテゴリーのアーカイブページの時は、category_nameというキーにその値が、そのページが月間アーカイブページの時は、yearというキーとmonthnumというキーにそれぞれ値が入っていることがわかります。
結局$query_stringでやってたことってなんだったの?
今回$query_stringを使ったコードで、$query_stringを使って何をやっていたのか?を簡単に言うと、wp_title関数から取得した値を配列に変換し、表示する記事の条件を指定する一部を担っていた、となります。上記のページではparse_str関数を使い、$query_stringで指定した条件と$argsで指定した条件を$argsにまとめ、その$argsによってWP_Queryのインスタンスを生成することでデータベースから希望の条件をすべて満たした記事を引っ張ってきて表示していました。
今回の旅のゴール地点
今回の旅の最終地点は、どうやって変数$query_stringからSQL文が形成され、データベースからデータを持ってきているかの全貌を明らかにすることです。なので、以下の順に処理を追って、データベースに接続している箇所とデータベースにどうクエリを渡しているかを明らかにします。
①まず取得された値を文字列として変数に格納している箇所を探す
②文字列と文字列をくっつけてSQL文の文字列にしている箇所を探す
③データベース接続して、SQL文をデータベース側に渡している箇所を探す
$query_stringからデータベースへの接続までの三千里
①$query_stringに入っていた値を変数に格納する
まず$query_stringに入っていた値を変数に格納します。今回WP_Queryのインスタンスを生成しているときにデータベースに接続がなされていたようだったので(SQLログを調べました)、WP_Queryのクラス定義をしているファイルの中で、$query_stringに入っていた値を変数に渡している箇所がないか探しました。すると、
| if($q['year']) $date_parameters['year']=$q['year']; if($q['monthnum']) $date_parameters['monthnum']=$q['monthnum']; |
という辺りで年月の値が渡っていることが明らかになりました。どうやらここでまずは値を変数に格納しているようです。
②文字列と文字列をくっつけてSQL文にする
その後、$data_parametersという変数の定義を見てみると、
| if($date_parameters){ $date_query=newWP_Date_Query(array($date_parameters)); $where.=$date_query->get_sql(); } |
というような処理を見つけます。ここで$whereをvar_dumpしてみると、なんとSQLログで見たことのある文字列の一部が…!どうやらここでSQL文の切れ端が作られていたみたいです。
この$whereを使ってSQL文の形の文字列を作っている箇所がないか探すと…
$this->request=$old_request="SELECT $found_rows $distinct $fields FROM {$wpdb->posts} $join WHERE 1=1 $where $groupby $orderby $limits";
なんと、めちゃくちゃSQLのSELECT文の文字列を作ってそうな場所にめぐりあいました。
③データベース接続して、SQL文をデータベース側に渡す
その後、この$this->requestを使ってデータベース接続してそうな場所がないか探します。すると、ちょっと気になる箇所が出てきました。
$ids=$wpdb->get_col($this->request);
この$idsをvar_dumpすると、記事更新日時が新しい順の、$query_stringで指定した条件に当てはまる記事IDが5件出力されます。ということは、$idsに代入された時点では、もうデータベース接続に成功しています。つまり、それ以前にデータベース接続をしているということがわかります。$idsに代入される直前にあたるのは、$wpdbか、get_col()になります。まずは$wpdbの方を見てみましょう。
$wpdbとデータベース接続
$wpdbはWordPressのグローバル変数の一つで、今までのファイルとは違うload.phpに定義があります。さっそく定義箇所を見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | functionrequire_wp_db(){ global$wpdb; require_once(ABSPATH.WPINC.'/wp-db.php'); if(file_exists(WP_CONTENT_DIR.'/db.php')) require_once(WP_CONTENT_DIR.'/db.php'); if(isset($wpdb)){ return; } $wpdb=newwpdb(DB_USER,DB_PASSWORD,DB_NAME,DB_HOST); } |
最後でデータベース接続におなじみのDB_USERなどを使ったインスタンスが生成されていることがわかります。あともう一息!
wpdbというインスタンスを生成していたので、今度はwpdbというクラスの定義を探してみます。すると、wp-db.phpというファイルの、wpdbクラスのコンストラクタでこんな記述を見つけました。
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 | publicfunction__construct($dbuser,$dbpassword,$dbname,$dbhost){ register_shutdown_function(array($this,'__destruct')); if(WP_DEBUG&&WP_DEBUG_DISPLAY) $this->show_errors(); // Use ext/mysqli if it exists unless WP_USE_EXT_MYSQL is defined as true if(function_exists('mysqli_connect')){ $this->use_mysqli=true; if(defined('WP_USE_EXT_MYSQL')){ $this->use_mysqli=!WP_USE_EXT_MYSQL; } } $this->dbuser=$dbuser; $this->dbpassword=$dbpassword; $this->dbname=$dbname; $this->dbhost=$dbhost; // wp-config.php creation will manually connect when ready. if(defined('WP_SETUP_CONFIG')){ return; } $this->db_connect(); } |
どうやら、$this->db_connect();の部分でデータベース接続を行っていそうです!db_connect関数の定義の一部を見てみると、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | publicfunctiondb_connect($allow_bail=true){ // 中略 $this->has_connected=true; $this->set_charset($this->dbh); $this->ready=true; $this->set_sql_mode(); $this->select($this->dbname,$this->dbh); returntrue; } |
このselectという関数を使って、データベースを選択していそうなことがわかります。
実際、select関数の定義を調べてみると、mysqli_select_db関数でデータベースの選択を行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | publicfunctionselect($db,$dbh=null){ if(is_null($dbh)) $dbh=$this->dbh; if($this->use_mysqli){ $success=mysqli_select_db($dbh,$db); }else{ $success=mysql_select_db($db,$dbh); } //以下略 } |
ということは、やはりこの段階でデータベースに接続を行っていそうです。これでWordPressがどういう経緯でデータベースを接続していたか、全貌が明らかになりましたね。
get_col()とクエリ
めでたくデータベース接続の流れを解き明かせた次は、get_col()を使ってどうクエリを渡しているのかを見ていきましょう。さっそくget_col()の定義を確認してみます。
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 | publicfunctionget_col($query=null,$x=0){ if($this->check_current_query&&$this->check_safe_collation($query)){ $this->check_current_query=false; } if($query){ $this->query($query); } $new_array=array(); // Extract the column values for($i=0,$j=count($this->last_result);$i<$j;$i++){ $new_array[$i]=$this->get_var(null,$x,$i); } return$new_array; } |
この中で、$queryをvar_dumpしてみると、②の段階で見つけたSQLのSELECT文の切れ端を含むSELECT文が出力されました。一方で$this->last_resultや$new_arrayをvar_dumpしてみると、指定の記事IDが取得されてきていることがわかります。つまり、query関数を実行している辺りでデータベースにクエリが渡っていそうなことがわかります。さっそくquery関数の定義を見てみましょう。
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 | publicfunctionquery($query){ // 中略 $query=apply_filters('query',$query); //ここでvar_dumpするとすべてのSQL文が表示される $this->flush(); // Log how the function was called $this->func_call="\$db->query(\"$query\")"; // If we're writing to the database, make sure the query will write safely. if($this->check_current_query&&!$this->check_ascii($query)){ $stripped_query=$this->strip_invalid_text_from_query($query); // strip_invalid_text_from_query() can perform queries, so we need // to flush again, just to make sure everything is clear. $this->flush(); if($stripped_query!==$query){ $this->insert_id=0; returnfalse; } } $this->check_current_query=true; // Keep track of the last query for debug. $this->last_query=$query; $this->_do_query($query); // 中略 if(preg_match('/^\s*(create|alter|truncate|drop)\s/i',$query)){ $return_val=$this->result; }elseif(preg_match('/^\s*(insert|delete|update|replace)\s/i',$query)){ if($this->use_mysqli){ $this->rows_affected=mysqli_affected_rows($this->dbh); }else{ $this->rows_affected=mysql_affected_rows($this->dbh); } // Take note of the insert_id if(preg_match('/^\s*(insert|replace)\s/i',$query)){ if($this->use_mysqli){ $this->insert_id=mysqli_insert_id($this->dbh); }else{ $this->insert_id=mysql_insert_id($this->dbh); } } //中略 // Log number of rows the query returned // and return number of rows selected $this->num_rows=$num_rows; $return_val =$num_rows; } return$return_val; } |
$this->last_query = $query;直後でvar_dumpしてみると、まだすべてのSQL文が文字列として表示されています。 一方で、その後は$this->insert_id = mysqli_insert_id( $this->dbh );など、クエリを実行した結果に対しての返り値を変数に代入している部分が存在します。ということは、$this->last_query = $query;以下で少なくとも文字列がデータベース上に入り、実行されていることがわかります。さっそく$this->last_query = $query;直後に実行される、_do_query関数を見てみましょう。
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 | privatefunction_do_query($query){ if(defined('SAVEQUERIES')&&SAVEQUERIES){ $this->timer_start(); } if(!empty($this->dbh)&&$this->use_mysqli){ $this->result=mysqli_query($this->dbh,$query); }elseif(!empty($this->dbh)){ $this->result=mysql_query($query,$this->dbh); } $this->num_queries++; if(defined('SAVEQUERIES')&&SAVEQUERIES){ $this->queries[]=array($query,$this->timer_stop(),$this->get_caller()); } } |
定義の中をよく見てみると、mysqli_query関数がありました。この関数はデータベース上でクエリを実行する関数です。つまり、この時点でまさにクエリが実行され、データベースからデータを取得してきたということがわかりました。
今回のまとめ
①$query_stringでは、取ってくる記事を指定する条件の一部を担っている
②$query_stringからデータベース接続までの流れは、値を変数に代入→値を使った文字列をどんどん他の文字列とつなぎ合わせていく→データベースにそのクエリを引き渡してデータを貰ってくるの3段階になっている
③グローバル変数$wpdbによってデータベース接続が、(今回は)get_col関数によってデータベースにクエリが引き渡されている