Inject Container in my Repository Class - symfony

i try to inject Container in my RepositoryClass, but it does not work.
BaseRepository:
<?php
namespace MyApp\ApplicationBundle\Repository;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class BaseRepository implements ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container=null)
{
echo "container";
var_dump($container);
}
public function __construct()
{
echo __CLASS__;
}
}
services.yml
services:
myapp.base_repository:
class: MyApp\ApplicationBundle\Repository\BaseRepository
calls:
- [ setContainer, [ '#service_container' ] ]
DefaultController:
$baseRep = new BaseRepository();
The only output that i get, is the echo FILE from the BaseRepository Construct.
The second way that i tried, is to inject the GuzzleClient self (this is the reason why i tried to inject the container, because i need my guzzle-configuraton-settings.
services.yml
myapp.base_repository:
class: MyApp\ApplicationBundle\Repository\BaseRepository
arguments: ['#csa_guzzle.client.mce']
BaseRepository:
use GuzzleHttp\Client;
class BaseRepository
{
public function __construct(Client $client)
{
var_dump($client);
echo __CLASS__;
}
}
But then i got the following error:
Type error: Argument 1 passed to
MyApp\ApplicationBundle\Repository\BaseRepository::__construct() must
be an instance of GuzzleHttp\Client, none given, called in
MyApp/src/Chameleon/DefaultBundle/Controller/DefaultController.php on
line 20
Anyone know what i can do?
Thank you!

To get the class that is managed by the Service Container you have to use said container to get the service with that id myapp.base_repository as Twifty says:
$this->get('myapp.base_repository');
// or more generally in classes implementing ContainerAwareInterface:
$this->container->get('myapp.base_repository');
If you create a new instance yourself you will have to manage all dependencies:
// In your controller extending Symfony's Controller:
$repository = new BaseRepository();
$repository->setContainer($this->container);
Similarly if you inject a Guzzle-client into the repository you have to either retrieve the service from the container or create it yourself with all the dependencies:
// $this->get() assumes you are in the controller as well
$repositoryWithClientFromServiceContainer = new BaseRepository(
$this->get('csa_guzzle.client.mce')
);
// This obviously works everywhere
$repositoryWithNewDefaultClient = new BaseRepository(
new GuzzleHttp\Client()
);
Furthermore injecting the service container into a class violates the dependency inversion you try to achieve by using the Service Container in the first place. This means, instead of making your repository ContainerAware you should only add the services you need in that repository, not the whole container. Just as you do in the 2nd example with the Guzzle-client.
Some people argue it's okay for controllers to violate that principle, but I personally prefer controller's being defined as services to be able to quickly see which dependencies they have by looking at the constructor.
As a general rule I would avoid using the ContainerAwareInterface.

Similarly if you inject a Guzzle-client into the repository you have
to either retrieve the service from the container or create it
yourself with all the dependencies:
// $this->get() assumes you are in the controller as well
$repositoryWithClientFromServiceContainer = new BaseRepository(
$this->get('csa_guzzle.client.mce')
);
// This obviously works everywhere
$repositoryWithNewDefaultClient = new BaseRepository(
new GuzzleHttp\Client()
);
Furthermore injecting the service container into a class violates the
dependency inversion you try to achieve by using the Service Container
in the first place. This means, instead of making your repository
ContainerAware you should only add the services you need in that
repository, not the whole container. Just as you do in the 2nd example
with the Guzzle-client.
Some people argue it's okay for controllers to violate that principle,
but I personally prefer [controller's being defined as services][1] to
be able to quickly see which dependencies they have by looking at the
constructor.
As a general rule I would avoid using the ContainerAwareInterface.
[1]: http://symfony.com/doc/current/cookbook/controller/service.html
Thank you.
So, it would be the better solution, if i inject only the guzzleClient, right?
As you can see, i have a few classes that extends from my BaseRepository and they need the guzzleClient.
But how is it possible to inject the guzzleClient for this scenario? If the programmer only want to create his basic "MyRep" Repositoryclass in the controller without any params.
services.yml
myapp.base_repository:
class: MyApp\ApplicationBundle\Repository\BaseRepository
arguments: ['#csa_guzzle.client.mce']
BaseRepository:
use GuzzleHttp\Client;
class BaseRepository
{
private $client = null;
public function __construct(Client $client)
{
var_dump($client);
$this->client = $client;
}
public getClient() {
return $this->client;
}
}
MyRepository:
MyRep extends BaseRepository:
use GuzzleHttp\Client;
class BaseRepository
{
public function __construct()
{
var_dump($this->getClient());
}
}
Thank you!

Related

Access Doctrine within a custom service in Symfony4

Aware that there is a lot of information around the net regarding this, I am still having a lot of trouble getting this to work.
I have created a custom service:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\AccommodationType;
use App\Entity\Night;
class AvailabilityChecks {
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->em->getDoctrine()->getRepository(AccommodationType::class)->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->em->getDoctrine()->getRepository(Night::class)->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
and have put this in services.yaml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
So when I try and use this in my controller, I get this error:
Too few arguments to function App\Service\AvailabilityChecks::__construct(), 0 passed in /mypath/src/Controller/BookController.php on line 40 and exactly 1 expected
I just can't figure out why it's not injecting the ORM stuff into the constructor! Any help greatly appreciated
The problem is in your BookController. Even though you didn't posted its code I can assume you create new AvailabilityChecks in it (on line 40).
In Symfony every service is intantiated by service container. You should never intantiate service objects by yourself. Instead BookController must ask service container for AvailabilityChecks service. How should it do it ?
In Symfony <3.3 we used generally :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function myAction()
{
$em = $this->get('doctrine.orm.entity_manager');
// ...
}
}
Nowadays services can be injected in controllers using autowiring which is way easier:
use Doctrine\ORM\EntityManagerInterface;
class MyController extends Controller
{
public function myAction(EntityManagerInterface $em)
{
// ...
}
}
You are using the wrong service for what you want to do. The alias doctrine that is used, e.g. in the AbstractController when you call getDoctrine() is bound to the service Doctrine\Common\Persistence\ManagerRegistry.
So the code you wrote fits better with that and you should either add #doctrine or #Doctrine\Common\Persistence\ManagerRegistry to the service definition.
Both with your current configuration or the changed one, you don't have to call $this->em->getDoctrine(), because $this->em is already equivalent to $this->getDoctrine() from your controller. Instead you could create a (private) method to make it look more like that code, e.g.:
private function getDoctrine()
{
return $this->em;
}
Then you can call $this->getDoctrine()->getRepository(...) or use $this->em->getRepository(...) directly.
In Symfony 4, you dont need to create it as services. This is automatically now. Just inject the dependencies what you need in the constructor. Be sure that you have autowire property with true value in services.yml (it is by default)
Remove this from services.yml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
You dont need EntityManagerInterface because you are not persisting anything, so inject repositories only.
<?php
namespace App\Service;
use App\Entity\AccommodationType;
use App\Entity\Night;
use App\Repository\AccommodationTypeRepository;
use App\Repository\NightRepository;
class AvailabilityChecks {
private $accommodationTypeRepository;
private $nightRepository
public function __construct(
AcommodationTypeRepository $acommodationTypeRepository,
NightRepository $nightRepository
)
{
$this->acommodationTypeRepository = $acommodationTypeRepository;
$this->nightRepository = $nightRepository;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->acommodationTypeRepository->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->nightRepository->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
In SF4, you no longer need to specify dependencies required by your custom service in the service.yaml file. All you have to do is to use dependency injection.
So remove config lines, and call your service directly in the controller method :
<?php
namespace App\Controller;
use App\Service\AvailabilityChecks ;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AppController extends AbstractController
{
public function index(AvailabilityChecks $service)
{
...
}
}
Having said that, i think you don't need custom service to do simple operations on database. Use repository instead.

Symfony override third party entity repository

I'm using a third party bundle with a custom doctrine entity repository. It includes a function which I'd like to override to customize it for my needs.
Has anyone ever done this? I couldn't find any documentation on overriding an entity repository.
Why I need to do this: a Service from that third party bundle calls this repository function and executes it. But I'd like to change one part of it.
What I tried:
create an own Entity repository class:
namespace NotificationBundle\Entity;
use Doctrine\ORM\EntityRepository;
use Citrax\Bundle\DatabaseSwiftMailerBundle\Entity\Email;
use Citrax\Bundle\DatabaseSwiftMailerBundle\Entity\EmailRepository as BaseRepository;
class EmailRepository extends BaseRepository
{
public function markCompleteSending(Email $email)
{
$email->setStatus(Email::STATUS_COMPLETE);
$email->setSentAt(new \DateTime());
$email->setErrorMessage('');
$em = $this->getEntityManager();
$em->remove($email);
$em->flush();
}
}
it extends the parent repository and overrides only this function, the rest I didn't touch. I then registered that repository as a service.
Anyway it's not working!
Anything else needed? Please let me know! I'd be glad about some help.
where the functino is called:
public function flushQueue(Swift_Transport $transport, &$failedRecipients = null)
{
if (!$transport->isStarted())
{
$transport->start();
}
$count = 0;
$emails = $this->repository->getEmailQueue($this->getMessageLimit());
foreach($emails as $email){
/*#var $message \Swift_Mime_Message */
$message = $email->getMessage();
try{
$count_= $transport->send($message, $failedRecipients);
if($count_ > 0){
$this->repository->markCompleteSending($email);
$count += $count_;
}else{
throw new \Swift_SwiftException('The email was not sent.');
}
}catch(\Swift_SwiftException $ex){
$this->repository->markFailedSending($email, $ex);
}
}
return $count;
}
that's the class DatabaseSpool that is the registered as the service which spools for my swiftmailer.
To override repo of DatabaseSwiftMailerBundle i can see (on git) that vendor has defined Citrax\Bundle\DatabaseSwiftMailerBundle\Entity\EmailRepository as a service named as repository.email and you can override services using symfony's CompilerPassInterface. In your bundle under DependencyInjection folder create your compiler pass and override repository.email service by defining your repo which should extend vendor's repo.
namespace Your\Bundle\DependencyInjection\Compiler;
use NotificationBundle\Entity\EmailRepository;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('repository.email');
$definition->setClass(EmailRepository::class);
}
}
As per official docs
If you want to modify service definitions of another bundle, you can use a compiler pass to change the class of the service or to modify method calls. In the following example, the implementing class for the original-service-id is changed to Acme\DemoBundle\YourService.
For more information have a look at Services & Configuration

How exactly can I define a controller as service using annotations?

This seems to be the fastest and simpliest way to use a controller as service, but I am still missing a step because it doesn't work.
Here is my code:
Controller/service:
// Test\TestBundle\Controller\TestController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController extends Controller {
// My actions
}
Use :
// Test\TestBundle\Controller\UseController.php
// ...
public function useAction() {
$testService = $this->get('test_service');
}
When I do that, I get the error
You have requested a non-existent service "test_service".
When I check the list of services with app/console container:debug, I don't see my newly created service.
What am I missing?
From Controller as Service in SensioFrameworkExtraBundle:
The #Route annotation on a controller class can also be used to assign the controller class to a service so that the controller resolver will instantiate the controller by fetching it from the DI container instead of calling new PostController() itself:
/**
* #Route(service="my_post_controller_service")
*/
class PostController
{
// ...
}
The service attribute in the annotation is just to tell Symfony it should use the specified service, instead of instantiating the controller with the new statement. It does not register the service on its own.
Given your controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController
{
public function myAction()
{
}
}
You need to actually register the controller as a service with the test_service id:
services:
test_service:
class: Test\TestBundle\Controller\TestController
The advantage of this approach is that you can inject your dependencies into the constructor by specifying them in the service definition, and you don't need to extend the base Controller class.
See How to define controllers as services and Controller as Service in SensioFrameworkExtraBundle.
For future folks, if you decide to use controller-as-a-service, you should better inject your services into the controller via the constructor instead of getting them through a service locator. The former is considered to be an antipattern, while the latter allows easy unit testing and is simply more verbose.
So instead of:
public function useAction() {
$testService = $this->get('test_service');
}
You should:
private $yourService;
public function __construct(YourService $yourService)
{
$this->yourService = $yourService;
}
public function useAction()
{
$this->yourService->use(...);
}
Don't create shortcuts, write solid, maintainable code.
For Symfony 3.4, we don't need to register controllers as services because they are already registered as services with the default services.yml configuration.
You just have to write this :
// current controller
public function myAction() {
$test = $this->get(SomeController::class)->someAction($param);
}

symfony2 injecting a dependence optimization

I have a form event subscriber which needs an entity repository.
I would like to inject this repository dependency ideally without having to use the constructors of the subscriber and its parents because this subscriber is needed in many different forms.
So basically I have the following chain :
Controller calls -> CustomManagerService instantiates-> Form instantiates -> EventSubscriber needs-> EntityRepository
the maanager is already a service. It is a pain both to transmit a constructor repository argument from the manager through the form to the subscriber and it is a pain to set each form as a service.
Why can't I instantiate the repository in the subscriber directly ? I have read it is a bad practice.
EDIT : this is what I have so far :
in my controller :
$unitRepository = $this->getDoctrine()->getRepository('UnitRepository');
$myManager = $this->get('my_manager')
$form = $myManager->createForm($unitRepository);
in myManager:
public function createForm(UnitRepository $unitRepository){
return $this->formFactory->createForm(
new xxxType($unitRepository)
}
in my form:
use MyBundle/AddUnitFieldSubscriber;
protected $unitRepository;
public function __construct(UnitRepository $unitRepository)
{
$this->unitRepository = $unitRepository;
}
public function buildform()
{
$builder->addEventSubscriber(new AddUnitFieldSubscriber($this->unitRepository));
}
in my subscriber:
protected $unitRepository;
public function __construct(UnitRepository $unitRepository)
{
$this->unitRepository = $unitRepository;
}
public function preSetData(FormEvent $event)
{
$unitRepository = $this->unitRepository;
$unitRepository->doStuff()
}
I found this extremely lenghty, and sometimes I have a form calling a subform which is the one using the eventSubscriber. if I set the forms as services, I also sometimes get errors cause I am instantiating them without the required first constructur parameter.
What would be the shortest path to do it right and to not repeat all this knowing only the subscriber need access to the repository ?
Thanks a lot !
I'm really not sure I understood everything or even anything, but I'm going to try a response.
I would suggest you to define a service SubscriberProvider which will be responsible of the instantiation of the subscriber and the injection of the repository in the subscriber (via a setter of the subscriber). You could retrieve an instance of subscriber using a method get, retrieve, create, provide or whatever you prefer of the service SubscriberProvider. You could then inject this provider in another service.
EDIT
Here is the definition of the service related to your form type:
services:
your_own_bundle.form.type.unit:
class: Your\OwnBundle\Form\Type\UnitType
arguments:
- "#doctrine"
tags:
- { name: form.type, alias: unit }
And its class:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Your\OwnBundle\Event\AddUnitFieldSubscriber;
class UnitType extends AbstractType
{
protected $unitRepository;
public function __construct(ManagerRegistry $doctrine)
{
$this->unitRepository = $doctrine->getRepository('UnitRepository');
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber(
new AddUnitFieldSubscriber($this->unitRepository)
);
}
Then, you can use this type like that:
$builder->add('available_unit', 'unit', array());
This way, you don't have to pass the repository to your manager.

Symfony2, autoload service in service

Question is simple but...
So we have main service:
class ManagerOne {}
and have several another services we want to use in main service:
class ServiceOne{}
class ServiceTwo{}
class ServiceThree{}
class ServiceFour{}
...
Each named as (in services.yml)
service.one
service.two
service.three
service.four
...
Locations of services is different, not in one folder (but I don't think it's a huge trouble for custom autoloader).
Regarding manual we can inject them via __construct() in main service (ManagerOne) but what if we got 20 such services need to be injected? Or use only that we need. Describe them in services as simple inject? O.o I think it's not good idea so.... Also we can inject container and that's it. BUT! Everywhere people saying that inject container worst solution.
What I want. I need method for ManagerOne service which will load service i need by 'service.name' or 'path' with checker 'service exist'.
You could use service tagging and tag each service you want to use in your ManagerOne class. And either use constructor dependency injection or method injection.
Example:
First of all you need a compiler pass to collect your tagged services:
namespace ...\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ExamplePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition("manager.one")) {
return;
}
$services = array();
foreach ($container->findTaggedServiceIds('managed_service') as $serviceId => $tag) {
$alias = isset($tag[0]['alias'])
? $tag[0]['alias']
: $serviceId;
// Flip, because we want tag aliases (= type identifiers) as keys
$services[$alias] = new Reference($serviceId);
}
$container->getDefinition('manager.one')->replaceArgument(0, $services);
}
}
Then you need to add the compiler pass to your bundle class:
namespace Example\ExampleBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use ...\DependencyInjection\Compiler\ExamplePass;
class ExampleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ExamplePass());
}
}
Now you can use your services:
# services.yml
manager.one:
class: ManagerClass
arguments:
- [] # will be replaced by our compiler pass
services.one:
class: ServiceOne
tags:
- { name: managed_service, alias: service_one }
services.two:
class: ServiceTwo
tags:
- { name: managed_service, alias: service_two }
But caution if you get your manager, all service classes will be automatically created. If this is a performance drawback for you could pass only the service ids (not the Reference) to your management class. Add the #service_container as second argument and create the service as needed.
Since 2017, Symfony 3.3 and Symplify\PackageBuilder this gets even easier.
Thanks to this package, you can:
drop tags
have simple 5 line CompilerPass using strict types over strings
Let's get to your example
Suppose you have
1 manager - UpdateManager class
many updaters - a class that implements UpdaterInterface
1. Service Config using PSR-4 autodiscovery
# app/config/services.yml
services:
_defaults:
autowire: true
App\:
resource: ../../src/App
2. Collecting Compiler Pass
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symplify\PackageBuilder\Adapter\Symfony\DependencyInjection\DefinitionCollector;
final class CollectorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $containerBuilder)
{
DefinitionCollector::loadCollectorWithType(
$containerBuilder,
UpdateManager::class,
UpdaterInterface::class,
'addUpdater'
);
}
}
It collect all services of UpdaterInterface type
and adds them via addUpdater() method to UpdateManager.
3. Register Compiler Pass in Bundle
namespace App;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class UpdaterBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new CollectorCompilerPass);
}
}
And that's all!
How to add new updater?
Just create class, that implements UpdaterInterface and it will be loaded to UpdateManager.
no tagging
no manual service registration
no boring work
Enjoy!

Resources