Наследование таблицы Doctrine с набором полей zf3

Я работаю над проектом, используя Zend Framework 3 и Doctrine 2, используя для интеграции DcotrineModule следующее моделирование сущностей, с которым у меня проблемы:

Наследование таблицы Doctrine с набором полей zf3

Чтобы работать с этим моделированием с помощью доктрины, которую я использую @InheritanceType, ниже приведены соответствующие выдержки из Entities:

Пессоа Сущность:

/**
 * Abstração de Pessoa
 *
 * @author Rodrigo Teixeira Andreotti <[email protected]>
 * 
 * @Entity
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name = "tipo", type = "string")
 * @DiscriminatorMap( { "pessoa" = "Pessoa", 
 *                      "pessoa_fisica" = "PessoaFisica",
 *                      "pessoa_juridica" = "PessoaJuridica" } )
 * @Table(name = "pessoa")
 */
abstract class Pessoa implements JsonSerializable, PessoaInterface
{

    use JsonSerializeTrait;

    /**
     * @Id
     * @GeneratedValue(strategy = "IDENTITY")
     * @Column(type = "integer", length=32, unique=true, nullable=false, name = "id_pessoa")
     * @var integer
     */
    protected $idPessoa;

    /**
     * Usuário
     * @OneToOne(targetEntity = "User\Entity\User", inversedBy = "pessoa", cascade = {"persist"})
     * @JoinColumn(name = "usuario", referencedColumnName = "id")
     * 
     * @var User
     */
    protected $usuario;

    /**
     * @OneToOne(targetEntity = "EnderecoPessoa", mappedBy = "pessoa", cascade = {"persist"})
     * @var EnderecoPessoa
     */
    protected $endereco;

    /**
     * Contatos da pessoa
     * @OneToMany(targetEntity = "ContatoPessoa", mappedBy = "pessoa", cascade = {"persist"}, orphanRemoval=true)
     * @var ArrayCollection|array
     */
    protected $contatos;

    const PESSOA_FISICA = "pessoa_fisica", PESSOA_JURIDICA = "pessoa_juridica";

    public function __construct()
    {
        $this->contatos = new ArrayCollection();
    }
}

Компания PessoaFisica:

/**
 * Abstração da pessoa física
 *
 * @Entity
 * @Table(name = "pessoa_fisica")
 * @author Rodrigo Teixeira Andreotti <[email protected]>
 */
class PessoaFisica extends Pessoa implements JsonSerializable {

    use JsonSerializeTrait;

    /**
     * Nome da pessoa física
     * @Column(type = "string", length=14)
     * @var string
     */
    private $nome;

    /**
     * Número do CPF da pessoa (quando brasileiro)
     * @Column(type = "string", length=14)
     * @var string
     */
    private $cpf;

    /**
     * Número do RG (quando brasileiro)
     * @Column(type = "string", length=13)
     * @var string
     */
    private $rg;

    /**
     * Data de nascimento
     * @Column(type = "date", name = "data_nascimento")
     * @var DateTime
     */
    private $dataNascimento;
}

ПессоаЮридика Юридическое лицо:

/**
 * Abstração de Pessoa Jurídica
 * 
 * @Entity
 * @Table(name = "pessoa_juridica")
 * @InheritanceType("JOINED")
 * @author Rodrigo Teixeira Andreotti <[email protected]>
 */
class PessoaJuridica extends Pessoa implements JsonSerializable {

    use JsonSerializeTrait;

    /**
     * @Id
     * @GeneratedValue(strategy = "IDENTITY")
     * @Column(type = "integer", length=32, unique=true, nullable=false, name = "id_pessoa")
     * @var integer
     */
    protected $idPessoa;

    /**
     * Nome fantasia
     * @Column(type = "string", length=32, name = "nome_fantasia")
     * @var String
     */
    protected $nomeFantasia;

    /**
     * Número do CNPJ
     * @Column(type = "string", length=14, unique=true, name = "cnpj") 
     * @var string
     */
    protected $cnpj;

    /**
     * Razão social da empresa
     * @Column(type = "string", length=32, name = "razao_social")
     * @var string Razão social da empresa, quando necessário
     */
    protected $razaoSocial;
}

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

  • Создайте форму с идентификатором клиента + Pessoa Fieldset
  • В наборе полей Pessoa я создал наборы полей для общей информации (пользователь, адрес, контакты и т. д.).
  • В наборе полей Pessoa он также включает два других набора полей, по одному для каждого дочернего класса Pessoa (PessoaFisica и PessoaJuridica) — и вот в чем проблема.

На экране ниже вы можете увидеть мою регистрационную форму: Наследование таблицы Doctrine с набором полей zf3

