Skip to content

Instantly share code, notes, and snippets.

@webdevilopers
Last active May 25, 2023 08:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save webdevilopers/5b0e90031b6a0c5da3bc to your computer and use it in GitHub Desktop.
Save webdevilopers/5b0e90031b6a0c5da3bc to your computer and use it in GitHub Desktop.
Inject Entity and Repository into Service with Symfony2
<?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"
@webdevilopers
Copy link
Author

My service constructor needs the Contract entity as first argument and the repository as second.

Or is it better to only inject the entityManager and get the Contract entity inside my service?

Discussion on Twitter:
https://twitter.com/webdevilopers/status/629557505266450432

@EmanueleMinotto
Copy link

I think you should inject only the EntityManager, the Invoice entity shouldn't be aware of the InvoiceRepository

@webdevilopers
Copy link
Author

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?!

@improved-broccoli
Copy link

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

@webdevilopers
Copy link
Author

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 finding 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? :)

@webdevilopers
Copy link
Author

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?

@symball
Copy link

symball commented Apr 2, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment