I'm trying to get the root dir in symfony2.
If I use:
$this->get('kernel')->getRootDir();
I get this error:
FatalErrorException: Error: Call to undefined method Test\Component\ClassLoader\DebugClassLoader::get()
How can I fix this?
Edit, seeing as this post has garnered so much attention and mine is at the top, the best way to get the root directory is to pass it in to your class as a constructor argument. You would use services.yml to do this, and in arguments:
serviceName:
class: Name\Of\Your\Service
arguments: %kernel.root_dir%
Then, the following code will have the root directory given to it when the framework instantiates it:
namespace Name\Of\Your;
class Service
{
public function __construct($rootDir)
{
// $rootDir is the root directory passed in for you
}
}
The rest of the answer below is the old, poor way of doing it without using Dependency Injection.
I want to make everyone aware that this is the Service Locator, which is an anti-pattern. Any developer should be able to see what a class, or controller, requires to function from the method signature only. Injecting a whole "container" is very generic, hard to debug and isn't the best way of doing things. You should use a Dependency Injection Container that allows you to inject specifically what you want anywhere in your application. Be specific. Check out a seriously awesome recursively instantiating dependency injection container called Auryn. Where your framework resolves your controller / action, place it there and use the container to create the controller and run the method instead. Boom! Instant SOLID code.
You're correct, the service container is accessed using $this->get('service').
However, in order to use $this->get(), you're going to need access to the get() method.
Controller Access
You gain access to this, and many other handy methods, by making sure your controller extends the base controller class that Symfony uses.
Make sure you're referencing the correct Controller base class:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloController extends Controller
{
/** The Kernel should now be accessible via the container **/
$root = $this->get('kernel')->getRootDir();
}
Service Access
If you want to access the container from a service, you're going to have to define your controller as a service. You can find more information in this post, this post and this post about how to do this. Another useful link. Either way, you now know what to look for. This post may also be useful:
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyClass
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function doWhatever()
{
/** Your container is now in $this->container **/
$root = $this->container->get('kernel')->getRootDir();
}
}
In your config.yml, define your new type:
myclass:
class: ...\MyClass
arguments: ["#service_container"]
You can read more about the service container in the docs.
The parameter kernel.root_dir points to the app directory. Normally to get to the root directory, I user kernel.root_dir/../
So in controller you can use $this->container->getParameter('kernel.root_dir')."/../"
In service definition you can use:
my_service:
class: \Path\to\class
arguments: [%kernel.root_dir%/../]
The best option is to declare tour class as a service in your services.yml file:
services:
myclass:
class: Your\Class\Namespace\MyClass
arguments: ["#service_container"]
and adapt yhe constructor of you class:
use Symfony\Component\DependencyInjection\ContainerInterface
class MyClass
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
}
Related
I'm trying to update Symfony 2.8 to Symfony 4 and I am having serious problems with the Services Injection.
I'm looking the new way to use Services inside Controllers, with auto-wiring:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
....
This way works fine, but I dislike the explicit way to refer MyService as a parameter of the function. This way I don't even need to register the Service in the services.yaml
Is there any way to use Services as in Symfony 2.8:
class DefaultController extends Controller
{
public function index()
{
$var = $this->get('AuxiliarService')->MyFunction(); /*Doesn't need to be explicit indicate before*/
....
With the services.yaml
services:
auxiliar_service:
class: AppBundle\Services\AuxiliarService
arguments:
entityManager: "#doctrine.orm.entity_manager"
container: "#service_container" #I need to call services inside the service
This way I don't need to indicate the Service as a parameter in the function of the Controller. In some cases, inside a Service, I need to call more than 10 services depends on the data, so indicate them as a parameter in the function is annoying.
Another doubt in Symfony 4, is how to call a Service inside another Service without pass it as an argument or parameter. It used to be possible by injecting the service container to be able to call a service inside a service:
$this->container->get('anotherService')
In Symfony 4, I think it is more expensive (in code) use Service because you have to explicitely indicate them when you are going to use them.
tldr; you can achieve that by using Service Subscribers & Locators.
In your controller:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// services you want to access through $this->get()
'auxiliar_service' => AuxiliarService:class,
]);
}
// rest of the implementation
}
If your service needs to implement a similar pattern, you'll need to implement ServiceSubscriberInterface (AbstractController, that you are extending for your controller, already does that for you).
class AuxiliaryService implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function has(string $id): bool
{
return $this->container->has($id);
}
protected function get(string $id)
{
return $this->container->get($id);
}
public static function getSubscribedServices()
{
return [
// array_merge is not necessary here, because we are not extending another class.
'logger' => LoggerInterface::class,
'service2' => AnotherService::class,
'service3' => AndMore::class
];
}
}
That being said, you are very probably not doing things right if you want to continue this way
Before Symfony 4+ you could do $this->get('service') because these controllers all had access to the container. Passing the dependency container around for this it is an anti-pattern, and shouldn't be done.
If you do not declare your dependencies, your dependencies are hidden. Users of the class do not know what it uses, and it's easier to break the system by changing the behaviour of one of the hidden dependencies.
Furthermore, with Symfony providing auto-wiring and a compiled container; dependency injection is both easier to implement and faster to execute.
That you are having trouble with implementing this probably reveals deeper issues with your code in general, and you should do some work on segregating the responsibilities of your classes. The fact that one service may depend on that many other services which you can't even know until runtime it's a very strong smell that the concerns are not well separated.
Try to adapt to the changes, it will do your application and yourself good in the long term (even if brings a small amount of pain right now).
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 started to create a project using Symfony 2. I need to share data between all controllers.
I have added a base controller which extends symfony\controller and each of my controllers extends this base controller
class BaseController extends Controller
class HomeController extends BaseController
This base controller will be used for things like assigning global twig variables ( I know I can do this in the config but some of the variables will be gotten from other config files and database ).
So I thought I could reference container since Controller is container aware, however it isn't at the point I am using the functions (from constructor).
public function __construct ()
I have seen people mention passing the container in as a parameter and mention services but I have had a look and cannot figure it out. All I want to achieve is this:
public function __construct (Container $container) {
$container->get('twig').addGlobal('foo');
}
This is a common stumbling block to Symfony 2 newbies. The controller/container question has been asked hundreds of time before so you are not alone(hint).
Why doesn't your controller constructor code work?
Start by looking under vendor/symfony...FrameworkBundle/Controller/Controller.php. Hmm. No constructor there so where the heck is the container coming from? We see that Controller extends ContainerAware. That seems promising. We look at ContainerAware (the namespace helps to find where the file is) and once again, no constructor. There is however a setContainer method so we can assume that the container is injected into the controller after the constructor is called. Quite common in a dependency injection based framework.
So now we know why the constructor code fails. The container has not yet been injected. Stupid design right? Time for a different framework? Not really. Let's face it, having to have all your controllers extend a base controller just to get some twig variables set is not really the best design.
The Symfony way to execute code before the controller action is executed is to make a controller event listener. It will look something like this:
namespace Cerad\Bundle\CoreBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => array(
array('doTwig', 0), // 0 is just the priority
));
}
public function doTwig(FilterControllerEvent $event)
{
// Ignore sub requests
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) return;
$this->container->get('twig')->addGlobal('foo');
}
}
// This goes in services.yml
parameters:
cerad_core__model_event_listener__class:
Cerad\Bundle\CoreBundle\EventListener\ModelEventListener
services:
cerad_core__model_event_listener:
class: '%cerad_core__model_event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
So now we have the desired functionality without the need for a base controller class.
Notice also that the controller can be accessed through the event. Since the controller has been created but the action method not yet called, you could call controller methods or inject data directly into the controller. This is seldom needed. In most cases, you would add additional information to the request object which then gets injected into the controller's action method.
It's really a nice design once you get comfortable with listeners and services.
Please read carefully that question - Symfony2 passing data between bundles & controllers, try to use code included in it.
You can use service to solve your problem, for example.
If you look at the Controller class you'll se the following:
class Controller extends ContainerAware
This means you can retrieve twig from the container as simple as this:
$twig = $this->get('twig');
But I would recommend you to use custom twig extension in your case.
I have a regular class in my Symfony2 project:
class RangeColumn extends Column{
//...
}
Now inside this class is a render function, in which I'd like to use Twig or the Translation Service of Symfony2 to render a specific template. How do I access this services in a proper way?
Code example:
<?php
class MyRegularClass
{
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function myFunction()
{
$this->translator->trans('sentence_to_translate');
}
}
And if you want your class to become a service:
In your services.yml file located in your bundle,
parameters:
my_regular_class.class: Vendor\MyBundle\Classes\MyRegularClass
services:
mybundle.classes.my_regular_class:
class: %my_regular_class.class%
arguments: [#translator]
For more details, see the chapter about the Symfony2 Service Container
Use dependency injection. It's a really simple concept.
You should simply pass (inject) needed services to your class.
If dependencies are obligatory pass them in a constructor. If they're optional use setters.
You might go further and delegate construction of your class to the dependency injection container (make a service out of it).
Services are no different from your "regular" class. It's just that their construction is delegated to the container.
According to Symfony2 Cookbook I'm trying to secure controller via dependecy injection, but I'm getting error Catchable Fatal Error: Argument 1 passed to Acme\ExampleBundle\Controller\DefaultController::__construct() must implement interface Symfony\Component\Security\Core\SecurityContextInterface, none given, called in /var/www/example/app/cache/dev/classes.php on line 4706 and defined in /var/www/example/src/Acme/ExampleBundle/Controller/DefaultController.php line 13
Here is my services.yml
parameters:
acme_example.default.class: Acme\ExampleBundle\Controller\DefaultController
services:
acme_example.default:
class: %acme_example.default.class%
arguments: [#security.context]
and controller:
namespace Acme\ExampleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class DefaultController extends Controller {
public function __construct(SecurityContextInterface $securityContext)
{
if(false === $securityContext->isGranted('IS_AUTHENTICATED_FULLY'))
{
throw new AccessDeniedException();
}
}
public function indexAction()
{
return new Response('OK');
}
}
If you configure your controllers as services you need to use a slightly different syntax when referencing them in your routes. Instead of AcmeExampleBundle:Default:index you should use acme_example.default:indexAction.
Make sure you use Symfony\Component\Security\Core\SecurityContextInterface; in your controller. Without it, the SecurityContextInterface type hint in the constructor won't resolve.
Also, make sure your controller is actually being called as a service. The error you posted is complaining that nothing was sent to the constructor, which sounds to me like you're using your controller the 'default' way. See this cookbook page on how to setup a controller as a service.
The class Symfony\Bundle\FrameworkBundle\Controller\Controller extends ContainerAware base class. This class ha whole the container accessible via $container local property, so you should not inject any services to a controller service, because you can access SecurityContext via $this->container->get('security.context').