Symfony container traits - php

Strange problem,
I have controller which uses \Symfony\Component\DependencyInjection\ContainerAwareTrait
class MainController
{
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
* #Route("/", name="_index")
* #Template()
*/
public function indexAction()
{
var_dump($this->container);
return array();
}
}
but result is NULL.
Tried on:
Symfony 2.5.*
MAMP 3.0
PHP 5.4 5.5
My searches have not helped me. I think the solution is easy.
Any ideas how to trace this error?
UPD: When i extend from Controller, container is available and everything is working properly. But according to symfony Controller reference extending is optional, i can use traits instead.

I'll venture a guess based on a quick glance into the Symfony source code: You still need to declare that you adhere to the ContainerAwareInterface Interface.
This is what the code looks like whenever Symfony is setting a container on a controller.
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
So then I suppose you need to do something like this:
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
// ...
class MainController implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* #Route("/", name="_index")
* #Template()
*/
public function indexAction()
{
var_dump($this->container);
return array();
}
}
As an aside, this is arguably a pretty good case for Duck Typing, particularly if they had named the method something a bit more specific or if it were cheaper to inspect the parameter types to methods at runtime

Related

Interface-based DI configuration in Symfony

In my Symfony project I'll have lot of classes that have similar dependecies (however, the classes are not directly related to each other). For example, most of them requires access to EventBus.
In other framework I was able to specify an interface for the class, for example:
interface EventBusAwareInterface
{
public setEventBus(EventBus $bus);
public getEventBus() : EventBus
}
and then configure DI container to recognize such objects that implements this interface, and call their setEventBus() method with proper argument.
I wonder if there's a method to do the same in Symfony4.
You can use _instanceof directive in your services.yaml like that:
services:
_instanceof:
App\EventBusAwareInterface:
calls:
- method: setEventBus
arguments:
- '#event.bus.service'
My original comment was not quire correct. You can use #inject but it seems to require an additional jms bundle. Could have sworn the container supported it out of the box but I guess not.
However, autowire supports a #required annotation which seems to do the trick.
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
trait RouterTrait
{
/** #var RouterInterface */
protected $router;
/**
* #param RouterInterface $router
* #required
*/
public function setRouter(RouterInterface $router)
{
$this->router = $router;
}
// Copied directly from Symfony ControllerTrait
protected function generateUrl(
string $route,
array $parameters = array(),
int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
return $this->router->generate($route, $parameters, $referenceType);
}
protected function redirect($url, $status = 302) : RedirectResponse
{
return new RedirectResponse($url, $status);
}
protected function redirectToRoute($route, array $parameters = array(), $status = 302) : RedirectResponse
{
return $this->redirect($this->generateUrl($route, $parameters), $status);
}
}
Now, any autowired service that uses the RouterTrait will automatically get the router injected.
Yes, something even simpler is very possible. However, I would not encourage over-usage as it can very quickly introduce things like method name collisions and reduce code readability.
That said, Symfony introduced service auto-wiring concept starting with 3.3 (I think), which can be used to have dependency injection with zero-config. In PHP, interfaces cannot contain implementation, but, traits can. So, you could do something like this:
trait FooTraitHandler
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
And then:
class RealService
{
use FooTraitHandler;
public function multiply($a, $b)
{
$this->logger->log(LogLevel::ALERT, "Doing some basic math!");
return $a * $b;
}
}
And finally, for example, your controller could inject this RealService service and use multiply method as usual.
So, couple of things worth mentioning:
You do not need pair of getter/setter - trait's member is visible in class utilizing it.
You can utilize many traits, achieving just what you wanted with many interfaces
Finally, if some of utilized traits have methods with same name, you'll get a fatal error. As per official docs:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved. To resolve naming conflicts between Traits used in the same class, the insteadof operator needs to be used to choose exactly one of the conflicting methods.
But, in my opinion, doing so deteriorates code readability substantially.
Hope this helps...

Syfmony - load service on boot

