Symfony 6, EasyAdmin 4, Custom Filters problem - symfony

I have an entity Company, for clients and suppliers
Products can be assigned to a supplier, and to a client (both), with 2 different foreign keys to the same Company entity.
All is working OK until, in a list of products, I added two custom filters, for clients and suppliers. If I use two default filter, both will show all companies (including clients and suppliers).
This is the code for clients filter (supplier is similar just changing the type value:
Class ClientFilterType
namespace App\Form\Type\Admin;
use App\Entity\Company;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
class ClientFilterType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Company::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->andWhere('c.type = :type_id')
->setParameter('type_id', 2)
->orderBy('c.name', 'ASC');
}
]);
}
public function getParent(): ?string
{
return EntityType::class;
}
}
Class ClientFilter:
namespace App\Controller\Admin\Filter;
use App\Form\Type\Admin\ClientFilterType;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Filter\FilterInterface;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDataDto;
use EasyCorp\Bundle\EasyAdminBundle\Filter\FilterTrait;
class ClientFilter implements FilterInterface
{
use FilterTrait;
public static function new(string $propertyName, $label = null): self
{
return (new self())
->setFilterFqcn(__CLASS__)
->setProperty($propertyName)
->setLabel($label)
->setFormType(ClientFilterType::class);
}
public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void
{
if ('' !== $filterDataDto->getValue()) {
$queryBuilder
->andWhere('entity.client = :value')
->setParameter('value', $filterDataDto->getValue());
}
}
}
If I use only one filter, results are OK, but if I use both, client value override supplier value!!!
Doctrine log lookig for supplier selected 13:
[doctrine.DEBUG] Executing statement: SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.short_name AS short_name_2, c0_.code_name AS code_name_3, c0_.cif AS cif_4, c0_.email AS email_5, c0_.adress AS adress_6, c0_.city AS city_7, c0_.state AS state_8, c0_.zip AS zip_9, c0_.status AS status_10, c0_.type_id AS type_id_11, c0_.country_id AS country_id_12 FROM company c0_ WHERE c0_.type_id = ? AND c0_.id IN (?) ORDER BY c0_.name ASC (parameters: array{"1":1,"2":"13"}, types: array{"1":1,"2":1})
Doctrine log lookig for products of supplier selected 13:
[doctrine.DEBUG] Executing statement: SELECT count(p0_.id) AS sclr_0 FROM product p0_ WHERE p0_.supplier_id = ? (parameters: array{"1":13}, types: array{"1":1})
Doctrine log lookig for supplier selected 13 and client selected 3:
[doctrine.DEBUG] Executing statement: SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.short_name AS short_name_2, c0_.code_name AS code_name_3, c0_.cif AS cif_4, c0_.email AS email_5, c0_.adress AS adress_6, c0_.city AS city_7, c0_.state AS state_8, c0_.zip AS zip_9, c0_.status AS status_10, c0_.type_id AS type_id_11, c0_.country_id AS country_id_12 FROM company c0_ WHERE c0_.type_id = ? AND c0_.id IN (?) ORDER BY c0_.name ASC (parameters: array{"1":1,"2":"13"}, types: array{"1":1,"2":1})
[doctrine.DEBUG] Executing statement: SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.short_name AS short_name_2, c0_.code_name AS code_name_3, c0_.cif AS cif_4, c0_.email AS email_5, c0_.adress AS adress_6, c0_.city AS city_7, c0_.state AS state_8, c0_.zip AS zip_9, c0_.status AS status_10, c0_.type_id AS type_id_11, c0_.country_id AS country_id_12 FROM company c0_ WHERE c0_.type_id = ? AND c0_.id IN (?) ORDER BY c0_.name ASC (parameters: array{"1":2,"2":"3"}, types: array{"1":1,"2":1})
Doctrine log looking for products of client 3... and supplier 3 !!!!
[doctrine.DEBUG] Executing statement: SELECT DISTINCT p0_.id AS id_0 FROM number p0_ WHERE p0_.supplier_id = ? AND p0_.client_id = ? LIMIT 50 (parameters: array{"1":3,"2":3}, types: array{"1":1,"2":1})
So I can't select both filters, client and supplier, at the same time...
Any idea how to fix it?

Related

Doctrine Query with Multiple Joins only populating Main Entity

