Symfony2 Use parameters in EntityRepository - php

I am wondering if this is even a good practice. But for my project i need to get a parameter from parameters.yml and use it inside EntityRepository.
So for this I created a service but still the call is not executed.
services:
xxx_repository:
class: XXX\DatabaseBundle\Repository\CitiesRepository
calls:
- [setTheParameter, ["%the_parameter%"]]
parameters.yml
...
the_parameter: 14400
...
And inside the CitiesRepository.php I am doing the following:
class CitiesRepository extends EntityRepository
{
/**
* #var
*/
protected $theParameter;
public function setTheParameter($theParameter)
{
$this->theParameter = $theParameter;
}
....
}
But $this->theParameter is always null.
SO i have 2 questions: Is this a healthy habit? And why is the result always null?

You need to use getRepository method of the doctrine service as factory:
xxx_repository:
class: XXX\DatabaseBundle\Repository\CitiesRepository
factory: ["#doctrine", "getRepository"]
arguments: ["DatabaseBundle:City"]
calls:
- ["setTheParameter", ["%the_parameter%"]]
And then you can access to this repository as service in your controller:
$this->get('xxx_repository');

Related

Adding services to a Controller through "container.service_subscriber" not working as expected

I am trying to use the container.service_subscriber tag on my Controller to make some services available without injecting them through the constructor. In our project we don't want to use the autowiring and also can't use the autoconfigure option.
The structure of the Controller is as follow:
I have a base BaseController which extends from the AbstractFOSRestController of FOSRestBundle which has some common used methods for all my Controllers. That service will be used as parent for my other Controllers.
The service definition looks like this:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
#autowire: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
The class looks like this:
/**
* User controller.
*/
class UserController extends AbstractCRUDController implements ClassResourceInterface
{
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
'servicexyz' => ServiceXYZ::class,
]);
}
.......
}
The problem I have is, if I set autowire: false, it always automatically sets the full container and with this the appropriate deprecation message (as I am not setting it myself):
User Deprecated: Auto-injection of the container for "WM\ApiBundle\Controller\UserController" is deprecated since Symfony 4.2. Configure it as a service instead.
When setting autowire: true Symfony does respect the container.service_subscriber tag and only sets the partial container (ServiceLocator), which also would solve the deprecation message. I would have expected that autowiring should not make any differences in this case because I am explicitly telling the service which other services it should have.
Am I using the tags wrong or do I have a general problem in understanding how to subscribe a service to a Controller?
The basic issue is that the builtin service subscriber functionality will only inject the service locator into the constructor. A conventional controller which extends AbstractController uses autoconfigure to basically override this and uses setContainer instead of the constructor.
# ApiBundle/Resources/config/services.yaml
services:
_defaults:
autowire: false
autoconfigure: false
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
class UserController extends AbstractController
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// ...
'logger' => LoggerInterface::class,
]);
}
public function index()
{
$url = $this->generateUrl('user'); // Works as expected
// $signer = $this->get('uri_signer'); // Fails as expected
$logger = $this->get('logger'); // Works as expected
return new Response('API Index Controller ' . get_class($this->container));
}
}
Results in:
API Index Controller Symfony\Component\DependencyInjection\Argument\ServiceLocator
Indicating that a service locator (as opposed to the global container is being injected).
You can also configure your service to use the setContainer method and eliminate the need for a constructor. Either approach will work.
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
calls: [['setContainer', ['#Psr\Container\ContainerInterface']]]
Solution to the problem is to extend the service definition of the Controller with a call to setContainer to inject the '#Psr\Container\ContainerInterface' service:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
calls:
- ['setContainer', ['#Psr\Container\ContainerInterface']]
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
This will give me a ServiceLocator as container containing only the regiestered services instead of the full container without using the autowire option.
Sidenote: Setting the #service_container would inject the full container.
For completeness, there was already an issue on the symfony project where this was discussed.

Doctrine manager class injection to controller doesn't work

