I followed the tutorial of Fabien Potiencier, about how to create your own Framework on top of the Symfony Components. Now i need a way. And I want to inject the Dependency Container to all my Controllers, without defining every single Controller as a Service.
In the orginal Symfony2 Framework all Controllers extends the Controller Class located in Symfony\Bundle\FrameworkBundle\Controller\Controller.php:
namespace Symfony\Bundle\FrameworkBundle\Controller;
class Controller extends ContainerAware
{
// ...
}
The Controller Class extends the ControllerAware Class, so you can do something like this in your Controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function someAction()
{
$this->container->get('dependencie_xyz);
}
}
So my question is: How can I accomplish the same in my Framework?
It took me a while, but i finally figured out how the Symfony2 Framework does it.
In the SymfonyFrameworkBundle is a custom ControllerResolver, which call the setContainer Method on the resolved controller. The controller has to be a instance of the ContainerAwareInterface.
Simplified version:
class ContainerAwareControllerResolver extends ControllerResolver
{
private $container;
public __construct(ContainerInterface $container)
{
$this->container = $container;
parent::__construct();
}
public function getController(Request $request)
{
$controller = parent::getController($request);
if($controller instanceof ContainerAware ){
$controller->setContainer($this->container);
}
}
}
Source:
https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php
It is too simply. The next code will help you
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Symfony\Component\DependencyInjection\ContainerAware as ContainerAware;
class TestService extends ContainerAware
{
public function __construct(Container $container) {
// in your example from official doc 'dependencie_xyz' is a name of service
$this->setContainer($container); // call parent setContainer() method, for identifying container variable, from now you can access to ServiceContainer using $this->container variable
$test_param = $this->container->getParameter('test_param'); // get test_param from config.yml
}
}
in service.yml
write smthing like this
services:
test_service:
class: Symfony\Bundle\FrameworkBundle\TestService
arguments: ['#service_container']
and post service container as argument
If you are not implementing any interface on controller you can add the this way and it will work. This is a small modification to c4pone implementation.
/**
* Description of ContainerAwareControllerResolver
*
* #author sbc
*/
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
class ContainerAwareControllerResolver extends ControllerResolver {
private $container;
public function __construct(LoggerInterface $logger = null, ContainerInterface $container = null) {
parent::__construct($logger);
$this->container = $container;
}
protected function instantiateController($class) {
$new_class = new $class();
$new_class->setContainer($this->container);
return $new_class;
}
The Controller Class extends the ControllerAware Class, so you can do something like this in your Controller:
Well, this is not true. If we take a look at the signature of the ContainerAware class, we see that this added a setContainer method so we can set the container. Symfony2 has created the Controller::get method to make some live easier.
We can see how they do it in the source code:
/**
* Gets a service by id.
*
* #param string $id The service id
*
* #return object The service
*/
public function get($id)
{
return $this->container->get($id);
}
You can put this in your own Controller class and let all your controllers extend that controller class.
Related
I want to fetch the user object in a controllers constructur in a Symfony 4.3.2 project. According to the docs on https://symfony.com/doc/4.0/security.html#retrieving-the-user-object, I just need to call $this->getUser(). And yes, this works in action methods.
BUT: trying to get the user in the constructor doesn't work, because the container will NOT be initialized here and the getUser method throws an exception "Call to a member function has() on null": the container is null at this point in time.
This works:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
public function indexAction()
{
dump($this->getUser());
}
}
This doesn't:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
public function __contruct()
{
dump($this->getUser());
}
public function indexAction()
{
}
}
And when I inject the container manually, then all is fine too:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
public function __construct(ContainerInterface $container)
{
$this->container = $container;
dump($this->getUser());
}
public function indexAction()
{
}
}
btw, this is the getUser method in AbstractController:
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
......
Is this a bug, that the container is not initialized in the constructor or is it a feature, that you have to initialize this by hand when you need the user in the constructor?
Edit: using the way shown in https://symfony.com/blog/new-in-symfony-3-2-user-value-resolver-for-controllers does work in actions, but it doesn't work in the constructor:
....
private $user;
public function __construct(UserInterface $user)
{
$this->user = $user;
}
produces the following error message: Cannot autowire service "App\Controller\TestController": argument "$user" of method "__construct()" references interface "Symfony\Component\Security\Core\User\UserInterface" but no such service exists. Did you create a class that implements this interface?. And that is where I would like to set the user object.
NEVER USE $security->getUser() or $this->getUser() in constructor!!
auth may not be complete yet. (In Service Instead, store the entire Security object. :
symfony.com/doc/security.html#a-fetching-the-user-object
... and you can use $this->getUser() in any Controller what extended with the AbstractController. (Just not in the constructor)
The container gets set by the ControllerResolver after the Controller has been instanced by calling the setContainer method that you mention. Thus, when the constructor is called the container is not available by design.
You might have a use case, but I don't see why you want to do this since in your controller methods you will have to access the $user property and it'll just save you typing get(). You can inject the whole container as shown in your sample or you can inject just the Security service.
use Symfony\Component\Security\Core\Security;
class TestController extends AbstractController
{
private $user;
public function __construct(Security $security)
{
$this->user = $security->getUser();
}
public function indexAction()
{
$user = $this->user; // Just saved you typing five characters
// At this point the container is available
}
}
I'm not actually setting the security service because it'll become available later through the container.
If you want to do this to enforce access control for the whole class you can use the Security annotations:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
/**
* #IsGranted('ROLE_USER')
*/
class TestController extends AbstractController
{
// Only authenticated user will be able to access this methods
}
I always use doctrine from controllers or from entity repository classes, now I am trying to use it from a static class but I can't find any example on how to do id.
Basically I'd need (I think) a way to create the entity manager in a static method.
thanks
M
You may call a setter function, injecting entity manager, where you call the static method:
MyController
Class MyController extends Controller
{
public function newAction()
{
$entityManager = $this->getDoctrine()->getManager();
SomeClass::setEntityManager($entityManager);
$result = SomeClass::myStaticMethod();
}
}
SomeClass
Class SomeClass
{
private static $entityManager;
public static function setEntityManager($entityManager)
{
self::$entityManager = $entityManager;
}
public static function myStaticMethod()
{
return $entityManager->getRepository(SomeEntity::class)->findAll();
}
}
I'm not sure from your question what you mean by static class/method, some code example might help. But you could declare this class as a service, which it sounds like it might be meant to be anyway, and then inject the entity manager as a dependency.
services.yml
services:
my_service:
class: Acme\AppBundle\Services\MyService
arguments: ["#doctrine.orm.entity_manager"]
Then in your class you will have the entity manager available like this:
<?php
namespace Acme\AppBundle\Services;
use Doctrine\ORM\EntityManager;
class MyService
{
/**
* Entity Manager
*
* #var Doctrine\ORM\EntityManager
*/
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
...
}
And you can then use this service in your controllers like so:
$this->get('my_service')->doSomething();
I am new at symfony and trying to create REST api using FOSRest Bundle. Also i want to use flysystem library as service in my controllers. It is simple as that
class FileController extends FOSRestController
{
public function getFilesAction()
{
$filesystem = $this->container->get('oneup_flysystem.application_filesystem');
...
}
}
This works fine, but my idea is to Inject this service into FileController so i can use $filesystem service in every method in my controller.
I read about it, that I have to make my controller as service, but then something went wrong. What is the proper way to make this injection.
We use something like this:
YourBundle/Resources/config/services.yml
controller.file:
class: YourBundle\Controller\FileController
arguments:
- #yourFileService
- #service_container
YourBundle/Controller/FileController.php
/**
* #Route("/file", service="controller.file")
*/
class FileController extends Controller
{
/**
* #var Filesystem
*/
private $filesystem;
/**
* #param Filesystem $filesystem
* #param $container
*/
public function __construct(
Filesystem $filesystem,
$container
) {
$this->filesystem = $filesystem;
$this->container = $container;
}
I have a function that I need a few controllers. Can i call it in some other way than to extend the class in which the function is?
Question:
class CategoryController extends Controller
{
public function getCategoriesAction()
{
$categories = $this->getDoctrine()->getRepository('ModelBundle:Category')->findAll();
return $categories;
}
}
How call this function in PostController?
You can define a service and inject entity manager in it, for retrieving data:
dummy.manager:
class: AppBundle\Model\DummyManager
arguments:
entityManager: "#doctrine.orm.entity_manager"
In your service, call findAll() method:
class DummyManager {
/** #var EntityManager */
protected $entityManager;
public function __construct($entityManager) {
$this->entityManager = $entityManager;
}
public function getCategories(){
return $this->entityManager->getRepository('ModelBundle:Category')->findAll();
}
}
And last step, in your controller:
class CategoryController extends Controller
{
public function getCategoriesAction()
{
$categories = $this->container->get('dummy.manager')->getCategories();
//...
}
}
I hope this helps.
You can also use pure PHP with traits
trait CategoryProvider {
public function getCategoriesAction() {
$categories = $this->container->get('dummy.manager')>getCategories();
//...
}
}
After that you can use this trait in your controllers
class Controller1 {
use CategoryProvider;
public function indexAction(){
//...
$this->getCategoriesAction();
//...
}
}
class Controller2 {
use CategoryProvider;
public function anotherAction(){
//...
$this->getCategoriesAction();
//...
}
}
You should not define model-style getters on a controller. The notion of a "getCategoriesAction" method for a controller should be vacated from your brain.
What you want to do is define this fetching behavior in the model layer. This can be done a bunch of different ways. Cristian just posted one way so I'll offer another.
You can define the Entity Repository itself as a service.
app/config/services.yml
services:
model.repository.category:
class: ModelBundle\Entity\CategoryRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments: ['ModelBundle:Category']
Then in your controller (assuming it's a container-aware controller) you can do this
$categories = $this->get('model.repository.category')->findAll();
Another approach, how #Med mentioned, is to declare your controller as service:
services:
categoryController:
class: AppBundle\Controller\CategoryController
arguments:
entityManager: #entityManager
I've defined a route in my app routing file:
RouteName:
pattern: /some/route
defaults: { _controller: MyAppBundle:Controller:action }
In a controller I can use:
$this->get('router')->generate('RouteName');
How would I simply access that from a fresh class I create, for example a view class that doesn't extend anything:
namespace My\AppBundle\View;
class ViewClass {
public function uri()
{
return getTheRoute('RouteName');
}
}
You need to inject "router" service into your ViewClass. Eg. in place where your define your ViewClass service:
viewclass.service:
class: Namespace\For\ViewClass
arguments:
router: "#router"
and then in your constructor:
public function __construct(\Symfony\Bundle\FrameworkBundle\Routing\Router $router)
{
$this->router = $router;
}
The clue is in how the $this->generateUrl() method works in Controllers. See:
/**
* Generates a URL from the given parameters.
*
* #param string $route The name of the route
* #param mixed $parameters An array of parameters
* #param Boolean $absolute Whether to generate an absolute URL
*
* #return string The generated URL
*/
public function generateUrl($route, $parameters = array(), $absolute = false)
{
return $this->container->get('router')->generate($route, $parameters, $absolute);
}
So you'll need to define your class as a service and inject the #router service. Either that or have your class implement ContainerAwareInterface, but the first method would definitely be better.
You should register your class as a service and insert the router as a dependency.
See the chapter on the service container in the excellent symfony2 docs.
If you're not familiar with the concepts of the service container and dependency injection, you might feel a bit overwhelmed. However, try your best to understand it because it is a essential part of the symfony2 architecture.
You could pass the entire container from your controller to your view class on instantiation. This is NOT BEST PRACTICE and not recommended.
class View
{
protected $container;
public function __construct(\Symfony\Component\DependencyInjection\Container $container)
{
$this->container = $container;
}
}
Then in your code you could use
$this->container->get('router')->generate($route, $parameters, $absolute);