Эта форма отображает или скрывает набор полей PessoaJuridica или PessoaFisica в соответствии с выбранным типом с использованием javascript, однако, поскольку они представляют собой разные наборы полей в форме, когда Zend гидратирует их, они также гидратируются как разные объекты, т. е. наследование не применяется к Объект Person, который должен быть выбран в соответствии с типом.

В основном, с моей точки зрения, должно было бы произойти, чтобы Zend мог не отображать наборы полей, ссылающиеся на дочерние классы класса Person, как на отдельные объекты, в тот момент, когда форма отображается с этими поля так (например):

человек [fsPeople] [имя]

человек [fsPessoaJuridica] [имяFantasica]

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

Каков был бы правильный способ сделать эту реализацию формы?

Из-за использования наследования вы создали отдельные сущности. Однако форма, которую вы изначально создаете в бэкэнде, работает с одной сущностью. Внешний интерфейс, который вы изменили, чтобы обрабатывать 2. Таким образом, ваш внешний интерфейс не соответствует вашему внутреннему. Поскольку из-за наследования у вас теперь есть 2 отдельных объекта, вы должны создать 2 отдельные формы, используя разные наборы полей (PessoaJuridica или PessoaFisica) в качестве базовых наборов полей.

rkeet 18.02.2019 15:21

Кроме того, вы можете удалить DiscriminatorMap, если вы не создадите его, Doctrine автоматически сгенерирует его для вас (так будет меньше ошибок).

rkeet 18.02.2019 15:21

Кроме того, вы объявляете "pessoa" = "Pessoa" в своем DiscriminatorMap, этого никогда не может произойти, поскольку вы сделали Pessoa класс abstract.

rkeet 18.02.2019 15:23

привет, спасибо за предложения! Получит удовольствие! Я действительно думал о создании двух форм или, в случае, наборов полей, разделенных для этого, но я подумал, что есть какой-то способ заставить эти наборы полей не генерировать отдельные объекты, но я думаю, что я буду использовать некоторую процедуру ajax для перезагрузки всего форму в соответствии с выбранным типом.

Rodrigo Teixeira Andreotti 18.02.2019 17:09

Я понятия не имел, что доктрина автоматически генерирует карту дискриминации, я собираюсь внести это изменение, и человек = человек, которого я поставил только потому, что доктрина сообщила мне об ошибках, но я проверю, решит ли это удаление карты дискриминации, это решит этот вопрос . Спасибо! С Уважением!

Rodrigo Teixeira Andreotti 18.02.2019 17:09

Конечно вещь. Если вы не можете понять это, обновите вопрос, указав, что вы пробовали и с чем столкнулись, это даст мне еще одно уведомление;)

rkeet 18.02.2019 19:20

Вы поняли это? Если это так, пожалуйста, напишите ответ, чтобы помочь другим. Если нет, не могли бы вы либо обновить вопрос, либо удалить его и создать новый?

rkeet 24.02.2019 20:05

Привет друг! Я собирался прийти сюда, чтобы прокомментировать, сегодня я смог закончить тесты, мне не хватило времени для работы над этим проектом (который является частным), но я реализовал два набора полей для человека (PersonFisica и PessoaJuridica), оба расширяющие персональный Fieldset, поля, которые не повторяются, и я перезагружаю форму во время выполнения и ajax, то есть, когда пользователь выбирает тип человека, я ищу новую форму по ajax и заменяю предыдущую форму, естественно, на правильную данные, как я хотел бы. Спасибо большое за вашу помощь! Я создам подробный ответ, чтобы оставить в качестве ссылки.

Rodrigo Teixeira Andreotti 24.02.2019 22:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
8
118
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Что ж, ответ от @rkeet очень помог мне понять, в чем проблема, что на самом деле не проблема =]

Due to the usage of inheritance, you've created separate Entities. However, the form you initially create in the back-end works with a single Entity. The front-end you've modified to handle 2. So your front-end does not match your back-end. As, due to the inheritance, you now have 2 separate Entities, you should create 2 separate forms, using different fieldsets (PessoaJuridica or PessoaFisica) as the base fieldsets.

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

Во-первых, следуя логике, описанной в его комментарии, я создал абстрактный набор полей для PessoaEntity с информацией, совместно используемой двумя типами людей, и расширил его до двух дочерних классов PessoaFisicaFieldset и PessoaJuridicaFieldset, которые я описываю ниже:

/**
 * Fieldset com dados para a pessoa
 *
 * @author Rodrigo Teixeira Andreotti <[email protected]>
 */
abstract class PessoaFieldset extends Fieldset implements InputFilterProviderInterface
{

