I'm working on a project using Zend Framework 3 and Doctrine 2, using for DcotrineModule integration, the following is the Entity modeling I'm having problems with:
To work with this modeling with the doctrine I'm using #InheritanceType, below are the relevant excerpts from Entities:
Pessoa Entity:
/**
* Abstração de Pessoa
*
* #author Rodrigo Teixeira Andreotti <ro.andriotti#gmail.com>
*
* #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 Entity:
/**
* Abstração da pessoa física
*
* #Entity
* #Table(name="pessoa_fisica")
* #author Rodrigo Teixeira Andreotti <ro.andriotti#gmail.com>
*/
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 Entity:
/**
* Abstração de Pessoa Jurídica
*
* #Entity
* #Table(name="pessoa_juridica")
* #InheritanceType("JOINED")
* #author Rodrigo Teixeira Andreotti <ro.andriotti#gmail.com>
*/
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;
}
So far everything works perfectly, the problem is when I need to generate a form for this information, I'm currently working on the "Customer" module, basically what I did for it was:
Create a form with client ID + Pessoa Fieldset
In the Pessoa Fieldset, I created the fieldsets for shared information (user, address, contacts etc)
In the Pessoa Fieldset, it also includes two other Fieldsets, one for each Pessoa's child class (PessoaFisica and PessoaJuridica) - and here come's the problem.
In the screen below you can see my registration form:
This form displays or hides the fieldset of PessoaJuridica or PessoaFisica according to the selected type using javascript, however as they are different fieldsets within the form, when zend hydrates them they are hydrated as different objects as well, ie the inheritance is not applied to the Person object, which should be selected according to the type.
Basically what, in my point of view, would need to happen, would be that there is a way for zend not to render the fieldsets referring to the child classes of the Person class as separate objects, at the moment the form is rendered with these fields so (for example) :
person [fsPeople] [name]
person [fsPessoaJuridica] [nameFantasica]
And this causes the zend not to generate the correct class to be saved in the database.
What would be the correct way to do this implementation of the form?
Well, the response from the #rkeet helped me a lot to understand where the problem was, which is not really a problem =]
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.
I'll leave the path I followed here, it might help someone with the same doubt as me.
First, following the logic explained in his comment, I created an abstract fieldset for the PessoaEntity with the information shared between the two types of person, and extended it into two child classes PessoaFisicaFieldset and PessoaJuridicaFieldset, which I describe below:
/**
* Fieldset com dados para a pessoa
*
* #author Rodrigo Teixeira Andreotti <ro.andriotti#gmail.com>
*/
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,
)
);
}
}
This is my PessoaFisicaFieldset class.
/**
* Fieldset com dados para a pessoa Física
*
* #author Rodrigo Teixeira Andreotti <ro.andriotti#gmail.com>
*/
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'
)
)
)
)
);
}
}
And here is my PessoaJuridicaFieldset
/**
* Fieldset com dados específicos para a pessoa jurídica
*
* #author Rodrigo Teixeira Andreotti <ro.andriotti#gmail.com>
*/
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]
)
)
);
}
}
And to complete I did the entity type treatment on the Controller that will load this form, as below: (only relevant parts)
//...
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);
}
// ...
}
}
}
//...
Again, thanks #rkeet!
Related
So in summary I'm trying to override a third party module to include an extra dropdown menu on the admin Ui
Heres the third party file at
Prince/Productattach/Block/Adminhtml/Productattach/Edit/Tab/Main.php
namespace Prince\Productattach\Block\Adminhtml\Productattach\Edit\Tab;
/**
*Class Main
*#package Prince\Productattach\Block\Adminhtml\Productattach\Edit\Tab
*/
class Main extends \Magento\Backend\Block\Widget\Form\Generic implements
\Magento\Backend\Block\Widget\Tab\TabInterface
{
/**
* #var \Magento\Store\Model\System\Store
*/
private $systemStore;
/**
* #var \Magento\Customer\Model\ResourceModel\Group\Collection
*/
private $customerCollection;
/**
* Main constructor.
* #param \Magento\Backend\Block\Template\Context $context
* #param \Magento\Framework\Registry $registry
* #param \Magento\Framework\Data\FormFactory $formFactory
* #param \Magento\Store\Model\System\Store $systemStore
* #param \Magento\Customer\Model\ResourceModel\Group\Collection $customerCollection
* #param array $data
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\Data\FormFactory $formFactory,
\Magento\Store\Model\System\Store $systemStore,
\Magento\Customer\Model\ResourceModel\Group\Collection $customerCollection,
array $data = []
) {
$this->_systemStore = $systemStore;
$this->_customerCollection = $customerCollection;
parent::__construct($context, $registry, $formFactory, $data);
}
/**
* Prepare form
*
* #return $this
*/
public function _prepareForm()
{
$model = $this->_coreRegistry->registry('productattach');
/*
* Checking if user have permissions to save information
*/
if ($this->_isAllowedAction('Prince_Productattach::save')) {
$isElementDisabled = false;
} else {
$isElementDisabled = true;
}
/** #var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create();
$form->setHtmlIdPrefix('productattach_main_');
$fieldset = $form->addFieldset(
'base_fieldset',
['legend' => __('Attachment Information')]
);
$customerGroup = $this->customerCollection->toOptionArray();
if ($model->getId()) {
$fieldset->addField('productattach_id', 'hidden', ['name' => 'productattach_id']);
}
$fieldset->addField(
'name',
'text',
[
'name' => 'name',
'label' => __('Attachment Name'),
'title' => __('Attachment Name'),
'required' => true,
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'description',
'textarea',
[
'name' => 'description',
'label' => __('Description'),
'title' => __('Description'),
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'files',
'file',
[
'name' => 'file',
'label' => __('File'),
'title' => __('File'),
'required' => false,
'note' => 'File size must be less than 2 Mb.', // TODO: show ACCTUAL file-size
'disabled' => $isElementDisabled
]
);
$fieldset->addType(
'uploadedfile',
\Prince\Productattach\Block\Adminhtml\Productattach\Renderer\FileIconAdmin::class
);
$fieldset->addField(
'file',
'uploadedfile',
[
'name' => 'uploadedfile',
'label' => __('Uploaded File'),
'title' => __('Uploaded File'),
]
);
$fieldset->addField(
'url',
'text',
[
'name' => 'url',
'label' => __('URL'),
'title' => __('URL'),
'required' => false,
'disabled' => $isElementDisabled,
'note' => 'Upload file or Enter url'
]
);
$fieldset->addField(
'customer_group',
'multiselect',
[
'name' => 'customer_group[]',
'label' => __('Customer Group'),
'title' => __('Customer Group'),
'required' => true,
'value' => [0,1,2,3], // todo: preselect ALL customer groups, not just 0-3
'values' => $customerGroup,
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'store',
'multiselect',
[
'name' => 'store[]',
'label' => __('Store'),
'title' => __('Store'),
'required' => true,
'value' => [0],
'values' => $this->systemStore->getStoreValuesForForm(false, true),
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'active',
'select',
[
'name' => 'active',
'label' => __('Active'),
'title' => __('Active'),
'value' => 1,
'options' => ['1' => __('Yes'), '0' => __('No')],
'disabled' => $isElementDisabled
]
);
$this->_eventManager->dispatch('adminhtml_productattach_edit_tab_main_prepare_form', ['form' => $form]);
if ($model->getId()) {
$form->setValues($model->getData());
}
$this->setForm($form);
return parent::_prepareForm();
}
/**
* Prepare label for tab
*
* #return string
*/
public function getTabLabel()
{
return __('Attachment Information');
}
/**
* Prepare title for tab
*
* #return string
*/
public function getTabTitle()
{
return __('Attachment Information');
}
/**
* {#inheritdoc}
*/
public function canShowTab()
{
return true;
}
/**
* {#inheritdoc}
*/
public function isHidden()
{
return false;
}
/**
* Check permission for passed action
*
* #param string $resourceId
* #return bool
*/
public function _isAllowedAction($resourceId)
{
return $this->_authorization->isAllowed($resourceId);
}
}
I have my module setup and my di.xml configured with the usual plus below to override the class.
<preference for="Prince\Productattach\Block\Adminhtml\Productattach\Edit\Tab" type="Vendor\Filecategory\Block\Adminhtml\Productattach\Edit\Tab"/>
then and exact replica of the class with namespace and my extra field added at
Vendor/Filecategory/Block/Adminhtml/Productattach/Edit/Tab/Main.php
namespace Vendor\Filecategory\Block\Adminhtml\Productattach\Edit\Tab;
use \Prince\Productattach\Block\Adminhtml\Productattach\Edit\Tab\Main as Main;
class MainExt extends Main
{
/**
* #var \Magento\Store\Model\System\Store
*/
private $systemStore;
/**
* #var \Magento\Customer\Model\ResourceModel\Group\Collection
*/
private $customerCollection;
/**
* Main constructor.
* #param \Magento\Backend\Block\Template\Context $context
* #param \Magento\Framework\Registry $registry
* #param \Magento\Framework\Data\FormFactory $formFactory
* #param \Magento\Store\Model\System\Store $systemStore
* #param \Magento\Customer\Model\ResourceModel\Group\Collection $customerCollection
* #param array $data
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\Data\FormFactory $formFactory,
\Magento\Store\Model\System\Store $systemStore,
\Magento\Customer\Model\ResourceModel\Group\Collection $customerCollection,
array $data = []
) {
$this->systemStore = $systemStore;
$this->customerCollection = $customerCollection;
parent::__construct($context, $registry, $formFactory, $data, $systemStore);
}
/**
* Prepare form
*
* #return $this
*/
public function _prepareForm()
{
$model = $this->_coreRegistry->registry('productattach');
/*
* Checking if user have permissions to save information
*/
if ($this->_isAllowedAction('Prince_Productattach::save')) {
$isElementDisabled = false;
} else {
$isElementDisabled = true;
}
/** #var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create();
$form->setHtmlIdPrefix('productattach_main_');
$fieldset = $form->addFieldset(
'base_fieldset',
['legend' => __('Attachment Information')]
);
$customerGroup = $this->customerCollection->toOptionArray();
if ($model->getId()) {
$fieldset->addField('productattach_id', 'hidden', ['name' => 'productattach_id']);
}
$fieldset->addField(
'name',
'text',
[
'name' => 'name',
'label' => __('Attachment Name'),
'title' => __('Attachment Name'),
'required' => true,
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'Category',
'select',
[
'name' => 'Category',
'label' => __('Category'),
'title' => __('Category'),
'value' => 0,
'options' => ['0' => __('Technical Specification'), '1' => __('Installation Instructions')],
]
);
$fieldset->addField(
'description',
'textarea',
[
'name' => 'description',
'label' => __('Description'),
'title' => __('Description'),
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'files',
'file',
[
'name' => 'file',
'label' => __('File'),
'title' => __('File'),
'required' => false,
'note' => 'File size must be less than 2 Mb.', // TODO: show ACCTUAL file-size
'disabled' => $isElementDisabled
]
);
$fieldset->addType(
'uploadedfile',
\Prince\Productattach\Block\Adminhtml\Productattach\Renderer\FileIconAdmin::class
);
$fieldset->addField(
'file',
'uploadedfile',
[
'name' => 'uploadedfile',
'label' => __('Uploaded File'),
'title' => __('Uploaded File'),
]
);
$fieldset->addField(
'url',
'text',
[
'name' => 'url',
'label' => __('URL'),
'title' => __('URL'),
'required' => false,
'disabled' => $isElementDisabled,
'note' => 'Upload file or Enter url'
]
);
$fieldset->addField(
'customer_group',
'multiselect',
[
'name' => 'customer_group[]',
'label' => __('Customer Group'),
'title' => __('Customer Group'),
'required' => true,
'value' => [0,1,2,3], // todo: preselect ALL customer groups, not just 0-3
'values' => $customerGroup,
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'store',
'multiselect',
[
'name' => 'store[]',
'label' => __('Store'),
'title' => __('Store'),
'required' => true,
'value' => [0],
'values' => $this->systemStore->getStoreValuesForForm(false, true),
'disabled' => $isElementDisabled
]
);
$fieldset->addField(
'active',
'select',
[
'name' => 'active',
'label' => __('Active'),
'title' => __('Active'),
'value' => 1,
'options' => ['1' => __('Yes'), '0' => __('No')],
'disabled' => $isElementDisabled
]
);
$this->_eventManager->dispatch('adminhtml_productattach_edit_tab_main_prepare_form', ['form' => $form]);
if ($model->getId()) {
$form->setValues($model->getData());
}
$this->setForm($form);
return parent::_prepareForm();
}
/**
* Prepare label for tab
*
* #return string
*/
public function getTabLabel()
{
return __('Attachment Information');
}
/**
* Prepare title for tab
*
* #return string
*/
public function getTabTitle()
{
return __('Attachment Information');
}
/**
* {#inheritdoc}
*/
public function canShowTab()
{
return true;
}
/**
* {#inheritdoc}
*/
public function isHidden()
{
return false;
}
/**
* Check permission for passed action
*
* #param string $resourceId
* #return bool
*/
public function _isAllowedAction($resourceId)
{
return $this->_authorization->isAllowed($resourceId);
}
}
However I keep getting Error
Incompatible argument type: Required type: \Magento\Store\Model\System\Store. Actual type: array;
I have tried flush cache and reindex but no luck. Please can someone tell me what im doing wrong here? Also happy to listen to any alternative ways of completing the same thing.
Thanks in advance.
I have managed to complete this by creating a plugin class instead of overriding the whole class. I have attached the post I followed for any one interested in adding a form field to n existing form attached to a third part module.
https://magento.stackexchange.com/questions/174209/magento-2-add-new-field-to-magento-user-admin-form
I submit a form in Symfony with a collection of sub-forms. Doctrine seems to only save the last entity in that collection. How should I get it to persist all the entities in collection?
I've double checked my code and followed a few tutorials to make sure I have it correct.
EnrichmentApplication
/**
* #ORM\Entity
*/
class EnrichmentApplication
{
//...
/**
* #var array
*
* #ORM\OneToMany(targetEntity="EnrichmentActivity", mappedBy="application", cascade={"persist"})
*/
private $activities;
/**
* #var array
*
* #ORM\OneToMany(targetEntity="EnrichmentActivityCosts", mappedBy="application", cascade={"persist"})
*/
private $activityCosts;
**
* #var string
*
* #ORM\Column(name="project_outline", type="text")
*/
private $projectOutline;
//...
public function __construct()
{
$this->activities = new ArrayCollection();
$this->activityCosts = new ArrayCollection();
}
/**
* Set activityTypes
*
* #param array $activityTypes
*
* #return EnrichmentApplication
*/
public function setActivityTypes($activityTypes)
{
$this->activityTypes = $activityTypes;
return $this;
}
/**
* Get activityTypes
*
* #return array
*/
public function getActivityTypes()
{
return $this->activityTypes;
}
/**
* Set activities
*
* #param array $activities
*
* #return EnrichmentApplication
*/
public function setActivities($activities)
{
$this->activities = $activities;
return $this;
}
/**
* Get activities
*
* #return array
*/
public function getActivities()
{
return $this->activities;
}
/**
* Add activity
*
* #param array $activity
*
* #return EnrichmentApplication
*/
public function addActivities(EnrichmentActivity $activity)
{
$activity->setApplication($this);
if (!$this->activities->contains($activity))
{
$this->activities->add($activity);
}
return $this;
}
/**
* Set activityCosts
*
* #param array $activityCosts
*
* #return EnrichmentApplication
*/
public function setActivityCosts($activityCosts)
{
$this->activityCosts = $activityCosts;
return $this;
}
/**
* Get activityCosts
*
* #return array
*/
public function getActivityCosts()
{
return $this->activityCosts;
}
/**
* Add activityCost
*
* #param EnrichmentActivityCosts $activityCost
*
* #return EnrichmentApplication
*/
public function addActivityCosts(EnrichmentActivityCosts $activityCost)
{
$activityCost->setApplication($this);
if (!$this->activityCosts->contains($activityCost))
{
$this->activityCosts->add($activityCost);
}
return $this;
}
/**
* Set projectOutline
*
* #param string $projectOutline
*
* #return EnrichmentApplication
*/
public function setProjectOutline($projectOutline)
{
$this->projectOutline = $projectOutline;
return $this;
}
/**
* Get projectOutline
*
* #return string
*/
public function getProjectOutline()
{
return $this->projectOutline;
}
//...
}
EnrichmentActivity
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
*
* #ORM\ManyToOne(targetEntity="EnrichmentApplication", inversedBy="activities")
* #ORM\JoinColumn(name="application_id", referencedColumnName="id")
*/
private $application;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
private $date;
/**
* #var \DateTime
*
* #ORM\Column(name="start_time", type="time")
*/
private $startTime;
/**
* #var \DateTime
*
* #ORM\Column(name="end_time", type="time")
*/
private $endTime;
/**
* #var int
*
* #ORM\Column(name="total_students", type="integer")
*/
private $totalStudents;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
public function __clone()
{
$this->id = null;
$this->application = null;
}
/**
* Set application
*
* #param EnrichmentApplication $application
*
* #return EnrichmentActivity
*/
public function setApplication($application)
{
$this->application = $application;
return $this;
}
/**
* Get application
*
* #return EnrichmentApplication
*/
public function getApplication()
{
return $this->application;
}
/**
* Set date
*
* #param \DateTime $date
*
* #return EnrichmentActivity
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set startTime
*
* #param \DateTime $startTime
*
* #return EnrichmentActivity
*/
public function setStartTime($startTime)
{
$this->startTime = $startTime;
return $this;
}
/**
* Get startTime
*
* #return \DateTime
*/
public function getStartTime()
{
return $this->startTime;
}
/**
* Set endTime
*
* #param \DateTime $endTime
*
* #return EnrichmentActivity
*/
public function setEndTime($endTime)
{
$this->endTime = $endTime;
return $this;
}
/**
* Get endTime
*
* #return \DateTime
*/
public function getEndTime()
{
return $this->endTime;
}
/**
* Set totalStudents
*
* #param integer $totalStudents
*
* #return EnrichmentActivity
*/
public function setTotalStudents($totalStudents)
{
$this->totalStudents = $totalStudents;
return $this;
}
/**
* Get totalStudents
*
* #return int
*/
public function getTotalStudents()
{
return $this->totalStudents;
}
}
EnrichmentApplicationType (the form)
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as Types;
use Lifo\TypeaheadBundle\Form\Type\TypeaheadType;
class EnrichmentApplicationType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$uniform_col_label = 'col-xs-12 col-sm-5 col-md-4 col-lg-3';
$uniform_col_element = 'col-xs-12 col-sm-7 col-md-8 col-lg-8';
$uniform_col_label_smaller = 'col-xs-12 col-sm-5 col-md-4 col-lg-2';
$uniform_col_element_smaller = 'col-xs-6 col-sm-2 col-md-2 col-lg-2';
$uniform_col_element_offset = ' col-sm-offset-5 col-md-offset-4 col-lg-offset-3';
$uniform_col_fullwidth = 'col-xs-12 col-sm-12';
$builder->add('manager', TypeaheadType::class, array(
'label' => 'Head of Department',
'route' => 'ajax_name_search',
'minLength' => 3,
'render' => 'fullName',
'attr' => array(
'data-second-glyph' => 'user',
'data-label-col' => 'col-xs-12 col-sm-5 col-md-4 col-lg-4 ',
'data-group-col' => 'col-xs-12 col-sm-7 col-md-8 col-lg-6'
)
))
->add('personResponsible', TypeaheadType::class, array(
'label' => 'Person responsible for activity delivery',
'route' => 'ajax_name_search',
'minLength' => 3,
'render' => 'fullName',
'attr' => array(
'data-second-glyph' => 'user',
'data-label-col' => 'col-xs-12 col-sm-5 col-md-5 col-lg-4',
'data-group-col' => 'col-xs-12 col-sm-7 col-md-7 col-lg-6'
)
))
//->add('author') //populated by LDAP
->add('activityTitle', Types\TextType::class, array(
'label' => 'Title of proposed activity',
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('activityTypes', Types\ChoiceType::class, array(
'label' => 'Activity type',
'choices' => $options['activityTypes'],
'expanded' => true,
'help' => 'Please select all that apply.',
'multiple' => true,
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('activities', Types\CollectionType::class, array(
'entry_type' => EnrichmentActivityType::class,
'allow_add' => true,
'by_reference' => false,
'entry_options' => array(
'empty_data' => new \AppBundle\Entity\EnrichmentActivity(),
),
'attr' => array(
'data-label-col' => 'col-sm-12',
'data-group-col' => 'col-sm-12'
)
))
->add('activityCosts', Types\CollectionType::class, array(
'entry_type' => EnrichmentActivityCostsType::class,
'allow_add' => true,
'entry_options' => array(
'empty_data' => new \AppBundle\Entity\EnrichmentActivityCosts(),
),
'attr' => array(
'data-label-col' => 'col-xs-12 col-sm-3 col-md-4 col-lg-3',
'data-group-col' => 'col-xs-12 col-sm-9 col-md-8 col-lg-9'
)
))
->add('projectOutline', Types\TextareaType::class, array(
'label' => 'Activity outline and rationale',
'help' => 'Provide background information of your activity, what you intend to do and how it will impact on students\' life skills development.',
#
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('projectObjectives', Types\CollectionType::class, array(
'entry_type' => Types\TextType::class,
'label' => 'Activity objectives & outputs',
'help' => 'Please describe what you intend to achieve. Will there be any specific outputs (eg. deliver a workshop/fundraiser)?',
'allow_add' => true,
'prototype' => true,
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('studentConsulted', Types\TextareaType::class, array(
'label' => 'How have the students been consulted/involved with the design of this activity?',
'help' => 'For example, have students requested this activity? Have they been involved in the planning?',
'attr' => array(
'data-label-col' => $uniform_col_fullwidth,
'data-group-col' => $uniform_col_fullwidth
)
))
->add('studentInvolvement', Types\TextareaType::class, array(
'label' => 'Will the students be involved in the delivery of the activity?',
'help' => 'Will students be participating only or will they also be volunteering to run this activity?',
'attr' => array(
'data-label-col' => $uniform_col_fullwidth,
'data-group-col' => $uniform_col_fullwidth
)
))
->add('communityContribution', Types\ChoiceType::class, array(
'choices' => $options['communityContrib'],
'expanded' => true,
'multiple' => true,
'required' => false,
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('studySuccess', Types\ChoiceType::class, array(
'label' => 'Study and Work Success',
'choices' => $options['studySuccess'],
'expanded' => true,
'multiple' => true,
'required' => false,
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('lifestyles', Types\ChoiceType::class, array(
'label' => 'Healthy & Happy Lifestyles',
'choices' => $options['lifestyles'],
'expanded' => true,
'multiple' => true,
'required' => false,
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('totalStudents', Types\IntegerType::class, array(
'label' => 'Total number of students:',
'scale' => 0,
'attr' => array(
'data-label-col' => $uniform_col_label_smaller,
'data-group-col' => $uniform_col_element_smaller
)
))
->add('studentsUnder16', Types\IntegerType::class, array(
'label' => 'Aged under 16 years:',
'scale' => 0,
'attr' => array(
'data-label-col' => $uniform_col_label_smaller,
'data-group-col' => $uniform_col_element_smaller
)
))
->add('students16To18', Types\IntegerType::class, array(
'label' => 'Aged 16 to 18 years:',
'scale' => 0,
'attr' => array(
'data-label-col' => $uniform_col_label_smaller,
'data-group-col' => $uniform_col_element_smaller
)
))
->add('studentsOver18', Types\IntegerType::class, array(
'label' => 'Aged over 18 years:',
'scale' => 0,
'attr' => array(
'data-label-col' => $uniform_col_label_smaller,
'data-group-col' => $uniform_col_element_smaller
)
))
->add('alsStudents', Types\IntegerType::class, array(
'label' => 'Students accessing Additional Learning Support',
'scale' => 0,
'attr' => array(
'data-label-col' => 'col-xs-12 col-sm-6 col-md-5 col-lg-4',
'data-group-col' => $uniform_col_element_smaller
)
))
->add('availableAccrossCollege', Types\ChoiceType::class, array(
'label' => 'Is the activity only open to students from across college?',
'choices' => array(
'Yes' => true,
'No' => false
),
'expanded' => true,
'attr' => array(
'data-label-col' => $uniform_col_fullwidth,
'data-group-col' => $uniform_col_element_offset
)
))
->add('availableOutside', Types\ChoiceType::class, array(
'label' => 'Is the activity open to students from outside college?',
'choices' => array(
'Yes' => true,
'No' => false
),
'expanded' => true,
'attr' => array(
'data-label-col' => $uniform_col_fullwidth,
'data-group-col' => $uniform_col_element_offset
)
))
->add('availableOnlyDepartment', Types\ChoiceType::class, array(
'label' => 'Is the activity only open to students from a specific department?',
'choices' => array(
'Yes' => true,
'No' => false
),
'expanded' => true,
'attr' => array(
'data-label-col' => $uniform_col_fullwidth,
'data-group-col' => $uniform_col_element_offset
)
))
->add('availableDepartment', Types\TextType::class, array(
'label' => 'If yes, please specify:',
'required' => false,
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
)
))
->add('availableOnlyCurriculumArea', Types\ChoiceType::class, array(
'label' => 'Is the activity only open to students from a specific curriculum area?',
'choices' => array(
'Yes' => true,
'No' => false
),
'expanded' => true,
'attr' => array(
'data-label-col' => $uniform_col_fullwidth,
'data-group-col' => $uniform_col_element_offset
)
))
->add('availableCurriculumArea', Types\TextType::class, array(
'label' => 'If yes, please specify:',
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
),
'required' => false,
))
->add('availableOnlyCourse', Types\ChoiceType::class, array(
'label' => 'Is the activity open to students from a specific course?',
'choices' => array(
'Yes' => true,
'No' => false
),
'expanded' => true,
'attr' => array(
'data-label-col' => $uniform_col_fullwidth,
'data-group-col' => $uniform_col_element_offset
)
))
->add('availableCourse', Types\TextType::class, array(
'label' => 'If yes, please specify:',
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
),
'required' => false,
))
->add('availableOutsideDetail', Types\TextType::class, array(
'label' => 'If yes, please specify:',
'attr' => array(
'data-label-col' => $uniform_col_label,
'data-group-col' => $uniform_col_element
),
'required' => false,
))
->add('behaviouralEngagement', Types\CheckboxType::class, array(
'required' => false,
))
->add('emotionalEngagement', Types\CheckboxType::class, array(
'required' => false,
))
->add('cognitiveEngagement', Types\CheckboxType::class, array(
'required' => false,
))
->add('noStudentsBronze', Types\IntegerType::class, array(
'label' => 'Bronze',
'scale' => 0,
'attr' => array(
'data-label-col' => 'col-xs-5 col-sm-2 col-md-2 col-lg-1',
'data-group-col' => 'col-xs-6 col-sm-2 col-md-2 col-lg-2'
)
))
->add('noStudentsSilver', Types\IntegerType::class, array(
'label' => 'Silver',
'scale' => 0,
'attr' => array(
'data-label-col' => 'col-xs-5 col-sm-2 col-md-2 col-lg-1',
'data-group-col' => 'col-xs-6 col-sm-2 col-md-2 col-lg-2'
)
))
->add('noStudentsGold', Types\IntegerType::class, array(
'label' => 'Gold',
'scale' => 0,
'attr' => array(
'data-label-col' => 'col-xs-5 col-sm-2 col-md-2 col-lg-1',
'data-group-col' => 'col-xs-6 col-sm-2 col-md-2 col-lg-2'
)
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\EnrichmentApplication',
'activityTypes' => null,
'communityContrib' => null,
'studySuccess' => null,
'lifestyles' => null,
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_enrichmentapplication';
}
}
Environment
CentOS 7
PHP 7.1
MariaDB 10
I stick into activities in your domain, so feel free to adopt it to your needs.
EnrichmentApplication
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class EnrichmentApplication
{
//...
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="EnrichmentActivity", mappedBy="application", cascade={"persist"})
*/
private $activities;
public function __construct()
{
$this->activities = new ArrayCollection();
}
/**
* #return Collection
*/
public function getActivities()
{
return $this->activities;
}
public function addActivity(EnrichmentActivity $activity)
{
if (!$this->getActivities()->contains($activity))
{
$this->getActivities()->add($activity);
$activity->setApplication($this);
}
}
public function removeActivity(EnrichmentActivity $activity)
{
if ($this->getActivities()->contains($activity))
{
$this->getActivities()->removeElement($activity);
$activity->setApplication(null);
}
}
}
EnrichmentActivity
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class EnrichmentActivity
{
//...
/**
* #var EnrichmentApplication|null
* #ORM\ManyToOne(targetEntity="EnrichmentApplication", inversedBy="activities")
*/
private $application;
/**
* #param EnrichmentApplication|null $application
*/
public function setApplication(?EnrichmentApplication $application)
{
$this->application = $application;
}
/**
* #return EnrichmentApplication|null
*/
public function getApplication()
{
return $this->application;
}
}
EnrichmentApplicationType
use AppBundle\Entity\EnrichmentApplication;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as Types;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EnrichmentApplicationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('activities', Types\CollectionType::class, array(
'entry_type' => EnrichmentActivityType::class,
'allow_add' => true,
'by_reference' => false
))
;
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => EnrichmentApplication::class,
'activityTypes' => null,
'communityContrib' => null,
'studySuccess' => null,
'lifestyles' => null,
));
}
}
After much debugging it turned out to be the empty_data property of the activities field that was overwriting the data coming from the form. I removed this and it works fine now
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as Types;
class EnrichmentApplicationType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...
->add('activities', Types\CollectionType::class, array(
'entry_type' => EnrichmentActivityType::class,
'allow_add' => true,
'by_reference' => false,
'entry_options' => array(
'empty_data' => new \AppBundle\Entity\EnrichmentActivity(), //<<this option
),
'attr' => array(
'data-label-col' => 'col-sm-12',
'data-group-col' => 'col-sm-12'
)
))
//...
;
}
I can't get to preselect values in a multiselect form element representing a many to many relation.
In my model $admin I have the proper data : an ArrayCollection containing the correct CampsTypes but in the form I can't get the multiselect to preselect the proper options.
Admins model
/**
* #var ArrayCollection CampsTypes $campstypes
*
* #ORM\ManyToMany(targetEntity="CampsTypes", inversedBy="admins", cascade={"persist"})
* #ORM\JoinTable(name="campstypes_admins",
* joinColumns={#ORM\JoinColumn(name="admins_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="campstypes_id", referencedColumnName="id")}
* )
*/
private $campstypes;
CampsType model
/**
* #var ArrayCollection Admins $admins
*
* #ORM\ManyToMany(targetEntity="Admins", mappedBy="campstypes", cascade={"persist"})
*/
private $admins;
Then I define my form select element as follow
[
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'campTypes',
'required' => false,
'options' => [
'object_manager' => $this->getServiceLocator()->get(EntityManager::class),
'target_class' => CampsTypes::class,
'property' => 'title',
'label' => 'Type de camps autorisés',
'instructions' => 'Ne rien sélectionner si edition d\'un super admin',
],
'attributes' => [
'class' => '',
'multiple' => 'multiple',
]
],
And finally here is my action to receive the form
protected function saveAdmin(Admins &$admin, &$form, &$msg)
{
$em = $this->getEntityManager();
/** #var CampTypesService $serviceCampTypes */
$serviceCampTypes = $this->getServiceLocator()->get(CampTypesService::class);
$form->bind($admin);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
if (empty($data['password'])) {
$form->remove('password');
}
$form->setData($data);
if ($form->isValid()) {
if (isset($data['campTypes'])) {
$ids = $form->get('campTypes')->getValue();
$campsTypes = new ArrayCollection($serviceCampTypes->getCampTypesByIds(array_values($ids)));
foreach ($campsTypes as &$campsType) {
/** #var CampsTypes $campsType*/
$campsType->addAdmin($admin);
}
$admin->setCampTypes($campsTypes);
}
$em->persist($admin);
$em->flush();
$msg = 'Sauvegarde des données effectuée';
return;
}
}
return;
}
I'm getting out of solution to try.
Any idea what I am doing wrong ?
Have you read this ? I have the feeling you're looking for the ObjectMultiCheckbox instead of the ObjectSelect Form Element.
Examples from my own code
Usage for a single select (use case: set/change a default currency for some other entity)
$this->add([
'type' => ObjectSelect::class,
'required' => true,
'name' => 'defaultCurrency',
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Currency::class,
'property' => 'id',
// Use these commented lines if you wish to use a Repository function ('name' => 'repositoryFunctionName')
// 'is_method' => true,
// 'find_method' => [
// 'name' => 'getEnabledCurrencies',
// ],
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Default currency'),
'label_attributes' => [
'class' => '',
'title' => _('Default currency'),
],
'label_generator' => function ($targetEntity) {
/** #var Currency $targetEntity */
return $targetEntity->getName(); // Generates option text based on name property of Entity (Currency in this case)
},
],
]);
Usage for multiple select (Use case: add/remove (multiple) roles to/from user)
$this->add([
'name' => 'roles',
'required' => false,
'type' => ObjectMultiCheckbox::class,
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Role::class,
'property' => 'id',
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Roles'),
'label_generator' => function ($targetEntity) {
/** #var Role $targetEntity */
return $targetEntity->getName();
},
],
]);
As a side note: you should really use factories more. I see you using the ServiceLocator throughout your class code, you can avoid that by injecting your needs via a Factory.
If you need more information, I suggest you have a look at a bunch of my past questions as well. I had quite a few, starting out similar to what you're looking for. Managed to figure quite a few of them out on my own and have tried to describe the solutions in depth.
So I made it work !
basically my main problem is that I was not using proper Doctrine naming convention for the fields name of the form.
So I had to reverse engineer my DB into doctrine entities to compare with what I had done to understand where it was not working.
And also to set the model in both end (admins in camptype and camptypse in admin) for the whole shabang to work.
Here are the working classes :
Admin form:
[
'type' => ObjectSelect::class,
'name' => 'campstypes',
'required' => false,
'options' => [
'object_manager' => $this->getServiceLocator()->get(EntityManager::class),
'target_class' => CampsTypes::class,
'property' => 'title',
'label' => 'Type de camps autorisés',
'instructions' => 'Ne rien sélectionner si edition d\'un super admin',
],
'attributes' => [
'class' => '',
'multiple' => 'multiple',
]
],
Admin Controller:
protected function saveAdmin(Admins &$admin, &$form, &$msg)
{
$em = $this->getEntityManager();
/** #var CampTypesService $serviceCampTypes */
$serviceCampTypes = $this->getServiceLocator()->get(CampTypesService::class);
$form->bind($admin);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
if (empty($data['password'])) {
$form->remove('password');
}
$form->setData($data);
if ($form->isValid()) {
if (isset($data['campstypes'])) {
$ids = $form->get('campstypes')->getValue();
$campsTypes = new ArrayCollection($serviceCampTypes->getCampTypesByIds(array_values($ids)));
foreach ($campsTypes as &$campsType) {
/** #var CampsTypes $campsType*/
$campsType->addAdmin($admin);
}
$admin->setCampstypes($campsTypes);
}
$em->persist($admin);
$em->flush();
$msg = 'Sauvegarde des données effectuée';
return;
}
}
return;
}
So by renaming properly the fields of my form and models and setting the data in both end model of the relation I got it to work.
So I've a problem that made me crazy :(, I create a form and when I use the class form in my controller I got this error:
Fatal error: Class 'Application\forms\ArticleForm' not found in C:\wamp2\www\test\module\Application\src\Application\Controller\BlogController.php
and even when I try to use Application\forms\ArticleForm this path doesn't found, this is a part of my action : Update the code :
public function addAction()
{
$form = new ArticleForm();// here the class doesn't found !!
//var_dump($form);die;
$form->initForm();
$request = $this->getRequest();
$form->setData($request->getPost());
And this is my ArticleForm :
class ArticleForm extends Form {
public function __construct()
{
parent::__construct('UserEntry');
$this->setAttribute('method', 'post');
$this->setAttribute('enctype', 'multipart/form-data');
$this->setAttribute('class', 'contact_form');
}
public function initForm()
{
$this->addFormFields(); //function where we added all fields
$articleInputFilter = new ArticleInputFilter();
$this->setInputFilter($articleInputFilter->getInputFilter()); //Asign input Filter to form
}
protected function addFormFields()
{
$this->addSubmit();
$this->addTitle();
$this->addContent();
$this->addDate();
$this->addPublication();
$this->addImage();
}
/**
*
*/
protected function addTitle()
{
$this->add(array(
'name' => 'title',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => _('Title')
),
));
}
/**
*
*/
protected function addContent()
{
$this->add(array(
'name' => 'content',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => _('Content')
),
));
}
/**
*
*/
protected function addDate()
{
$this->add(array(
'name' => 'date',
'attributes' => array(
'type' => 'date',
),
'options' => array(
'label' => _('Date'),
'id' => 'datepicker',
),
));
}
/**
*
*/
protected function addPublication()
{
$this->add(array(
'name' => 'publication',
'attributes' => array(
'type' => 'checkbox',
),
'options' => array(
'label' => _('Publication'),
'use_hidden_element' => true,
'checked_value' => 1,
'unchecked_value' => 'no',
),
));
}
/**
*
*/
protected function addImage()
{
$this->add(array(
'name' => 'Image',
'attributes' => array(
'type' => new ImageForm(),
),
'options' => array(
'label' => _('Image')
),
));
}
/**
*
*/
protected function addSubmit()
{
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => _('Add'),
'class' => 'submit',
),
));
}
}
Finally this is my ArticleInputFilter :
class ArticleInputFilter extends InputFilter implements InputFilterAwareInterface
{
/**
* #var string
*/
public $title;
/**
* #var int
*/
public $image;
/**
* #var string
*/
public $content;
/**
* #var Date
*/
public $date;
/**
* #var Boolean
*/
public $publication;
/**
* #param $data
*/
public function exchangeArray($data)
{
$this->title = (isset($data['title'])) ? $data['title'] : $this->title;
$this->image = (isset($data['image'])) ? $data['image'] : $this->image;
$this->content = (isset($data['content'])) ? $data['content'] : $this->content;
$this->publication = (isset($data['publication'])) ? $data['publication'] : $this->publication;
$this->date = (isset($data['date'])) ? $data['date'] : $this->date;
}
/**
* #param InputFilterInterface $inputFilter
* #return void|InputFilterAwareInterface
* #throws \Exception
*/
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
/**
* #return InputFilter|InputFilterInterface
*/
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 6,
'max' => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'content',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 10,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'publication',
'required' => false,
)));
$inputFilter->add($factory->createInput(array(
'name' => 'date',
'required' => true,
)));
$inputFilter->add($factory->createInput(array(
'name' => 'image',
'required' => true,
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
So please if someone has any idea or solution to my problem I will be very appreciative.
Probably an autoloading problem.
Where is defined your ArticleForm ?
Note : you'd better use the form element manager to get form instance. You can read more on this subject here
I had the same problem. The answer is pretty simple. The problem is in the autoloader so you need to "refresh" or "sincronyze everything" before you get started. So at the top of the function addAction() or the file just require the autoload.php file that is inside of the vendor folder to "update" your project. Something like this:
require 'vendor/autoload.php';
Obviously, you need to write your own path.
I hope that would help.
So i work in a Zend Framework project and i am using Doctrine, i create my Form, Controller and Entities, but when i run my project i got this error :
Object provided to Escape helper, but flags do not allow recursion
This is my Entity :
namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\Form\Annotation;
/**
* Article
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Application\Entity\ArticleRepository")
*/
class Article
{
/**
* #ORM\Column(name="publication", type="boolean")
*/
private $publication;
public function __construct()
{
$this->date = new \Datetime();
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
private $date;
/**
* #var string
*
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #ORM\OneToOne(targetEntity="Application\Entity\Image", cascade={"persist","remove"})
*/
private $image;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
* #return Article
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set date
*
* #param \DateTime $date
* #return Article
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set content
*
* #param string $content
* #return Article
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* Set publication
*
* #param boolean $publication
* #return Article
*/
public function setPublication($publication)
{
$this->publication = $publication;
return $this;
}
/**
* Get publication
*
* #return boolean
*/
public function getPublication()
{
return $this->publication;
}
/**
* Set image
*
* #param \Application\Entity\Image $image
* #return Article
*/
public function setImage(\Application\Entity\Image $image = null)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return \Application\Entity\Image
*/
public function getImage()
{
return $this->image;
}
}
And this is Form with fields validation:
class ArticleForm extends Form implements ObjectManagerAwareInterface
{
/**
* #var EntityManager
*/
protected $em;
public function init()
{
$this->add(array(
'name' => 'title',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'Title'
),
));
$this->add(array(
'name' => 'id',
'attributes' => array(
'type' => 'hidden',
),
));
$this->add(array(
'name' => 'content',
'attributes' => array(
'type' => 'textera',
),
'options' => array(
'label' => 'Content'
),
));
$this->add(array(
'name' => 'date',
'attributes' => array(
'type' => 'text',
'class' => 'datepicker',
),
'options' => array(
'label' => 'Date',
),
));
$this->add(array(
'name' => 'publication',
'attributes' => array(
'type' => 'Checkbox',
),
));
$this->add(array(
'name' => 'url',
'attributes' => array(
'type' => 'file',
'id' => 'files',
'class'=> 'upload'
),
'options' => array(
'label' => 'Url'
),
));
$this->add(array(
'name' => 'alt',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'Alt'
),
));
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Go',
'class' => 'submit',
),
));
$this->setInputFilter($this->createInputFilter());
}
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
}
public function createInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 6,
'max' => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'content',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 10,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'publication',
'required' => false,
)));
$inputFilter->add($factory->createInput(array(
'name' => 'date',
'required' => true,
)));
$inputFilter->add($factory->createInput(array(
'name' => 'image',
'required' => true,
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
public function setObjectManager(ObjectManager $objectManager) {
$this->objectManager = $objectManager;
}
/**
* Get the object manager
*
* #return ObjectManager
*/
public function getObjectManager() {
return $this->objectManager;
}
}
Then my Action :
public function addAction()
{
$form = new ArticleForm($this->getObjectManager());
$article = new Article();
$request = $this->getRequest();
$hydrator = new DoctrineHydrator($this->getObjectManager(), get_class($article));
$form->setHydrator($hydrator);
$form->bind($article);
if ($this->zfcUserAuthentication()->hasIdentity()) {
if ($request->isPost())
{
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getObjectManager()->persist($article);
$this->getObjectManager()->flush();
return $this->redirect()->toRoute('blog');
}
}
}
else
{
return $this->redirect()->toRoute('user');
}
return array('form' => $form);
}
Finally my View where i think i have an error :
<?php
$form = $this->form;
$form->setAttribute('action', $this->url('add', array('action' => 'add')));
$form->prepare();
?>
<?php
echo $this->form()->openTag($form);
?>
<ul>
<li>
<?php echo $this->formHidden($form->get('id'));?>
<li>
<li>
<label>Publication:</label>
<?php echo $this->formInput($form->get('publication'));?>
</li>
<li>
<label>Title:</label>
<?php echo $this->formInput($form->get('title'));?>
</li>
// ....
<li>
<?php echo $this->formSubmit($form->get('submit'));?></li>
</ul>
<?php
echo $this->form()->closeTag();
?>
That it's, this is almost my codes, i tried everything and i didn't found any solution, and i think the error in my view, so please if someone has any idea i will be very appreciative
its propably because of the date-object.
try to change the type of the form element to date:
$this->add(array(
'name' => 'date',
'type' => 'Date',
'attributes' => array(
'type' => 'text',
'class' => 'datepicker',
),
'options' => array(
'label' => 'Date',
),
));
You can get this error when you set an object as attribute instead of a string:
$element->setAttribute('class', $object)
where $element can be a Form, Fieldset or Element
In general, Object provided to Escape helper, but flags do not allow recursion means that the Escape view helper was expecting, but did not get, either a scalar or an object with a __toString() method. It's a fancy way of saying "dude, I can't print this."
The solution is to either do your own rendering (without using the form view helpers), or ensure that the form element value is something you can echo.
You have an init function. Comment out your __construct function and write it like this:
public function __construct(ObjectManager $em, $name = null, $options = array())
{
$this->setObjectManager($em);
parent::__construct($name, $options);
//here you add all the form elements
//(meaning: just put all the content of your init() function here)
//if that's a construct function, you also need to add:
//private $inputFilter;
//at the top of your class, it is not declared in your code
}
Here's the construct function you provided:
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
}
The error was thrown, because you wrote:
$form = new ArticleForm($this->getObjectManager());
So the first parameter you passed to the __construct function was an instance of objectManager, and when it was processed in Zend\View\Helper\Escaper\AbstractHelper, there was something wrong. I can't tell you exactly what's going on there, but if you declare the __construct function as I demonstrated, everything works fine.