Sylius Grid EntityFilter - sort or use repository - symfony

I added a large entity filter to one of my Sylius grid configurations. I haven't found any configuration options besides class name and from the looks of it, option values are just ordered by ID. Is there a way to use a repository method or at least provide sort field? Do I need to use a custom filter for this?

You can define what repository method to use on your YAML file and what arguments you want to send to that method:
sylius_grid:
grids:
app_user: # Your grid name
driver:
name: doctrine/orm
options:
class: "%app.model.user%"
repository:
method: myCustomMethod
arguments:
id: resource.id
sorting:
name: asc
limits: [10, 25, 50, 100]
Check the Sylius Grid Bundle documentation for more information: Configuration Reference

Here's how to define a new type :
final class CustomFilterType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'shop',
EntityType::class,
[
'class' => Shop::class,
'label' => false,
'multiple' => true, //if you need multiple selection
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('s');
// define your query builder here
},
'choice_label' => function ($shop) { /** #var $shop Shop */
return $shop->getName();
},
]
);
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix(): string
{
return 'sylius_grid_filter_entity_multiple';
}
}
Then in your services.yaml :
app.grid_filter.custom_filter:
class: Sylius\Component\Grid\Filter\SelectFilter
tags: [{
name: 'sylius.grid_filter',
type: 'custom_type',
form_type: App\Form\Grid\Filter\CustomFilterType
}]
And finally, in your grid definition:
filters:
shops:
type: custom_type
label: app.ui.shops

Related

OroPlatform: Override core entity form builder

Context
I'm trying to change the form type of one field on one of the core entity: Business Unit
The default form field is TextField and I want to change it to ChoiceType.
Here is my custom field on Business Unit entity created with migration :
$table->addColumn('periodicite', 'string', [
'oro_options' => [
'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
'entity' => ['label' => 'Périodicité'],
],
]);
Issue
I've seen on the Oro documentation that entity_config.yml could solve my problem. I've tried to put these lines but it doesn't work :
entity_config:
business_unit:
entity:
items:
periodicite:
form:
type: Symfony\Component\Form\Extension\Core\Type\ChoiceType
options:
choices:
Mensuel: Mensuel
Trimestriel: Trimestriel
placeholder: false
required: true
label: "Périodicite"
I have also tried to create a new migration to change the field type on my custom field but it doesn't work
<?php
namespace Baltimore\Bundle\AppBundle\Migrations\Schema\v1_1;
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityConfigBundle\Migration\UpdateEntityConfigFieldValueQuery;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
use Oro\Bundle\OrganizationBundle\Entity\BusinessUnit;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class UpdateBusinessUnitField implements Migration, ExtendExtensionAwareInterface
{
/** #var ExtendExtension */
protected $extendExtension;
/**
* #inheritdoc
*/
public function setExtendExtension(ExtendExtension $extendExtension)
{
$this->extendExtension = $extendExtension;
}
public function up(Schema $schema, QueryBag $queries)
{
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
BusinessUnit::class,
'periodicite',
'form',
'form_type',
ChoiceType::class
)
);
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
BusinessUnit::class,
'periodicite',
'form',
'form_options',
[
'choices' => [
'Mensuel' => 'Mensuel',
'Trimestriel' => 'Trimestriel',
'Annuel' => 'Annuel',
],
]
)
);
}
}
I have found a solution with the changeColumn method in my migration file and it works like a charm.
By the way, these properties works also with the addColumn method.
public function up(Schema $schema, QueryBag $queries)
{
$table = $schema->getTable('oro_business_unit');
$table->changeColumn('periodicite', [
'oro_options' => [
'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
'entity' => ['label' => 'Périodicité'],
'form' => [
'form_type' => ChoiceType::class,
'form_options' => [
'choices' => [
'Mensuel' => 'Mensuel',
'Trimestriel' => 'Trimestriel',
'Semestriel' => 'Semestriel',
'Annuel' => 'Annuel'
]
]
],
],
]);
}
I don't know about the possibility to override entity config metadata using the YAML file. If there is - please share the documentation you used to implement it in the comments.
But for sure, you can manage the same using the schema migration, like in this example:
class UpdateOpportunityRelationFormType implements Migration
{
/**
* {#inheritdoc}
*/
public function up(Schema $schema, QueryBag $queries)
{
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
Quote::class,
'opportunity',
'form',
'form_type',
OpportunitySelectType::class
)
);
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
Quote::class,
'opportunity',
'form',
'form_options',
['attr' => ['readonly' => true]]
)
);
}
}

Sylius EntityFilter choice from a part of resources

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';
}
}