So far I have tried the following but I keep only getting the main Entity information joined entities do not make it to the result:
Option 1(Using ResultSetMapping Builder):
$rsm = new ResultSetMappingBuilder(
$this->_em,
ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT
);
$rsm->addRootEntityFromClassMetadata(
'CountryApp\StoreBundle\Entity\Product', 'p'
);
$rsm->addJoinedEntityFromClassMetadata(
'CountryApp\StoreBundle\Entity\Category', 'c', 'p', 'category'
);
$rsm->addJoinedEntityFromClassMetadata(
'CountryApp\StoreBundle\Entity\CustomerProductPrice', 'cpp', 'p', 'customerPrices'
);
$result = $this->_em
->createNativeQuery(
'
SELECT
p.id,
p.code,
p.name,
p.cost,
p.rrp,
p.status,
p.notes,
p.out_of_stock_since,
p.available_in,
c.id,
c.name,
c.code,
cpp.id,
cpp.price
FROM product as p
JOIN category as c ON c.id = p.category_id AND p.status != "DELETED"
LEFT JOIN customer_product_price as cpp ON cpp.product_id = p.id AND cpp.customer_id = :customer
', $rsm
)
->setParameter('customer', $customerId)
->getResult(Query::HYDRATE_ARRAY)
;
Option 2:(using QueryBuild and FetchMode)
$qb = $this->createQueryBuilder('p');
$result = $qb
->select('p')
->addSelect('c')
->addSelect('cpp')
->join(
'CountryApp\StoreBundle\Entity\Category',
'c',
Join::WITH,
$qb->expr()
->eq('c', 'p.category')
)
->leftJoin(
'CountryApp\StoreBundle\Entity\CustomerProductPrice',
'cpp',
Join::WITH,
$qb->expr()
->andX(
$qb->expr()
->eq('p', 'cpp.product'),
$qb->expr()
->eq('cpp.customer', ':customer')
)
)
->setParameter('customer', $customerId)
->getQuery()
->setFetchMode(
'CountryApp\StoreBundle\Entity\Category', 'product', ClassMetadata::FETCH_EAGER
)
->setFetchMode(
'CountryApp\StoreBundle\Entity\CustomerProductPrice', 'product', ClassMetadata::FETCH_EAGER
)
->getResult(Query::HYDRATE_ARRAY)
;
Please advise your thoughts as to what could make this work. I want to obtain the following structure:
[
0 => [
Product[
..
]
Category[
..
]
CustomerProductPrice[
..
]
],
1 => [
Product[
..
]
Category[
..
]
CustomerProductPrice[
..
]
],
..
.
]
While using Doctrine you define your relationships inside of your entity.
You can read more here https://symfony.com/doc/current/doctrine/associations.html always read the documentation and best practices. I dunno if you are using Symfony or not, but this is a great example and more understandable than Doctrine docs.
/**
* #ORM\Entity()
*/
class Product
{
// ...
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="products")
*/
private $category;
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
}
As you see here you define an entity that holds all associations and properties.
Normally association will be lazy loaded by default if you call $product->getCategory() you category will be lazy loaded. If you do not like lazy loading you can always eager fetch with
/**
* #ManyToOne(targetEntity="Category", cascade={"all"}, fetch="EAGER")
*/
And you will receive an array of products where each product will have a property named category and will contain Category entity inside it.
This is the main difference between CakePHP because in CakePHP you get all associations separately and in Symfony, you get a tree of associations.
And your queries seems too complex and in most cases, you do not have to modify queries like that at all. But be careful with lazy loads if you lazy load data on huge lists you will end up with poor performance.

How to build this query on Symfony with QueryBuilder?