I posted another question trying to find a way to statically access a repository class outside of a controller in a custom "helper" class.
So far the only way I have figured out how to achieve this is using the code below. If anyone wants to chime into the other question about "best practice" or "design patterns" please do.
I opened this question to seek the best method on having a singleton service (?) loaded when symfony boots so other classes can access it statically without any dependency injection. I haven't had much luck on finding any official docs or common practices. I know singleton is anti practice, but is the method below the best way, or is there a more ideal solution?
services.yml
parameters:
entity.device: Asterisk\DbBundle\Entity\Device
services:
asterisk.repository.device:
class: Asterisk\DbBundle\Entity\Repositories\DeviceRepository
factory: ["#doctrine.orm.asterisk_entity_manager", getRepository]
arguments:
- %entity.device%
tags:
- {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
DeviceRepository
class DeviceRepository extends \Doctrine\ORM\EntityRepository
{
/** #var ExtendedEntityRepository */
protected static $instance;
public function __construct(EntityManager $entityManager, ClassMetadata $class)
{
parent::__construct($entityManager, $class);
if(static::$instance instanceof static == false)
static::$instance = $this;
}
public static function getInstance()
{
return static::$instance;
}
public function onKernelRequest($event)
{
return;
}
}
Glad to see you are not running around anymore.
Your approach is not going to work unless someone grabs the repository out of the container first so self::$instance is initialized. But you really don't want to do this anyways. Super hacky.
You want to inject the repository service into your kernel listener. Trying to make the repository act as a kernel listener is just not a good design. So just make a service for your repository and then a second one for the listener. It may seem a bit strange at first but it really does work well in practice and it's the way S2 is designed.
If for some reason you are stuck with the notion that you have to be able to access the container globally then be aware that your kernel is defined globally(take a look at app.php) and it has a getContainer method in it.
$repo = $_GLOBAL['kernel']->getContainer()->get('asterisk.repository.device');
But again, there should be no need to do this.
==============================
Update - It looks like you are trying to use the listener functionality just to setup singletons. You should try to avoid singletons but if you really think you need them then the global access to the kernel can be used:
class DeviceRepository extends \Doctrine\ORM\EntityRepository
{
/** #var ExtendedEntityRepository */
protected static $instance;
public static function getInstance()
{
if (!static::$instance) {
static::$instance = $_GLOBAL['kernel']->getContainer()->get('asterisk.repository.device');
}
return static::$instance;
}
Poor design but at least it get's rid of the listener hack and it avoids creating the repository until it's actually needed. It aslo means you can access the repository from commands (listeners are not setup when commands are called).
I do not understand what the profit will be about this method. The idea of the servicecontainer is to make just one instance of each class and give a reference (or pointer if you like) to any method who asks to use this same instance. Let me proof it:
Service definition:
// app/config.yml
services:
app.test:
class: Vendor\AppBundle\Service\Test
and a custom class:
// src/AppBundle/Service/Test.php
namespace AppBundle/Service;
class Test {
public $test = 0;
}
and a controller:
// src/AppBundle/Controller/DefaultController
namespace AppBundle/Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction()
{
$instance1 = $this->get('app.test');
$instance2 = $this->get('app.test');
$instance1->test = 1;
echo $instance2->test; // RETURNS 1 !!!
exit;
}

Laravel 4 Static Facades Setup in Packages

I'm playing around with packages and I'm able to my code to work (in my controllers) when I do this:
App::make('Assets')->js('bla');
Now I want to set up a static facade so I can do this:
Assets::js('bla');
for this and I'm getting errors. I've been following this blog entry and haven't had any trouble up to this point. But now I'm stuck with a " Call to undefined method" error.
I'm not sure what code you'd need to see, so here's everything: https://github.com/JoeCianflone/msl/tree/jc-working
Specifically here is my workbench: https://github.com/JoeCianflone/msl/tree/jc-working/workbench/Joecianflone/Assets
And here is the controller where I was messing around with it: https://github.com/JoeCianflone/msl/blob/jc-working/app/controllers/HomeController.php
Any help greatly appreciated.
Looks like it was an issue with namespacing, I got it working by changing this:
<?php namespace Joecianflone\Assets\Facades;
use Illuminate\Support\Facades\Facade;
class Assets extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'Assets'; }
}
to this:
class Assets extends \Illuminate\Support\Facades\Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'Joecianflone\Assets\Assets'; }
}
What I'm not sure about is why the code from the tutorial worked but mine didn't. I must have skipped a step.
Just a sidenote, if you plan to share your code with the communuty (please do) i encourage you to use 5.3 syntax. Laravel requirements is 5.3 so dont use 5.4 in your package.

