I'm using SonataAdminBundle and I have a question about filters in the class MyEntityAdmin.
I have a first function protected function configureFormFields(FormMapper $formMapper) for list all fields to be shown on create/edit forms.
If I have a field type entity, I can do something like this:
->add('commercial', 'entity', array(
'class' => 'MyBundle:User',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->groupBy('u.id')
->orderBy('u.id', 'ASC')
->setParameters(array(1 => 'Commercial'));
},)
)
But I have another function protected function configureDatagridFilters(DatagridMapper $datagridMapper) for Fields to be shown on filter forms, and I have do to the same thing, a custom query on an entity field type, but if I do the same, I have the error:
No attached service to type named `entity`
How can I do that ?
Filters configuration is quite different than form configuration in sonata admin bundle.
Look at documentation: http://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/filter_field_definition.html
When you add new filter through configuratDataFilters it receives parameters: field name, filter type, filter configuration, form field type and form field configuration.
So if you want only to override query_buider for entity choice type you should try call like this:
->add('commercial', null, array(), 'entity', array(
'class' => 'MyBundle:User',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->groupBy('u.id')
->orderBy('u.id', 'ASC')
->setParameters(array(1 => 'Commercial'));
}
))
Related
I am working on a Symfony project, using Sonata.
Context:
I got different entities:
Product (ID, categories (relation), characteristicValues (relation))
Category (ID, characteristics (relation))
Characteristic (ID, id_category (relation), label).
CharacteristicValue (ID, id_product (relation), id_characteristic (relation), value)
Relations:
Product --OneToMany--> CharacteristicValue
Category -->OneToMany--> Characteristic
Characteristic -->OneToMany--> CharacteristicValue
Product --ManyToMany--> Category
Problem:
I need to get all characteristics of a the categories of a product (and their values if they're set) in the ProductAdmin, and show an input for each of them (like Characteristic1 : value1).
What I did:
I tried to call a function the CharacteristicValueRepository in the ProductAdmin, but the repository was not instantiated.
The code of ProductAdmin is really basic:
final class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Product information', ['class' => 'col-md-6'])
->add('name', TextType::class, [
'label' => 'Name of the product'
])
->add('categories', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
'multiple' => true,
'label' => 'Categories of the product'
])
->end();
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('name');
$datagridMapper->add('categories');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('id');
$listMapper->addIdentifier('name');
$listMapper->addIdentifier('categories');
}
}
Notes:
I am using the last version of everything (Symfony, Sonata, ...)
If someone knows how to help me, I would be really grateful!
You need to configure custom form type for example ProductCharacteristicsType. While using Form Event listeners you could fetch all characteristics and form a proper collection. What you have here is EAV (Entity attribute value) model. It may cause confusion for Symfony, but it is manageable. On SonataAdmin you must use that custom type of yours.
I'm trying to provide a selection with >300 choices
$form->add('products', EntityType::class, array(
'class' => Product::class,
'query_builder' => function (ProductRepository $er) use ($customerId) {
return $er->QBByCustomer($customerId);
},
'choice_label' => 'l.name',
));
The QueryBuilder:
public function QBByCustomer($customer = null)
{
return $this->QB()
->addSelect('p.name AS HIDDEN name')
->join('p.customer', 'c')
->join('p.label', 'l')
->where('c.customer = :customer')
->setParameter('customer', $customer)
->addOrderBy('name')
;
}
When I render the form, Doctrine generates >300 queries to load each related objects.
Is there a way to tell Doctrine to use the labels from the QueryBuilder I gave, instead of firing as much query as selectable items ?
Since the displayed label is fetched from a linked entity (label), we have to help Doctrine to load it.
Here is how the QueryBuilder should look like:
'query_builder' => function (ProductRepository $er) use ($customerId) {
$qb = $er->QBByCustomer($customerId);
$qb->addSelect('partial p.{id}');
$qb->addSelect('partial l.{id, name}');
return $qb;
},
'choice_label' => 'label.name',
A car machine oil is applicable to several car models, but in my case, there are hundreds of car models in our system, I don't want to load them all to a page, then , let a user to choose specific car models, it would be better to use ajax call to get car model by car brand , car series. the user choose a sub collection of items , post these selected to server.
In the form type ,I add a form field like this below, as we know, if I don't set its option choices as empty array, the entity field will get all car models , which will result in huge performance penalty.
->add('applicableModels', 'entity', array(
'class' => 'VMSP\CarBundle\Entity\CarModel',
'choices'=>array(),
'multiple'=>true,
'property_path' => "modelName",
'label' => 'vmsp_product.product.form.applicable_model',
)
)
So, how do you add a sub set of items from a big collection , and assign these selected items to entity type?
The simple answer is that you can define a custom query builder for each element, you add to the form, i.e.
$data = $builder->getData();
\\...
->add('applicableModels', 'entity', array(
'class' => 'VMSP\CarBundle\Entity\CarModel',
'multiple' => true,
'property_path' => "modelName",
'label' => 'vmsp_product.product.form.applicable_model',
'query_builder' => function (EntityRepository $er) use ($data) {
$qb = $er->createQueryBuilder('e')
->where('e.manufacturer IN (:manufacturer)')
->setParameter('manufacturer', $data['manufacturer']);
return $qb->orderBy('e.name');
},
))
So you may have a sort of a "wizard" where user select a manufacturer, the page reloads, and the cars shown are only a subset.
In a very similar situation I ended up doing things a little differently, though. In the form, the choices are set to an empty array. On frontend, the options are filled via Ajax calling a separate API. Then on the form "pre-submit" event, I fill the field with the selected option.
$builder->addEventListener(
FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if (!empty($data['applicable_model'])) {
$form->add('applicable_model', 'entity', array(
'class' => 'VMSP\CarBundle\Entity\CarModel',
'query_builder' => function (EntityRepository $er) use ($data) {
$qb = $er->createQueryBuilder('e')
->where('e.id IN (:applicable_model)')
->setParameter('applicable_model', $data['applicable_model']);
return $qb;
},
));
}
Update: I learned that the addEventListener part can probably be replaced by a DataTransforme instead, see https://stackoverflow.com/a/31938905/410761
If you got for example the following Entities and relations:
PurchasedService * ---> 1 Service * ---> 1 ServiceCategory
how can you create a formType listing all entries from ServiceCategory within PurchasedServiceType?
As:
$builder
->add('servicecategory', 'entity', array(
'class' => 'InvoicingBundle:ServiceCategory',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('sc')
->orderBy('sc.serviceCategoryName', 'ASC');
},
))
Results in ERROR:
Neither the property "servicecategory" nor one of the methods
"getServicecategory()", "isServicecategory()", "hasServicecategory()",
"__get()" exist and have public access in...
I would expect to call the Entity ServiceCategory directly?
And it should. But I feel that you missed the namespace. In your case you have:
'class' => 'InvoicingBundle:ServiceCategory',
Always the InvoicingBundle is localized in some directory - you should have src/{theMissingDirectory}/InvoicingBundle/Entity/ServiceCategory , so the pattern is:
'class' => '{theMissingDirectory}InvoicingBundle:ServiceCategory',
in example:
'class' => 'AcmeInvoicingBundle:ServiceCategory',
Please try in this way and it should work.
Regards,
I have a Product and a Category entity.
Category entity is a adjacency list/materialized-path model.
Product has a relation to Category which has a relation back to Product
In my ProductType class I would like a selectmenu with all categories at a specific level grouped by parent name.
$builder->add('category', 'entity', array(
'label' => 'Category',
'class' => 'Test\AdminBundle\Entity\Category',
'property' => 'name',
'group_by' => 'parentName',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) {
$qb = $er->createQueryBuilder('c');
return $qb->where($qb->expr()->like('c.path', ':path'))
->orderBy('c.path', 'ASC')
->setParameter('path', '%/%/%');
},
));
Category got a getParentName method:
public function getParentName()
{
if (null === $this->getParent()) {
return null;
}
return $this->getParent()->getName();
}
It works as expected but a query is executed for every parent (a lot). If I make a join with parent the parent will also be selectable in the selectmenu which I don't want.
How do I limit the queries?
Or filter a joined result?
I was not fetchjoining... adding a select to the querybuilder made it work:
$qb = $er->createQueryBuilder('c');
return $qb->select('c, p')
->leftJoin('c.parent', 'p')
->where($qb->expr()->like('c.path', ':path'))
->orderBy('c.path', 'ASC')
->setParameter('path', '%/%/%');