Please I am a beginner on symfony and I have 2 entities: category and professionnal with ORM many to many. And 'City' is an entity with ORM oneToMany with 'professionnal'.
In professionnel I have a property city. I want to show professionnal grouped by categories who have a special cityId putted on parameter in the url.
I put this query on sql
(for example city_id= 10873)
, it gives me the results.
SELECT * FROM sub_category AS a LEFT JOIN professionnel ON professionnel.city_id = 10873
but I don't know how to write with querybuilder.
I put this solution but I have errors:
$city = $paramFetcher->get('city');
$queryBuilder = $em->getRepository(SubCategory::class)
->createQueryBuilder('a')
->leftJoin('App\Entity\Professionnel','p')
->where('p.city = :city')
->setParameter('city', $city);
return $queryBuilder->getQuery()->getResult();
in log:
request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\Exception\SyntaxErrorException: "An exception occurred while executing 'SELECT c0_.id AS id_0, c0_.name AS name_1 FROM category c0_ LEFT JOIN professionnel p1_ WHERE p1_.city_id = ?' with params ["10873"]: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'WHERE p1_.city_id = '10873'' at line 1"
I search on forums too and I found that I can replaced the 'ON' of leftJoin as following, but shows me all professionnals:
$city = $paramFetcher->get('city');
$queryBuilder = $em->getRepository(SubCategory::class)
->createQueryBuilder('a')
->leftJoin('App\Entity\Professionnel','p','WITH','p.city = :city')
->setParameter('city', $city);
return $queryBuilder->getQuery()->getResult();
For the mapping, I put it like this:
class Category
{
/**
* Many professionnels have Many categories.
* #ORM\ManyToMany(targetEntity="App\Entity\Professionnel", mappedBy="Categories")
* #ORM\JoinTable(name="professionnel_category")
*/
private $professionnels;
}
class Professionnel extends User
{
/**
* Many professionel have Many categories.
* #ORM\ManyToMany(targetEntity="App\Entity\Category", inversedBy="professionnels")
* #Groups({"Default", "professionnel", "user"})
*/
private $categories;
}
Thank you so much for your help.
So, assuming that you have your entity relation set up correctly (please update your question with those), I see one mistake above.
If you look at the official Doctrine Query Builder documentation, the leftJoin method's third argument is either WITH or ON constant and not join predicate. That one comes as a 4th argument. But, even then, that join predicate is only used is a very specific use-case, where you want to further define your join relation. By looking at your example, I doubt that is your case.
With that being said, I think your code should look like this:
$city = $paramFetcher->get('city');
$queryBuilder = $em->getRepository(Category::class)
->createQueryBuilder('a');
->leftJoin('App\Entity\Professionnel','p')
->where('p.city_id = :city')
->setParameter('city', $city);
return $queryBuilder->getQuery()->getResult();
Now the question: is that really just a city_id (plain integer) or is that relation to some entity named City?
Please update your question with those details and I will update the answer, if necessary...
Hope this helps...
Firstly, you need describe relations in entities. Read more about this here
Secondary, you must call in join field (read as property), not a Entity.
inside CategoryRepository:
/** #return ArrayCollection|Category[] */
public function getByCityId(int $cityId): ArrayCollection
{
$qb = $this->createQueryBuilder('category');
$qb->leftJoin('category.professional', 'professional')
->leftJoin('category.city', 'city')
->where($qb->expr()->eq('city.id', $cityId));
return $qb->getQuery()->getResult();
}
Inside SomeController:someAction()
$cityId = $paramFetcher->get('city');
$categoriesList = $this->get('doctrine.orm.entity_manager')
->getRepository(Category::class)
->getByCityId($cityId);
As result you'll be have list of category, with joined professional and city.
I suggest to read this article on symfony docs

How to change Query to contain data from joined table?

I have simple Entity:
id
username
guard
"guard" is id of another user from the same Entity. I have to render view with simple table:
username | name of guard
-------------------------
John | Bob
I tried to do that by query:
$ur = $this->getDoctrine()->getRepository(User::class)->createQueryBuilder('u')
->leftJoin(User::class, 'u2', \Doctrine\ORM\Query\Expr\Join::WITH, 'u.guard = u2.id')
->getQuery()
->getResult();
but it gives me just id and username, no joined data.
I know that entire query should be like:
SELECT
*
FROM
user u0_
LEFT JOIN user u1_ ON (u0_.guard = u1_.id)
but I can't find the way to implement that by QueryBuilder and then to access that in twig template.
Regards
OK, I found out the mistakes in my code:
I tried to set that OneToOne realtion and that was small mistake, but I needed here ManyToOne.
/**
* Many Users have One Guard (User)
* #ORM\ManyToOne(targetEntity="User")
*/
private $guard = 0;
When I did that Symfony automatically force me to change my code and in column "guard" I have to insert User object.
After that I don't need join anymore - just select data from table and guard column includes User object which I can use in Twig, etc.
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function findAllDB()
{
$qb = $this->createQueryBuilder('u');
$query = $qb->getQuery();
return $query->execute();
}
}

Key "productTitle" for array with keys "0, catTitle" does not exist in EagleShopBundle:global:product.html.twig

