I'm relatively new to Symfony, and I'm having trouble some trouble.
I'm trying to type hint a custom RequestValidator class in the method being called when the endpoint is called.
Using Symfony 3.4
However, I am getting the following error:
Controller "ApiBundle\Endpoints\Healthcheck\v1\Index::check()" requires that you provide a value for the "$request" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
Here is my setup:
services.yml file
...
_defaults:
autowire: true
autoconfigure: true
...
routing.yml
api.Healthcheck:
path: /healthcheck
controller: ApiBundle\Endpoints\Healthcheck\v1\Index::check
defaults: { _format: json }
methods:
- GET
And then - inside the Index class, I have the following:
<?php
namespace ApiBundle\Endpoints\Healthcheck\v1;
use ApiBundle\Responses\ApiResponse;
class Index extends ApiResponse
{
public function check(HealthcheckRequest $request) {
var_dump($request);die;
}
}
When I do debug:autowiring I see my HealthcheckRequest in the list.
Further, when I do the same and try type-hint in the constructor of the Index class, it all works.
And finally, if I try and type hint the Symfony/HttpFoundation/Request, inside the check() method, it instantiates it correctly.
In summary:
Not working :
check(HealthcheckRequest $request)
Working:
__construct(HealtcheckRequest $request)
check(SymfonyRequest $request)
Am I doing something wrong? Any help is appreciated.
It's part of services.yaml already in Symfony 4, but introduced in version 3.3, so this might help:
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
ApiBundle\Endpoints\:
resource: '../../Endpoints/*'
tags: ['controller.service_arguments']
Related
Is it possible to make symfony single controller as service
I am trying to make a single controller as service not the whole bundle
code which i have tried is in service.yml (CMSBundle)
cms.exampleController:
class: Website\CMSBundle\Controller\ExampleController
autowire: true
and trying to inject the service from a bundle which is already service, public and autowrie true
example in controller
namespace Website\CMSBundle\Controller;
use Common\UtilityBundle\Listener\ContactData; (UtilityBundle in this every thing is service)
class ExampleController extends Controller
{
public function testAction(Request $oRequest, ContactData $oContactData)
{
//this will become error because $oContactData is always null
}
}
error message
Controller "Website\CMSBundle\Controller\ExampleController::testAction()" requires that you provide a value for the "$oContactData" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
My routes
example_details:
path: /test
defaults: { _controller: WebsiteCMSBundle:Example:test, eventId:null }
In file service.yaml i have:
parameters:
security.allows.ip:
- '127.0.0.1'
- '127.0.0.2'
Or:
parameters:
security.allows.ip: ['127.0.0.1', '127.0.0.2']
And configuration for DI:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
And i want to configure service for class:
security.class:
class: App\Class
arguments:
- '%security.allows.ip%'
And finally I have message:
Cannot autowire service "App\Class": argument "$securityConfiguration" of method "__construct()" must have a type-hint or be given a value explicitly.
And constructor definition is:
public function __construct(array $securityConfiguration)
Could you help me with it? In symfony 2.8 it works, but for this configuration I have this error. Other sevices for type hint string is ok, but not for this class. If I add container interface to construct for this class and getting parameter by ->getParameter('security.allows.ip') it works. Why?
In order for autowire to work, the typehint need to match a service id. The problem here is that you have another class into which you are trying to inject your rather poorly named App\Class
class SomeOtherClass {
public function __construct(App\Class $appClass)
When you created your AppClass service, you gave it an id of security.class. So autowire looks for a service id of App\Class, does not find it and then attempts to create one. And of course it cannot autowire an array.
One way to fix this is by using an alias:
security.class:
class: App\Class
arguments:
- '%security.allows.ip%'
App\Class: '#security.class'
A second (recommended) approach is to do away with the security.class id completely
App\Class:
arguments:
- '%security.allows.ip%'
And if you really want to be the cool kid on the block, you can even drop the arguments keyword.
App\Class:
$securityConfiguration: '%security.allows.ip%'
I am writing a service in Symfony 3 according to latest docs.
I wrote a class:
<?php
namespace julew\AdminBundle\Service;
class FileService {
public function create() {
die('I am here!');
}
}
My app/config/services.yml looks like:
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
julew\AdminBundle\:
resource: '../../src/julew/AdminBundle/*'
exclude: '../../src/julew/AdminBundle/{Entity,Repository}'
But only what i get is this error:
Class Symfony\Bundle\FrameworkBundle\Test\WebTestCase not found in C:\xampp\htdocs\roch\app/config\services.yml (which is being imported from "C:\xampp\htdocs\roch\app/config\config.yml").
What am i doing wrong?
EDIT 1
The problem was, that automatically generated bundles had tests in it - had to add them to exclude paths.
But now other problem occured. Service is not injected via controler type-hint. Error that i get:
Controller "julew\RochNotesBundle\Controller\ParserController::indexAction()" requires that you provide a value for the "$fileService" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
The controller method looks like:
public function indexAction(FileService $fileService) {
.
.
.
}
And at the top i added:
use julew\AdminBundle\Service\FileService;
If you want to inject various services into a controller, it can be a little easier to do so in the constructor, and avoid the use of the controller.service_arguments tag (which has to be explicitly added for the controller definition in the services.yml).
I want to pass the EntityManager instance into the constructor of my controller, using this code:
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Doctrine\ORM\EntityManager;
class UserController extends Controller
{
public function __construct( EntityManager $entityManager )
{
// do some stuff with the entityManager
}
}
I do the constructor injection by putting the parameters into the service.yml file:
parameters:
# parameter_name: value
services:
# service_name:
# class: AppBundle\Directory\ClassName
# arguments: ["#another_service_name", "plain_value", "%parameter_name%"]
app.user_controller:
class: AppBundle\Controller\UserController
arguments: ['#doctrine.orm.entity_manager']
the service.yml is included in the config.yml and when I run
php bin/console debug:container app.user_controller
I get:
Information for Service "app.user_controller"
=============================================
------------------ -------------------------------------
Option Value
------------------ -------------------------------------
Service ID app.user_controller
Class AppBundle\Controller\UserController
Tags -
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired no
Autowiring Types -
------------------ -------------------------------------
However, calling a route which is mapped to my controller, I get:
FatalThrowableError in UserController.php line 17: Type error:
Argument 1 passed to
AppBundle\Controller\UserController::__construct() must be an instance
of Doctrine\ORM\EntityManager, none given, called in
/home/michel/Documents/Terminfinder/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
on line 202
I cant figure out, why the EntityManager is not getting injected?
When using the base classController.php the Container is usually auto-wired by the framework in theControllerResolver.
Basically you are trying to mix up how things actually work.
To solve your problem you basically have two solutions:
Do no try to inject the dependency but fetch it directly from the Container from within your action/method.
public function listUsers(Request $request)
{
$em = $this->container->get('doctrine.orm.entity_manager');
}
Create a controller manually but not extend the Controller base class; and set ip up as a service
To go a bit further on this point, some people will advise to do not use the default Controller provided by Symfony.
While I totally understand their point of view, I'm slightly more moderated on the subject.
The idea behind injecting only the required dependencies is to avoid and force people to have thin controller, which is a good thing.
However, with a little of auto-determination, using the existing shortcut is much simpler.
A Controller / Action is nothing more but the glue between your Views and your Domain/Models.
Prevent yourself from doing too much in your Controller using the ContainerAware facility.
A Controller can thrown away without generate business changes in your system.
Since 2017 and Symfony 3.3+, there is native support for controllers as services.
You can keep your controller the way it is, since you're using constructor injection correctly.
Just modify your services.yml:
# app/config/services.yml
services:
_defaults:
autowire: true
AppBundle\:
resouces: ../../src/AppBundle
It will:
load all controllers and repositories as services
autowire contructor dependencies (in your case EntityManager)
Step further: repositories as services
Ther were many question on SO regarding Doctrine + repository + service + controller, so I've put down one general answer to a post. Definitelly check if you prefer constructor injection and services over static and service locators.
Did you use following pattern to call the controller AppBundle:Default:index? if yes that should be the problem. If you want to use controller as a service you have to use the pattern: app.controller_id:indexAction which uses the id of the service to load the controller.
Otherwise it will try to create an instance of the class without using the service container.
For more information see the symfony documentation about this topic https://symfony.com/doc/current/controller/service.html
The entity manager is available in a controller without needing to inject it. All it takes is:
$em = $this->getDoctrine()->getManager();
in service.yml
test_product.controller:
class: MyBundle\Controller\Test\ProductController
arguments: ["#product_manager.service"]
in controller
class ProductController extends Controller
{
/**
* #var ProductManager
*/
private $productManager;
public function __construct(ProductManager $productManager){
$this->productManager = $productManager;
}
}
in routing.yml
test_product_addNew:
path: /test/product/addNew
defaults: { _controller:test_product.controller:addNewAction }
I want to use ProductManger in contructor to do some stuff but it gives me this error
Catchable Fatal Error: Argument 1 passed to
MyBundle\Controller\Test\ProductController::__construct()
must be an instance of MyBundle\Services\ProductManager,
instance of Symfony\Bundle\TwigBundle\Debug\TimedTwigEngine given,
called in
..../app/cache/dev/appDevDebugProjectContainer.php
on line 1202 and defined
I am new to symfony, any help is appreciated
You have inverted the logical of services.
First, it's your manager wich must be defined as a service because it's it you will need to call from controller.
// services.yml
product_manager:
class: MyBundle\Path\To\ProductManager
Then call directly your manager defined as a service in your controller.
// Controller
class ProductController extends Controller
{
[...]
$this->get('product_manager');
[...]
}
And you do not need to overload __construct() methode. Only call ->get(any_service) where you need it.
Also your route is wrong. You have to define controller from is namespace.
// routing.yml
test_product_addNew:
path: /test/product/addNew
defaults: { _controller:MyBundle:Product:addNew }
Since Symfony 3.3 (released May 2017) you can use contructor injection and autowiring with ease:
# services.yml
services
_defaults:
autowire: true
MyBundle\Controller\Test\ProductController: ~
Keep rest as you already had.
Do you want to know more about there features? Check this post with examples.