Object vs array result - symfony

In my search filter (FormBuilder) to return a list of filtering results, I have a form with a relation to an object (multiple checkbox). It doesn't work.
I think I have a problem in my repository to get an array vs object result.
<?php
namespace AdminBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints as Assert;
use AdminBundle\Entity\Products;
use AdminBundle\Form\ProductsType;
class ProductsController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$form = $this->createFormBuilder()
->add('category', 'entity', array(
'class' => 'AdminBundle:Category',
'label' => false,
'choice_label' => 'name',
'required' => false,
'expanded' => true,
'placeholder' => 'Tous',
'multiple' => true
))
// ...
;
This is my repository:
<?php
namespace AdminBundle\Repository;
class ProductsRepository extends \Doctrine\ORM\EntityRepository
{
public function ListAll($data)
{
$query = $this->createQueryBuilder('a')->orderBy('a.updated','DESC');
// CATEGORY
if($data['category'] !== null)
{
$query
->innerJoin('a.category', 'cat')
->andWhere('cat.id = :catid')
->setParameter('catid', $data['category'])
;
/*
$query
->leftJoin('a.category', 'category')
->setParameter('category', $data['category'])
;
*/
}
// ...
}
}
This repository works for just a single select: multiple => false. When I set multiple => true, I get an SQL violation about object vs. array.
How can I deal with the repository for multiple checkboxes?

You must use ChoiceType instead of entityType
class CategoryRepository extends \Doctrine\ORM\EntityRepository
{
public function getForSearch()
{
$qb = $this->createQueryBuilder('category');
$qb->orderBy('category.name');
$query = $qb->getQuery();
$results = $query->getResult();
$categories = array();
foreach ($results as $category) {
$categories[$category->getName()] = $category->getId();
}
return $categories;
}
In form
$categories = $em->getRepository(Category::class)->getForSearch();
->add('categories', ChoiceType::class, array(
'choices' => $categories,
'required' => false,
'placeholder' => 'Select',
))
And in controller :
if ($search_form->isSubmitted() && $search_form->isValid()) {
$data = $search_form->getData();
$em->getRepository(Product::class)->ListAll($data);
I also think we can use a data transformer but I do not how to do that

Related

Symfony 5 why can't I use FormsEvents::POST_SUBMIT in my form event listener?

I'm trying to add an EventListener to my Symfony form but I have a problem with the first parameter $listener of $builder->addEventListener. I want to use FormEvents::POST_SUBMIT to generate a new field after the submit. Basically I want to display list of cities based on the postal code.
The error tells me that the object is of the wrong type but I don't see which object I could use instead because the documentation tells me to do so. I'm working on Symfony 5.2
Here is my form code and the error :
<?php
namespace App\Form;
use App\Entity\Advert;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Event\PostSubmitEvent;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CreateAdvertType extends AbstractType
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$repositoryCities=$this->entityManager->getRepository('App\Entity\Cities');
$cities = $repositoryCities->findByPostal("86330");
$repositoryMake=$this->entityManager->getRepository('App\Entity\Make');
$makes = $repositoryMake->findAll();
$builder
->add('car_make',EntityType::class, array(
'class' => 'App\Entity\Make',
'choices' => $makes,
))
->add('car_model')
->add('car_motorisation')
->add('car_fuel',ChoiceType::class, array(
'choices' => [
'Diesel' => 'diesel',
'Essence' => 'essence',
'Electrique' => 'electrique',
'Hybride' => 'hybride',
],
))
->add('price', IntegerType::class, array(
'attr' => array(
'min' => 0,
'max' => 20,
)
))
->add('code_postal')
->add('description')
->add('save', SubmitType::class, ['label' => 'Create Task'])
->addEventListener(FormEvents::POST_SUBMIT,function (FormEvents $event){
$repository=$this->getDoctrine()->getManager()->getRepository('App\Entity\Cities');
$form = $event->getForm();
$cities = $repository->findByPostal($form->getData()['code_postal']);
$form->add('city' ,EntityType::class, array(
'class' => 'App\Entity\Cities',
'choices' => $cities
));
})
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Advert::class,
]);
}
}
Argument 1 passed to App\Form\CreateAdvertType::App\Form\{closure}() must be an instance of Symfony\Component\Form\FormEvents, instance of Symfony\Component\Form\Event\PreSetDataEvent given, called in /var/www/html/trymycar/vendor/symfony/event-dispatcher/EventDispatcher.php on line 230
Should be FormEvent (singular not plural).
// plural HERE ---v singular HERE ---v
->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
// ...
})

Custom Variables in custom FormType

