I can't read a parameter from parameters.yml in my controller.
I want to do this:
//My Controller
class ExampleController extends Controller
{
function someMethod($argument)
{
dump($this->getParameter('free_proxy'));die();
and in parameters.yml I got:
parameters:
free_proxy: "http://xxx:8080"
I get an error: Call to a member function getParameter() on null
I've tested some solutions like adding some services and using get and stuff but nothing works.
EDIT: also, I tried this:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$freeProxy: '%free_proxy%'
Then using:
$this->container->getParameter('free_proxy');
But I got an error: Unused binding "$freeProxy" in service...
So there are two mysteries here. First is why is the container not being injected which in turn causes getParameter to fail. And second, why does bind generate that unused binding error.
You did not show your routing but I suspect that somewhere along the line you actually have:
$exampleController = new ExampleController();
If so then this explains why getParameter is failing. You really need to let Symfony create the controller based on the route. Otherwise the container is not injected and other controller magic is skipped.
I installed a fresh 3.4 app with the old directory structure and added a parameter
composer create-project symfony/framework-standard-edition s34
# app/config/parameters.yml
parameters:
free_proxy: "http://xxx:8080"
I then tweaked the default controller using the default route annotation:
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$freeProxy = $this->getParameter('free_proxy');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR.$freeProxy,
]);
}
}
And everything worked as expected. The Symfony request handler takes care of injecting the container and thus gives you access to the parameters. If you cannot get this working then please update your question with your routing information.
I then took a look at the bind issue. You really want to inject these parameters instead of pulling them. I updated services.yml
# app/config/services.yml
services:
bind:
$freeProxy: '%free_proxy%'
And started getting those unused binding errors. It turns out that bind does not work for action injection. Not really sure why. I don't use it much but I really would have expected that just adding $freeProxy to your action method would work. In any event, here is a working example of the proper way to do things.
class ExampleController extends Controller
{
private $freeProxy;
public function __construct($freeProxy)
{
$this->freeProxy = $freeProxy;
}
/**
* #Route("/example", name="example")
*/
function someMethod()
{
dump($this->freeProxy);
dump($this->getParameter('free_proxy'));die();
}
}
I then went to a fresh 4.2 project and tried action injection:
class IndexController extends AbstractController
{
public function index($freeProxy)
{
return new Response("Index $freeProxy");
}
}
Action injection works as expected for 4.2 but not 3.4. Constructor injection works fine in either version.
documentation show like this :
parameters.yml :
parameters:
mailer.transport: sendmail
to set :
$container->setParameter('mailer.transport', 'sendmail');
to get :
$container->getParameter('mailer.transport');
Related
I am new to Symfony (5.3) and would like to extend the RequestBodyParamConverter (FOSRestBundle 3.0.5) to create a REST api. Using #ParamConverter annotation with the RequestBodyParamConverter works fine. However, I would like to create a custom converter, which does the exact same job as RequestBodyParamConverter plus a little extra work.
My first guess was to simply extend RequestBodyParamConverter and provide my custom subclass in the #ParamConverter annotation. However, RequestBodyParamConverter is defined as final and thus cannot be extended...
Injecting RequestBodyParamConverter / fos_rest.request_body_converter into a custom converter class (see example below) also fails because the service cannot be found. I assume this is because it is defined a private?
So, my last idea was to create a RequestBodyParamConverter inside my custom converter class. While this works, I am not sure if this is the right way to solve this problem. This way RequestBodyParamConverter is created twice. This is nothing special of course, but is this the Symfony way to solve this or are there other solutions?
Example:
Inject RequestBodyParamConverter in custom converter class
class MyParamConverter implements ParamConverterInterface {
protected $parentConverter;
public function __construct(ParamConverterInterface $parentConverter) {
$this->parentConverter = $parentConverter;
}
public function apply(Request $request, ParamConverter $configuration): bool {
doExtraWork();
return $this->parentConverter->apply(...);
}
}
// config/services.yaml
My\Project\MyParamConverter:
tags:
- { name: request.param_converter, converter: my_converter.request_body }
arguments:
# both fails since service is not found
$parentConverter: '#FOS\RestBundle\Request\RequestBodyParamConverter'
# OR
$parentConverter: '#fos_rest.request_body_converter'
Create RequestBodyParamConverter in custom converter class
class MyParamConverter implements ParamConverterInterface {
protected $parentConverter;
public function __construct(...parameters necessary to create converter...) {
$this->parentConverter = new RequestBodyParamConverter(...);
}
...
}
Symfony provide a way to decorate a registered service
To use it you need the FOS service id registered in the container.
To get it you can use this command
symfony console debug:container --tag=request.param_converter
Retrieve the Service ID of the service you want to override.
Then you can configure your service to decorate FOS one
My\Project\MyParamConverter:
decorates: 'TheIdOf_FOS_ParamConverterService'
arguments: [ '#My\Project\MyParamConverter.inner' ] # <-- this is the instance of fos service
Maybe you'll need to add the tags to this declaration, I'm not sure.
Let me know if you're facing an error.
I'm writing a Symfony 4 bundle and inside, in a compiler pass, I create multiple service definitions based on an abstract one (also enabling autowiring based on the argument name):
$managerDefinition = new ChildDefinition(Manager::class);
$managerDefinition->replaceArgument(0, $managerName);
...
$container->registerAliasForArgument($managerId, Manager::class, $managerName . 'Manager');
And this is the abstract service definition:
services:
MyBundle\Manager:
abstract: true
arguments:
- # manager name
So, in my App controller I can have this and it works correctly:
public function __construct(MyBundle\Manager $barManager)
{
// $barManager is MyBundle\Manager
}
Now, let's say at some point I decide to extend the Manager class in my App with additional methods:
class MyManager extends \MyBundle\Manager
{
public function newMethod() {
...
}
}
I override the bundle's abstract service like this:
services:
MyBundle\Manager:
class: App\Manager
abstract: true
arguments:
- # manager name
Everything still works as expected:
public function __construct(MyBundle\Manager $barManager)
{
// $barManager is App\Manager
$barManager->newMethod(); // Works
}
However, the IDE complains that newMethod() does not exist, as it doesn't exist in the typehinted MyBundle\Manager.
So, it seems more correct to change my constructor definition to let it know the actual class it's going to receive:
public function __construct(App\Manager $barManager)
However, I can't write this, as auto-wiring no longer works.
I suppose I could write a compiler pass in my App that registers autowiring for my custom App\Manager, but that seems like an overkill.
I can't shake the feeling that I'm doing something fundamentally wrong.
I guess my question is, what would be the best way to allow easy overriding of the abstract Manager definition in the bundle?
I've used the thephpleague/tactician-bundle with Symfony before, but this is the first time I've used it with Symfony 4.* (specifically 4.1.4) and attempted to use a single handler Class for my Application Service.
When I execute a command in the Controller
public function postAction(Request $request, CommandBus $commandBus)
{
$form = $this->createForm(VenueType::class);
$form->submit($request->request->all(), true);
$data = $form->getData();
if($form->isValid()) {
$command = new CreateVenueCommand($data);
$commandBus->handle($command);
return $form->getData();
}
return $form;
}
... I get the following error:
"error": {
"code": 500,
"message": "Internal Server Error",
"exception": [
{
"message": "Could not invoke handler for command App\\Application\\Command\\CreateVenueCommand for reason: Method 'handle' does not exist on handler",
"class": "League\\Tactician\\Exception\\CanNotInvokeHandlerException",
"trace": [
I've seemingly followed the installation documents for the tactician-bundle and installed it using Flex. As far as I can tell everything is configured correctly, so I'm unsure what I'm missing in my implementation.
Implementation
As per the thephpleague/tactician-bundle installation guide I've installed using Flex and the bundle is registered and the config package installed:
tactician:
commandbus:
default:
middleware:
- tactician.middleware.locking
- tactician.middleware.doctrine
- tactician.middleware.command_handler
After creating the DTO Command Class 'CreateVenueCommand', I created the handler Class:
use App\Infrastructure\Domain\Model\VenueRepositoryInterface;
use App\Application\Command\CreateVenueCommand;
use App\Domain\Entity\Venue;
class VenueApplicationService
{
private $venueRepository;
public function __construct(VenueRepositoryInterface $venueRepository)
{
$this->venueRepository = $venueRepository;
}
/**
* #param CreateVenueCommand $aCommand
* #throws \Exception
*/
public function createVenue(CreateVenueCommand $aCommand)
{
$aVenue = new Venue($aCommand->getData())
if ($aVenue === null) {
throw new \LogicException('Venue not created');
}
$this->venueRepository->add($aVenue);
}
Then I registered the handler Class as a Service taking advantage of Symfony's autowiring and Tacticians typehints:
App\Application\VenueApplicationService:
arguments:
- '#App\Infrastructure\Persistence\Doctrine\DoctrineVenueRepository'
tags:
- { name: tactician.handler, typehints: true }
So according to the installation documents, typehints work if:
The method must be public.
The method must accept only one parameter.
The parameter must be typehinted with a class name.
Also, and this is specific to my use case:
If you have multiple commands going into a single handler, they will all be detected, provided they follow the rules above. The actual name of the method is NOT important.
So when I invoke the commandbus in the Controller Class, I'm unsure why I'm getting the error above.
If I change the Command Handler method to:
public function handle(CreateVenueCommand $aCommand)
{
... then it works fine. This would seem to suggest that the typehints aren't working as documented.
It seems in this case that the actual name of the method IS important. ... or I've made some form of error in my implementation ... or I'm misunderstanding the multiple commands going into a single handler use case??
Any assistance would be greatly appreciated.
Solution
With a big thanks to kunicmarko20 for pointing me in the right direction.
Specifically for my use case I simply needed to use one of Tacticians MethodNameInflector classes, configured in Symfony thus:
tactician:
commandbus:
default:
middleware:
- tactician.middleware.locking
- tactician.middleware.doctrine
- tactician.middleware.command_handler
method_inflector: tactician.handler.method_name_inflector.handle_class_name
... then it was simply a matter of naming each Handler method in my Application Service class 'handle{whateverYouLike}Command
Here under 1. is explained how the naming works, if you want to use a different name than in this table you can implement MethodNameInflector Interface and provide a name of the method.
I am trying to create service in symfony2 which will verify if session contains certain information and if not redirect the user to another controller. I want this piece of code to work as a service as I will be using it in many controllers.
I have problem as manual on Symfony2 book does not provide information how service file should look like. Should it be a normal php class?
Please find below dump of my files with information on error that I receive.
In \AppBundle\Services I create file my_isbookchosencheck.php containing:
<?php
namespace AppBundle\my_isbookchosencheck;
class my_isbookchosencheck
{
public function __construct();
{
$session = new Session();
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
My service.yml:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
My conntroller file:
/**
* This code is aimed at checking if the book is choseen and therefore whether any further works may be carried out
*/
$checker = $this->get('my_isbookchosencheck');
Error:
FileLoaderLoadException in FileLoader.php line 125: There is no extension able to load the configuration for "my_isbookchosencheck" (in C:/wamp/www/symfony_learn/app/config\services.yml). Looked for namespace "my_isbookchosencheck", found "framework", "security", "twig", "monolog", "swiftmailer", "assetic", "doctrine", "sensio_framework_extra", "fos_user", "knp_paginator", "genemu_form", "debug", "acme_demo", "web_profiler", "sensio_distribution" in C:/wamp/www/symfony_learn/app/config\services.yml (which is being imported from "C:/wamp/www/symfony_learn/app/config\config.yml").
There are few mistakes that you made, which I am going to explain in short, and I will give you an example of the service you want to create.
You created your service in AppBundle\Services, yet your namespace is registered differently - namespace AppBundle\Services\my_isbookchosencheck;. It should be namespace AppBundle\Services;. I would also advise you to use singular names when creating directories - in this case Service would be better, instead of Services.
You're using your __constructor directly to apply some logic and return the result of it. Better way would be to create a custom method, which could be accessed when necessary.
You're creating new instance of Session which means that you wont be able to access anything that was previously added and stored in session. The right way here, would be to inject RequestStack which holds the current Request and get the session from there.
I believe you also registered your service wrong. In your services.yml file, it should be under services: option. This is why you got the error you pasted.
So, let's see how your service should like.
services.yml
services:
book_service:
class: AppBundle\Service\BookService
arguments:
- #request_stack
- #router
BookService.php
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
class BookService {
/* #var $request Request */
private $request;
/* #var $router RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router) {
$this->request = $requestStack->getCurrentRequest();
$this->router = $router;
}
public function isBookChoosen() {
$session = $this->request->getSession();
// Now you can access session the proper way.
// If anything was added in session from your controller
// you can access it here as well.
// Apply your logic here and use $this->router->generate()
}
}
Now in your controller you can simply use it like this:
$this->get('book_service')->isBookChoosen()
Well this is a short example, but I hope you got the idea.
try
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
in your services.yml, and check that you use the correct namespaces.
Your Class is fine and it should work, however may i suggest that you use
symfony2 session service instead of creating the session object yourself, you can pass it as a constructor argument:
<?php
// namespace edited
namespace AppBundle\Services;
use Symfony\Component\HttpFoundation\Session\Session;
class my_isbookchosencheck
{
public function __construct(Session $session);
{
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
and then edit your services.yml accordingly, so the service container will inject the session object:
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
arguments: [#session]
Also check out his question on so:
How do you access a users session from a service in Symfony2?
Services are just regular PHP classes, nothing special. But you must register it in order to be recognized by the system. Here are the steps how you do it,
Create a regular PHP class (you can inject other services if it requires)
namespace Acme\DemoBundle\Service;
class MyService
{
private $session;
public function _construct(SessionInterface $session /* here we're injecting the session service which implements the SessionInterface */)
{
$this->session = $session;
}
// other methods go here, which holds the business logic of this class
}
ok, we created a class, we need to register it to be able to use it by service container, here how you do it:
the simplest way is to put it into config.yml file, like this:
services:
my_service:
class: Acme\DemoBundle\Service\MyService
arguments:
- #session
or, another way, is to create a file (e.g. services.yml, may be in config folder), and import it inside the config.yml file (the content of the file is the same as the first way):
imports:
- { resource: services.yml }
or, you can create a services.yml(the content of the file is the same as the first way) file inside you bundle's Resources folder, specify it under the load method of your Extension class (under the DependencyInjection folder), (this way requires some special directory and file structure, read about it in the doc):
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources'));
$loader->load('services.yml');
}
}
In you case, you're not registering your service, the service container just couldn't find it. Register it by one of the above ways.
Is there a possibility to use generateUrl() method outside of controllers?
I tried to use it in a custom repository class with $this->get('router'), but it didn't work.
update
I've found a temporary solution here:
http://www.phamviet.net/2012/12/09/symfony-2-inject-service-as-dependency-in-to-repository/
I injected the whole service container into my repository, although it's "not recommended".
But it works for now.
update2
Injecting router instead of the whole container is probably a better idea :)
If you take a look in the source code of Controller::generateUrl(), you see how it's done:
$this->container->get('router')->generate($route, $parameters, $referenceType);
Basically you just enter the name of the route ($route here); if exists, some parameters ($parameters) and the type of reference (one of the constants of the UrlGeneratorInterface)
Don't inject the container into your repository... Really, don't !
If I were you, I would create a service and injects the router in it. In this service, I would create a method, that uses the repository and adds the needed code using the router.
That's way less dirty and easy to use/understand for another developer.
Inject the router itself into your EntityRepsitory (like described on Development Life blog's post Symfony 2: Injecting service as dependency into doctrine repository), then you can use $this->router->generate('acme_route');
in symfony 4 and Sylius when the FormType extends an (ex.) AbstractResourceType
class PostType extends AbstractResourceType
{
private $router;
public function __construct(RouterInterface $router, $dataClass, $validationGroups = [])
{
$this->router = $router;
parent::__construct($dataClass, $validationGroups);
}
}
Services.yaml :
app.post.form.type:
class: App\Form\Admin\Post\PostType
tags:
- { name: form.type }
arguments: ['#router.default', '%app.model.post.class%' ]