Can't generate new routes in Sonata Admin

I'm struggling to make a custom action in Sonata Admnin, I followed the guide Sonata documentation and tried to copy the code from another of my projects where it worked fine.
My Admin class is:
namespace BlogBundle\Admin;
use AppBundle\Form\Type\FacebookType;
use Sonata\AdminBundle\Route\RouteCollection;
use Sonata\CoreBundle\Form\Type\BooleanType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Admin\AdminInterface;
class PostAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('facebookAction');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id',TextType::class,[
'label' => 'entity.post.id'
])
->add('active',BooleanType::class,[
'label' => 'entity.post.active'
])
->add('_action', null, [
'actions' => [
'show' => [],
'edit' => [],
'delete' => [],
'facebook' => [
'template' => 'BlogBundle:Admin:empty.html.twig'
]
]
]);
;
}
//...
}
Service declaration:
services:
admin.blog.post:
class: BlogBundle\Admin\PostAdmin
arguments: [~, BlogBundle\Entity\Post, BlogBundle:PostAdmin]
tags:
- { name: sonata.admin, manager_type: orm, label: 'admin.name.post', group: 'admin.group.blog'}
And the CRUD Controller:
namespace BlogBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
class PostAdminController extends Controller
{
public function facebookAction()
{
}
}
Looking at the profiler the route is not being generated and the button for my custom route is not displayed on the list even if it is set on the configureListFields function.
Symfony's version is 3.2.2 and Sonata 3.10.3

symfony2: multiple entities one form