Got some question about passing custom variables to a custom FormType
I have a custom FormType named KontoType:
I pass some custom variables to it and this works like as expected, if i override the method buildForm and dump the passed $options array, the mandant exists and is a entity.
But how the heck can i now pass this custom variable to the function getChoices() which loads the choices based on the mandant in this custom FormType?
Even if i did reset the $options in the override buildForm function like $options['choices'] = $this->getChoices($options['mandant']) the select box is empty if i render this form.
<?php
namespace App\Form\Type;
use App\Entity\Core\Finanzen\Konto;
use App\Entity\Core\Organisation\Mandant;
use App\Services\LocaleService;
use Doctrine\Common\Persistence\ObjectManager;
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\Translation\TranslatorInterface;
class KontoType extends AbstractType
{
/**
* #var ObjectManager
*/
private $manager;
/**
* #var TranslatorInterface
*/
private $translator;
/**
* #var LocaleService
*/
private $localeService;
public function __construct(ObjectManager $manager, TranslatorInterface $translator, LocaleService $localeService)
{
$this->manager = $manager;
$this->translator = $translator;
$this->localeService = $localeService;
}
private function getChoices(Mandant $mandant=null)
{
return $this->manager->getRepository(Konto::class)->findBuchbar(true, $mandant);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'label' => 'konto.name',
'help' => 'konto.help',
'choices' => null,
'attr' => array(
'class' => 'bs-select',
'aria-hidden' => 'true',
'ref' => 'input',
'multiple' => false,
'tabindex' => 1,
'data-live-search' => true,
'data-size' => 6
),
'choice_label' => function ($choiceValue, $key, $value) {
return $choiceValue->getKonto()." ".$this->localeService->doTranslate($choiceValue);
},
'choice_value' => function(Konto $konto = null) {
return $konto ? $konto->getId() : '' ;
},
'required' => true,
'multiple' => false,
'empty_data' => null,
'label_attr' => array(
'class' => 'control-label'
),
'placeholder' => 'message.bitte wählen',
'mandant' => null
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$options['choices'] = $this->getChoices($options['mandant']);
parent::buildForm($builder, $options); // TODO: Change the autogenerated stub
}
public function getParent() {
return ChoiceType::class;
}
}

Pass one id to a composite key in form

I have a form in which I chose student id and a id of a course which make a composite key in a UserCourse entity, and with that a status of that course (passed, enrolled etc).
What I want is to pass the value of currently logged in student to the form to populate the userid field with current user id, so that it only has to choose the course and status and submit it.
I have tried using default_value and data => $userId but failed.
This is the UserController
/**
* #Route("/courses/{userId}/new", name="new_usercourse")
*/
public function newMylistAction(Request $request, $userId)
{
$userCourse = new UserCourse();
$userCourse->setUserId($userId);
$form = $this->createForm('AppBundle\Form\UserCourseType', $userCourse);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($userCourse);
$em->flush();
return $this->redirectToRoute('mycourses');
}
return $this->render('student/new_mycourses.html.twig', array(
'usercourse' => $userCourse,
'form' => $form->createView()
));
}
This is the UserCourseType Form. userId is also an EntityType
namespace AppBundle\Form;
use AppBundle\Entity\Course;
use AppBundle\Entity\UserCourse;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserCourseType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('userId', null)
->add('courseId', EntityType::class, array(
'class' => 'AppBundle\Entity\Course',
'choice_label' => 'name'
))
->add('status', ChoiceType::class, array(
'choices' => array(
'Passed' => 'passed',
'Enrolled' => 'enrolled',
'Null' => '',
)))
->add('save', SubmitType::class, array('label' => 'Create'));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => UserCourse::class
));
}
}
Hope you can help
before flush() set user id
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$userCourseRepo=$em->getRepository('AppBundle:UserCourse');
$userCourseIddata = $userCourseRepo->find($id);
$userCourse->setUserId($userCourseIddata);////set userid
$em->persist($userCourse);
$em->flush();
return $this->redirectToRoute('mycourses');
}

Modifying Symfony Forms html attributes / overriding a single attribute

