Наследование таблицы доктрины с помощью набора полей zf3

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

Модель сущности

Чтобы работать с этим моделированием с помощью доктрины, я использую @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;
}

Компания PessoaJuridica:

/**
 * 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 Fieldset я создал наборы полей для общей информации (пользователь, адрес, контакты и т. Д.)
  • В Pessoa Fieldset он также включает два других Fieldset, по одному для каждого дочернего класса Pessoa (PessoaFisica и PessoaJuridica) - и вот в чем проблема.

На экране ниже вы можете увидеть мою регистрационную форму:  Экран системы

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

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

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

человек [fsPessoaJuridica] [nameFantasica]

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

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


person Rodrigo Teixeira Andreotti    schedule 16.02.2019    source источник
comment
Из-за использования наследования вы создали отдельные сущности. Однако форма, которую вы изначально создаете в серверной части, работает с одной сущностью. Внешний интерфейс, который вы изменили, чтобы обрабатывать 2. Итак, ваш интерфейс не соответствует вашей серверной части. Поскольку из-за наследования у вас теперь есть 2 отдельных объекта, вы должны создать 2 отдельные формы, используя разные наборы полей (PessoaJuridica или PessoaFisica) в качестве базовых наборов полей.   -  person rkeet    schedule 18.02.2019
comment
Кроме того, вы можете удалить DiscriminatorMap, если вы не создадите его, Doctrine автоматически сгенерирует его для вас (меньше подверженности ошибкам).   -  person rkeet    schedule 18.02.2019
comment
Кроме того, вы объявляете "pessoa" = "Pessoa" в своем DiscriminatorMap, этого никогда не произойдет, поскольку вы создали Pessoa класс abstract.   -  person rkeet    schedule 18.02.2019
comment
привет спасибо за предложения! Будет хорошо! Я действительно думал о создании двух форм или в случае наборов полей, разделенных для этого, но я подумал, что есть способ заставить эти наборы полей не генерировать отдельные объекты, но я думаю, что я использую некоторую процедуру ajax для перезагрузки всего форма согласно выбранному типу.   -  person Rodrigo Teixeira Andreotti    schedule 18.02.2019
comment
Я понятия не имел, что доктрина автоматически генерирует дискриминаторную карту, я собираюсь внести это изменение, а человек = Человек, которого я поставил только потому, что доктрина сообщила мне об ошибках, но я проверю, решает ли удаление дискриминаторной карты этот вопрос . А пока спасибо! С Уважением!   -  person Rodrigo Teixeira Andreotti    schedule 18.02.2019
comment
Конечно. Если вы не можете понять это, обновите вопрос, указав, что вы пробовали и с чем столкнулись, это даст мне еще одно уведомление;)   -  person rkeet    schedule 18.02.2019
comment
Вы в этом разобрались? Если да, опубликуйте ответ, чтобы помочь другим. Если нет, не могли бы вы обновить вопрос или удалить его и создать новый?   -  person rkeet    schedule 24.02.2019
comment
Привет друг! Я собирался приехать сюда, чтобы прокомментировать, я смог завершить тесты сегодня, мне не хватило времени на работу над этим проектом (который является частным), но я реализовал два набора полей для человека (PersonFisica и PessoaJuridica), оба расширяли Personal Fieldset, поля, которые не повторяются, и я перезагружаю форму во время выполнения и ajax, то есть, когда пользователь выбирает тип человека, я ищу новую форму с помощью ajax и заменяю предыдущую форму, конечно, правильной данные, как я бы хотел. Спасибо большое за вашу помощь! Я создам подробный ответ, чтобы оставить ссылку.   -  person Rodrigo Teixeira Andreotti    schedule 25.02.2019


Ответы (1)


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

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

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

Во-первых, следуя логике, описанной в его комментарии, я создал абстрактный набор полей для 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);
                    }

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

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

person Rodrigo Teixeira Andreotti    schedule 25.02.2019