Создание надежного кода для полей, в седьмом Друпале

Это перевод моего блогпоста Writing robust code that uses fields, in Drupal 7

В семерке изменился способ прямого доступа к полям (cck в друпале 6.x). В шестерке мы пишем:

<?php
$field_val = $node->field_yourfield[0]['value'];
?>

В семерке уже надо писать:

<?php
$field_val = $node->field_yourfield[LANGUAGE_NONE][0]['value'];
?>

(во всяком случае, так рекомендуют писать официальные доки).

То есть, у нас появилось разделение значений поля по языкам.

Я уж не знаю, насколько это упростило создание кода, который хорошо работает с несколькими языками (сложных мультиязычных проектов в d7 я пока не делал) - надеюсь, упростило серьезно, но для "обычных" сайтов с одним активным языком этот подход добавляет некоторую головную боль разработчикам.

Проблема здесь в том, что надеяться на правильную работу константы LANGUAGE_NONE нельзя!

Если админ сайта включает модуль Locale, и, например, активирует англ. язык, ваш код надо будет менять на что-то типа:

<?php
$field_val = $node->field_yourfield['en'][0]['value'];
?>

Очевидно, что программистам нужен более надежный способ доступа к значением полей, вне зависимсти от языковых настроек на сайте.

Первый подход: field_language()

Первый способ который я попробовал - определение активного языка для поля и ноды.

"Главный" язык контента:
http://api.drupal.org/api/drupal/developer--globals.php/global/language_...

Для поля:
http://api.drupal.org/api/drupal/modules--field--field.multilingual.inc/...

Но это работает не совсем так как я ожидал - когда ноды только создаются, если нода создается на англ. языке, все еще приходится использовать константу LANGUAGE_NONE. (Не могу дать 100% гарантию что это работает именно так, потому что уже прошло некоторое кол-во времени с момента моих разбирательств - но проблемы были в том что приходилось в разные этапы жизни ноды работать то с LANGUAGE_NONE то с ключом языка ('en', 'ru')

<?php
$language = field_language('node', $node, 'field_yourfield');
$field_val = $node->field_yourfield[$language][0]['value'];
?>

Я погуглил и нашел

Второй подход: field_get_items()

http://www.davereid.net/content/hlkd7fotw-field-get-items

<?php
$field_val = field_get_items('node', $node, 'field_yourfield');
?>

который работает неплохо. Но этот подход не совершенен - т.к. в одну строку кода не получится дернуть, например, второе значение в массиве multiple поля. (Я имею ввиду, что не получится сделать field_get_items('node', $node, 'field_yourfield')[1] - ну, во всяком случае, до php5.4) .

Так же, если вам надо быстрый доступ до пяти полей ноды, придется вызывать field_get_items() пять раз, по разу на каждое поле, а значит ваш код будет выглядеть... не идеально, мягко говоря.

и вот третий подход который я обнаружил, мне он показался самым удобным:

Третий подход: entity_metadata_wrapper

entity_metadata_wrapper это вспомогательный объект из клевого модуля Entity (fago - мой герой)

вот как выглядит его использование:

<?php
$obj = entity_metadata_wrapper('node', $node);
$field = $obj->field_yourfield->value();
?>

ну, неплохо - но пока не супер :) Что действительно круто, так это то что можно используя entity_metadata_wrapper подгружать объекты reference (поля user reference, node reference) на лету:

<?php
$involved_users = array();
//grab usernames from user reference field of a node
$project = entity_metadata_wrapper('node', $node);
// field_users is user reference field
foreach ($project->field_users as $acc) {
  $involved_users[] = $acc->value()->name;
}

var_dump($involved_users);
?>

когда мы вызываем метод value(), entity_metadata_wrapper знает, что поле - это user reference поле, и подгружает нужный аккаунт пользователя на лету. В шестерке это выглядело бы так:

<?php
// Drupal6 code
$involved_users = array();
//grab usernames from user reference field of a node

// field_users is user reference field, $project is node

foreach ($project->field_users as $acc) {
  $acc_object = user_load($acc['uid']);
  $involved_users[] = $acc_object->name;
}

var_dump($involved_users);
?>

Что еще интересно - $project->field_users это не массив, это объект, т.е. можно вызвать $project->field_users->value() чтобы получить массив всех аккаунтов на которое поле ссылается (говорим про multiple поле, опять же).

В то же время, этот объект поддерживает доступ как к массиву, т.е. можно использовать $project->field_users[0] или скормить field_users foreach'у как в примере выше. Если говорить языком PHP5, класс объекта поля реализует интерфейсы IteratorAggregate, ArrayAccess и Countable.

Еще немного примеров из readme.txt модуля entity:

<?php
$wrapper->author->mail = 'sepp@example.com';
?>

Чтобы получить текстовое значение, очищенное для безопасного вывода на экран, можно использовать

$wrapper->title->value(array('sanitize' => TRUE));

(в примере - получение заголовка ноды или entity). Если свойство возвращается "очищенным" по умолчанию, например body у ноды, возможно понадобится получить "неочищенное" значение. Для этого есть опция 'decode', которая гарантирует что все теги будут убраны, а HTML сущности раскодированы:

$wrapper->body->value->value(array('decode' => TRUE));

Т.е. так возвращаются данные в виде в каком они должны показываться пользователю. Если вам надо совсем сырые данные, без процессинга:

$wrapper->body->value->raw();

Еще можно сохранять ноды (и сущности, конечно):

<?php
$node = node_load(323);
$wrapper = entity_metadata_wrapper('node', $wrapper);
$wrapper->title = 'New title for the node';
$wrapper->save();
?>

Небольшая подсказка: для создания сущностей используем entity_create() или шорткат - entity_property_values_create_entity - которая принимает массив значений.

Я свй выбор сделал и в основном использую третий подход в своем коде.

Апдейт от Tom Nightingale:

Надо упомянуть что entity_metadata_wrapper работает только с полями которые "описали" свою структуру Entity API через свои хуки property_info(). Многие contrib модули все еще должны обновиться чтобы правильно заработать.

Blog categories: Drupal