I'm trying to join two tables and print a value in twig template but I'm having this issue.
This is my Controller action.
/**
* #Route("products/display/{id}")
* #Template()
*/
public function displayAction($id) {
$em = $this->container->get('doctrine.orm.entity_manager');
$qb = $em->createQueryBuilder();
$qb->select('p, pc.catTitle')
->from('EagleShopBundle:Products', 'p')
->leftJoin('EagleShopBundle:ProductCategory', 'pc', \Doctrine\ORM\Query\Expr\Join::WITH, 'pc.id = p.category')
->where($qb->expr()->eq('p.id', '?5'))
->setParameter(5, $id);
$product = $qb->getQuery()->getOneOrNullResult();
return $this->render("EagleShopBundle:global:product.html.twig", array(
'product' => $product,
'image_path' => '/bundles/eagleshop/images/'
));
}
This is my twig file line related to the issue,
<h1>{{product.productTitle}}</h1>
I guess issue is related to this line
$qb->select('p, pc.catTitle')
This is the error I get,
Key "productTitle" for array with keys "0, catTitle" does not exist in
EagleShopBundle:global:product.html.twig
You could try next query:
$qb->select('p, partial pc.{id, catTitle}')
// if you need full productCategory object then write just 'p, pc'
->from('EagleShopBundle:Products', 'p')
->leftJoin('p.category', 'pc')
//productCategory is the field
//in product entity which has relation to product category entity,
//paste your field (not column!) name here
//if it is not productCategory
->where('p.id = :productId')
->setParameter('productId', $id);
P.S.
It is better to move queries to entity repositories :)
P.P.S.
Doctrine partial objects
UPD
Fixed query - with right field name

Error with 'orderby' column not found

I have this very simple method in my repository class which fetches a list as query builder object:
public function fetchListAsQueryBuilder(User $user, $receiverType, $limit, $offset)
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$query = $queryBuilder
->select(['no'])
->from('SLCoreBundle:Notification', 'no')
->where('no.receiver = :user')
->andWhere('no.receiverType = :receiverType')
->orderBy('no.createdAt', 'DESC')
->setParameters([
'user' => $user,
'receiverType' => $receiverType,
])
->setMaxResults($limit)
->setFirstResult($offset)
;
return $query;
}
this method works perfectly in my prod server, but gives an error in my local machine, php versions are same(5.5.9), here is an error:
An exception occurred while executing 'SELECT DISTINCT id_6 FROM
(SELECT s0_.receiver_type AS receiver_type_0, s0_.importance AS
importance_1, s0_.seen AS seen_2, s0_.deleted AS deleted_3,
s0_.created_at AS created_at_4, s0_.updated_at AS updated_at_5, s0_.id
AS id_6, s0_.reason AS reason_7 FROM sl_notification s0_ WHERE
s0_.receiver_id = ? AND s0_.receiver_type = ?) dctrn_result ORDER BY
s0_.created_at DESC LIMIT 25 OFFSET 0' with params [2, 1]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 's0_.created_at' in 'order clause'
My entity has been configured like this:
Mapped superclass AbstractMessage:
abstract class AbstractMessage
{
use CreatedUpdatedAtTrait;
// here go properties, setters and getter
Notification class:
class Notification extends AbstractMessage
{
// here go properties, setters and getters
And CreateUpdatedAtTrait:
trait CreatedUpdatedAtTrait
{
/**
* #var \DateTime
*/
private $createdAt;
/**
* #var \DateTime
*/
private $updatedAt;
// Here go setters and getters
}
Schema (AbstractMessage) :
<mapped-superclass name="SL\CoreBundle\Entity\AbstractMessage">
...
<field name="createdAt" column="created_at" type="datetime">
<gedmo:timestampable on="create" />
</field>
<field name="updatedAt" column="updated_at" type="datetime">
<gedmo:timestampable on="update" />
</field>
</mapped-superclass>
here is the db table:
I'dont understand what causes this error, my others entities work well with this trait, and also my other queries with orderBy method and mappedsuperclass classes work without any error. And also very interesting part is if I remove orderBy my method is working and I am able to get the createdAt value ($object->getCreatedAt()). Can anyone help me to solve this problem?
Edit: I forgot to mention, that I've recently updated vendors to the latest versions(sf-2.6.6, DoctrineORM-2.5.0).
I think it's cause by doctrine 2.5 +
So i switched back to 2.4.7 and its working again !
In my composer.json
"require": {
...
"doctrine/orm": "2.4.7",
...
}
And update
php composer.phar update

Resources