Sylius EntityFilter choice from a part of resources - symfony

I used a Sylius 1.0.0-beta1 and ported EntityFilter from dev-master due to lack of this functionality in last stable version. Everything works fine but is there any way of choosing not from all resources but only from part of them?
I need to make a filter based on Taxonomies. I have a few taxons which are city names and all of them have parent taxon called City (code: city). So I want to display in that filter all children of city taxon.
My grid configuration is shown below:
sylius_grid:
grids:
smartbyte_admin_products_by_event_archetype:
...
filters:
...
taxon:
type: app_entity
options:
fields: [taxon.id]
class: "%sylius.model.taxon.class%"
city:
type: app_taxon
The first filter from configuration works and filters fine, except it takes all taxons, but I need to show only some.
I tried also make my own filter (the second one) but I get a text field instead of entity field in filter. Following the docs I created custom one. Here is my try:
<?php
namespace SyliusExtensionBundle\Form\Type\Filter;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class TaxonFilterType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('city', EntityType::class, array(
'class' => 'Sylius\Component\Core\Model\Taxon',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('t')
->leftJoin('t.parent', 'taxon')
->where("taxon.code = 'city'");
},
'label' => 'Miasto',
'required' => false
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'label' => false,
'placeholder' => 'sylius.ui.all',
])
->setDefined('fields')
->setAllowedTypes('fields', 'array')
;
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'sylius_grid_filter_taxon';
}
}
Service configuration:
services:
sylius.grid_filter.entity:
class: SyliusExtensionBundle\Grid\Filter\EntityFilter
tags:
- { name: sylius.grid_filter, type: app_entity, form-type: SyliusExtensionBundle\Form\Type\Filter\EntityFilterType }
- { name: sylius.grid_filter, type: app_taxon, form-type: SyliusExtensionBundle\Form\Type\Filter\EntityFilterType }
sylius.form.type.grid_filter.entity:
class: SyliusExtensionBundle\Form\Type\Filter\EntityFilterType
tags:
- { name: form.type, alias: sylius_grid_filter_entity }
app.form.type.grid_filter.taxon:
class: SyliusExtensionBundle\Form\Type\Filter\TaxonFilterType
tags:
- { name: form.type, alias: sylius_grid_filter_taxon }
And lastly filter templates:
sylius_grid:
templates:
filter:
app_entity: "SyliusExtensionBundle:Grid/Filter:entity.html.twig"
app_taxon: "SyliusExtensionBundle:Grid/Filter:entity.html.twig"
Please guide my how can I restrict EntityFilter or how to make the custom filter work. I spent many hours on this subject and cannot see whereis the error.
Current effect below:
EDIT:
Current TaxonFilterType according to Paweł Jędrzejewski tips. Still doesn't work and dont detect fields option in configuration.
<?php
/**
* Created by PhpStorm.
* User: Krzysztof Wędrowicz krzysztof#wedrowicz.me
* Date: 23.01.17
* Time: 14:56
*/
namespace SyliusExtensionBundle\Form\Type\Filter;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class TaxonFilterType extends AbstractType {
public function getParent()
{
return EntityType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'label' => false,
'placeholder' => 'sylius.ui.all',
'class' => 'Sylius\Component\Core\Model\Taxon',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('t')
->leftJoin('t.parent', 'taxon')
->where("taxon.code = 'city'")
;
},
'required' => false
])
->setDefined('fields')
->setAllowedTypes('fields', 'array')
;
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'sylius_grid_filter_city';
}
}

Currently this is not possible via configuration. I will add it to the backlog, but not sure when it can be implemented. That being said, your custom filter is a good idea. You should do a small change and it should work: The form type should have EntityType::class in getParent() instead of using the buildForm method. And the custom query builder should be configured in configureOptions method, then it will render a proper field. Here is full code that should work:
<?php
namespace AcmeExtension\Form\Type\Filter;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class CityFilterType extends AbstractType
{
public function getParent()
{
return EntityType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'label' => false,
'placeholder' => 'sylius.ui.all',
'class' => 'Sylius\Component\Core\Model\Taxon',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('t')
->leftJoin('t.parent', 'taxon')
->where("taxon.code = 'city'")
;
},
'required' => false
])
->setDefined('fields')
->setAllowedTypes('fields', 'array')
;
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'sylius_grid_filter_city';
}
}

