Failed count entities with multiple identifiers #5730

Closed
opened 2026-01-22 15:15:51 +01:00 by admin · 4 comments
Owner

Originally created by @peter-gribanov on GitHub (Oct 5, 2017).

Originally assigned to: @lcobucci on GitHub.

Entity:

/**
 * @ORM\Table(name="favorite__news")
 * @ORM\Entity()
 */
class FavoriteNews
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     * @ORM\ManyToOne(targetEntity="User")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
     *
     * @var User
     */
    private $user;

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     * @ORM\ManyToOne(targetEntity="News")
     * @ORM\JoinColumn(name="news_id", referencedColumnName="id", nullable=false)
     *
     * @var News
     */
    private $news;

    /**
     * @ORM\Column(name="favorite_at", type="DateTimeImmutable", nullable=false)
     *
     * @var \DateTimeImmutable
     */
    private $favorite_at;

   // some methods ...
}

Query:

$total = $em
    ->createQueryBuilder()
    ->select('COUNT(fc)')
    ->from(FavoriteNews::class, 'fc')
    ->where('fc.user = :user')
    ->setParameter('user', $user)
    ->getQuery()
    ->getSingleScalarResult()
;

Result:

[Semantical Error] line 0, col 13 near 'fc) FROM FavoriteNews': Error: Invalid PathExpression. Must be a StateFieldPathExpression.

For resolve this problem i must use concrete field in COUNT():

$total = $em
    ->createQueryBuilder()
    ->select('COUNT(fc.news)')
    ->from(FavoriteNews::class, 'fc')
    ->where('fc.user = :user')
    ->setParameter('user', $user)
    ->getQuery()
    ->getSingleScalarResult()
;

But this is not always possible 1, 2.