    private $em;
    private $userFs;
    private $enderecoFs;
    private $contatoFs;

    public function __construct(ObjectManager $em,
            UserFieldset $userFs,
            PessoaEnderecoFieldset $enderecoFs,
            ContatoFieldset $contatoFs)
    {
        parent::__construct('pessoa');
        $this->em = $em;
        $this->userFs = $userFs;
        $this->enderecoFs = $enderecoFs;
        $this->contatoFs = $contatoFs;
        $this->init();
    }

    protected function getEm()
    {
        return $this->em;
    }

    public function init()
    {
        $this
                ->setHydrator(new DoctrineObject($this->getEm()));

        $this->add(array(
            'type' => 'Hidden',
            'name' => 'id_pessoa',
            'attributes' => array(
                'id' => 'txtId'
            )
        ));

        $this->add(array(
            'type' => 'hidden',
            'name' => 'tipo',
        ));


        $this->add($this->userFs);

        $this->add($this->enderecoFs);

        $elCollection = new Collection;
        $elCollection
                ->setName('contatos')
                ->setLabel('Informações de Contato')
                ->setCount(1)
                ->setShouldCreateTemplate(true)
                ->setAllowAdd(true)
                ->setAllowRemove(true)
                ->setTargetElement($this->contatoFs);


        $this->add($elCollection);

        $this->add(array(
            'type'  =>  'Button',
            'name'  =>  'btAddContato',
            'options' => array(
                'label' => '<i class = "fa fa-fw fa-plus"></i> Adicionar',
                'label_options' => array(
                    'disable_html_escape' => true
                )
            ),
            'attributes' => array(
                'class' => 'btn btn-info',
                'id'    =>  'btAddContato'
            )
        ));
    }

    public function getInputFilterSpecification(): array
    {
        return array(
            'id_pessoa' =>  array(
                'required'  =>  false,
                'filters'   =>  array(
                    ['name'=>'Int']
                )
            ),
            'tipo'  =>  array(
                'required'  =>  true,
            )
        );
    }

}

Это мой класс PessoaFisicaFieldset.

/**
 * Fieldset com dados para a pessoa Física
 *
 * @author Rodrigo Teixeira Andreotti <[email protected]>
 */
class PessoaFisicaFieldset extends PessoaFieldset implements InputFilterProviderInterface
{

    private $em;

    public function __construct(ObjectManager $em, 
            \User\Form\UserFieldset $userFs, 
            PessoaEnderecoFieldset $enderecoFs, 
            \Common\Form\ContatoFieldset $contatoFs)
    {
        parent::__construct($em, $userFs, $enderecoFs, $contatoFs);
        $this->init();
    }



    public function init()
    {
        parent::init();
        $this
                ->setObject(new PessoaFisica());

        $this->get('tipo')->setValue(\Pessoa\Entity\Pessoa::PESSOA_FISICA);



        $this->add(array(
            'type' => 'Text',
            'name' => 'cpf',
            'options' => array(
                'label' => 'CPF',
                'label_attributes' => array(
                    'class' => 'col-sm-12'
                )
            ),
            'attributes' => array(
                'class' => 'form-control form-control-line',
                'id' => 'txtCpf'
            )
        ));

        $this->add(array(
            'type' => 'Text',
            'name' => 'nome',
            'options' => array(
                'label' => 'Nome',
                'label_attributes' => array(
                    'class' => 'col-sm-12'
                )
            ),
            'attributes' => array(
                'class' => 'form-control form-control-line',
                'id' => 'txtNome'
            )
        ));

        $this->add(array(
            'type' => 'Text',
            'name' => 'rg',
            'options' => array(
                'label' => 'RG',
                'label_attributes' => array(
                    'class' => 'col-sm-12'
                )
            ),
            'attributes' => array(
                'class' => 'form-control form-control-line',
                'id' => 'txtRazaoSocial'
            )
        ));

        $this->add(array(
            'type' => 'DateTime',
            'name' => 'dataNascimento',
            'options' => array(
                'format' => 'd/m/Y',
                'label' => 'Data de Nascimento',
                'label_attributes' => array(
                    'class' => 'col-sm-12'
                )
            ),
            'attributes' => array(
                'class' => 'form-control form-control-line data',
            )
        ));
    }