Related

EasyAdmin (v2): filter list view by the property of a related entity

I am willing to implement exactly the kind of list filtering that is described in EasyAdmin (v.2) documentation on Symfony (Custom dynamic filters), i.e. filtering the list view of an entity by the property of a related entity (e.g. list orders by customers of a given country).
With only the following code blocks available in the doc I am quite lost on how to create a custom EntityTypeFilter with the usual two select lists (is same/is not same list & list of available Country for Order via the Customer association). What should be in the configureOptions() and getParent() functions?
Thanks !
# config/packages/easy_admin.yaml
easy_admin:
entities:
Users:
class: App\Entity\Order
list:
filters:
# 'country' doesn't exist as a property of 'Order' so it's
# defined as 'not mapped' to avoid errors
- property: 'country'
type: 'App\Form\Filter\CustomerCountryFilterType'
mapped: false
// App\Form\Filter\CustomerCountryFilterType
// ...
public function filter(QueryBuilder $queryBuilder, FormInterface $form, array $metadata)
{
if (null !== $form->getData()) {
$queryBuilder
->leftJoin('entity.customer', 'customer')
->andWhere('customer.country = :country')
->setParameter('country', $form->getData());
}
}
Try this:
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Country::class
]);
}
public function getParent()
{
return EntityType::class;
}
If you want to sort by country name, you can add a custom query builder to the options:
use Doctrine\ORM\EntityRepository;
// ...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Country::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
}
]);
}

EntityType - Can not use query_builder

I have a problem with my FormType in Symfony. I have a field that allows me to check one or more checkboxs whose values represent objects from another entity. (In this case, types of holidays).
Except that I do not want to display them all, so I use a query_builder as such:
->add('typesConges', EntityType::class, [
'class' => TypeConge::class,
'choice_label' => 'nom',
'expanded' => true,
'multiple' => true,
'query_builder' => function (TypeCongeRepository $repoTypes) {
return $repoTypes->getTypesNotNull();
}
])
But it raised this error :
The name "Heures supp" contains illegal characters. Names should start
with a letter, digit or underscore and only contain letters, digits,
numbers, underscores ("_"), hyphens ("-") and colons (":").
However, if I remove the query_builder, I have all my TypeConge ( the "Heures supp" aswell).
GestionSoldes.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class GestionSoldes
{
/**
* Types de congés
*
* #var Collection|TypeConge[]
*/
private $typesConges;
/**
* All types
*
* #var boolean
*/
private $allTypes;
public function __construct()
{
$this->typesConges = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return Collection|TypeConge[]
*/
public function getTypesConges(): Collection
{
return $this->typesConges;
}
public function addTypesConge(TypeConge $typesConge): self
{
if (!$this->typesConges->contains($typesConge)) {
$this->typesConges[] = $typesConge;
}
return $this;
}
public function removeTypesConge(TypeConge $typesConge): self
{
if ($this->typesConges->contains($typesConge)) {
$this->typesConges->removeElement($typesConge);
}
return $this;
}
public function getAllTypes(): ?bool
{
return $this->allTypes;
}
public function setAllTypes(bool $allTypes): self
{
$this->allTypes = $allTypes;
return $this;
}
}
Form:
<?php
namespace App\Form;
use App\Entity\TypeConge;
use App\Entity\GestionSoldes;
use App\Repository\TypeCongeRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
class GestionSoldesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('typesConges', EntityType::class, [
'class' => TypeConge::class,
'choice_label' => 'nom',
'expanded' => true,
'multiple' => true,
'query_builder' => function (TypeCongeRepository $repoTypes) {
return $repoTypes->getTypesNotNull();
}
])
->add('allTypes', CheckboxType::class, [
'required' => false,
'label' => 'Tous les types de congés',
'label_attr' => [
'class' => 'custom-control-label',
'for' => 'allTypes'
],
'attr' => [
'class' => 'custom-control-input',
'id' => 'allTypes'
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => GestionSoldes::class,
]);
}
}
My repo function:
/**
* Retourne les types de congés qui ont un solde initial différent de null ( pour form EntityType )
*
* #return void
*/
public function getTypesNotNull()
{
return $this->createQueryBuilder('t')
->where('t.soldeInitial is not null')
->orderBy('t.nom', 'ASC');
}
As it shows no errors when you remove te query-builer, The problem must come from the query-builder.
As the only moment you use the property nom in the query-builder is in
->orderBy('t.nom', 'ASC');
The problem is here.
Make sure that your ORM (doctrine or else) is ok with string containing spaces.
to verify my point try to remove the orderBy from your query

