-
-
Save webdevilopers/5b0e90031b6a0c5da3bc to your computer and use it in GitHub Desktop.
<?php | |
use Plusquam\Bundle\ContractBundle\Service\Invoice; | |
class ContractAdminController extends Controller | |
{ | |
/** | |
* Invoice action | |
* | |
* @return Response | |
*/ | |
public function invoiceAction(Request $request) | |
{ | |
// ... | |
// $invoice = $this->get('contract.invoice'); | |
$invoice = new Invoice($contract, $em->getRepository('PlusquamContractBundle:Contract')); | |
} | |
} |
<?php | |
namespace Plusquam\Bundle\ContractBundle\Service; | |
use Doctrine\ORM\EntityRepository; | |
class Invoice | |
{ | |
private $repository; | |
private $contract; | |
private $filters; | |
private $timeAndExpense; | |
public function __construct($contract, EntityRepository $repository) { | |
$this->contract = $contract; | |
$this->repository = $repository; | |
} | |
public function setFilters($filters) | |
{ | |
$this->filters = $filters; | |
} | |
public function getTimeAndExpense() | |
{ | |
return $this->repository->getTimeAndExpenseByContract($this->filters); | |
} | |
} |
services: | |
contract.repository: | |
class: Doctrine\ORM\EntityRepository | |
factory_service: doctrine.orm.entity_manager | |
factory_method: getRepository | |
arguments: | |
- Plusquam\Bundle\ContractBundle\Entity\Contract | |
contract.invoice: | |
class: Plusquam\Bundle\ContractBundle\Service\Invoice | |
arguments: | |
- "@contract.repository" |
I think you should inject only the EntityManager, the Invoice
entity shouldn't be aware of the InvoiceRepository
Actually the Invoice
class is the service, not the entity @EmanueleMinotto!
I guess passing the entity via constructor (since it is required and can change) and using setter injection for the repository (which is always the same servce) is best practice?!
IMHO, constructor injection is preferable to setter injection.
There are multiple drawbacks with setter injection, e.g:
With setter injection you have two-step initialization, so you have to make sure you inject that dependency before executing a specific part of your class Invoice
. So it means you need to remind which dependency you need for which part of code.
Let's imagine you change your class implementation, and now you need that dependency elsewhere in your class. You would have to check every call to your class, to see if you need to inject the dep or not.
Injecting deps through ctor makes the class construction process more understandable. Just by looking at the constructor you know how and from what your class is built.
But I recommend you to read (and bookmark) this article: http://martinfowler.com/articles/injection.html#ConstructorVersusSetterInjection
I totally get your point @benoit2011 !
This example seemed a very special case to me. First of all it looks strange looking at the consctruction e.g. in the controller code:
$invoice = new Invoice($contract, $em->getRepository('PlusquamContractBundle:Contract'));
Normally you expect the complete Invoice
service to work when passing the contract without knowing that you require a repository. Calling the entityManager here to get the repo looks stange.
I think it looks much better if you did something like:
new Invoice($contractId)
new Invoice($contractValueObject)
Then the Invoice
service would have the entityManager
(NOT THE repo) injected.
And finally the entityManager
can do both what we need in the constructor:
- find - or even a more performant custom query -
Contract
- use the repo to call the query for my reporting:
getTimeAndExpenseByContract
Looking at my example I don't even think that find
ing the Contract
is required.
I only need:
- to verify the
Contract
(value) object - call one or to repo methods
I think nowerdays value objects are a good use case to verify objects - just like they can be used in constructors of models resp. entities.
Maybe @mathiasverraes or @matthiasnoback have another hint for us? :)
Looking at this latest comment by @Ocramius I think using the constructor AND the setter is no good choice:
Optional dependencies Do Not Exist!
http://ocramius.github.io/extremely-defensive-php/#/51
But still: how to solve this?
May I ask what approach you ended up taking with this?
I am in the process of writing a manager service which will need the Doctrine Entity Manager and a specific repository and pondering whether I should just inject the entity manager and then retrieve the repository from within the service or inject both independently.
My current thinking is that I should inject them both independently because:
- It would remove the need to perform checking and halt execution of certain methods within the service if the repository didn't exist
- Injecting both separately makes it clearer what the service needs to function from a black box perspective
- Symfony would handle the missing parameters (if that were the case) within the application bootstrap cycle
My service constructor needs the
Contract
entity as first argument and therepository
as second.Or is it better to only inject the
entityManager
and get theContract
entity inside my service?Discussion on Twitter:
https://twitter.com/webdevilopers/status/629557505266450432