I am trying to figure out how to modify html attributes on the fly with Symfony2 forms.
The situation is a case where a default placeholder is used most of the time, but occasionally, the developer needs to write a custom message.
My Form type looks like this:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use My\AwesomeBundle\Transformer\ProcedureCodeTransformer;
class ProcedureType extends AbstractType
{
private $em;
private $user;
public function __construct($em, $securityContext)
{
$this->em=$em;
$this->user=$securityContext->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ProcedureTransformer( $this->em );
$builder->addModelTransformer( $transformer );
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
public function getParent() {
return 'hidden';
}
public function getName() {
return 'procedure';
}
}
The default render then has "Select Procedure" for the data-select2-placeholder element and javascript is used to display it. This is then used in a more complex type:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProcedureType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('biller', 'billers', array( 'placeholder' => 'New Text'));
[...]
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults( array(
'data_class' => 'My\AwesomeBundle\Entity\Procedure'
) );
}
public function getName(){
return 'my_awesomebundle_proceduretype';
}
}
I would like 'New Text' to be placed into the data-select2-placeholder html attribute. However, if I call the builder like this:
$builder->add('procedure',
new ProcedureCodeType()),
array( 'attr' => array('data-select2-placeholder' => 'New Text') )
);
The entire html attribute array is replaced. this is not surprising. Is there a function in the form builder that has eluded me to add or modify a single html attribute?
Unfortunately, there is no way to modify a single attr the way you would want it. This is the closest and most simple solution I could come with:
What you have to do in your situation is to use callback functions in your options.
For each default attribute in your form types, instead of a normal value, you can use a callback function that accepts a single input representing your options (Symfony class Symfony\Component\OptionsResolver\Options). The function is called when building the form and the return value is then used as the value of the option.
That way, you can build the resulting option depending on the other provided options. An example:
use Symfony\Component\OptionsResolver\Options;
class ProcedureType extends AbstractType
{
...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$attr = function(Options $options) {
$default = array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
);
return array_replace($default, $options['new_attr']);
};
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => $attr,
'new_attr' => array(),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
That way, what you have to modify will be new_attr.
You might want to create a new class that resolves the nested options for you.
<?php
namespace My\AwesomeBundle\Form\Attr;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
class ProcedureCodeAttr
{
/**
* #var array
*/
protected $options;
/**
* #param array $options
*/
public function __construct(array $options = array())
{
$resolver = new OptionsResolver();
$this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
/**
* #param OptionsResolverInterface $resolver
*/
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
));
}
/**
* #return array
*/
public function toArray()
{
return $this->options;
}
/**
* #param array $options
* #return array
*/
public static function resolve(array $options = array())
{
$attr = new static($options);
return $attr->toArray();
}
}
Then in your form type class you'd use it like so
$builder->add('procedure', new ProcedureCodeType()), array(
'attr' => ProcedureCodeAttr::resolve(array(
'data-select2-placeholder' => 'New Text'
))
));
Symfony 4.4 and above now support nested options https://symfony.com/doc/4.4/components/options_resolver.html#nested-options
Instead of defining a nested array, create a callback with OptionsResolver.
For example, a custom form type exists with the following defaults:
class CustomType extends AbstractType {
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('attr', function (OptionsResolver $attrResolver) {
$attrResolver->setDefaults([
'class' => 'form-control-lg',
'placeholder' => 'My Default Placeholder',
'autocomplete' => 'off',
]);
});
$resolver->setDefaults([
'label' => 'Name',
'help' => 'Enter a name',
]);
}
....
}
Attr is now a callback, not an array, now in any forms that use it just defined the attr properties like normal.
class MyFormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', CustomType::class, [
'label' => 'My Form',
'help' => 'My Form Help',
'attr' => [
'placeholder' => 'My Form Placeholder',
],
])
....
}
The generated form will have the class: 'form-control-lg' and the label: 'My Form Placeholder'.

How to use Repository custom functions in a FormType

The problem I'm facing is I have to create a selectbox in a form that holds all the parent entities (Category Entity). Now i managed to do this with:
$builder->add('parent', 'entity', array(
'class' => 'KprCentarZdravljaBundle:Category',
'query_builder' => function($repository) use ($param, $catID) {
return $repository->createQueryBuilder('p')
->where('p.id != :id AND p.parent = :parent')
->setParameters(array('id' => $param, 'parent' => $catID));},
'property' => 'name',
'required' => false,
'attr' => array('data-placeholder' => '--Izaberite Opciju--'),
));
As u can see i pass 2 arguments first is the current category.id(a category cant be its own parent) and a second which is a parent id, because i want all the children from that parent. This works nice but it doesn't give me the parents children's children.
I created a CategoryRepository with a recursive function that returns all the children:
<?php
namespace Kpr\CentarZdravljaBundle\Entity;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Kpr\CentarZdravljaBundle\Entity\Category;
class CategoryRepository extends EntityRepository
{
public function findByParenting($parent)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->add('select', 'cat')
->add('from', 'KprCentarZdravljaBundle:Category cat')
->add('where', 'cat.parent = :parent')
->setParameter('parent', $parent);
// $qb instanceof QueryBuilder
$query = $qb->getQuery();
$results = $query->getResult();
foreach($results as $result){
if($result->getParent()){
$newResult = $this->findByParenting($result->getId());
$results = array_merge($results, $newResult);
}
}
return $results;
}
}
How can I use the findByParenting($parent) function in a entity field?
I posted the answer: Symfony2 choice field not working. Thanks redbirdo.
You are having this error because you have to return the QueryBuilder object on your findByParenting($parent) function
public function findByAllocation($alloc)
{
return $qb = $this->createQueryBuilder('r')
->select('r')
->where('r.showRangeStart < :alloc', 'r.showRangeStop >= :alloc')
->setParameter('alloc', $alloc)
;
}

Resources