- PVSM.RU - https://www.pvsm.ru -
Формы – в Symfony2 один из самых мощных инструментов, они представляют множество возможностей. Много секретов работы с Symfony2 описано в Книге рецетов [1]. Хочу представить вам перевод одного рецепта работы с формами, в Symfony 2 – использование дата трансформеров [2].
Часто возникает необходимость преобразовывать данные, введенные пользователем в форму в другой формат для использования в вашей программе. Можно легко сделать это вручную в контроллере, но как поступить, если вы хотите использовать эту форму в разных местах? Скажем, у вас есть объект «Task» (задачи) связанный соотношением кодин-к-одному с объектом «Issue» (проблемы), для каждой «Task» моможет быть указана опционально «Issue», которую она решает. Если в форму редактирования задач «Task», добавить выпадающий список из проблем «Issue», то нам будет очень тяжело в нем ориентироваться. Можна добавить текстовое поле вместо, выпадающего списка и вводить просто номер «Issue».
Вы можете попробовать сделать преобразование в контроллере, но это не самое лучшая идея. Было бы намного лучше, если бы номер «Issue» автоматически преобразовался объект «Issue». В этом случае в игру вступают «Data Transformers» (трансформеры данных).
Сначала создадим IssueToNumberTransformer класс – это класс будет отвечать за преобразование и из номера «Issue» в объект «Issue»:
// src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php
namespace AcmeTaskBundleFormDataTransformer;
use SymfonyComponentFormDataTransformerInterface;
use SymfonyComponentFormExceptionTransformationFailedException;
use DoctrineCommonPersistenceObjectManager;
use AcmeTaskBundleEntityIssue;
class IssueToNumberTransformer implements DataTransformerInterface
{
/**
* @var ObjectManager
*/
private $om;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* Transforms an object (issue) to a string (number).
*
* @param Issue|null $issue
* @return string
*/
public function transform($issue)
{
if (null === $issue) {
return "";
}
return $issue->getNumber();
}
/**
* Transforms a string (number) to an object (issue).
*
* @param string $number
* @return Issue|null
* @throws TransformationFailedException if object (issue) is not found.
*/
public function reverseTransform($number)
{
if (!$number) {
return null;
}
$issue = $this->om
->getRepository('AcmeTaskBundle:Issue')
->findOneBy(array('number' => $number))
;
if (null === $issue) {
throw new TransformationFailedException(sprintf(
'An issue with number "%s" does not exist!',
$number
));
}
return $issue;
}
}
Можно создать новый объект «Issue», когда пользователь ввел неизвестный номер а не выкидывать TransformationFailedException.
Теперь у нас есть трансформер, нужно просто добавить его к нашему полю «Issue» в той или иной форме.
use SymfonyComponentFormFormBuilderInterface;
use AcmeTaskBundleFormDataTransformerIssueToNumberTransformer;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
// this assumes that the entity manager was passed in as an option
$entityManager = $options['em'];
$transformer = new IssueToNumberTransformer($entityManager);
// add a normal text field, but add our transformer to it
$builder->add(
$builder->create('issue', 'text')
->addModelTransformer($transformer)
);
}
// ...
}
В этом примере необходимо передать EntityManager в качестве опции при создании формы. Позже вы узнаете, как можно создать пользовательское поле для номера «Issue», чтобы избежать необходимости передачи EntityManager
$taskForm = $this->createForm(new TaskType(), $task, array(
'em' => $this->getDoctrine()->getEntityManager(),
));
Круто, мы это сделали! Теперь пользователь сможет ввести номер в текстовом поле, и оно будет преобразовано объект «Issue». Это означает, что после успешного связывания ($form->bindRequest($request)), фреймворк форм предаст реальный объект «Issue» в метод :: setIssue () вместо номера «Issue».
Обратите внимание, что при добавление трансформера необходимо использовать несколько более сложный синтаксис чем при добавлении поля. Ниже приведен не правильный пример, как трансформер будет применяться ко всей форме, а не к конкретному полю:
// Это не правильно - трансформер будет применен ко всей форме
// смотрите пример выше для правильного использования трансформера
$builder->add('issue', 'text')
->addModelTransformer($transformer);
Новое в версии 2.1: название методов трансформетров были изменены в Symfony 2.1. prependNormTransformer стал addModelTransformer и appendClientTransformer стал addViewTransformer.
В приведенном выше примере, трансформер был использован в качестве «трансформера модели». В самом деле, есть два различных типа трансформеров и три различных типа исходных данных.
Есть два различных типа трансформеров, которые помогают нам преобразовывать данные из одного представления в другой.
Какой трансформер вам нужен, зависит от конкретной ситуации.
Чтобы использовать «View Transformer», вызывайте метод addViewTransformer.
В нашем примере, поле — текстовое поле, и мы ожидаем что текстовое поле будет всегда возвращать скалярные данные в «norm» и «view» форматах. И в данном случае наиболее приемлемый трансформер — «model transformer», который преобразует «norm data» в «model data» и обратно (номер «Issue» в объект «Isuuse» и обратно).
Разница между трансформерами очень тонкая и вы всегда должны думать что из себя должны представлять «norm» нормализированные данные. Например для текстового поля «norm» нормализированные данные — текстовая сторока, а для поля «date» — объект DateTime.
В примере который мы описали выше, мы используем трансформер для текстового поля. Сделать это было довольно просто, но у этого подхода есть два недостатка:
Потому нам наверное нужно создать пользовательский «кастомный» тип поля. Для начала создадим класс для пользователького типа поля:
// src/Acme/TaskBundle/Form/Type/IssueSelectorType.php
namespace AcmeTaskBundleFormType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use AcmeTaskBundleFormDataTransformerIssueToNumberTransformer;
use DoctrineCommonPersistenceObjectManager;
use SymfonyComponentOptionsResolverOptionsResolverInterface;
class IssueSelectorType extends AbstractType
{
/**
* @var ObjectManager
*/
private $om;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new IssueToNumberTransformer($this->om);
$builder->addModelTransformer($transformer);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected issue does not exist',
));
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'issue_selector';
}
}
Далее, зарегистрируем свой тип сервиса и пометим его тегом form.type так, чтобы поле распознавалось как пользовательский тип:
<service id="acme_demo.type.issue_selector" class="AcmeTaskBundleFormTypeIssueSelectorType">
<argument type="service" id="doctrine.orm.entity_manager"/>
<tag name="form.type" alias="issue_selector" />
</service>
Теперь можно использовать наш специальный тип issue_selector:
// src/Acme/TaskBundle/Form/Type/TaskType.php
namespace AcmeTaskBundleFormType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('task')
->add('dueDate', null, array('widget' => 'single_text'));
->add('issue', 'issue_selector');
}
public function getName()
{
return 'task';
}
}
Автор: vkalmuk
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/16022
Ссылки в тексте:
[1] Книге рецетов: http://symfony.com/doc/current/cookbook/index.html
[2] использование дата трансформеров: http://symfony.com/doc/current/cookbook/form/data_transformers.html
Нажмите здесь для печати.