Symfony 3: cannot access the container from inside a controller - php

I'm migrating my app from Symfony 2.8 to Symfony 3.3.
From inside a controller of mine I have this:
public function indexAction()
{
$email = new Email();
$form = $this->createForm(GetStartedType::class, $email, [
'action' => $this->generateUrl('get_started_end'),
'method' => 'POST',
]);
return [
'form' => $form->createView(),
];
}
But I receive this exception:
Call to a member function get() on null
My controller extends Symfony\Bundle\FrameworkBundle\Controller\Controller:
/**
* {#inheritdoc}
*/
class DefaultController extends Controller
{
...
}
So I have access to the container.
Putting some dumps around in the Symfony's code, I see that the container is correctly set:
namespace Symfony\Component\DependencyInjection;
/**
* ContainerAware trait.
*
* #author Fabien Potencier <fabien#symfony.com>
*/
trait ContainerAwareTrait
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* Sets the container.
*
* #param ContainerInterface|null $container A ContainerInterface instance or null
*/
public function setContainer(ContainerInterface $container = null)
{
dump('Here in the ContainerAwareTrait');
dump(null === $container);
$this->container = $container;
}
}
This dumps
Here in the ContainerAwareTrait
false
So the autowiring works well and sets the container.
But in the ControllerTrait I have this:
trait ControllerTrait
{
/**
* Generates a URL from the given parameters.
*
* #param string $route The name of the route
* #param mixed $parameters An array of parameters
* #param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface)
*
* #return string The generated URL
*
* #see UrlGeneratorInterface
*/
protected function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
dump('Here in the ControllerTrait');
die(dump(null === $this->container));
return $this->container->get('router')->generate($route, $parameters, $referenceType);
}
...
this is the dump:
Here in the ControllerTrait
true
So here the container is null and this causes the error.
Anyone can help me solve this issue?
Why is the container null?
If may help, this is the services.yml configuration (the default that cames with Symfony):
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
This question is posted as issue on the Symfony's issue tracker.

The S3.3 autowire capability makes it a bit easier to define controllers as services.
The usual motivation behind defining controllers as services is to avoid injecting the container. In other words you should be explicitly injecting each service a controller uses. The autowire capability allows you to use action method injection so you don't have to inject a bunch of stuff in the constructor.
However, the base Symfony controller class provides a number of helper function which use about 12 different services. It would be painful indeed to inject these one at a time. I had sort of thought that the autowire capability might take care of this for you but I guess not.
So you basically need to add a call to setContainer in your service definition. Something like:
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
[[setContainer, ['#service_container']]]
tags: ['controller.service_arguments']
The autowire capability is very much a work in progress so I would not be surprised if this changes for 3.4/4.0.

This problem is fixed by PR #23239 and is relased in Symfony 3.3.3.

Related

Autowire Class with Interface as param in Symfony4

Given a class Publisher like this:
<?php
namespace App\Util\Publisher;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Messenger\MessageBusInterface;
class Publisher
{
protected $topic = null;
protected $bus;
/**
* Publisher constructor.
* #param MessageBusInterface $bus
*/
public function __construct(MessageBusInterface $bus)
{
$this->topic = getenv('TOPIC_MAIN_URL');
$this->bus = $bus;
}
...
}
I would like to autowire it in my controllers like this:
/**
* #Route("/_exp/_exp", name="exp")
*/
public function expAction(Publisher $publisher)
{
...
}
and I added this to my services.yaml:
Symfony\Component\Messenger\MessageBusInterface: ~
App\Util\Publisher\Publisher:
autowire: true
arguments: ['#Symfony\Component\Messenger\MessageBusInterface']
But I get an error:
Cannot instantiate interface Symfony\Component\Messenger\MessageBusInterface
Is that related to the MessageBusInterface or am I doing something wrong with the autowiring. I followed The Symfony docs for autowiring and they seem to be the same?
Thank you!
I believe MessageBusInterface service is already declared by Symfony Messenger component.
Try to remove Symfony\Component\Messenger\MessageBusInterface: ~ from your services.yaml, otherwise you are overriding the default definition.
A note for clarification: MessageBusInterface service does not really exists, it in an alias over the "default bus" service. You can declare other buses, cf documentation

Symfony4 - Can't Autowire Service

