Формы Symfony2 и коллекции - Заказ, внедрение OrderItem

Таким образом, я провел приблизительно 5 или 6 часов сегодня, борясь с формами Symfony2, и в пункте, где я хотел бы некоторый совет от других членов сообщества. Я попробовал более чем 3 различных метода, чтобы достигнуть того, что я после и не имел никакого успеха. Я прочитал докторов, googled все, спросил других, и я только немного более обеспечен чем тогда, когда я начал.

Мой вариант использования

Я строю систему, где можно заказать билеты. Но основная проблема состоит в том, как проектировать часть заказа системы.

  • у Билета есть имя и даты начала и даты окончания, где это доступно (другой материал также, но позволяет, сохраняет пример простым.
  • <сильному> Заказу можно было выбрать многочисленные Билеты и для каждого Билета, есть количество.
  • Заказ есть Клиент . Эта часть прекрасна и работает денди !

После чтения вокруг и попытки разных вещей, я заключил, что, чтобы представлять Билет и количество Заказа, мне было нужно другое предприятие, от которого ЗаказБилет соответствует ЗаказItem https://github.com/beberlei/AcmePizzaBundle и Пицца - мой Билет.

  • ЗаказБилет есть Билет и количество.

На моей странице заказа, где заказ создается, я хочу следующее:

  • форма для Потребительских деталей - имя, электронная почта, адрес. Эта часть хорошо работает .
  • форма для Билетов . Я хочу показанное название Билета в textbox или даже последовательности; не в избранной коробке (который является тем, что происходит теперь). Я хочу, чтобы количество было определено рядом с названием билета. Если нет никакого набора количества, это означает, что билет не отобран.
  • Билеты должны быть фильтрованы , где они доступны в зависимости от сегодняшней даты - это достигается в другом месте (в администраторе бэкенда, где они создаются) при помощи таможенного метода хранилища на типе формы с закрытием конструктора запросов.

Мой бэкэнд

The Заказ/ЗаказБилет/Билет design is largely based on https://github.com/beberlei/AcmePizzaBundle

Билет

/**
 * @ORM\Entity(repositoryClass="Foo\BackendBundle\Entity\БилетsRepository")
 * @ORM\HasLifecycleCallbacks
 * @ORM\Table(name="Билетs")
 */
class Билетs
{
   //id fields and others

    /**
     * @Assert\NotBlank
     * @ORM\Column(type="string", nullable=true)
     */
    protected $name;

    /**
     * @ORM\Column(type="date", name="available_from", nullable=true)
     */    
    protected $availableFrom;

    /**
     * @ORM\Column(type="date", name="available_to", nullable=true)
     */    
    protected $availableTo;
}

ЗаказБилет

/**
 * @ORM\Table()
 * @ORM\Entity
 */
class ЗаказБилет
{
   //id field

    /**
     * @ORM\Column(name="quantity", type="integer")
     */
    private $quantity;

    /**
     * @ORM\ManyToOne(targetEntity="Билетs")
     */
    protected $Билет;

    /**
     * @ORM\ManyToOne(targetEntity="Заказs", inversedBy="Билетs")
     */
    protected $Заказ;

   //getters and setters for quantity, Билет and Заказ
}

Заказ

/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 * @ORM\Table(name="Заказs")
 */
class Заказs
{   
   //id field and other stuff

    /**
     * @ORM\OneToMany(targetEntity="ЗаказБилет", mappedBy="Заказ", cascade={"persist"})
     **/
    protected $Билетs;

    /**
     * @ORM\ManyToOne(targetEntity="Клиент", cascade={"persist"})
     */
    protected $Клиент;

    public function __construct()
    {
        $this->Билетs = new \Doctrine\Common\Collections\ArrayCollection();
    }

   //getters, setters, add for Билетs and Клиент
}

Клиент

/**
 * @ORM\Table()
 * @ORM\Entity
 */
class Клиент
{
   //id, name, email, address fields

}

Это создает схему как так (стол, называющий различия, от авто поколения):

CREATE TABLE `Билетs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `available_from` date DEFAULT NULL,
  `available_to` date DEFAULT NULL,
  PRIMARY KEY (`id`)
);
CREATE TABLE `Клиент` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `address` longtext COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
);
CREATE TABLE `ЗаказБилет` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `Билет_id` int(11) DEFAULT NULL,
  `Заказ_id` int(11) DEFAULT NULL,
  `quantity` int(11) NOT NULL,
  PRIMARY KEY (`id`)
);
CREATE TABLE `Заказs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `Клиент_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

Формы

class КлиентType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email')
            ->add('name')
            ->add('address')
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Foo\BackendBundle\Entity\Клиент'
        ));
    }

    public function getName()
    {
        return 'foo_backendbundle_Клиентtype';
    }
}

class ЗаказБилетType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('quantity', 'integer')
            ->add('Билет')
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Foo\BackendBundle\Entity\ЗаказБилет'
        ));
    }

    public function getName()
    {
        return 'foo_backendbundle_ЗаказБилетtype';
    }
}

class ЗаказsType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('Клиент', new КлиентType())
            ->add('Билетs', 'collection', array(
                'type' => new ЗаказБилетType(),
                'allow_add'    => true,
                'allow_delete' => true,
                'prototype'    => true,
            ))
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Foo\BackendBundle\Entity\Заказs',
        ));
    }

    public function getName()
    {
        return 'foo_backendbundle_Заказstype';
    }
}

Форма

<form action="{{ path('index') }}" method="post" {{ form_enctype(form) }}>
    

Билетs

{{ form_errors(form) }} <table> <thead> <tr> <td>Билет</td> <td>Quantity</td> </thead> <tbody> {% for Билетrow in form.Билетs %} <tr> <td>{{ form_widget(Билетrow.Билет) }}</td> <td>{{ form_widget(Билетrow.quantity) }}</td> </tr> {% endfor %} </tbody> </table>

Клиент

{% for Клиент in form.Клиент %} {{ form_row(Клиент) }} {% endfor %} </form>

И наконец диспетчер

class DefaultController extends Controller
{
    /**
     * @Route("/", name="index")
     * @Template()
     */
    public function indexAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
       //IMPORTANT - the Билетs are prefiltered for active Билетs, these have to be injected into the Заказ atm. In other places I use this method on the query builder
        $Билетs = $em->getRepository('FooBackendBundle:Билетs')->findActive();

       //check no Билетs

        $Заказ = new Заказs();

       //To prepopulate the Заказ with the available Билетs, we have to do it like this, due to it being a collection,
       //rather than using the Формы query_builder like everywhere else
        foreach($Билетs as $Билет) {
            $ot = new ЗаказБилет();
            $ot->setБилет($Билет);
            $ot->setQuantity(0);
            $ot->setЗаказ($Заказ);
            $Заказ->addБилет($ot);
        }

        $form = $this->createForm(new ЗаказsType(), $Заказ);

        if ($request->isMethod('POST')) {

            $form->bind($request);

           //IMPORTANT here I have to remove the previously added Билетs where the quantity is 0 - as they're not wanted in the Заказ.  Is there a better way to do this?
           //if the quantity of Билет is 0, do not add to Заказ
           //note we use the validation callback in Заказ to check total quantity of ЗаказБилетs is > 0
            $Заказ->removeБилетsWithNoQuantity();

            if ($form->isValid()) {

                $em->persist($Заказ);
                $em->flush();

                return $this->redirect($this->generateUrl('Заказ_show', array('id' => $Заказ->getId())));
            }
        }

        return array('form' => $form->createView());
    }
}

Резюме

This works and will save the Заказ correctly, but I'm not sure it is the correct way to do what I want, and it does not display as I want.

You can see below in the images how it looks and how the Заказ goes through. It is worth noting that in each of the Билет drop downs is the rest of the Билетs but which are not active.

The Заказ page:

Заказ1

The Заказ Резюме page after save:

Заказ2

The 3 Билетs displayed are the ones that have been filtered, and I only want these Билетs on Форма. I ONLY WANT TO SEE THE Билет NAME, NOT AN EDITABLE DROP DOWN.

The core problem is that they are presented as editable drop downs. I may just want a text string of the Билет name, or maybe even the Билет price in the future. I'm not sure how to achieve this. I know that the Билет field and relationship must be rendered somehow so that it can be bound in the controller. So basically I want to be able to use the Билет entity and it's fields on the same row as the quantity text box.

So let's step out of the crapstorm of Symfony2's Формы and put this in perspective - in the normal world, obviously I'd just retrieve the Билетs, then for each Билет, I'd print the Билет name, any other stuff I wanted, a hidden Билет id, then an input for the Билет quantity. Back into SF2 a little - I guess I need the Билет entity available whilst looping the ЗаказБилет collection.

Пожалуйста, помоги мне!

5
nl ja de

1 ответы

Самое легкое решение на основе вашего кода выше было бы, создают таможенный тип для вашего предприятия Билета, которое просто показывает текущий билет как этикетку, и также создайте трансформатор данных для него.

namespace WineVision\BackendBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\FormBuilderInterface;

use WineVision\BackendBundle\Form\Transformer\TicketToIdTransformer;

class TicketLabelType extends AbstractType
{
    private $om;

    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new TicketToIdTransformer($this->om);
        $builder->addViewTransformer($transformer);
    }    

    public function getParent()
    {
        return 'hidden';
    }

    public function getName()
    {
        return 'ticket_label_type';
    }    
}

Тогда создайте виджет в Resources/Form/fields.html.twig

{% block ticket_label_type_widget %}
    {% spaceless %}
    <input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %} />
    {{ form.vars.data.ticketNameMethod }}
    {% endspaceless %}
{% endblock %}

TicketToIdTransformer:

namespace WineVision\BackendBundle\Form\Transformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;

use Doctrine\Common\Persistence\ObjectManager;

class TicketToIdTransformer implements DataTransformerInterface
{
    private $om;

    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    public function transform($ticket)
    {
        if (null === $ticket) {
            return "";
        }

        if (!$ticket instanceof \WineVision\BackendBundle\Entity\Ticket) {
            throw new UnexpectedTypeException($ticket, '\WineVision\BackendBundle\Entity\Ticket');
        }


        return $ticket->getId();
    }

    public function reverseTransform($id)
    {

        if ('' === $id || null === $id) {
            return null;
        }

        return $this->om
                    ->getRepository('WineVisionBackendBundle:Ticket')
                    ->findOneBy(array('id' => $id));

    }
}

Тогда создайте обслуживание для своего TicketType и передайте доктрину orm.entity_manager как аргумент, и в вашем OrderTicketType, используйте

$builder->add('ticket', 'ticket_label_type');

Это должно решить вашу проблему для кода, который вы дали выше. Чтобы далее расширить решение, вы не должны предварительно населять каждый заказ с каждым типом билета, и вместо этого создавать обычай collectiontype, который использует События Формы, чтобы населить коллекцию со всеми областями билета.

Надежда это помогает! Я приношу извинения, если есть какие-либо синтаксические ошибки здесь. Я скопировал код с одного из моих заявлений и изменил его для ваших потребностей.

3
добавлено
symfony.com/doc/2.0/cookbook/form/dynamic_form_generation.ht‌ ​ ml .. Вы могли передать querybuilder как аргумент конструктора вашей форме и подписчику формы. у xanido есть хорошее описание каждого события формы здесь: stackoverflow.com/a/9654501/312201 .. Если вы делаете некоторый googling, you' ll находят некоторые хорошие примеры на различных вещах, которых можно достигнуть с событиями формы.
добавлено автор Mike, источник
Слова благодарности очень для вашего ответа и примеров - действительно полезный. Ключевая вещь I' устраненный ve получает доступ к данным с {{form.vars.data.ticketNameMethod}} I' m собирающийся начало, чтобы изучить события формы, у вас есть какие-либо примеры, которые выполняют ту же самую задачу как предварительное заселение объекта? Можно ли использовать события формы и установить ли конструктор запросов?
добавлено автор jmoz, источник
phpGeeks
phpGeeks
3 620 участник(ов)

Best PHP chat Еще: @dbGeeks - базы данных @phpGeeksJunior - новичкам @moscowProgers - IT Москва @ebanoePhp - весёлый канал о PHP @laravel_pro - Laravel @jobGeeks - вакансии @jsChat - JS Правила: https://t.me/phpGeeks/764859 ДР - 28.03.2016

PHP
PHP
1 309 участник(ов)

Группа про современный PHP. Обсуждаем ООП, TDD, BDD, DDD, SOLID, GRASP и прочие крутые базворды Для ознакомления: https://gist.github.com/mkusher/711bd46f0b62fbae851182e6fb3b1839 Группа PHP для новичков @phpGeeksJunior Вакансии: https://t.me/fordev

PHP — вакансии, поиск работы и аналитика
PHP — вакансии, поиск работы и аналитика
1 251 участник(ов)

Публикуем вакансии и запросы на поиск работы по направлению PHP, Laravel, Symfony, Yii и т.д. Здесь всё: full-time, part-time, remote и разовые подработки. См. также: @qa_jobs, @devops_jobs, @javascript_jobs, @nodejs_jobs, @uiux_jobs, @products_jobs

symfony
symfony
1 045 участник(ов)

Сообщество Symfony, Symfony Components, Symfony Framework. Вакансии: https://t.me/symfony_careers Официальный slack: https://symfony.com/slack-invite

phpGeeksJunior
phpGeeksJunior
980 участник(ов)

Группа для новичков. Не стесняйтесь задавать вопросы по php. Не флудить!!!! Правила и полезные ссылки https://gist.github.com/exileed/a53dd0617b35a705ff44b38c8028e6a5 Бест от пхпгикс https://t.me/best_of_phpgeeks

phpclub.ru
phpclub.ru
872 участник(ов)

Официальный чат phpclub.ru - остерегайтесь подделок #rules Правила группы - уважайте друг друга. Скриншоты -> ссылками. Код -> pastebin.com. Вакансии строго -> https://goo.gl/4bNxym, в чат ссылку. За рекламу и мат - БАН!

symfony
symfony
354 участник(ов)

Devall | PHP
Devall | PHP
272 участник(ов)

Пристанище для восходящих звёзд разработки, которые перейдут на более адекватные языки. http://combot.org/chat/-1001014863761 Инвайт: j.mp/devallphp