Guess custom form type when embedding custom classes

Two simple classes form my app model: Money and Product.
As Money app form being reusable, I've decided to create MoneyType extending AbstractType.
// App\Entity\Product
/**
* #ORM\Embedded(class="Money\Money")
*/
private $price;
// App\Form\ProductType
$builder->add('price', MoneyType::class)
// App\Form\Type\MoneyType
class MoneyType extends AbstractType
{
private $transformer;
public function __construct(MoneyToArrayTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('amount', NumberType::class, [
'html5' => true,
'constraints' => [
new NotBlank(),
new PositiveOrZero(),
],
'attr' => [
'min' => '0',
'step' => '0.01',
],
])
->add('currency', ChoiceType::class, [
'choices' => $this->getCurrenciesChoices(),
'constraints' => [
new NotBlank(),
],
]);
$builder->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'data_class' => null
]);
}
...
}
Is it possible to guess the field type without specifying it explicitly for obtaining the following code?
// App\Form\ProductType
$builder->add('price')
Any help is welcome. Thank you in advance.
You can implement a custom TypeGuesser that reads the doctrine metadata and checks if the field is an embeddable of the desired type. This is a basic implementation
namespace App\Form\TypeGuesser;
use App\Form\Type\MoneyType;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Doctrine\ORM\EntityManagerInterface;
class MoneyTypeGuesser implements FormTypeGuesserInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function guessType($class, $property)
{
if (!$metadata = $this->em->getClassMetadata($class)) {
return null;
}
if (
isset($metadata->embeddedClasses[$property]) &&
'Money\Money' == $metadata->embeddedClasses[$property]['class']
) {
return new TypeGuess(MoneyType::class, [], Guess::HIGH_CONFIDENCE);
}
}
// Other interface functions ommited for brevity, you can return null
}
You can see all the interface methods that you need to implement here.
The Form TypeGuesser is mostly based on the annotation #var Money\Money and you should be able to build your down guesser for your own types, see https://symfony.com/doc/current/form/type_guesser.html
Also take a look at https://github.com/symfony/symfony/blob/4.3/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php on how to guess the type by doctrine orm specific types.
You could derive your own app specific guesser with those two examples.

symfony easyadmin form field type entity with filter list