    public function getInputFilterSpecification(): array
    {
        return array(
            'nome'  =>  array(
                'required'  =>  true,
                'filters'   => array(
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim']
                )
            ),
            'rg'    =>      array(
                'required'  =>  false,
                'filters'   =>  array(
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim']
                )
            ),
            'cpf'   =>      array(
                'required'  =>  false,
                'filters'   =>  array(
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim']
                ),
                'validators'    =>  array(
                    ['name' => CpfValidator::class]
                )
            ),
            'dataNascimento'    =>  array(
                'required'  =>  true,
                'filters'   =>  array(
                    array(
                        'name' => 'Zend\Filter\DatetimeFormatter',
                        'options' => array (
                            'format' => 'd/m/Y',
                        ),
                    ),
                ),
                'validators'    =>  array(
                    array(
                        'name' => Date::class,
                        'options'   =>  array(
                            'format'    =>  'd/m/Y'
                        )
                    )
                )
            )
        );
    }

}

А вот и мой PessoaJuridicaFieldset

/**
 * Fieldset com dados específicos para a pessoa jurídica
 *
 * @author Rodrigo Teixeira Andreotti <[email protected]>
 */
class PessoaJuridicaFieldset extends PessoaFieldset implements InputFilterProviderInterface
{

    public function __construct(ObjectManager $em, 
            \User\Form\UserFieldset $userFs, PessoaEnderecoFieldset $enderecoFs, 
            \Common\Form\ContatoFieldset $contatoFs)
    {
        parent::__construct($em, $userFs, $enderecoFs, $contatoFs);
        $this->init();
    }

    public function init()
    {
        parent::init();
        $this
                ->setObject(new PessoaJuridica());

        $this->get('tipo')->setValue(\Pessoa\Entity\Pessoa::PESSOA_JURIDICA);


        $this->add(array(
            'type' => 'Text',
            'name' => 'cnpj',
            'options' => array(
                'label' => 'CNPJ',
                'label_attributes' => array(
                    'class' => 'col-sm-12'
                )
            ),
            'attributes' => array(
                'class' => 'form-control form-control-line',
                'id' => 'txtCnpj'
            )
        ));



        $this->add(array(
            'type' => 'Text',
            'name' => 'razaoSocial',
            'options' => array(
                'label' => 'Razão Social',
                'label_attributes' => array(
                    'class' => 'col-sm-12'
                )
            ),
            'attributes' => array(
                'class' => 'form-control form-control-line',
                'id' => 'txtRazaoSocial'
            )
        ));

        $this->add(array(
            'type' => 'Text',
            'name' => 'nomeFantasia',
            'options' => array(
                'label' => 'Nome Fantasia',
                'label_attributes' => array(
                    'class' => 'col-sm-12'
                )
            ),
            'attributes' => array(
                'class' => 'form-control form-control-line',
                'id' => 'txtNomeFantasia'
            )
        ));
    }

    public function getInputFilterSpecification(): array
    {
        return array(
            'razaoSocial'  =>  array(
                'required'  =>  true,
                'filters'   => array(
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim']
                )
            ),
            'nomeFantasia'    =>      array(
                'required'  =>  true,
                'filters'   =>  array(
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim']
                )
            ),
            'cnpj'   =>      array(
                'required'  =>  true,
                'filters'   =>  array(
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim']
                ),
                'validators'    =>  array(
                    ['name' => CnpjValidator::class]
                )
            )
        );
    }

}

И для завершения я выполнил обработку типа объекта в контроллере, который загрузит эту форму, как показано ниже: (только соответствующие части)

//...
if ($id) {
            $cliente = $this->repository->getById($id);
            $form->remove('pessoa');
            // loads form according to the type loaded from the database
            if (!$request->isXmlHttpRequest()) {
                if ($cliente->getPessoa() instanceof \Pessoa\Entity\PessoaFisica) {
                    $form->add($this->pessoaFisicaFieldset);
                } elseif ($cliente->getPessoa() instanceof \Pessoa\Entity\PessoaJuridica) {
                    $form->add($this->pessoaJuridicaFieldset);
                }
                var_dump($cliente->getPessoa());
            }
            $form->bind($cliente);
        }



        if ($request->isPost()) {
            $form->remove('pessoa');
            // loads form according to the type selected in the post
            if ($request->getPost('tipo') == \Pessoa\Entity\Pessoa::PESSOA_FISICA) {
                $form->add($this->pessoaFisicaFieldset);
            } elseif ($request->getPost('tipo') == \Pessoa\Entity\Pessoa::PESSOA_JURIDICA) {
                $form->add($this->pessoaJuridicaFieldset);
            }


            $form->get('tipo')->setValue($request->getPost('tipo'));


            $form->setData($request->getPost());

            if (!$request->isXmlHttpRequest()) {
                if ($form->isValid()) {
                    $cliente = $form->getObject();

                    if ($cliente->getId() != 0) {
                        $cliente->getPessoa()->setCadastradoEm(new \DateTime);
                    }

                    // ...
                }
            }
        }
//...

Еще раз спасибо @rket!

Другие вопросы по теме