I use assert to check values of my form. I am not able to work with assert on a collection. The main goal it to check if each values are not empty and is a number.
I tried to use this link to solve my issue without success.
Here is part of my entity :
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
class Myclass
{
private $id;
/**
* #Assert\NotBlank()
* #Assert\Regex(pattern="/^0[1-9]([-. ]?[0-9]{2}){4}$/",message="Invalid")
*/
private $numbers;
...
public function __construct()
{
$this->numbers= new ArrayCollection();
}
...
public function addNumber($number)
{
$this->numbers[] = $number;
return $this;
}
public function removeNumber($number)
{
$this->numbers->removeElement($number);
}
public function getNumbers()
{
return $this->numbers;
}
}
And here is a part of my form :
namespace MyBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyclassType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('numbers',"Symfony\Component\Form\Extension\Core\Type\CollectionType",array(
'required'=>true,
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'entry_type'=>"Symfony\Component\Form\Extension\Core\Type\TextType",
'entry_options' => array(
'required' => true,
'attr' => array('class' => 'form-control'),
)
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Myclass'
));
}
public function getBlockPrefix()
{
return 'mybundle_myclass';
}
}
"All" assert seems to be the job All (The Symfony Reference)
Here is the solution :
/**
* #Assert\All({
* #Assert\NotBlank(),
* #Assert\Regex(pattern="/^0[1-9]([-. ]?[0-9]{2}){4}$/",message="Invalid")
* })
*/
private $numbers;
You'll need to make a Custom
http://symfony.com/doc/current/cookbook/validation/custom_constraint.html
This has a good example that I use to find a unique entities: How to validate unique entities in an entity collection in symfony2 You can change the logic to check values.
Related
I have a form based on a collection. I would like that form to be set with a part of the data I have in my entity (not all).
I have search but haven't find the answer I'm looking for.
I tought using PRE_SET_DATA but the way I understand it doesn't work.
Can someone help me to do that?
I have an entity Service
<?php
namespace App\Entity;
use App\Repository\ServiceRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=ServiceRepository::class)
*/
class Service
{
//[...]
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="services")
*/
private $performer;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="performer_services")
*/
private $service_performer;
//[...]
public function getPerformer(): ?user
{
return $this->performer;
}
public function setPerformer(?user $performer): self
{
$this->performer = $performer;
return $this;
}
public function getServicePerformer(): ?User
{
return $this->service_performer;
}
public function setServicePerformer(?User $service_performer): self
{
$this->service_performer = $service_performer;
return $this;
}
}
My Collection Form
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PerformerServiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('performer_services',CollectionType::class,[
'entry_type' => ServicePerformerNoMoneyUnitType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
])
->add('enregistrer', SubmitType::class)
;
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
My Entity Form (based on Service)
<?php
namespace App\Form;
use App\Entity\Service;
use App\Repository\UserRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Security\Core\Security;
use App\Entity\User;
class ServicePerformerNoMoneyUnitType extends AbstractType
{
public function __construct(Security $security)
{
$this->security = $security;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price', MoneyType::class, [
'currency' => false])
->add('servicePerformer', EntityType::class, [
'class' => 'App\Entity\User',
'query_builder' => function (UserRepository $er) {
return $er->findByPartners($this->security->getUser());
},
])
->add('description')
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$service = $event->getData();
$userService = new User;
$performerService = new User;
if ($service <> null)
{
$userService = $service->getPerformer();
dump($userService);
$performerService = $service->getServicePerformer();
dump($performerService);
}
$form = $event->getForm();
// checks if the Product object is "new"
// If no data is passed to the form, the data is "null".
// This should be considered a new "Product"
if ($userService == $performerService) {
$form->removeElement();
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Service::class,
]);
}
}
What I would like to do is not show rows where Performer = Service Performer.
I have an Article entity and I'm adding multiple categories to it dynamically. For that, I created a CollectionType field in the Article form. I'm able to add multiple categories in the frontend but when I save the Article I'm getting the following error.
Error 500: Expected argument of type "MyBundle\Entity\Category", "Doctrine\Common\Collections\ArrayCollection" given
Also please note that I'm adding categories to the Article dynamically, I have an add another button in the frontend for adding more categories to Article.
I have the following code:
Article.php
namespace MyBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
class Article {
/**
* #var Collection|Category[]
*
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Category")
*/
private $category;
/**
* #return Collection|Category[]
*/
public function getCategories(): Collection
{
return $this->category;
}
/**
* #param Category $category
* #return Article
*/
public function setCategories(Category $category): self
{
if (!$this->category->contains($category)) {
$this->category->add($category);
}
return $this;
}
}
Category.php
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Category {
/**
* #var string
*
* #ORM\Column(type="string", nullable=false, unique=true)
*/
private $name;
/**
* #return string
*/
public function getName(): ?string
{
return $this->name;
}
/**
* #param string $name
* #return Tenant
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
}
CategoryType.php
namespace MyBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use MyBundle\Entity\Category;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name'
]);
}
public function getBlockPrefix()
{
return null;
}
}
ArticleType.php
namespace MyBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use MyBundle\Entity\Article;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('category', CollectionType::class, [
'entry_type' => CategoryType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
]);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Article',
'role_repo' => null,
'mode' => null,
'article_entity' => null,
'languages' => [],
'payment_terms' => []
));
$resolver->setRequired('article_repo');
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'mybundle_article_entity';
}
}
I have the following code in my ArticleController.php
public function addArticleAction(Request $request)
{
/** #var AdminAccount $admin */
$admin = $this->getUser();
$article = new Article(Editor::is($admin->getId())->at(self::class));
$form = $this->createForm(
'MyBundle\Form\ArticleType',
$article,
[
'languages' => array_flip(Languages::getLanguages()),
'payment_terms' => array_flip(PaymentTerms::getTermOptions()),
'mode' => 'add',
'article_entity_repo' => null
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($article);
$em->flush();
return $this->redirectToRoute('admin_article');
}
return $this->render('admin/article/add.html.twig', array(
'article' => $article,
'form' => $form->createView()
));
}
I have the following code in my template add.html.twig
<h3>Articles</h3>
<ul class="category" data-prototype="{{ form_widget(form.category.vars.prototype.name)|e('html_attr') }}">
{% for category in form.category %}
<li>{{ form_row(category.name) }}</li>
{% endfor %}
</ul>
I'm able to save the Article if I change my setCategories in Article entity
/**
* #param ArrayCollection $category
* #return Article
*/
public function setCategories(ArrayCollection $category): self
{
foreach($category as $_category) {
if (!$this->category->contains($_category['name'])) {
$this->category->add($_category['name']);
}
}
return $this;
}
But I don't think this is the best approach
If you have a ManyToMany relation, you article has a collection of categories.
Don't forget to put plural everywhere. So your setCategories should take a collection as an argument and set it. Then create an addCategory() and removeCategory methods.
If I'm correct you don't create the categories at the same time than the article, right ? So you don't need a collectiontype but an EntityType. If I'm wrong then you do need a collectiontype but in CategoryType, you don't have an EntityType but a TextType.
I'm new to symfony and still learning, my question is how do I populate a select drop-down in a form with an static array of choices. Say I have a class named Cake, I'd like to be able to fill a drop-down for the status of Cake from the array statuses created in the same CakeEntity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CakeRepository")
*/
class Cake
{
/**
* #ORM\Column(type="string", length=50)
*/
private $status;
private $statuses = array(
'not_ready' => 'Not Ready',
'almost_ready' => 'Almost Ready',
'ready'=>'Ready',
'too_late'=>'Too late'
);
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
public function getStatuses()
{
return $this->statuses;
}
}
My Controller looks like:
namespace App\Controller;
use App\Entity\Cake;
use App\Form\CakeType;
use App\Repository\CakeRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/cake")
*/
class CakeController extends AbstractController
{
/**
* #Route("/new", name="cake_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$cake = new Cake();
$form = $this->createForm(CakeType::class, $cake);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$cake->setCreatedAt(\DateTime::createFromFormat('d-m-Y', date('d-m-Y')));
$cake->setCreatedBy(1);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($cake);
$entityManager->flush();
return $this->redirectToRoute('cake_index');
}
return $this->render('cake/new.html.twig', [
'cake' => $cake,
'form' => $form->createView(),
]);
}
My CakeEntity:
<?php
namespace App\Form;
use App\Entity\cake;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class CakeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
->add('status', ChoiceType::class,
[
'choices'=>function(?Cake $cake) {
return $cake->getStatuses();
}
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Cake::class,
]);
}
}
When trying to browse /cake/new I get the error:
An error has occurred resolving the options of the form "Symfony\Component\Form\Extension\Core\Type\ChoiceType": The option "choices" with value Closure is expected to be of type "null" or "array" or "\Traversable", but is of type "Closure".
You could declare getStatuses on Cake as static, or use public constants. E.g.:
class Cake
{
// with static variables
private static $statuses = [
'not_ready' => 'Not Ready',
'almost_ready' => 'Almost Ready',
'ready' => 'Ready',
'too_late' => 'Too late',
];
public static function getStatuses()
{
return self::$statuses;
}
// or with public const
public const STATUSES = [
'not_ready' => 'Not Ready',
'almost_ready' => 'Almost Ready',
'ready' => 'Ready',
'too_late' => 'Too late',
];
}
This seems reasonable, as the return value is not instance but class specific.
You could then use:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', ChoiceType::class, [
'choices'=> Cake::getStatuses(),
]);
// or
$builder->add('status', ChoiceType::class, [
'choices'=> Cake::STATUSES,
]);
}
If the choices actually depend on a given Cake instance, you could pass it via the options array or use form events.
I have a Filter and FilterCollection object. The FilterCollection holds a collection of Filters, just like the name indicate.
Now I need to validate everything, so I created a FilterType and FilterTypeCollection Forms. In the FilterCollectionType I have:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('filters', CollectionType::class, array(
'entry_type' => FilterType::class
));
}
And in the FilterCollection definition I have the following:
/**
* #var array
* #Assert\Valid()
*/
private $filters = [];
I created a paramConverter so I could convert elements from my request into FilterCollection ones. In the apply method I try to validate everything by using:
public function apply(Request $request, ParamConverter $configuration)
$filterCollection = new FilterCollection();
$form = $this->formFactory->create(
FilterTypeCollection::class,
$filterCollection
);
$form->submit($request->query->all());
if ($form->isSubmitted() && $form->isValid()) {
$request->attributes->set($configuration->getName(), $filterCollection);
return true;
} else {
throw new FormValidationException($form);
}
}
I was expecting that the validation not only validates the FilterCollection but also the Filters. But the validations I have in my Filter definition, are not working, even if I have validations that should fail, it still passes. I think the validator is not passing on the Filter elements.
Any idea on what might be happening?
I finally got it to work. Perhaps you made the same mistake as me, forgetting to add "data_class" in the configureOptions in the formType.
Anyway, here's the code that works (on fresh install of Symfony 3.3)
DefaultController.php
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Filter;
use AppBundle\Entity\FilterCollection;
use AppBundle\Form\FilterCollectionType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
// add first filter, so we don't have to implement the collection javascript etc to test quickly
$collection = new FilterCollection();
$collection->filters[] = new Filter();
$form = $this->createForm(FilterCollectionType::class, $collection);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
echo "valid input"; // we don't want to see this ;)
}
}
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'form' => $form->createView()
]);
}
}
Filter.php
<?php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Filter {
/**
* #var string
* #Assert\NotBlank()
* #Assert\Regex(pattern="/[0-9]+/")
*/
public $name;
}
FilterCollection.php
<?php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class FilterCollection {
/**
* #var Filter[]
* #Assert\Valid()
*/
public $filters = [];
}
FilterType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Filter'
]);
}
}
FilterCollectionType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FilterCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('filters', CollectionType::class, [
'entry_type' => FilterType::class,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\FilterCollection',
]);
}
public function getName()
{
return 'app_bundle_filter_collection_type';
}
}
Note: I didn't make a ParamConverter like you did, but that seems beside the point of the question. You can change the code to use a ParamConverter easily.
Ok here is a quick overview of what I am trying to do. I have a "Client" entity with a relationship to a "ClientDomain" entity. I need to have a form that will show me a list of all the ClientDomains for a given client. In the controller I know what client i need to filter for but im unsure how to pass that information to the formBuilder.
Heres what i have so far:
//src/NameSpace/ClientBundle/Entity/Client.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Client{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $client_id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="ClientDomain", mappedBy="client")
*/
protected $domains;
...
}
And the form:
//src/LG/ClientBundle/Form/ClientDomainSelectionForm.php
namespace LG\ProjectBundle\Form\Projects;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ClientDomainSelectionForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('client_domain', 'entity', array(
'class' => 'LG\ClientBundle\Entity\ClientDomain',
'query_builder'=> function(EntityRepository $er) {
return $er->createQueryBuilder('cd')
/* NEEDS TO FIND DOMAINS BY CLIENT X */
},
'property' => 'domain',
'label' => 'Domain: '
));
}
}
And then finally the controller:
//src/LG/ClientBundle/Controller/DomainSelectorController.php
namespace LG/ClientBundle/Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use LG\ClientBundle\Entity\Client;
use LG\ClientBundle\Entity\ClientDomain;
use LG\ClientBundle\Entity\ClientActiveDomain;
use LG\ClientBundle\Form\ClientDomainSelectionForm;
/**
* #Route("")
*/
class DomainSelectorController extends Controller{
/**
* #Route("/client/{client_slug}/select-domain", name="lg.client.clientdomainselection.selectclient")
* #Template
*/
public function selectDomainAction(Request $request, Client $client){
$activeDomain = new ClientActiveDomain();
$form = $this->createForm(new ClientDomainSelectionForm(), $activeDomain );
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($activeDomain );
$em->flush();
return $this->redirect(/*huge long url*/);
}
}
return array(
'form' => $form->createView(),
);
}
}
As you can see I have access to the client entity in the controller im just not sure how to give that to the form builder so that it will only return domains for the current client.
I Have found the answer, you just need to add a constructor to the form and pass in the client from the controller like so:
//src/LG/ClientBundle/Form/ClientDomainSelectionForm.php
namespace LG\ProjectBundle\Form\Projects;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ClientDomainSelectionForm extends AbstractType {
protected $client;
public function __construct(Client $client) {
$this->client = $client;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$client = $this->client;
$builder->add('client_domain', 'entity', array(
'class' => 'LG\ClientBundle\Entity\ClientDomain',
'query_builder'=> function(\Doctrine\ORM\EntityRepository $er) use ($client) {
return $er->createQueryBuilder('cd')
->where('cd.client = :client')
->orderBy('cd.domain', 'ASC')
->setParameter('client',$client->getClientId());
},
'property' => 'domain',
'label' => 'Domain: '
));
}
}
And Then in the controller:
//src/LG/ClientBundle/Controller/DomainSelectorController.php
...
public function selectDomainAction(Request $request, Client $client){
...
$form = $this->createForm(new ClientDomainSelectionForm($client), $activeDomain );
...
}
...