I have 2 entities:
ADS\LinkBundle\Entity\Link:
type: entity
table: null
repositoryClass: ADS\LinkBundle\Entity\LinkRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
dateAdded:
type: datetime
expirationDate:
type: datetime
nullable: true
designator:
type: string
length: 255
nullable: false
unique: true
slug:
type: string
length: 255
nullable: true
unique: true
manyToOne:
company:
targetEntity: ADS\UserBundle\Entity\Company
inversedBy: link
joinColumn:
name: company_id
referencedColumnName: id
nullable: true
createdBy:
targetEntity: ADS\UserBundle\Entity\User
inversedBy: link
joinColumn:
name: createdBy_id
referencedColumnName: id
domain:
targetEntity: ADS\DomainBundle\Entity\Domain
inversedBy: link
joinColumn:
name: domain_id
referencedColumnNames: id
oneToMany:
paths:
targetEntity: ADS\LinkBundle\Entity\Path
mappedBy: link
cascade: [persist]
lifecycleCallbacks: { }
and
ADS\LinkBundle\Entity\Path:
type: entity
table: null
repositoryClass: ADS\LinkBundle\Entity\PathRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
pathAddress:
type: string
length: 255
pathWeight:
type: string
length: 255
manyToOne:
link:
targetEntity: ADS\LinkBundle\Entity\Link
inversedBy: paths
joinColumn:
name: link_id
referencedColumnName: id
lifecycleCallbacks: { }
I have everything figured out except for the paths portion of the entity. This is for an A/B split test, so each link can have 2 paths. Each path will consist of a web address, and a number ( 0 - 100 )
Here is my form in it's current state:
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PathType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('pathAddress')
->add('pathWeight')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Path'));
}
public function getName() { return 'ads_linkbundle_link'; }
}
and
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LinkType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('designator')
->add('domain', 'entity', array(
'class' => 'ADS\DomainBundle\Entity\Domain',
'property' => 'domainAddress'
))
->add('paths', 'collection', array('type' => new PathType(), 'allow_add' => true))
->add('Submit', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Link'));
}
public function getName() { return 'ads_linkbundle_link'; }
}
What I need to figure out, is when creating a link, I need to also be able to create the correct path and weight to go with it. The paths won't be in the database before a link is created.
Here is what I have for my controller:
public function newAction(Request $request) {
$entity = new Link();
$form = $this->createForm(new LinkType(), $entity);
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$code = $this->get('ads.default');
$em = $this->getDoctrine()->getManager();
$user = $this->getUser();
$entity->setDateAdded(new \DateTime("now"));
$entity->setCreatedBy($user);
$entity->setSlug($code->generateToken(5));
$entity->setCompany($user->getParentCompany());
$em->persist($entity);
$em->flush();
return new Response(json_encode(array('error' => '0', 'success' => '1')));
}
return new Response(json_encode(array('error' => count($form->getErrors()), 'success' => '0')));
}
return $this->render('ADSLinkBundle:Default:form.html.twig', array(
'entity' => $entity,
'saction' => $this->generateUrl('ads.link.new'),
'form' => $form->createView()
));
}
Thanks to #Onema ( read the comments above ), I've figured this out. By reading the documentation at http://symfony.com/doc/current/cookbook/form/form_collections.html It gave me information I needed to get this done.
First step in doing what I needed to do, was to create a new form type called PathsType.php which houses the fields associated with the Paths Entity
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PathType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('pathAddress')
->add('pathWeight')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Path'));
}
public function getName() { return 'ads_linkbundle_path'; }
}
Then modifying the LinkType.php to utilize this new form
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LinkType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('designator')
->add('domain', 'entity', array(
'class' => 'ADS\DomainBundle\Entity\Domain',
'property' => 'domainAddress'
))
->add('paths', 'collection', array(
'type' => new PathType(),
'allow_add' => true,))
->add('Submit', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Link'));
}
public function getName() { return 'ads_linkbundle_link'; }
}
The addition of allow_add makes it so that you can add multiple instances of that form.
Within the view, I now utilize the data-prototype attribute. In the documentation, it has the example using a list item - so that's where I started.
<ul class="tags" data-prototype="{{ form_widget(form.paths.vars.prototype)|e }}"></ul>
Then came the jQuery functions ( listed on the documentation link above, simple copy/paste will work )
This got the system working, with 1 small issue and that in my paths entity, I have a relationship to the Link entity but it was not noticing this relationship and had the link_id field as null
To combat this, we edit LinkType.php one more time, and add by_reference = false to the collection definition. We then edit the addPath method inside the entity to look like so:
public function addPath(\ADS\LinkBundle\Entity\Path $paths)
{
$paths->setLink($this);
$this->paths->add($paths);
}
This sets the current link object, as the link the path is associated with.
At this point, the system is working flawlessly. It's creating everything that it needs to, only need to adjust the display a little bit. I personally opted to use a twig macro to modify the html output contained in data-prototype
my macro as it currently sits (incomplete - but working ) which I added to the beginning of my form.html.twig
{% macro path_prototype(paths) %}
<div class="form-group col-md-10">
<div class="col-md-3">
<label class="control-label">Address</label>
</div>
<div class="col-md-9">
{{ form_widget(paths.pathAddress, { 'attr' : { 'class' : 'form-control required' }}) }}
</div>
</div>
{% endmacro %}
In the HTML for the form itself, I removed the list creation, and replaced it with:
<div class="form-group">
{{ form_label(form.paths,'Destination(s)', { 'label_attr' : {'class' : 'col-md-12 control-label align-left text-left' }}) }}
<div class="tags" data-prototype="{{ _self.path_prototype(form.paths.vars.prototype)|e }}">
</div>
</div>
I then modified my javascript to use the div as a starting point instead of the ul in the example.
<script type="text/javascript">
var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('Add Another Destination');
var $newLinkLi = $('<div></div>').append($addTagLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of tags
$collectionHolder = $('div.tags');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
addTagForm($collectionHolder, $newLinkLi);
$addTagLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm($collectionHolder, $newLinkLi);
});
});
function addTagForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
console.log(index);
if (index == 1) {
console.log('something');
$('a.add_tag_link').remove();
}
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = newForm;
$newLinkLi.before($newFormLi);
}
</script>
Being that these paths are destination addresses for an A/B split test within my marketing app, I opted to limit the paths to 2 per link. And with this, I have successfully setup a form to use a collections type.

choice field symfony "Notice: Array to string conversion "

Trying to validate a choice field (multiple checkboxes) Im having this problem:
"Notice: Array to string conversion "
My validation file looks like this one:
Cgboard\AppBundle\Forms\UploadImageEntity:
properties:
image:
...
cgnetworks:
- Choice:
choices: [flickr, tumblr] //<--- this is giving me problems!!!
My form entity class (Im not going to save this to db for now):
class UploadImageEntity {
public $image;
public $cgnetworks;
}
And my form class:
class UploadImageForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('image', 'file')
->add('cgnetworks', 'choice', [
'choices' => $this->getCgNetworks(),
'multiple' => TRUE,
'expanded' => TRUE
]
);
}
public function getCgNetworks()
{
return [
'tumblr' => 'Tumblr',
'flickr' => 'Flickr'
];
}
}
Any idea?
perhaps you need to specify multiple in your validation
cgnetworks:
- Choice:
choices: [flickr, tumblr] //<--- this is giving me problems!!!
multiple: true
Check your Entity field getter. If you have something else instead of
public function getValue(){
return $this->value;
}
You can reach this error.
Form builder uses get and set entity methods, that's why you need to return an allowable value.

Resources