allan.simon
allan.simon

Reputation: 4306

Doctrine2: get count of one to many relation directly mapped in entity

I'm using doctrine2 and symfony3.1

I have a list of Movie for which people can buy Ticket, using a one to many relationship

On the dashboard I would like to display the list of movies, with the number of tickets bought for each

I get the data I want by doing this

    $manager = $this->getDoctrine()->getManager();
    $builder = $manager->createQueryBuilder();

    $results = $builder
        ->select('m')
        ->from('AppBundle:Movie', 'm')
        ->addSelect($builder->expr()->count('t'))
        ->leftJoin('m.tickets', 't')
        ->groupBy('m.id')
        ->getQuery()
        ->getResult()
    ;

it produces for 100 movies, 1 requests:

SELECT
       e0_.title AS title_0,
       e0_.is_published AS is_published_1,
       e0_.description AS description_2,
       e0_.author AS author_3,
       e0_.director AS director_4,
       e0_.artist_list AS artist_list_5,
       e0_.tags_list AS tags_list_6,
       e0_.creation_time AS creation_time_7,
       e0_.id AS id_8,
       COUNT(t1_.id) AS sclr_9,
       e0_.place_id AS place_id_10,
       e0_.organization_id AS organization_id_11,
       e0_.inner_image1_id AS inner_image1_id_12,
       e0_.inner_image2_id AS inner_image2_id_13,
       e0_.inner_image3_id AS inner_image3_id_14,
       e0_.image_id AS image_id_15
FROM event e0_
LEFT JOIN event_occurence e2_ ON e0_.id = e2_.event_id
LEFT JOIN ticket t1_ ON e2_.id = t1_.occurence_id
WHERE e0_.organization_id = '956744cb-6f76-4328-8ea5-c9715d762509'
GROUP BY e0_.id
LIMIT 100;

which is perfectly what I want in term of SQL request emitted.

The problem is on the ORM side, that the results is organized like this

[
   [ movie, nbrTickets],
   [ movie, nbrTickets],
   [ movie, nbrTickets],
]

I would like to know how to give hints to Doctrine so that nbrTickets become a property of Movie directly, (currently I have to iterate myself) while still doing only 1 SQL query (I strongly emphasi so I don't want to do movie.tickets | length in my twig

The answer should meet the following requirements:

The answer "it's currently not possible with doctrine" is also acceptable.

Upvotes: 2

Views: 3325

Answers (2)

allan.simon
allan.simon

Reputation: 4306

Currently it seems there's no simple way to get it mapped directly in the entity. You can only do approximative solution: (which get the job done but not as elegantly)

  • using $movie->tickets->count() which is elegant but too costly (you do 1 more sql request for each movie)
  • using a addSelect (my current solution) which does 1 request to get all the data, but which need additional code to map the count in your entity.

It seems there was a solution in Doctrine 1 as explained here as you were able to access a special attribute $this->_values containing the non-mapped value from within the entity which seems to no more be the case.

Upvotes: 1

user4257783
user4257783

Reputation:

You need proper annotations for the relation to work in Movie and Ticket entity.

Movie entity:

 * One user has Many tickets.
 * @ORM\OneToMany(targetEntity="Ticket", mappedBy="user")
 */
private $tickets;

Ticket entity:

 * Many tickets have One movie.
 * @ORM\ManyToOne(targetEntity="movie", inversedBy="tickets")
 * @ORM\JoinColumn(name="movie_id", referencedColumnName="id")
 */
private $movie;

For the relation you need to define an array collection in the constructor

public function __construct() {
    $this->tickets = new ArrayCollection();
}

You need to create a getTickets function for the Movie entity like that:

 * tickets
 *
 * @return arrayCollection
 */
public function getTickets()
{
    return $this->tickets;
}

Then you retrieve the movies with the following repository call:

    $movies = $this->getDoctrine()
        ->getRepository('AppBundle:movies')
        ->findAll();

and finally you retrieve the tickets:

    $tickets = $movies[0]->getTickets()

and you count them with:

    $tickets->count()

Upvotes: 1

Related Questions