2026年5月21日
2026年5月21日
ACFリレーションシップフィールドが正しく動かない問題を解決する方法
はじめに
ACFのRelationship(リレーションシップ)フィールドやPost Objectフィールドで、選択した投稿が保存されない・フロントエンドで取得できない・管理画面の検索が遅すぎるといった問題は、フィールド設定の誤り・データ形式の不一致・クエリの最適化不足が原因です。
症状・原因
- Relationshipフィールドで選択した投稿が保存後に消える
get_field('related_posts')が投稿オブジェクトの配列ではなくIDの配列を返す- 管理画面でRelationshipフィールドの検索が重くタイムアウトする
- 双方向リレーションシップが片方しか更新されない
解決手順
ステップ1:リレーションシップフィールドの状態を確認する
# Relationshipフィールドの保存データを確認
wp eval "
\$post_id = 123;
// ACFのget_fieldで取得(return_format設定に依存)
\$related = get_field('related_posts', \$post_id);
echo 'Type: ' . gettype(\$related) . PHP_EOL;
if (is_array(\$related)) {
echo 'Count: ' . count(\$related) . PHP_EOL;
foreach (\$related as \$item) {
if (is_object(\$item)) {
echo ' Post: ' . \$item->ID . ' - ' . \$item->post_title . PHP_EOL;
} else {
echo ' ID: ' . \$item . PHP_EOL;
}
}
}
// postmetaに直接保存されたデータを確認
\$raw = get_post_meta(\$post_id, 'related_posts', true);
echo 'Raw meta: ' . print_r(\$raw, true) . PHP_EOL;
"
ステップ2:フィールド設定とreturn_formatを修正する
// functions.php: ACF Relationshipフィールドをコードで定義
add_action('acf/init', function(): void {
acf_add_local_field_group([
'key' => 'group_related_content',
'title' => '関連コンテンツ',
'fields' => [
[
'key' => 'field_related_posts',
'label' => '関連記事',
'name' => 'related_posts',
'type' => 'relationship',
'post_type' => ['post', 'product'], // 対象の投稿タイプ
'taxonomy' => [],
'filters' => ['search', 'post_type', 'taxonomy'],
'min' => 0,
'max' => 5,
'return_format' => 'object', // 'id' or 'object'
],
[
'key' => 'field_main_category',
'label' => 'メインカテゴリ',
'name' => 'main_category',
'type' => 'post_object',
'post_type' => ['category'],
'allow_null' => true,
'multiple' => false,
'return_format' => 'id', // IDだけ保存してメモリを節約
'ui' => true,
],
],
'location' => [[['param' => 'post_type', 'operator' => '==', 'value' => 'post']]],
'active' => true,
]);
});
ステップ3:リレーションシップデータを正しく取得・表示する
// functions.php: Relationshipフィールドの値を取得してループ
// ① return_format='object'の場合(推奨:追加クエリ不要)
function get_related_posts_html(int $post_id): string {
$related = get_field('related_posts', $post_id);
if (!$related || !is_array($related)) {
return '';
}
$html = '<ul class="related-posts">';
foreach ($related as $post) {
if (!($post instanceof WP_Post)) continue;
setup_postdata($post);
$html .= sprintf(
'<li><a href="%s">%s</a></li>',
esc_url(get_permalink($post)),
esc_html($post->post_title)
);
}
wp_reset_postdata();
$html .= '</ul>';
return $html;
}
// ② return_format='id'の場合(IDから投稿を取得)
function get_related_posts_by_ids(int $post_id): array {
$ids = get_field('related_posts', $post_id);
if (!$ids || !is_array($ids)) {
return [];
}
// 一括でまとめて取得(N+1クエリを防ぐ)
return get_posts([
'post__in' => $ids,
'orderby' => 'post__in', // IDの順序を維持
'numberposts' => -1,
'post_status' => 'publish',
'no_found_rows' => true, // ページネーション不要ならtrue
]);
}
ステップ4:双方向リレーションシップを実装する
// functions.php: 双方向リレーションシップの自動同期
add_action('acf/save_post', function(int $post_id): void {
// 無限ループ防止
if (defined('DOING_ACF_RELATIONSHIP_SYNC') && DOING_ACF_RELATIONSHIP_SYNC) return;
define('DOING_ACF_RELATIONSHIP_SYNC', true);
$related_ids = get_field('related_posts', $post_id, false); // IDで取得
if (!is_array($related_ids)) $related_ids = [];
// 以前のリレーションシップを取得(削除されたものを処理するため)
$previous_ids = get_post_meta($post_id, '_previous_related_posts', true) ?: [];
// 新しく追加されたID
$added = array_diff($related_ids, $previous_ids);
// 削除されたID
$removed = array_diff($previous_ids, $related_ids);
// 追加されたポストに逆方向のリレーションを追加
foreach ($added as $related_id) {
$reverse = get_field('related_posts', (int)$related_id, false) ?: [];
if (!in_array($post_id, $reverse, true)) {
$reverse[] = $post_id;
update_field('related_posts', $reverse, (int)$related_id);
}
}
// 削除されたポストから逆方向のリレーションを削除
foreach ($removed as $related_id) {
$reverse = get_field('related_posts', (int)$related_id, false) ?: [];
$reverse = array_values(array_diff($reverse, [$post_id]));
update_field('related_posts', $reverse, (int)$related_id);
}
// 現在のIDリストを保存(次回の差分計算用)
update_post_meta($post_id, '_previous_related_posts', $related_ids);
}, 20);
ステップ5:パフォーマンスを最適化する
// functions.php: Relationshipフィールドの検索クエリを最適化
// ① 管理画面の検索対象を絞り込む(重い場合)
add_filter('acf/fields/relationship/query/name=related_posts', function(array $args): array {
$args['posts_per_page'] = 20; // 一度に取得する件数を制限
$args['post_status'] = 'publish';
$args['orderby'] = 'modified';
$args['order'] = 'DESC';
// カスタムメタが必要な場合は除外(重いmeta_queryを避ける)
unset($args['meta_query']);
return $args;
});
// ② フロントエンドでのキャッシュ(Transient利用)
function get_cached_related_posts(int $post_id): array {
$cache_key = 'related_posts_' . $post_id;
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
$related = get_field('related_posts', $post_id) ?: [];
set_transient($cache_key, $related, HOUR_IN_SECONDS * 6);
return $related;
}
// 投稿更新時にキャッシュを削除
add_action('acf/save_post', function(int $post_id): void {
delete_transient('related_posts_' . $post_id);
}, 30);
注意事項
get_field()の戻り値の型はフィールド設定の「Return Format」によって変わります。object設定ならWP_Postオブジェクトの配列、id設定なら整数IDの配列が返ります。テンプレートで使う際は必ず型を確認してください- Relationshipフィールドで保存できる最大件数はフィールド設定の
maxで制限できます。制限なしにすると大量データの保存でDBが肥大化する恐れがあります - 双方向リレーションシップの自動同期は
acf/save_post内で再帰的に実行されるため、必ずDOING_ACF_RELATIONSHIP_SYNCのような定数で無限ループを防いでください
まとめ
ACF Relationshipフィールドの修復は①get_post_meta()でrawデータを確認してreturn_format設定と一致させ、②acf_add_local_field_group()でフィールドをPHP定義に移行、③return_format='object'で追加クエリなし取得またはget_posts(['post__in'=>$ids])でN+1クエリを防ぎ、④acf/save_postフックで双方向リレーションを自動同期(無限ループ防止必須)、⑤acf/fields/relationship/queryフィルターで管理画面検索を最適化してTransientでフロント表示をキャッシュする手順で解決します。