$ composer show --latest 'doctrine/*'
doctrine/annotations                v1.4.0  v1.4.0  Docblock Annotations Pa...
doctrine/cache                      v1.6.2  v1.6.2  Caching library offerin...
doctrine/collections                v1.4.0  v1.4.0  Collections Abstraction...
doctrine/common                     v2.7.3  v2.7.3  Common Library for Doct...
doctrine/dbal                       v2.5.13 v2.5.13 Database Abstraction Layer
doctrine/doctrine-bundle            1.6.11  1.6.11  Symfony DoctrineBundle
doctrine/doctrine-cache-bundle      1.3.1   1.3.1   Symfony Bundle for Doct...
doctrine/doctrine-migrations-bundle v1.2.1  v1.2.1  Symfony DoctrineMigrati...
doctrine/inflector                  v1.1.0  v1.1.0  Common String Manipulat...
doctrine/instantiator               1.0.5   1.0.5   A small, lightweight ut...
doctrine/lexer                      v1.0.1  v1.0.1  Base library for a lexe...
doctrine/migrations                 v1.5.0  v1.5.0  Database Schema migrati...
doctrine/orm                        v2.5.11 v2.5.11 Object-Relational-Mappe...
Originally created by @peter-gribanov on GitHub (Oct 5, 2017). Originally assigned to: @lcobucci on GitHub. Entity: ```php /** * @ORM\Table(name="favorite__news") * @ORM\Entity() */ class FavoriteNews { /** * @ORM\Id * @ORM\GeneratedValue(strategy="NONE") * @ORM\ManyToOne(targetEntity="User") * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false) * * @var User */ private $user; /** * @ORM\Id * @ORM\GeneratedValue(strategy="NONE") * @ORM\ManyToOne(targetEntity="News") * @ORM\JoinColumn(name="news_id", referencedColumnName="id", nullable=false) * * @var News */ private $news; /** * @ORM\Column(name="favorite_at", type="DateTimeImmutable", nullable=false) * * @var \DateTimeImmutable */ private $favorite_at; // some methods ... } ``` Query: ```php $total = $em ->createQueryBuilder() ->select('COUNT(fc)') ->from(FavoriteNews::class, 'fc') ->where('fc.user = :user') ->setParameter('user', $user) ->getQuery() ->getSingleScalarResult() ; ``` Result: ``` [Semantical Error] line 0, col 13 near 'fc) FROM FavoriteNews': Error: Invalid PathExpression. Must be a StateFieldPathExpression. ``` For resolve this problem i must use concrete field in `COUNT()`: ```php $total = $em ->createQueryBuilder() ->select('COUNT(fc.news)') ->from(FavoriteNews::class, 'fc') ->where('fc.user = :user') ->setParameter('user', $user) ->getQuery() ->getSingleScalarResult() ; ``` But this is not always possible [1](https://github.com/Happyr/Doctrine-Specification/blob/master/src/Specification/CountOf.php#L33), [2](https://github.com/Happyr/Doctrine-Specification/pull/144/files#diff-bb3505b529d55256f6d5a5441de8d141R23). ``` $ composer show --latest 'doctrine/*' doctrine/annotations v1.4.0 v1.4.0 Docblock Annotations Pa... doctrine/cache v1.6.2 v1.6.2 Caching library offerin... doctrine/collections v1.4.0 v1.4.0 Collections Abstraction... doctrine/common v2.7.3 v2.7.3 Common Library for Doct... doctrine/dbal v2.5.13 v2.5.13 Database Abstraction Layer doctrine/doctrine-bundle 1.6.11 1.6.11 Symfony DoctrineBundle doctrine/doctrine-cache-bundle 1.3.1 1.3.1 Symfony Bundle for Doct... doctrine/doctrine-migrations-bundle v1.2.1 v1.2.1 Symfony DoctrineMigrati... doctrine/inflector v1.1.0 v1.1.0 Common String Manipulat... doctrine/instantiator 1.0.5 1.0.5 A small, lightweight ut... doctrine/lexer v1.0.1 v1.0.1 Base library for a lexe... doctrine/migrations v1.5.0 v1.5.0 Database Schema migrati... doctrine/orm v2.5.11 v2.5.11 Object-Relational-Mappe... ```
admin added the ImprovementCan't Fix labels 2026-01-22 15:15:51 +01:00
admin closed this issue 2026-01-22 15:15:51 +01:00
Author
Owner

@peter-gribanov commented on GitHub (Oct 6, 2017):

I can't use specifications because of this problem:

$favorited = $this->rep->isSatisfiedBy(Spec::andX(
    new UserNews($user),
    Spec::eq('news', $news)
), new CacheId(3600, $this->newsId($user, $news)));

I have to use a QueryBuilder:

$favorited = (bool) $this->em
    ->createQueryBuilder()
    ->select('COUNT(fc.favorite_at)')
    ->from(FavoriteNews::class, 'fn')
    ->innerJoin('fc.news', 'n')
    ->andWhere('fn.user = :user')
    ->andWhere('fn.news = :news')
    ->andWhere('n.enabled = :enabled')
    ->setParameter('user', $user)
    ->setParameter('news', $news)
    ->setParameter('enabled', true)
    ->getQuery()
    ->useResultCache(true, 3600, $this->newsId($user, $news))
    ->getSingleScalarResult()
;

There is another way to solve the problem. Add id field and make it as primary key.
But id is only needed for Doctrine. In project, it is not needed.

FavoriteNews with id
/**
 * @ORM\Table(name="favorite__news")
 * @ORM\Entity()
 */
class FavoriteNews
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(name="id", type="integer", nullable=false, options={"unsigned": true})
     *
     * @var int
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="User")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
     *
     * @var User
     */
    private $user;

    /**
     * @ORM\ManyToOne(targetEntity="News")
     * @ORM\JoinColumn(name="news_id", referencedColumnName="id", nullable=false)
     *
     * @var News
     */
    private $news;

    /**
     * @ORM\Column(name="favorite_at", type="DateTimeImmutable", nullable=false)
     *
     * @var \DateTimeImmutable
     */
    private $favorite_at;

   // some methods ...
}
@peter-gribanov commented on GitHub (Oct 6, 2017): I can't use specifications because of this problem: ```php $favorited = $this->rep->isSatisfiedBy(Spec::andX( new UserNews($user), Spec::eq('news', $news) ), new CacheId(3600, $this->newsId($user, $news))); ``` I have to use a QueryBuilder: ```php $favorited = (bool) $this->em ->createQueryBuilder() ->select('COUNT(fc.favorite_at)') ->from(FavoriteNews::class, 'fn') ->innerJoin('fc.news', 'n') ->andWhere('fn.user = :user') ->andWhere('fn.news = :news') ->andWhere('n.enabled = :enabled') ->setParameter('user', $user) ->setParameter('news', $news) ->setParameter('enabled', true) ->getQuery() ->useResultCache(true, 3600, $this->newsId($user, $news)) ->getSingleScalarResult() ; ``` There is another way to solve the problem. Add `id` field and make it as primary key. But `id` is only needed for Doctrine. In project, it is not needed. <details> <summary>FavoriteNews with id</summary> ```php /** * @ORM\Table(name="favorite__news") * @ORM\Entity() */ class FavoriteNews { /** * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") * @ORM\Column(name="id", type="integer", nullable=false, options={"unsigned": true}) * * @var int */ private $id; /** * @ORM\ManyToOne(targetEntity="User") * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false) * * @var User */ private $user; /** * @ORM\ManyToOne(targetEntity="News") * @ORM\JoinColumn(name="news_id", referencedColumnName="id", nullable=false) * * @var News */ private $news; /** * @ORM\Column(name="favorite_at", type="DateTimeImmutable", nullable=false) * * @var \DateTimeImmutable */ private $favorite_at; // some methods ... } ``` </details>
Author
Owner

@peter-gribanov commented on GitHub (Oct 6, 2017):

I don't understand why we can't transform DQL:

SELECT COUNT(e) FROM FavoriteNews e

into SQL:

SELECT COUNT(*) FROM favorite__news e
@peter-gribanov commented on GitHub (Oct 6, 2017): I don't understand why we can't transform DQL: ```sql SELECT COUNT(e) FROM FavoriteNews e ``` into SQL: ```sql SELECT COUNT(*) FROM favorite__news e ```
Author
Owner

@lcobucci commented on GitHub (Nov 26, 2017):

@peter-gribanov we have to consider that COUNT(*), COUNT(id), and COUNT(1) have different behaviour (specially if you have nullable fields) and since the ORM cannot infer which behaviour the user wants when having composite fields it expects the user to provide the correct field(s). So unfortunately this is something we won't otherwise it might break things.

It's also important to state that COUNT(*) is not recommended because it will potentially block writing operations for some specific platforms.

@lcobucci commented on GitHub (Nov 26, 2017): @peter-gribanov we have to consider that `COUNT(*)`, `COUNT(id)`, and `COUNT(1)` have different behaviour (specially if you have nullable fields) and since the ORM cannot infer which behaviour the user wants when having composite fields it expects the user to provide the correct field(s). So unfortunately this is something we won't otherwise it might break things. It's also important to state that `COUNT(*)` is not recommended because it will potentially block writing operations for some specific platforms.
Author
Owner

@lcobucci commented on GitHub (Nov 26, 2017):

I'll close this issue as Can't Fix due to given explanation.

@lcobucci commented on GitHub (Nov 26, 2017): I'll close this issue as `Can't Fix` due to given explanation.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5730