I have 2 Doctrine entities (Environment and EnvironmentConfig). They have a bi-directional OneToOne relationship.
Each entity has their own Fieldset so that re-use is easy.
To create an Environment it can also have an EnvironmentConfig, however not required. To allow them to be made at the same time I have an EnvironmentForm that uses the EnvironmentFieldset and the EnvironmentConfigFieldset.
The Form renders properly. However, it saves the Environment but not the EnvironmentConfig.
Where is it that I've gone wrong in setting this up and how to fix it?
Code below, leaving out getters/setters, would be too much.
Entities
// abstract class AbstractEntity has $id + getters/setters.
class Environment extends AbstractEntity
{
/**
* #var string
* #ORM\Column(name="name", type="string", length=255, nullable=false)
*/
protected $name;
/**
* #var EnvironmentConfig
* #ORM\OneToOne(targetEntity="Environment\Entity\EnvironmentConfig", inversedBy="environment")
*/
protected $config;
/**
* #var EnvironmentScript
* #ORM\OneToOne(targetEntity="EnvironmentScript")
*/
protected $script;
//Getters/setters
}
class EnvironmentConfig extends AbstractEntity
{
/**
* #var string
* #ORM\Column(name="name", type="string", length=255, nullable=false)
*/
protected $name;
/**
* #var Environment
* #ORM\OneToOne(targetEntity="Environment\Entity\Environment", mappedBy="config")
*/
protected $environment;
//Getters/setters
}
Fieldsets
class EnvironmentFieldset extends AbstractFieldset
{
/**
* {#inheritdoc}
*/
public function init()
{
//Loads id element validation
parent::init();
$this->add([
'name' => 'name',
'type' => Text::class,
'options' => [
'label' => _('Name'),
'label_attributes' => [
'class' => 'col-xs-2 col-form-label',
],
],
'attributes' => [
'id' => 'name',
'class' => 'form-control'
],
]);
$this->add([
'name' => 'environment_config',
'type' => EnvironmentConfigFieldset::class,
'options' => [
'use_as_base_fieldset' => false,
],
]);
$this->add([
'type' => ObjectSelect::class,
'name' => 'environment_script',
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => EnvironmentScript::class,
'property' => 'id',
'display_empty_item' => true,
'empty_item_label' => '---',
'label_generator' => function ($targetEntity) {
return $targetEntity->getId() . ' - ' . $targetEntity->getName();
},
],
]);
}
}
class EnvironmentConfigFieldset extends AbstractFieldset
{
/**
* {#inheritdoc}
*/
public function init()
{
//Loads id element validation
parent::init();
$this->add([
'name' => 'name',
'type' => Text::class,
'options' => [
'label' => _('Name'),
'label_attributes' => [
'class' => 'col-xs-2 col-form-label',
],
],
'attributes' => [
'id' => 'name',
'class' => 'form-control'
],
]);
}
}
Form
class EnvironmentForm extends AbstractForm
{
/**
* EnvironmentForm constructor.
* #param null $name
* #param array $options
*/
public function __construct($name = null, array $options)
{
//Also adds CSRF
parent::__construct($name, $options);
}
/**
* {#inheritdoc}
*/
public function init()
{
//Call parent initializer. Adds submit button.
parent::init();
$this->add([
'name' => 'environment',
'type' => EnvironmentFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
}
}
Edit: added debug data and AddAction()
Above debugging done on the persist() line in the function below.
public function addAction($formName, array $formOptions = null, $route, array $routeParams = [])
{
if (!$this->formElementManager instanceof FormElementManagerV2Polyfill) {
throw new InvalidArgumentException('Dependency FormElementManagerV2Polyfill not set. Please check Factory for this function.');
}
if (!class_exists($formName)) {
throw new ClassNotFoundException('Given class could not be found. Does it exist?');
}
/** #var AbstractForm $form */
$form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions));
/** #var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$entity = $form->getObject();
$this->getEntityService()->getEntityManager()->persist($entity);
$this->getEntityService()->getEntityManager()->flush();
$this->flashMessenger()->addSuccessMessage(
_('Successfully created object.')
);
$this->redirectToRoute($route, $this->getRouteParams($entity, $routeParams));
}
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
You created a field called 'environment_config' but in class Environment you called protected $config;. Both names must be the same. Same problem for 'environment_script' field and $script attribute.
Another thing, you want to create a EnvironmentConfig dynamically so you must add in $config annotation a cascade option to be able to create a $config from Environment:
/**
* #var EnvironmentConfig
* #ORM\OneToOne(targetEntity="Environment\Entity\EnvironmentConfig", inversedBy="environment", cascade={"persist"})
*/
protected $config;
Related
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!
My current setup is as described below. What i want to achive is. One Distributor can have multiple categories. But One category can have 1 Distributor 1:N <=> N:1. But it fails when i click create category even if the distributor input field is empty.
Category
/**
* #var string
*
* #ORM\Id()
* #ORM\Column(type="string", nullable=false, unique=true)
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #var string
* #ORM\Column(type="string", nullable=false)
*/
private $title;
/**
* #var Distributor
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Distributor", inversedBy="category")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $distributor;
Distributor:
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Category", mappedBy="distributor")
*/
private $category;
public function __construct()
{
$this->category = new ArrayCollection();
}
CategoryForm:
$builder
->add('parent', EntityType::class, [
'class' => Category::class,
'choice_label' => 'title',
'multiple' => true,
'required' => false,
'attr' => [
'class' => 'select2 form-control select2insidemodal js-example-matcher'
]
])
->add('title', TextType::class, [
'label' => 'Title',
'required' => true,
'by_reference' => true
])
->add('distributor', EntityType::class, [
'class' => Distributor::class,
'choice_label' => 'name',
'required' => false,
'attr' => [
'class' => 'select2 form-control select2insidemodal js-example-matcher'
]
]);
Create Category Action
public function createAction(Request $request)
{
$category = new Category();
$categoryForm = $this->createForm(CategoryForm::class, $category);
$categoryForm->handleRequest($request);
if ($categoryForm->isSubmitted() && $categoryForm->isValid()) {
$result = $this->categoryService->create($category);
}
return $this->render(
'#app_bar/Category/categoryNew.twig',
[
'form' => $categoryForm->createView(),
]
);
}
The error message I get :
Expected argument of type "AppBundle\Entity\Category",
"Doctrine\Common\Collections\ArrayCollection" given
As i understood , parent is not a collection, so change the form parent multiple option to false:
->add('parent', EntityType::class, [
'class' => Category::class,
'choice_label' => 'title',
'multiple' => false,
'required' => false,
'attr' => [
'class' => 'select2 form-control select2insidemodal js-example-matcher'
]
])
still strugling with a Symfony3 and Doctrine. Just trying to make simply autofilling form from database. I got 2 tables - countries and states.
countries.php
namespace AppBundle\Entity\Forms;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* #ORM\Entity
* #ORM\Table(name="countries")
*/
class Countries
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $country_code;
/**
* #ORM\Column(type="string")
*/
private $country_name;
public function __toString()
{
return $this->country_name;
}
/**
* #var Collection|States[]
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Forms\States", mappedBy="country_code")
*/
protected $states;
public function __constructor()
{
$this->states = new ArrayCollection();
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getCountryCode()
{
return $this->country_code;
}
/**
* #param mixed $country_code
*/
public function setCountryCode($country_code)
{
$this->country_code = $country_code;
}
/**
* #return mixed
*/
public function getCountryName()
{
return $this->country_name;
}
/**
* #param mixed $country_name
*/
public function setCountryName($country_name)
{
$this->country_name = $country_name;
}
/**
* #return States[]|Collection
*/
public function getStates()
{
return $this->states;
}
/**
* #param States[]|Collection $states
* #return $this
*/
public function setStates($states)
{
$this->states = $states;
}
states.php
/**
* Created by PhpStorm.
* User: Alvydas Budrys
* Website: http://alvy.lt
* Date: 2017-02-07
* Time: 6:13 AM
*/
namespace AppBundle\Entity\Forms;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="states")
*/
class States
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Forms\Countries", inversedBy="states")
* #ORM\JoinColumn(name="country_code", referencedColumnName="id", onDelete="CASCADE")
*/
private $country_code;
public function __toString()
{
return $this->name;
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getCountryCode()
{
return $this->country_code;
}
/**
* #param mixed $country_code
*/
public function setCountryCode($country_code)
{
$this->country_code = $country_code;
}
Also formfile:
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormInterface;
use AppBundle\Entity\Forms\States;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'FirstName'
)))
->add('lastname', TextType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'Lastname'
)))
->add('email', EmailType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'Email'
)))
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'mapped'=>false,
'first_options' => ['label' => false,'mapped'=>false,'attr' => array(
'placeholder' => 'Password'
) ],
'second_options' => ['label' => false,'mapped'=>false, 'attr' => array(
'placeholder' => 'Confirm Password'
) ],
])
->add('accType',ChoiceType::class,
array(
'choices' => array(
'Personal' => '1',
'Business' => '2',),
'choices_as_values' => true,
'multiple'=>false,
'expanded'=>true,
'required' =>true,
'data' => true,
'mapped'=>false,
))
->add('city', TextType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'City'
)))
->add('postcode', TextType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'Postcode'
)))
->add('CountryName', EntityType::class, array(
'class' => 'AppBundle\Entity\Forms\Countries',
'label' => false,
//'mapped'=>false,
'choice_translation_domain' => true,
))
->add('companyName', TextType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'Company Name'
)))
->add('companyNumber', TextType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'Company Number'
)))
->add('companyVat', TextType::class,
array('label' => false,
'mapped'=>false,
'attr' => array(
'placeholder' => 'VAT'
)))
;
$formModifier = function (FormInterface $form, States $states = null) {
$positions = null === $states ? array() : $states->getStates();
$form->add('states', EntityType::class, array(
'class' => 'AppBundle\Entity\Forms\States',
'placeholder' => '',
'choices' => $positions,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getCountryName());
}
);
$builder->get('CountryName')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$countries = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $countries);
}
);
}
}
but stil getting errors:
Type error: Argument 2 passed to AppBundle\Form\UserType::AppBundle\Form\{closure}() must be an instance of AppBundle\Entity\Forms\States, instance of AppBundle\Entity\Forms\Countries given, called in /var/www/hostera.dev/public_html/src/AppBundle/Form/UserType.php on line 139
Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalThrowableError: "Type error: Argument 2 passed to AppBundle\Form\UserType::AppBundle\Form\{closure}() must be an instance of AppBundle\Entity\Forms\States, instance of AppBundle\Entity\Forms\Countries given, called in /var/www/hostera.dev/public_html/src/AppBundle/Form/UserType.php on line 139" at /var/www/hostera.dev/public_html/src/AppBundle/Form/UserType.php line 111
I think something bad with Doctrine Association, but I can't figure out what.
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.
Ok this my code where i try to map
This is my user.php
<?php
namespace User\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
/**
* #ORM\Entity
* #ORM\Table(name="users")
* #property string $user_name
* #property string $pass_word
* #property int $id
*/
class User implements InputFilterAwareInterface {
protected $inputFilter;
/**
* #ORM\ManyToMany(targetEntity="Subject\Entity\Subject", inversedBy="users")
* #JoinTable(name="users_subjects")
* #var Collection
*/
protected $userName;
protected $password;
private $subjects;
/** #ORM\Id() #ORM\Column(type="integer") #ORM\GeneratedValue(strategy="AUTO") #var int */
protected $id;
public function __get($property) {
return $this->$property;
}
public function __construct() {
$this->subjects = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Magic setter to save protected properties.
*
* #param string $property
* #param mixed $value
*/
public function __set($property, $value) {
$this->$property = $value;
}
/**
* Convert the object to an array.
*
* #return array
*/
public function getArrayCopy() {
return get_object_vars($this);
}
public function populate($data = array()) {
$this->id = $data['id'];
$this->userName = $data['userame'];
$this->password = $data['password '];
}
public function setInputFilter(InputFilterInterface $inputFilter) {
throw new \Exception("Not used");
}
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$inputFactory = new InputFactory();
$inputFilter->add($inputFactory->createInput(array('name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
))));
$inputFilter->add($inputFactory->createInput(array('name' => 'Username',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
)));
$inputFilter->add($inputFactory->createInput(array('name' => 'Password',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
//put your code here
}
?>
And here goes my subject.php
<?php
namespace Subject\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
/**
*
* #ORM\Entity
* #ORM\Table(name="subject")
* #property string $subjectname
* #property int $users
* #property int $id
*/
class Subject implements InputFilterAwareInterface {
protected $inputFilter;
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $subjectname;
/**
* #ORM\ManyToMany(targetEntity="User\Entity\User", mappedBy="subjects")
*/
protected $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
$this->addUser($this->users);
}
/** #return User|null */
public function getUsers() {
return $this->users;
}
/** #param User $user */
public function addUser(User $user) {
if ($users !== null || $user instanceof User) {
$this->users->add($users);
} else {
throw new InvalidArgumentException('$user must be instance of Entity\User or null!');
}
}
/**
* Magic getter to expose protected properties.
*
* #param string $property
* #return mixed
*/
public function __get($property) {
return $this->$property;
}
/**
* Magic setter to save protected properties.
*
* #param string $property
* #param mixed $value
*/
public function __set($property, $value) {
$this->$property = $value;
}
/**
* Convert the object to an array.
*
* #return array
*/
public function getArrayCopy() {
return get_object_vars($this);
}
/**
* Populate from an array.
*
* #param array $data
*/
public function populate($data = array()) {
$this->id = $data['id'];
$this->subjectname = $data['subjectname'];
$this->users = $data['user_id'];
//print_r($data);
}
public function setInputFilter(InputFilterInterface $inputFilter) {
throw new \Exception("Not used");
}
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'id',
// 'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'subjectname',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'users',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
Since i am mapping many to many i dont know why i am getting this error
"The target-entity User\Entity\User cannot be found in 'Subject\Entity\Subject#users'."