Symfony2 - How to use __construct() in a Controller and access Securty.Context?

I am having some trouble with Symfony2. Namely in how to use the __construct() function. the Official Documentation is shockingly bad!
I want to be able to use the following:
public function __construct()
{
parent::__construct();
$user = $this->get('security.context')->getToken()->getUser();
}
How ever I get the following error:
Fatal error: Cannot call constructor in /Sites/src/DEMO/DemoBundle/Controller/Frontend/HomeController.php on line 11
Line 11 is "parent::__construct();"
I removed it and got the following, new error
Fatal error: Call to a member function get() on a non-object in /Sites/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php on line 242
I think I might need to set up the ContainerInterface DIC, but I have no idea how to do this (I tried and failed, miserably)
Any ideas folks?
Update - Tried changing to extend ContainerAware and got this error:
Fatal error: Class DEMO\DemoBundle\Controller\Frontend\HomeController cannot extend from interface Symfony\Component\DependencyInjection\ContainerAwareInterface in /Sites/src/DEMO/DemoBundle/Controller/Frontend/HomeController.php on line 43
Using the following code in the controller:
<?php
namespace DEMO\DemoBundle\Controller\Frontend;
use Symfony\Component\DependencyInjection\ContainerAware;
class HomeController extends ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
I'm assuming you are extending the default Symfony controller? If so, a look at the code will reveal the answer:
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware;
class Controller extends ContainerAware
{
Notice that there is no Controller::__construct defined so using parent::__construct will not get you anywhere. If we look at ContainerAware:
namespace Symfony\Component\DependencyInjection;
class ContainerAware implements ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
Again, no constructor and the container is not available until setContainer is called. So override setContainer and put your logic there. Or else just make a stand alone controller that does not extend the base controller class and inject your dependencies directly into the constructor.
Update Aug 2017
Still getting a few hits on this. If you really want to execute something before each controller then use a kernel controller listener. If all you need is the user then of course use getUser(). And please don't override setContainer(). In some cases it would work but it would just convolute your code.
I also frequently want an instance of the current User in most of my controllers. I find it is easiest to just do something like this:
class SomeController extends Controller
{
protected $user;
public function getUser()
{
if ($this->user === null) {
$this->user = $this->get('security.context')->getToken()->getUser();
}
return $this->user;
}
}
However, this is an overly simplistic example case. If you want to do more work before a Controller action is started, I suggest you define your Controller as a Service.
Also take a look at this article: Moving Away from the Base Controller
I have to retrieve the 'facade' manager for my rest api's resource. Not using the constructor and using a private function seems the easiest and simplest for me.
/**
* Class ExchangesController
* #RouteResource("Exchange")
*/
class ExchangesController extends Controller
{
/**
* Get exchange manager
* #return ExchangeManager
*/
protected function getExchangeManager()
{
return $this->get('exchange_manager');
}
/**
* #ApiDoc(
* description="Retrieve all exchanges",
* statusCodes={
* 200="Successful"
* }
* )
*/
public function cgetAction()
{
return $this->getExchangeManager()->findAll();
}
PS It's ok for me to use private/protected functions in my controller as long as it contains zero conditionals
You cannot call getUser() or get() for services in controller constructors. If you remember that, you will save lots of debugging time.
I know the question is very old, but I didn't found an answer until now. So I'll share it.
The goal here, is to execute a code everytime a action in our controller is called.
The __construct method doesn't work, because it's called before anything else, so you can't access the service container.
The trick is to overload each method automatically when they are called :
<?php
namespace AppBundle\DefaultController;
class DefaultController extends Controller {
private function method1Action() {
return $this->render('method1.html.twig');
}
private function method2Action() {
return $this->render('method2.html.twig');
}
public function __call($method, $args) {
$user = $this->get('security.tokenStorage')->getToken()->getUser();
// Do what you want with the User object or any service. This will be executed each time before one of those controller's actions are called.
return call_user_func_array(array($this, $method), $args);
}
}
Warning ! You have to define each method as a private method ! Or the __call magic method won't be called.
There are only two solutions to this problem:
Use a private method as pointed out by #Tjorriemorrie here. But this is a dirty method for purists. (I'm using this! :D );
Define the controller as a service, but this way you will lose all the shortcuts provided by Symfony\Bundle\FrameworkBundle\Controller\Controller. Here is the article that shows how to do this.
As told, personally, in my situation, I prefere a solution like this:
class MyController extends Controller
{
/** #var AwesomeDependency */
private $dependency;
public function anAction()
{
$result = $this->getDependency();
}
/**
* Returns your dependency.
*/
private function getDependency()
{
if (null === $this->dependency)
$this->dependency = $this->get('your.awesome.dependency');
return $this->dependency;
}
}
This is typically a class that I call MyManager where I put the code that I use in more than one action in the controller or that unusefully occupies lines (for example the code to create and populate forms, or other code to do heavy tasks or tasks that require a lot of code).
This way I mantain the code in the action clear in its purposes, without adding confusion.
Maybe the use of a property to store the dependency is an overoptimization, but... I like it :)
As i see, Controller extends ContainerAware, and if we take a look of ContainerAware it implements ContainerAwareInterface. So, ContainerAware must have declared the exact methods in it's interface. Add this line
public function __construct();
to the ContainerAwareInterface definition and it will be solved.

Preserving auto-completion abilities with Symfony2 Dependency Injection

I'm using PHP Storm as my IDE, but I believe that other IDE's such as Netbeans will have the same issue as I'll explain below.
When using a framework like Symfony2, we have the wonderful world of Dependency Injection added. So objects can simply be instantiated using code like the following snippet:
$myThingy = $this->get('some_cool_service');
This is very handy, as objects are already configured beforehand. The one problem is, that auto-completion breaks entirely in basically any PHP IDE, as the IDE does not know what type the get() method is returning.
Is there a way to preserve auto-completion? Would creating for example an extension of Controller be the answer? For example:
class MyController extends Controller {
/**
* #return \MyNamespace\CoolService
*/
public getSomeCoolService() {
return new CoolService();
}
}
and then for application controllers, specify MyController as the base class instead of Controller?
What about using a Factory class, or any other possible methods?
It is more involving, but you can still do this with eclipse PDT:
$myThingy = $this->get('some_cool_service');
/* #var $myThingy \MyNamespace\CoolService */
UPDATE:
The example on this page shows you may also use the other way round with phpStorm:
$myThingy = $this->get('some_cool_service');
/* #var \MyNamespace\CoolService $myThingy */
You could define private properties in your controllers
class MyController extends Controller
{
/**
* #var \Namespace\To\SomeCoolService;
*/
private $my_service;
public function myAction()
{
$this->my_service = $this->get('some_cool_service');
/**
* enjoy your autocompletion :)
*/
}
}
I use base Controller class for bundle. You need to annotate the return in method. At least that works on Eclipse.
/**
* Gets SomeCoolService
*
* #return \Namespace\To\SomeCoolService
*/
protected function getSomeCoolService()
{
return $this->get('some_cool_service');
}
I don't like /*var ... */, because it gets too much into code.
I don't like private properties, because you can wrongly assume that services are already loaded.
I use Komodo Studio, and tagging variables with #var, even inside methods, preserves auto completion for me.
namespace MyProject\MyBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Request;
class WelcomeController extends ContainerAware
{
public function indexAction()
{
/*#var Request*/$request = $this->container->get('request');
$request->[autocomplete hint list appears here]
}
}
working with netbeans IDE 7.1.2 PHP

Categories