Let\'s say that I have two tables in my database : Rabbits and Carrots. Rabbits can have 0 or multiples carrots and a carrot belongs to a single rabbit. That\'s a 1,n relati
Back to basics. Forget about repository vs service and just focus on rabbits and carrots.
class Rabbit
{
/** @ORM\OneToMany(targetEntity="Carrot", mappedBy="rabbit" */
protected $carrots;
public function getCarrots() { return $this->carrots; }
public function getMoreExpensiveCarrots()
{
// Get all carrots
$carrots = $this->getCarrots()->toArray();
// Sort by price
// http://php.net/manual/en/function.usort.php
usort(&$carrots,array($this,'compareTwoCarrotsByPrice'));
// Now slice off the top 10 carrots (slice - carrots, hah, kindo of funny
$carrots = array_slice($carrots, 0, 10);
// And return the sorted carrots
return $carrots;
}
public function compareTwoCarrotsByPrice($carrot1,$carrot2)
{
if ($carrot1->getPrice() > $carrot2->getPrice()) return -1;
if ($carrot1->getPrice() < $carrot2->getPrice()) return 1;
return 0;
}
}
Remove all the carrot stuff from your query and just get a list of rabbits.
Your original template will now work exactly as expected:
{% for rabbit in rabbits %}
{% for carrot in rabbit.getMoreExpensiveCarrots %}
{{ carrot.price }}
{% endfor %}
{% endfor %}
The only downside here is that for each rabbit, a separate query for all carrots will be automatically generated by Doctrine. At some point performance will be hindered. When you reach that point then you can go back to your original query and see how to bring the carrots in more efficiently. But get the above class working first as I think this might be your blocking point.
Your entity classes should care only about the object they represent, and have absolutely no knowledge of the entity manager or repositories.
A possible solution here is to use a service object (RabbitService
) that contains a getMoreExpensiveCarrots
method. The service is allowed to know of the entity manager and the repositories, so it's here that you perform any complex operations.
By using a service object, you maintain separation of concerns, and ensure that your entity classes do the job they're meant to, and nothing more.
You could also go with your second option, assuming the carrots are stored in an ArrayCollection. You'd simply perform whatever sorting logic you need within the method. This would be fine since you'd be operating on data that was made available to the entity.
Let me take a shot at explaining it. Doctrine 2 ORM does require a bit of rethinking. You currently have the mindset that a rabbit should be able to query it's carrots whenever it needs them. You need to change that mindset.
For Doctrine 2, the idea is to give the rabbits their carrots as soon as you create the rabbit. In other words, it's done at query time. The assumption is that whatever process is querying the database for rabbits knows that you also want a specific set of carrots.
In this case you want the 10 most expensive carrots so your process needs to know that. So now you go back to @Arms answer and make yourself a RabbitManager service with a method called loadRabbitsWithExpensiveCarrots. The return value would be an array of rabbits with their carrots already filled in.
Updated this to address the comments.
A Repository tends to focus on one type of entity i.e. a Rabbit Repository is best used when only dealing with Rabbits. As you start to encounter more complex requirements that require multiple type of entities it can start to become difficult to determine which repository a given function should be in. Your application program (controller) is going to have to know which Repository to bring in and perhaps more about the internals than it really needs to know.
A Symfony 2 service hides all the details about how your rabbits and carrots are organized. It's a more general concept and can deal with more complex sort of queries involving multiple entities. It's a basic building block of S2 and you really do need to get comfortable with it.
For your current requirement, either approach will work.
Still not sure exactly what you mean. Perhaps you can post a sample query? Keep in mind that you can certainly add a getExpensiveCarrots() method to your rabbit object. The method would start be calling getCarrots(). The carrots returned would have already been loaded by the initial query. Your method would filter and sort.
And keep in mind that we are trying to deal with the case where a rabbit might have hundreds or thousands of carrots attached to it. So we are trying to avoid loading all the carrots just for performance consideration. It might be easier to start by not loading the carrots at all in initial query. Instead, the first time your rabbit->getCarrots() is called, Doctrine 2 will automatically issue a query and load all carrots for that rabbit. And then once again your getExpensiveCarrots method would filter and sort as needed.
Hope this help. Probably just confused you even more.