I'm working on a project saving IP Addresses and IP Ranges. Currently, my Range entity has references to two IP Address entities (the network and broadcast entities) and has a OneToMany relationship to the hosts. The IP Address entity has a ManyToOne relationship to the IP Range, that is optional.
Here is my IP Address entity:
class IPAddress {
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// relations to other tables
/**
* #var IPRange
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\IPRange", inversedBy="hosts", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="ip_range_id", referencedColumnName="id", nullable=true)
*/
private $ipRangeId;
// entity variables
/**
* #var mixed
* #ORM\Column(name="ip_address", type="binary", length=16, nullable=false, unique=true)
* #SIAssert\IpPacked(version="all")
*/
private $ipAddress;
/**
* #var string
* #ORM\Column(name="ip_address_text", type="string", length=50, nullable=false)
*/
private $ipAddressText;
/**
* #var int
* #ORM\Column(name="cidr", type="smallint", nullable=false, options={"unsigned"=true})
*/
private $cidr;
/**
* #var mixed
* #ORM\Column(name="gateway", type="binary", length=16, nullable=false)
*/
private $gateway;
/**
* #var string
* #ORM\Column(name="gateway_text", type="string", length=50, nullable=false)
*/
private $gatewayText;
/**
* #var int
* #ORM\Column(name="type", type="smallint", options={"unsigned"=true})
*/
private $type;
/**
* #var string
* #ORM\Column(name="description", type="string", length=50)
*/
private $description;
// getters and setters not shown
}
And my IP Range entity:
class IPRange {
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// relations to other tables
/**
* #var IPAddress
* #ORM\OneToOne(targetEntity="AppBundle\Entity\IPAddress", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="network", referencedColumnName="id", nullable=false)
*/
private $network;
/**
* #var IPAddress
* #ORM\OneToOne(targetEntity="AppBundle\Entity\IPAddress", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="broadcast", referencedColumnName="id", nullable=false)
*/
private $broadcast;
/**
* #var array
* #ORM\OneToMany(targetEntity="AppBundle\Entity\IPAddress", mappedBy="ipRangeId")
*/
private $hosts;
// entity variables
/**
* #var string
* #ORM\Column(name="description", type="string", length=50, nullable=false)
*/
private $description;
/**
* #var int
* #ORM\Column(name="cidr", type="smallint", nullable=false, options={"unsigned"=true})
*/
private $cidr;
/**
* #var string
* #ORM\Column(name="notes", type="text", length=65535, nullable=true)
*/
private $notes;
// getters and setters not shown
}
My current flow when I create a new IP Range:
generate new IP Range entity (display form)
get information back from form
generate IP Address entities with a null for the IP Range entity (IP Range not yet persisted, because the network and broadcast IP Address entities are not created)
persist my IP Range entity
get that ID and rewrite all my IP Address entities with the a valid IP Range entity
I must be missing something, because it seems like a lot of steps to programmatically create entities.
Is there a better and more efficient way of doing this?
Edited ... added my form types ...
My IPRangeType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', TextType::class, array(
'required' => true,
'label' => 'Description:',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('notes', TextareaType::class, array(
'required' => false,
'label' => 'Notes:',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('cidr', ChoiceType::class, array(
'required' => true,
'multiple' => false,
'expanded' => false,
'choices' => array (
'IPv4' => [
'/30 (255.255.255.252)' => 30,
'/29 (255.255.255.248)' => 29,
'/28 (255.255.255.240)' => 28,
'/27 (255.255.255.224)' => 27,
'/26 (255.255.255.192)' => 26,
'/25 (255.255.255.128)' => 25,
'/24 (255.255.255.0)' => 24,
'/23 (255.255.254.0)' => 23,
'/22 (255.255.252.0)' => 22,
'/21 (255.255.248.0)' => 21,
'/20 (255.255.240.0)' => 20,
],
'IPv6' => [
'/64 network' => 64,
]
),
'label' => 'CIDR (subnet):',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('network', IPAddressType::class, array(
'required' => true,
))
->add('gateway_select', ChoiceType::class, array(
'mapped' => false,
'required' => true,
'multiple' => false,
'expanded' => false,
'choices' => array (
'- enter in valid IP and CIDR -' => 0,
),
'label' => 'Gateway:',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('json_data', HiddenType::class, array(
'mapped' => false,
))
;
}
My IPAddressType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('ipAddress', TextType::class, array(
'required' => true,
'label' => 'IP Address:',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('cidr', ChoiceType::class, array(
'required' => true,
'multiple' => false,
'expanded' => false,
'choices' => array (
'IPv4' => [
'/30 (255.255.255.252)' => 30,
'/29 (255.255.255.248)' => 29,
'/28 (255.255.255.240)' => 28,
'/27 (255.255.255.224)' => 27,
'/26 (255.255.255.192)' => 26,
'/25 (255.255.255.128)' => 25,
'/24 (255.255.255.0)' => 24,
'/23 (255.255.254.0)' => 23,
'/22 (255.255.252.0)' => 22,
'/21 (255.255.248.0)' => 21,
'/20 (255.255.240.0)' => 20,
],
'IPv6' => [
'/64 network' => 64,
]
),
'label' => 'CIDR (subnet):',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('gateway', TextType::class, array(
'required' => true,
'label' => 'Gateway:',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('type', ChoiceType::class, array(
'required' => true,
'multiple' => false,
'expanded' => false,
'choices' => IPAddress::TYPE,
'label' => 'Address Type:',
'label_attr' => array(
'class' => 'text-right middle',
),
))
->add('description', TextType::class, array(
'required' => true,
'label' => 'Description:',
'label_attr' => array(
'class' => 'text-right middle',
),
))
;
$builder->get('ipAddress')
->addModelTransformer(new IPToStringTransformer());
$builder->get('gateway')
->addModelTransformer(new IPToStringTransformer());
}
Sounds very roundabout. I'll loosely detail for you what I would do as a general approach.
I would create two form types, call one IpAddressType and the other IpRangeType. Because you're sharing purposes in IpAddress you should only need one type, it's all the same data.
So your form type will look something like:
(this is symfony 2.3 syntax - there will be some differences if you have a newer version - but the approach is the same)
class IpRangeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('cidr'); // and other single fields
$builder->add('network', new IpAddressType());
$builder->add('broadcast', new IpAddressType());
$builder->add('hosts', 'collection', ['type' => new IpAddressType()]);
}
public function getName()
{
return 'ip_range';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'YourBundle\Entity\IpRange',
));
}
}
Sub form type.
class IpAddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('ipAddress');
$builder->add('ipAddressText'); // etc etc
}
public function getName()
{
return 'ip_address';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'YourBundle\Entity\IpAddress',
));
}
}
Then in your controller:
$ipRange = new IpRange();
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new IpRangeType(), $ipRange);
$form->handleRequest($request);
if ($form->isValid()) {
$em->persist($ipRange);
$em->flush();
return $this->redirect($this->generateUrl('your_success_route'));
}
It should be that simple.
Note: you will need to set at least cascade=persist on IpRange $hosts property, otherwise it won't persist IpAddress items added to the collection.
Because of cascade persist on IpRange network, broadcast and hosts properties you don't need to persist individual items, the ipAddress entities will automatically be associated with the parent entity.
You will need to follow this approach for adding your host entries:
http://symfony.com/doc/current/form/form_collections.html
Also please have a look at the forms documentation in general, you should never need to 'generate' an entity if a form is done correctly (other than in very special cases).
Correctly implemented the form should return your complete entities with the data added in the form and all you have to do is validate & persist.
Docs for handling form submissions here: http://symfony.com/doc/current/forms.html#handling-form-submissions
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'
]
])
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;
I have entity User and field city and country ManyToOne and if user enter for profile I create form - PersonalInformation and dont know how to bring the country to the city and then the user would drop down list.Now I have error and dont now how to solved:
Notice: Object of class Proxies\__CG__\PillsBundle\Entity\Country could not be converted to int
Entity
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #var \PillsBundle\Entity\Cities
*
* #ORM\ManyToOne(targetEntity="\PillsBundle\Entity\Cities")
* #ORM\JoinColumn(name="city_id", referencedColumnName="id", nullable=true)
*/
private $city;
/**
* #var \PillsBundle\Entity\Country
*
* #ORM\ManyToOne(targetEntity="\PillsBundle\Entity\Country")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=true)
*/
private $country;
and City&Country
class Cities
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="city", type="string", length=255)
*/
private $city;
class Country
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="country", type="string", length=255)
*/
private $country;
I create form
class CityType extends AbstractType
{
private $em;
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getCity()
{
$citiess = $this->em->getRepository('PillsBundle:Cities')->findAll();
$new_cities = array();
foreach($citiess as $citie) {
$new_cities[$citie->getCity()] = $citie->getCity();
}
asort($new_cities);
return $new_cities;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->getCity(),
'multiple' => false,
'required' => false,
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'cities';
}
}
and country
class CountryType extends AbstractType
{
private $em;
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getCountry()
{
$countrys = $this->em->getRepository('PillsBundle:Country')->findAll();
$new_country = array();
foreach($countrys as $country) {
$new_country[$country->getCountry()] = $country->getCountry();
}
asort($new_country);
return $countrys;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->getCountry(),
'multiple' => false,
'required' => false,
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'country';
}
}
and use this type in form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstname', null, array('label' => 'First Name', 'max_length' => 255, 'required' => false))
->add('lastname', null, array('label' => 'Last Name', 'max_length' => 255, 'required' => false))
->add('email', null, array('label' => 'Email', 'max_length' => 255, 'required' => false))
->add('city', 'cities', array('label' => 'Location','required' => false, 'mapped' => true, 'attr' => array('placeholder' => 'Select Location') ))
->add('country', 'country', array('label' => 'Country','required' => false, 'mapped' => true, 'attr' => array('placeholder' => 'Select Country') ))
->add('skype', null, array('label' => 'Skype', 'max_length' => 255, 'required' => false))
->add('telephone', null, array('label' => 'Phone', 'max_length' => 255, 'required' => false, 'attr' => array('data-inputmask' => "'alias': 'date'")))
->add('save', 'submit');
}
and template:
<div class="form-group">
{{ form_label(infoForm.country, label|default(null), {'label_attr': {'class': 'control-label'}}) }}
{{ form_widget(infoForm.country, {'attr': {'class': 'form-control select2 select2_sample4'}}) }}
</div>
<div class="form-group">
{{ form_widget(infoForm.city, {'attr': {'class': 'form-control input-xlarge select2me'}}) }}
</div>
and action:
$formType = new DeveloperPersonalInformationType();
$form = $this->createForm($formType, $developer);
$personalInformationForm = $form->createView();
the second parameter from the add() function must be a formfield-type e.g. text or textarea (see full details). I think you want to use the type 'entity'.
$builder
->add('country', 'entity', , array(
'class' => 'PillsBundle:Country',
'choice_label' => 'Select Country',
));
If the Country entity object does not have a __toString() method the choice_label option is needed.
I have the segments checkbox in my form and when I submit, these values doesn't appears in post's return. The segment isn't mapped because it's a external value from database.
Users.php (Entity)
/**
* Users
*
* #ORM\Table(name="users", indexes={#ORM\Index(name="fk_users_users_groups1_idx", columns={"users_groups_id"})})
* #ORM\Entity
*/
class Users
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=45, nullable=true)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=45, nullable=true)
*/
private $email;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=32, nullable=true)
*/
private $password;
/**
* #var boolean
*
* #ORM\Column(name="active", type="boolean", nullable=true)
*/
private $active;
/**
* #var \DateTime
*
* #ORM\Column(name="created_at", type="datetime", nullable=true)
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updated_at", type="datetime", nullable=true)
*/
private $updatedAt;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var UsersGroups
*
* #ORM\ManyToOne(targetEntity="UsersGroups", cascade={"all"}, fetch="EAGER")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="users_groups_id", referencedColumnName="id")
* })
*/
private $usersGroups;
public function __construct()
{
$this->usersGroups = new ArrayCollection();
}
UsersType.php (Form)
class UsersType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text', array(
'label' => 'Nome',
'attr' => array(
'class' => 'form-control'
)
))
->add('email', 'text', array(
'label' => 'E-mail',
'attr' => array(
'class' => 'form-control'
)
))
->add('password', 'password', array(
'label' => 'Senha',
'attr' => array(
'class' => 'form-control'
)
))
->add('usersGroups', 'entity', array(
'class' => 'Ad\SisBundle\Entity\UsersGroups',
'label' => 'Grupo do usuário',
'attr' => array(
'class' => 'form-control'
)
))
->add('active', 'checkbox', array(
'label' => 'Ativo',
'attr' => array(
'class' => 'form-control'
)
))
->add('segments', 'entity', array(
'class' => 'Ad\SisBundle\Entity\Segments',
'query_builder' => function( EntityRepository $segments ) {
return $segments->createQueryBuilder("s");
},
'multiple' => true,
'mapped' => false,
'expanded' => true,
))
;
}
And this is the post's result
Users {#2011 ▼
-name: "xxx"
-email: "xxx#hotmail.com"
-password: "312321"
-active: true
-createdAt: null
-updatedAt: null
-id: 1
-usersGroups: UsersGroups {#2013 ▶}
}
I've tried this:
$segments = $editForm->get('segments')->getData();
I believe that if the segment information was the object of users, it would be easier to handle the information.
This is the right way to do this? How can I get the segment data?
If the segments are not mapped it won't passed to the entity.
you can either get it from the request in a preSubmit event subscriber(look at
http://symfony.com/doc/current/components/form/form_events.html#event-subscribers)
or , changing mapped to true, and pass it to the entity but leave the field of the entity without the ORM annotation if you don't want it to be persisted.