It's the first time I try to autowire a service in symfony4, as symfony4 is new i'm never sure if the anwser I find online is working or is outdated..
In my services.yaml:
services:
[...]
smugmug_controller:
class: App\Controller\SmugmugController
arguments:
- '#smugmug_service'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
smugmug_service:
class: App\Services\SmugmugService
arguments:
$consumerKey: "%smugmug.consumer_key%"
$consumerSecret: "%smugmug.consumer_secret%"
$oauthToken: "%smugmug.oauth_token%"
$oauthTokenSecret: "%smugmug.oauth_token_secret%"
$allowedRootId: "%smugmug.allowed_root_id%"
In my Smugmug Service:
class SmugmugService
{
private $consumerKey;
private $consumerSecret;
private $oauthToken;
private $oauthTokenSecret;
private $allowedRootId;
private $galleryNameFromDropbox = "dropbox";
/**
* Constructor.
*
* #param string $consumerKey
* #param string $consumerSecret
* #param string $oauthToken
* #param string $oauthTokenSecret
* #param string $allowedRootId
*/
public function __construct(String $consumerKey, String $consumerSecret, String $oauthToken, String $oauthTokenSecret, String $allowedRootId) {
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->oauthToken = $oauthToken;
$this->oauthTokenSecret = $oauthTokenSecret;
$this->allowedRootId = $allowedRootId;
}
In my Controller:
class SmugmugController extends Controller {
private $smugmugService;
public function __construct(SmugmugService $smugmugService) {
$this->smugmugService = $smugmugService;
}
And when I try to call a route from my controller, I have this error:
Cannot autowire service "App\Services\SmugmugService": argument
"$consumerKey" of method "__construct()" is type-hinted "string", you
should configure its value explicitly.
I know that I call a controller with an injected service, who himself have injected parameters (is it the problem ?). Any help ?
#Cerad answer:
You want to stop using service ids like smugmug_controller. Use the fully qualified class name instead. In fact if you replace the id with the class name then you can remove the class attribute. And anytime you look at an example, always make sure it is for S4 with autowire. It's all in the docs.

Grouped routes in multiple controllers in Symfony 4

I'm rewriting my Silex-based application to Symfony 4, as the Silex will be deprecated in a while from now. Everything works great so far, but I have a problem with nested routes.
I had lots of nested (child routes) in Silex application with different controllers assigned to them.
$app->match('/api', function (ControllerCollection $api) {
$api->get('/homepage', 'ControllerOne::index');
$api->get('/contact', 'ControllerTwo::index');
});
This was pretty easy in Silex, but now in Symfony 4, I'm using annotations for the routes' management and it seems like I can't find a way to group those routes.
It's annoying especially when it comes to routes with _locale as the syntax for those routes is pretty long and still.. it's not a good way to have it everywhere in case I need to change the _locale prefix some day to something like /home/{_locale}/.
ControllerOne extends Controller
{
/**
* #Route("/{_locale}/",
* name="root",
* methods="GET",
* requirements={"_locale": "en|fr"}
* )
*
* #return Response
*/
public function index(): Response
{
return $this->render('some.html.twig');
}
}
ControllerTwo extends Controller
{
/**
* #Route("/{_locale}/homepage",
* name="homepage",
* methods="GET",
* requirements={"_locale": "en|fr"}
* )
*
* #return Response
*/
public function index(): Response
{
return $this->render('some2.html.twig');
}
}
UPDATE
I had an idea to create some sort of PrefixedController where I'd specify the prefix over the class and the extend that PrefixedController instead of the basic Controller, but it seems to don't work.
/**
* #Route("/{_locale}", requirements={"_locale": "en|fr"})
*/
controller PrefixedController extends Controller
{
}
controller ControllerOne extends PrefixedController
{
/**
* #Route("/", methods="GET")
* #Return Response
*/
public function index(): Response
{
return $this->render('some.html.twig');
}
}
But when I navigate to /en/ it can't match the route.
This can be done in the main routing file where the routing resources are imported. In Symfony 4 it is in config/routes/annotations.yaml. Then to provide a prefix /{_locale} for the imported routes uses the prefix option:
# config/routes/annotations.yaml
controllers:
resource: '../src/Controller/'
type: annotation
prefix: /{_locale}
The path of each route being loaded from the new routing resource will now be prefixed with the placeholder /{_locale}.

scheb/two_factor_bundle: how to use exclude_pattern for authenticated route?

I'm using Scheb's two factor bundle in a Symfony3 project and I'd like to handle the exclude_pattern parameter differently than it does, but I don't know how.
Normally, exclude_pattern is used to exclude an unauthenticated route from the two-factor authentication, like debug pages or static content:
# config/config.yml
scheb_two_factor:
...
exclude_pattern: ^/(_(profiler|wdt)|css|images|js)/
its behavior being implemented like this:
/* vendor/scheb/two-factor-bundle/Security/TwoFactor/EventListener/RequestListener.php */
public function onCoreRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// Exclude path
if ($this->excludePattern !== null && preg_match('#'.$this->excludePattern.'#', $request->getPathInfo())) {
return;
}
...
}
I'd like to handle exclude_pattern also for authenticated routes, so that I can skip two-factor authentication when I call them. For authenticated I mean within access_control section under security.yml, like this:
# app/config/security.yml
security:
...
access_control:
- { path: ^/test, role: ROLE_USER }
Right now, if I add an authenticated route under exclude_pattern, all I get is a AccessDeniedException, probably because the bundle requires that the access_decision_manager parameter to be set as strategy: unanimous.
The purpose is long to tell and English is not my native language, but if you really need to know it I can try to explain.
I tagged the question with both symfony3 and symfony2 because I'm using Symfony 3.0 but I'm pretty sure it's identical in Symfony 2.8.
I found a solution by overriding the Voter class from the bundle:
// AppBundle/Security/TwoFactor/Voter.php
namespace AppBundle\Security\TwoFactor;
use Scheb\TwoFactorBundle\Security\TwoFactor\Session\SessionFlagManager;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class Voter extends \Scheb\TwoFactorBundle\Security\TwoFactor\Voter
{
/**
* #var string
*/
protected $excludePattern;
/**
* Voter constructor.
* #param SessionFlagManager $sessionFlagManager
* #param array $providers
* #param $excludePattern
*/
public function __construct(SessionFlagManager $sessionFlagManager, array $providers, $excludePattern)
{
parent::__construct($sessionFlagManager, $providers);
$this->excludePattern = $excludePattern;
}
/**
* #param TokenInterface $token
* #param mixed $object
* #param array $attributes
*
* #return mixed result
*/
public function vote(TokenInterface $token, $object, array $attributes)
{
if ($this->excludePattern !== null && preg_match('#'.$this->excludePattern.'#', $object->getPathInfo()))
{
return true;
}
parent::vote($token, $object, $attributes);
}
}
# app/config/services.yml
services:
...
scheb_two_factor.security_voter:
class: 'AppBundle\Security\TwoFactor\Voter'
arguments:
- '#scheb_two_factor.session_flag_manager'
- ~
- '%scheb_two_factor.exclude_pattern%'
This way, whenever the GetResponseEvent is triggered, the right Voter is invoked which votes true if the exclude_pattern matches the path.

ZF2 service factory not injected

I have problems solving a catchable error:
Catchable fatal error: Argument 1 passed to
Market\Service\UserService::__construct() must be an instance of
Market\Mapper\UserMapperInterface,
In the constructor of my UserService I use UserMapperInterface (dependency injection). I created a factory class for that and I registered it in my service_manager config. Nothing is happening and an error is shown.
I created factory and services 19287319827 times but now I don't know how I can solve this noob fatal error.
Config:
'service_manager' => array(
'factories' => array(
'Market\Service\UserServiceInterface' => 'Market\Factory\UserServiceFactory',
)
),
Service:
class UserService implements UserServiceInterface
{
/**
* #var object Market\Mapper\UserMappperInteface
*/
protected $userMapper;
/**
* UserService constructor.
*
* #param UserMapperInterface $userMapperInterface
*/
public function __construct(UserMapperInterface $userMapperInterface)
{
$this->userMapper = $userMapperInterface;
}
Factory:
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Market\Service\UserService;
class UserServiceFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$userMapperInteface = $serviceLocator->get('Market\Mapper\UserMapperInterface');
return new UserService($userMapperInteface);
}
}
I can't believe that I can't solve this noob fatal error. I'm looking and looking for the last 2 hour in my code and I have been checking everything and I think everything looks good but the class is not injected in my factory.
Your problem lies in this line:
$userMapperInteface = $serviceLocator->get('Market\Mapper\UserMapperInterface');
It probably returns null or at least something else then the expected UserMapperInterface instance. Since you did not share any code on how and where this Market\Mapper\UserMapperInterface is defined or constructed it is hard to say what exactly goes wrong...
Please share more details on that part if you can't figure it out yourself.

Categories