最近はWordpress+Gatsbyでサイト作ってる。
WordPressではないHeadressCMSを使うか、WordpressをHeadressCMS代わりにするかという二択になったとき、コストの低い方や運用の手間の少ない方が当然選ばれやすいのだが、「編集者が使いやすいもの」という条件を増やすと後者になりやすいし、長年運用されてたWordpressのリニューアルだと継続してWorpdressになりがちである。
投稿された西暦一覧と西暦ごとの投稿数を返したい
そんなWordpress+Gatsbyサイトを作ってる時に西暦だけのナビゲーションが必要になった。
post_type=post
における年月日ごとのアーカイブページとそのナビゲーションはwp_get_archives
関数1つで簡単に作れるんだけど、これをGraphQLでやろうとすると…???🤔
カテゴリーやタグは簡単に取れるんだけど、年月日的なのは存在していないようす。
テンプレートタグが返すのはHTMLテキストなのでそのままではparseして出力するしかない。それはそれで手っ取り早いのだが、wp_get_archives
関数の中身だけピンポイントで返すフィールドなんてのは存在してないし、JSXでごにょごにょできるように配列で西暦とカウント数だけ欲しいのだ。
投稿全件取得してgatsby-nodeでページ生成してるから、そのついでに西暦ナビゲーションも作ればいいのでは、と思ったんだけども、ナビゲーションコンポーネントの中でStaticQuery叩いて生成したいなぁという気持ちになったのでそのように作ってみることにした。
西暦一覧と西暦ごとの投稿数を取得
これはテンプレートタグである wp_get_archives
の中身を拝借して作った。
文字列ではなくて配列を返すようにしておく。
// functions.php
function get_year_archives() {
global $wpdb, $wp_locale;
$defaults = array(
'type' => 'yearly',
'limit' => '',
'order' => 'DESC',
'post_type' => 'post',
'year' => get_query_var( 'year' ),
'monthnum' => get_query_var( 'monthnum' ),
'day' => get_query_var( 'day' ),
'w' => get_query_var( 'w' ),
);
$parsed_args = wp_parse_args( $defaults );
$post_type_object = get_post_type_object( $parsed_args['post_type'] );
if ( ! is_post_type_viewable( $post_type_object ) ) {
return;
}
$parsed_args['post_type'] = $post_type_object->name;
$join = apply_filters( 'getarchives_join', '', $parsed_args );
$sql_where = $wpdb->prepare( "WHERE post_type = %s AND post_status = 'publish'", $parsed_args['post_type'] );
$where = apply_filters( 'getarchives_where', $sql_where, $parsed_args );
$order = strtoupper( $parsed_args['order'] );
$limit = $parsed_args['limit'];
$query = "SELECT YEAR(post_date) AS `year`, count(ID) as posts FROM $wpdb->posts $join $where GROUP BY YEAR(post_date) ORDER BY post_date $order $limit";
$last_changed = wp_cache_get_last_changed( 'posts' );
$key = md5( $query );
$key = "get_year_archives:$key:$last_changed";
$results = wp_cache_get( $key, 'posts' );
$output = array();
if ( ! $results ) {
$results = $wpdb->get_results( $query );
wp_cache_set( $key, $results, 'posts' );
}
if ( $results ) {
foreach ( (array) $results as $result ) {
array_push($output, array(
'year' => (int) $result->year,
'count' => (int) $result->posts
));
}
}
return $output;
}
WpGraphQLのフィールド追加
WordPressでGraphQLを使えるようにするプラグインであるWpGraphQLにはフィールドを追加する関数が備わっているので、それを利用する。
オブジェクトのキーと値の型定義(①、②)と、フィールドの設定(③)が必要。
add_action('graphql_register_types', function () {
$itemName = 'postYearCount';
$responseName = 'postYearCounts';
$itemProps = [
'year' => [ 'type' => 'Integer' ],
'count' => [ 'type' => 'Integer' ]
];
// ①オブジェクト単体のキーと値の型
register_graphql_object_type($itemName, [
'fields' => [
'year' => [ 'type' => 'Integer' ],
'count' => [ 'type' => 'Integer' ]
]
]);
// ②フィールドが返すキーと値の型
register_graphql_object_type($responseName, [
'fields' => [
'count' => [ 'type' => 'Integer' ],
'items' => [ 'type' => [ 'list_of' => $itemName ]],
'errors' => [ 'type' => [ 'list_of' => 'String' ]]
]
]);
// ③新しいフィールドの設定
register_graphql_field('RootQuery', $responseName, [
'type' => $responseName,
'args' => null,
'description' => __( '投稿のナビゲーション用', 'sample' ),
'resolve' => function($root, $args, $context, $info) {
$archives = get_year_archives();
return [
'count' => count($archives),
'items' => $archives
];
}
]);
});
GraphiQL IDEで表示できたらおk。
Gatsbyでの利用
register_graphql_field
によって追加したフィールドは、RootQueryを指定しているのでGraphiQL IDEではルートに並んでいたが、Gatsby側ではwp内に含まれる。
この場所の違い知ってないと、散々探した挙句追加されてないと勘違いしかねない…😂
あとはuseStaticQueryなどでよしなに使える。
const { wp: { newsYearCounts }, } = useStaticQuery(query)
const query = graphql`
query PostYearCounts {
wp {
postYearCounts {
items {
count
year
}
}
}
}
`
gatsby-source-wordpress 側にある説明では、以下のPostフィールドに追加する例がある。
add_action('graphql_register_types', function() {
register_graphql_field( 'Post', 'testGatsbyField', [
'type' => 'String',
'description' => __( 'Test field for demonstration', 'your-textdomain' ),
'resolve' => function() {
return 'gatsby test!';
}
]);
});
第一引数であるObject Typesの指定を変えれば、任意のフィールドに思うまま追加し放題なのだ。
Nuxtを3年使ってたから同じものを作るならNuxtの方が早いんだけど、Vue界隈は2と3が入り乱れてる微妙な時期なので、Vue3が浸透するまではTypeScript復習がてらReact触っとこうかなという気持ちでいる。