Двунаправленные связи в ACF: настройка и ограничения

Обзор

В двунаправленной связи каждый объект (запись, страница, пользовательский тип записи, пользователь или таксономия) имеет поле связи, которое ссылается на другой объект. Данные связи хранятся в поле ACF на обоих объектах, что упрощает запросы.

В настоящее время настройка Bidirectional доступна только для полей Relationship, User, Taxonomy и Post Object

Настройка

При редактировании группы полей поддерживаемые типы полей будут показывать новую вкладку «Advanced» с переключателем Bidirectional.

Настройка двунаправленного поля.

Когда эта опция включена, вы сможете выбрать одно или несколько целевых полей, которые будут обновляться для каждого выбранного значения этого поля, ссылаясь обратно на редактируемый элемент. Это мощная и сложная функция. Поэтому стоит заранее продумать, какие связи вы хотите создать между полями разных типов элементов. Вот несколько примеров:

Поле Relationship, отображаемое для типа записи wc_product, под названием «Related Products». Если включить двунаправленность и установить Target Field в то же поле («This Field»), ID записи wc_product, которую вы редактируете, будет автоматически добавляться в поле related products у каждого выбранного значения.
Поле User, отображаемое для типа записи «Business Sectors», где выбранный пользователь указывает, с кем связаться по поводу этого бизнес-сектора. Если включить двунаправленность и установить Target Field в поле связи, выводимое у пользователя, при обновлении бизнес-сектора автоматически будут заполняться все сектора, за которые он отвечает, на его странице автора.

Основная сложность двунаправленных полей заключается в том, чтобы определить, где поле будет отображаться на экранах редактирования. ACF поддерживает вывод одного и того же поля в нескольких местах, например в записях, пользователях, таксономиях, страницах опций, блоках и т. д., поэтому «ID элемента», который обновляется, будет зависеть от того, где оно сейчас отображается. Обычно это ID записи, но это также может быть ID пользователя или ID термина таксономии. Однако нельзя сохранить ID пользователя в поле Post Object, поэтому Target Field для поля, отображаемого в профиле пользователя, должно быть полем User, которое будет обновляться.

Включенные двунаправленные поля, соответственно, будут обновлять свои целевые поля только тогда, когда они отображаются в Post, User или Taxonomy, и не будут работать на страницах опций, в блоках, виджетах или любом другом типе расположения, который не сводится к ID записи, пользователя или термина таксономии. Каждый раз, когда ACF пытается обновить Target Field, он проверяет, совместим ли тип обновляемого элемента с типом целевого поля. Если нет, обновление будет пропущено.

Вы также не можете выстраивать цепочку двунаправленных обновлений; будет обновлено только выбранное Target Fields с текущим ID редактируемого элемента. Если у Target Field тоже включена двунаправленность, он не будет обновляться дальше исходного целевого поля.

Известные проблемы

Настройка bidirectional в настоящее время только включает одностороннюю синхронизацию данных в целевое поле и не настраивает обратную двунаправленность (если только вы не выберете This Field в качестве Target Field). Если вы хотите включить двустороннюю синхронизацию данных между двумя разными полями, вам нужно включить настройку bidirectional и на поле Target Field тоже, а затем указать в качестве цели исходное поле.

Двунаправленные поля в настоящее время поддерживают только Target Fields, которые являются полями верхнего уровня, а не полями, вложенными в любой другой тип поля, включая поля Group.

В настоящее время, хотя вы можете выбрать поле, которое разрешает только одно значение или ограниченное число значений, это не учитывается, когда поле обновляется из исходного двунаправленного поля. Это означает, например, что если вы пытаетесь сделать связь один ко многим из поля «User» в записи, и более чем один User ссылается на эту запись, она не будет автоматически удалена у другого User, у которого она была выбрана ранее, пока вы вручную не сохраните запись. Это не должно вызывать ошибок отображения или шаблонизации, поскольку поля, настроенные на единственное значение, будут возвращать только одно значение внутри get_field, но может привести к путанице в интерфейсе.

Реализация через код в старых версиях

До ACF 6.2 единственным способом создать двунаправленную связь был код.

Просмотреть код
Следующий фрагмент добавляет функцию, которая подключается к фильтру acf/update_value (он срабатывает до сохранения значения). Он обновит значение пользовательского поля у каждой выбранной записи, добавив ID текущей записи, а также удалит ID текущей записи из ранее выбранных записей, которые больше не выбраны.

Эта функция не содержит жестко прописанных имен полей, поэтому будет работать с любым полем связи. Единственное изменение требуется в параметре add_filter(). В нем содержится имя поля связи, которое в примере ниже называется ‘related_posts’.

functions.php

function bidirectional_acf_update_value( $value, $post_id, $field  ) {
    
    // переменные
    $field_name = $field['name'];
    $field_key = $field['key'];
    $global_name = 'is_updating_' . $field_name;
    
    
    // прерываем выполнение, если этот фильтр был вызван функцией update_field(), которая вызывается внутри цикла ниже
    // - это предотвращает бесконечный цикл
    if( !empty($GLOBALS[ $global_name ]) ) return $value;
    
    
    // задаем глобальную переменную, чтобы избежать бесконечного цикла
    // - также можно было бы вызвать remove_filter(), а затем снова add_filter(), но так проще
    $GLOBALS[ $global_name ] = 1;
    
    
    // проходим по выбранным записям и добавляем этот $post_id
    if( is_array($value) ) {
    
        foreach( $value as $post_id2 ) {
            
            // загружаем существующие связанные записи
            $value2 = get_field($field_name, $post_id2, false);
            
            
            // разрешаем, чтобы у выбранных записей не было значения
            if( empty($value2) ) {
                
                $value2 = array();
                
            }
            
            
            // прерываем выполнение, если текущий $post_id уже найден в $value2 выбранной записи
            if( in_array($post_id, $value2) ) continue;
            
            
            // добавляем текущий $post_id к значению 'related_posts' выбранной записи
            $value2[] = $post_id;
            
            
            // обновляем значение выбранной записи (для производительности используем ключ поля)
            update_field($field_key, $value2, $post_id2);
            
        }
    
    }
    
    
    // находим записи, которые были удалены
    $old_value = get_field($field_name, $post_id, false);
    
    if( is_array($old_value) ) {
        
        foreach( $old_value as $post_id2 ) {
            
            // прерываем выполнение, если это значение не было удалено
            if( is_array($value) && in_array($post_id2, $value) ) continue;
            
            
            // загружаем существующие связанные записи
            $value2 = get_field($field_name, $post_id2, false);
            
            
            // прерываем выполнение, если значения нет
            if( empty($value2) ) continue;
            
            
            // находим позицию $post_id в $value2, чтобы удалить его
            $pos = array_search($post_id, $value2);
            
            
            // удаляем
            unset( $value2[ $pos] );
            
            
            // обновляем значение невыбранной записи (для производительности используем ключ поля)
            update_field($field_key, $value2, $post_id2);
            
        }
        
    }
    
    
    // сбрасываем глобальную переменную, чтобы этот фильтр работал как обычно
    $GLOBALS[ $global_name ] = 0;
    
    
    // возвращаем
    return $value;
    
}

add_filter('acf/update_value/name=related_posts', 'bidirectional_acf_update_value', 10, 3);

Обновлено: 01.06.2026