I use symfony 3.4 and easycorp/easyadmin-bundle 1.17
This is my class "Quotation", the "artisan" field is an "under registration" of the "Person" entity (person.type = 1) :
class Quotation
{
/** others fields... */
/**
* 1 => 'artisan', 2 => 'customer'
*/
private $type;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="artisanQuotations", cascade= { "persist" })
* #ORM\JoinColumn(name="artisan_id", referencedColumnName="id")
*/
private $artisan;
/** getters and setters ... */
I have a problem with a form field using a custom field type
form:
fields:
...
- { property: 'artisan', label: '', type: 'AppBundle\Form\Field\ArtisanType' }
I created this form field type to be able to filter the list thanks to the query_builder :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('artisan', EntityType::class, array(
'class' => 'AppBundle:Person',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('person')
->where('person.type = 1');
},
'attr' => array('data-widget' => 'select2'),
'multiple' => false,
'expanded'=> false
));
}
my form is displayed perfectly well but when i submit this form i have an error :
Expected argument of type "AppBundle\Entity\Person", "array" given
Thank you in advance for your help
Instead of using the buildForm method you should use configureOptions here. That way your form is not extended by another subform which results in the array.
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ArtisanType extends AbstractType
{
/**
* #param \Symfony\Component\OptionsResolver\OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => 'AppBundle:Person',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('person')
->where('person.type = 1');
},
'attr' => array('data-widget' => 'select2'),
'multiple' => false,
'expanded'=> false,
]);
}
/**
* #return string|null
*/
public function getParent()
{
return EntityType::class;
}
}

Symfony2 Choice Form Data from ORM

Have this form:
class ScanType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('scantarget', 'entity', array(
'class' => 'AppBundle:Website',
'property' => 'url'
));
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'AppBundle\Entity\Website',
);
}
}
Need to populate it with only urls for the particular userid
In the controller:
/**
* #Route("/verification-ok", name="verifyurlok")
*/
public function verifyurlActionOK(Request $request)
{
$user = $this->getUser();
if($user)
{
$userid=$user->getId();
$websites = $this->getDoctrine()->getRepository('AppBundle:Website')->findByUser($userid);
$form = $this->createForm(ScanType::class, $websites);
However, the $websites is not passed properly to FormBuilder and in my Select box I see all entries :( All possible values for Website entity.
How to Display only passed $websites in the form (select box)? So only websites for a specific userid?
Thanks,
Update 1
Form
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ScanType extends AbstractType
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $this->tokenStorage->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'The FriendMessageFormType cannot be used without an authenticated user!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'class' => 'AppBundle\Entity\Website',
'property' => 'user',
'query_builder' => function (EntityRepository $er) use ($user) {
// build a custom query
return $er->createQueryBuilder('u')->addOrderBy('user', 'DESC');
// or call a method on your repository that returns the query builder
// the $er is an instance of your UserRepository
// return $er->createOrderByFullNameQueryBuilder();
},
);
// create the field, this is similar the $builder->add()
// field name, field type, data, options
$form->add('url', ScanType::class, $formOptions);
}
);
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'AppBundle\Entity\Website',
);
}
}
Controller:
$form = $this->createForm(ScanType::class);
config.yml
services:
app.form.scan:
class: AppBundle\Form\ScanType
arguments: ['#security.token_storage']
tags:
- { name: form.type }
Unfortunately, get this error:
request.CRITICAL: Uncaught PHP Exception Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException: "The options "class", "property", "query_builder" do not exist. Defined options are: "action", "allow_extra_fields", "attr", "auto_initialize", "block_name", "by_reference",
Solution:
Thanks #Medard
Form:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ScanType extends AbstractType
{
private $websites;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->websites = $options['trait_choices'];
$builder->add('scantarget', 'entity', array(
'class' => 'AppBundle:Website',
'property' => 'url',
'choices' => $this->websites
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'trait_choices' => null,
));
}
}
Controller:
$form = $this->createForm(ScanType::class, $websites, array(
'trait_choices' => $websites
));
Works!!!
Well, you are not setting the choices. Try this:
class ScanType extends AbstractType
{
private $websites;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->websites = $options['trait_choices'];
$builder->add('scantarget', 'entity', array(
'class' => 'AppBundle:Website',
'property' => 'url',
'choices' => $this->websites
));
}
public function setDefaultOptions(array $options) {
return array(
'data_class' => 'AppBundle\Entity\Website',
'trait_choices' => null,
);
}
}
In the controller pass through the websites:
$form = $this->createForm(ScanType::class, $websites, array(
'trait_choices' => $websites,
));
Your form displays all the Website entries because you're not restricting them when building the form, in the add() method.
You should read this section fo the cookbook : http://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-user-data

Resources