In my bundle I need to initialize my doctrine manager class (as a service and using ManagerRegistry) in constructor of controller, but symfony still throws this exception:
Type error: Too few arguments to function AdminBundle\Controller\RegistraceController::__construct(), 0 passed in C:\apache\htdocs\mujProjekt\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Controller\ControllerResolver.php on line 198 and exactly 1 expected
Controller:
namespace AdminBundle\Controller;
use AdminBundle\Manager\AdminManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* Class DefaultController
* #package AdminBundle\Controller
* #Route("/registrace")
*/
class RegistraceController extends Controller
{
/**
* #var AdminManager
*/
private $manager;
public function __construct(AdminManager $manager)
{
$this->manager = $manager;
}
...
AdminManager:
namespace AdminBundle\Manager;
use AdminBundle\Entity\Uzivatel;
use Doctrine\Common\Persistence\ManagerRegistry;
class AdminManager
{
private $em;
public function __construct(ManagerRegistry $Doctrine)
{
$this->em = $Doctrine->getManager('default');
}
...
AdminBundle\Resources\config\services.yml :
services:
# admin.example:
# class: AdminBundle\Example
# arguments: ["#service_id", "plain_value", "%parameter%"]
admin.admin_manager:
class: AdminBundle\Manager\AdminManager
arguments:
["#doctrine"]
I tried to clear cache, but no success. The services.yml from AdminBundle is correctly included in config.yml.
orm config in config.yml:
orm:
auto_generate_proxy_classes: '%kernel.debug%'
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AdminBundle: ~
I'm using Symfony 3.3 and PHP 7.1.
if you want to inject your AdminManager in your RegistraceController, you have to define the RegistraceController as a service. Look at https://symfony.com/doc/current/controller/service.html. There are some drawbacks of this approach, because you do not inherit from Symfony‘s base Controller. So, you have to inject the Router and the Template Engine too, if you need them. But I like defining my Controller as services. It‘s much cleaner when you see dependencies.
Instead of this, you can use the Symfony Container inside your controller as an Inversion Of Controll Container and get your service with $this->get('admin.admin_manager'); from inside your action.
So i think your service yml need to look like that:
services:
admin.admin_manager:
class: AdminBundle\Manager\AdminManager
arguments: ["#doctrine"]
admin.admin_controller:
class: AdminBundle\Controller\RegistraceController
arguments: ["#admin.admin_manager"]
Look up here Symfony Service Container
Hope it will help!
Greetings :)
Thank you all for your replies. Fortunately I solved my problem by adding this to services in app/config/services.yml.
AdminBundle\Controller\:
resource: '%kernel.project_dir%/src/AdminBundle/Controller'
public: true
tags: ['controller.service_arguments']

Dependency Injection in Symfony

I'm building Symfony 3.3 application. I have a helper in a Console folder:
abstract class AbstractHelper implements HelperInterface
{
protected $httpClient;
public function __construct(HttpInterface $httpClient)
{
$this->httpClient = $httpClient;
}
}
And I have implementation of HttpInterface named HttpGuzzle into Service folder. How could I help Symfony to figure out I want to inject HttpGuzzle into AbstractHelper constructor? I tried to add these line to services.yml but it doesn't work:
AppBundle\Command\AbstractHelper:
arguments:
$httpClient: AppBundle\Service\HttpGuzzle
If i run the tests it throws an error:
ArgumentCountError: Too few arguments to function AppBundle\Command\AbstractHelper::__construct(),
0 passed in ~/Projects/app/tests/AppBundle/Console/HelperTest.php
on line 17 and exactly 1 expected
With this:
helper:
class: AppBundle\Command\AbstractHelper:
arguments: [AppBundle\Service\HttpGuzzle]
I get an error:
You have requested a non-existent service "helper".
In services.yml You have to define the HttpGuzzle service itself, like this:
httpguzzle:
class: AppBundle\Service\HttpGuzzle
Then you can use pass it to the helper like this:
helper:
class: AppBundle\Command\AbstractHelper
arguments: ["#httpguzzle"]

Symfony2 Outside class

I use symfony2 (2.6) and I have class to global variable to twig. For Example, class menu:
namespace Cms\PageBundle\Twig;
use Doctrine\ORM\EntityManager;
class Menu {
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function show(){
/******/
}
}
and services.yml
services:
class_menu:
class: Cms\PageBundle\Twig\Menu
arguments: ['#doctrine.orm.entity_manager']
twig_menu:
class: Cms\PageBundle\Twig\Menu
See:
ContextErrorException in Menu.php line 9:
Catchable Fatal Error: Argument 1 passed to
Cms\PageBundle\Twig\Menu::__construct() must be an instance of
Doctrine\ORM\EntityManager, none given, called in
/home/cms/public_html/app/cache/dev/appDevDebugProjectContainer.php on
line 3834 and defined
General, any class (outside) have problem with the constructor and (argument) doctrine.
Why?
Symfony2 getdoctrine outside of Model/Controller
This error is totally expected. Symfony2 expects to create service instance by invoking the __construct constructor. If you want to keep the single class in play, you will need to remove that __construct and use setter dependency injection instead.
There is an official documentation on this: Optional Dependencies: Setter Injection
Basically, you do not pass the EntityManager instance during the creation of an service instance but rather "set it later".
Hope this helps.
Update:
If you fallback to your original solution, make sure you pass EntityManager in both instances:
services:
class_menu:
class: Cms\PageBundle\Twig\Menu
arguments: ['#doctrine.orm.entity_manager']
twig_menu:
class: Cms\PageBundle\Twig\Menu
arguments: ['#doctrine.orm.entity_manager']

Symfony2: How to access to service from repository

I have class ModelsRepository:
class ModelsRepository extends EntityRepository
{}
And service
container_data:
class: ProjectName\MyBundle\Common\Container
arguments: [#service_container]
I want get access from ModelsRepository to service container_data. I can't transmit service from controller used constructor.
Do you know how to do it?
IMHO, this shouldn't be needed since you may easily break rules like SRP and Law of Demeter
But if you really need it, here's a way to do this:
First, we define a base "ContainerAwareRepository" class which has a call "setContainer"
services.yml
services:
# This is the base class for any repository which need to access container
acme_bundle.repository.container_aware:
class: AcmeBundle\Repository\ContainerAwareRepository
abstract: true
calls:
- [ setContainer, [ #service_container ] ]
The ContainerAwareRepository may looks like this
AcmeBundle\Repository\ContainerAwareRepository.php
abstract class ContainerAwareRepository extends EntityRepository
{
protected $container;
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
}
}
Then, we can define our Model Repository.
We use here, the doctrine's getRepository method in order to construct our repository
services.yml
services:
acme_bundle.models.repository:
class: AcmeBundle\Repository\ModelsRepository
factory_service: doctrine.orm.entity_manager
factory_method: getRepository
arguments:
- "AcmeBundle:Models"
parent:
acme_bundle.repository.container_aware
And then, just define the class
AcmeBundle\Repository\ModelsRepository.php
class ModelsRepository extends ContainerAwareRepository
{
public function findFoo()
{
$this->container->get('fooservice');
}
}
In order to use the repository, you absolutely need to call it from the service first.
$container->get('acme_bundle.models.repository')->findFoo(); // No errors
$em->getRepository('AcmeBundle:Models')->findFoo(); // No errors
But if you directly do
$em->getRepository('AcmeBundle:Models')->findFoo(); // Fatal error, container is undefined
I tried some versions. Problem was solved follows
ModelRepository:
class ModelRepository extends EntityRepository
{
private $container;
function __construct($container, $em) {
$class = new ClassMetadata('ProjectName\MyBundle\Entity\ModelEntity');
$this->container = $container;
parent::__construct($em, $class);
}
}
security.yml:
providers:
default:
id: model_auth
services.yml
model_auth:
class: ProjectName\MyBundle\Repository\ModelRepository
argument
As a result I got repository with ability use container - as required.
But this realization can be used only in critical cases, because she has limitations for Repository.
Thx 4all.
You should never pass container to the repository, just as you should never let entities handle heavy logic. Repositories have only one purpose - retrieving data from the database. Nothing more (read: http://docs.doctrine-project.org/en/2.0.x/reference/working-with-objects.html).
If you need anything more complex than that, you should probably create a separate (container aware if you wish) service for that.
I would suggest using a factory service:
http://symfony.com/doc/current/components/dependency_injection/factories.html
//Repository
class ModelsRepositoryFactory
{
public static function getRepository($entityManager,$entityName,$fooservice)
{
$em = $entityManager;
$meta = $em->getClassMetadata($entityName);
$repository = new ModelsRepository($em, $meta, $fooservice);
return $repository;
}
}
//service
AcmeBundle.ModelsRepository:
class: Doctrine\ORM\EntityRepository
factory: [AcmeBundle\Repositories\ModelsRepositoryFactory,getRepository]
arguments:
- #doctrine.orm.entity_manager
- AcmeBundle\Entity\Models
- #fooservice
Are you sure that is a good idea to access service from repo?
Repositories are designed for custom SQL where, in case of doctrine, doctrine can help you with find(),findOne(),findBy(), [...] "magic" methods.
Take into account to inject your service where you use your repo and, if you need some parameters, pass it directly to repo's method.
I strongly agree that this should only be done when absolutely necessary. Though there is a quite simpler approach possible now (tested with Symfony 2.8).
Implement in your repository "ContainerAwareInterface"
Use the "ContainerAwareTrait"
adjust the services.yml
RepositoryClass:
namespace AcmeBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use AcmeBundle\Entity\User;
class UserRepository extends EntityRepository implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function findUserBySomething($param)
{
$service = $this->container->get('my.other.service');
}
}
services.yml:
acme_bundle.repository.user:
lazy: true
class: AcmeBundle\Repository\UserRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments:
- "AcmeBundle:Entity/User"
calls:
- method: setContainer
arguments:
- '#service_container'
the easiest way is to inject the service into repository constructor.
class ModelsRepository extends EntityRepository
{
private $your_service;
public function __construct(ProjectName\MyBundle\Common\Container $service) {
$this->your_service = $service;
}
}
Extending Laurynas Mališauskas answer, to pass service to a constructor make your repository a service too and pass it with arguments:
models.repository:
class: ModelsRepository
arguments: ['#service_you_want_to_pass']

Categories