Создание надежного кода для полей, в седьмом Друпале
Это перевод моего блогпоста 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 модули все еще должны обновиться чтобы правильно заработать.

Comments
Post new comment