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"]
I have a few logic classes implemented as services. They do some similar stuff, so I wanted to setup a hierarchy:
namespace \Company\Bundle\Service;
abstract class ParentLogic {
protected $user;
public function __construct($security_token_storage) {
$this->user = $security_token_storage->getToken()->getUser();
}
}
class ChildLogic extends ParentLogic {
}
Here is how I have the services setup
services:
parentlogic:
abstract: true
class: Company\Bundle\Service\ParentLogic
arguments: [#security.token_storage]
childlogic:
class: Company\Bundle\Service\ChildLogic
parent: parentlogic
however when I try to use the childlogic service in a controller
namespace Company\Bundle\Controller;
class TestController {
static public function getLogicService() {
return $this->get('childlogic');
}
}
I get an error saying the argument to the constructor is missing:
Warning: Missing argument 1 for Company\Bundle\Service\parentlogic::__construct(), called in /file/path/to/company/app/cache/dev/classes.php on line 2220 and defined
Is this possible? If so what am I doing wrong, or better how is it done correctly?
What Cerad said in the comment is probably true. Constructor arguments are not used on parent services. We have a similar setup with controllers where we have an AbstractController as a parent service and concrete controllers that inherit from AbstractController. However in our service definition we add any dependencies to the parent using setter-calls:
services:
abstract_controller:
class: Bundle\Controller\AbstractController
abstract: true
calls:
- [setSomething, ['#something']]
- [setAnotherThing, ['#anotherThing']]
concrete_controller:
class: Bundle\Controller\ConcreteController
parent: abstract_controller
Works like a charm.
I reckon if you wish to use constructor arguments instead you should specify them explicitly in your child service definitions as well since on creation of the child service the parent constructor will be called.
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');
I have the following controller:
namespace Acme\CompanyBundle\Controller;
use Symfony\Component\DependencyInjection\Container;
/**
* Company controller.
*
*/
class CompanyController extends Controller
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function getData()
{
$userObj = $this->container->get('security.context')->getToken()->getUser();
}
}
In my services.yml file, I have injected Container class:
parameters:
acme.controller.company.class: Acme\ContainerBundle\Controller\CompanyController
services:
acme.controller.company:
class: %acme.controller.company.class%
arguments: [#service_container]
When loading this controller, I get following error:
Catchable Fatal Error: Argument 1 passed to
Acme\CompanyBundle\Controller\CompanyController::__construct() must be
an instance of Symfony\Component\DependencyInjection\Container, none
given, called in C:\wamp\www\symfony\app\cache\dev\classes.php on line
2785 and defined in
C:\wamp\www\symfony\src\Acme\CompanyBundle\Controller\CompanyController.php
line ...
As you could see, this is a simple injection of Container object into a controller but throws nice errors. What is the problem here?
Similar issue is posted in another SO thread here.
You don't need to inject the container in controllers as long as they extend the base Controller class, which yours do.
Just do:
namespace Acme\CompanyBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* Company controller.
*
*/
class CompanyController extends Controller
{
public function getData()
{
$userObj = $this->get('security.context')->getToken()->getUser();
}
}
By default, routes look something like this:
cerad_player_wanabe_list:
pattern: /player-request/list
defaults:
_controller: CeradPlayerWanabeBundle:Player/PlayerList:list
The Symfony\Component\HttpKernel\HttpKernel::handle($request) method pulls the _controller attribute from the request object. If the attribute has two colons in it then it translates the attribute into a class name and creates an instance using the new operator. If the instance implements the ContainerAwareInterface then the container is injected into the controller instance. The controller service you defined is not used. Hence the error about no argument being passed to the constructor.
On the other hand, if _controller has only one colon then the controller is pulled as a service from the container. There is no checking for the ContainerAwareInterface. It's up to you to inject the dependencies via your service definition.
This is all documented in: http://symfony.com/doc/current/cookbook/controller/service.html
So for this particular question, your route should be something like:
cerad_player_wanabe_list:
pattern: /player-request/list
defaults:
_controller: acme.controller.company:action
This does raise the question of why you are trying to define the controller as a service. The default approach already does exactly what you want so you are not gaining anything.
The rationale for defining services as containers is that you can control exactly what dependencies the controller uses. Makes the controller easier to understand and test.
Injecting the complete container pretty much destroys the value of defining the controller as a service.
Never and never inject the container inside something (services, controller or whatever)
Instead try to inject the securityContext or access it through the helper method of symfony controller as suggested above.
The token it's not an object just because probably the route of the controller it's not under a firewall
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']