Symfony not respecting overriding class argument for test environment - symfony

I have a bundle with a services.yml where the service definition uses a parameter contained within the same file for the class parameter, like so:
parameters:
application.servicename.class: Application\Service\ServiceName
services:
application.servicename:
class: %application.servicename.class%
Now I want to override the service class for my test environment. However, overriding the parameter in config_test.yml does not result in an object of the overriding class being instantiated.
Adding the following to config_test.yml:
parameters:
application.servicename.class: Application\Mock\Service\ServiceName
...still causes the service to be instantieted from Application\Service\ServiceName. If I try passing application.servicename.class as an argument to the service and dump it in the constructor, the overriden value of Application\Mock\Service\ServiceName is displayed.
Why is Symfony not respecting the overridden value for the service class when preparing the service?

You should move
parameters:
application.servicename.class: Application\Service\ServiceName
From services.yml to config.yml becasuse in my opninion you are overriding the value of the paremeter in config_test.yml with the value you have in services.yml

I think what you're looking for is a Extension class in your Bundle:
http://symfony.com/doc/current/cookbook/bundles/extension.html
I think you might be able to change priorities loading the config files
Here's an example of implementation
public function load(array $configs, ContainerBuilder $container {
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('your.config.parameter', $config['your']['config']['parameter']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
}

As it turns out, this problem was not related to Symfony loading configuration but rather an assumption that the incorrect class was loaded. This assumption was caused by the fact that the method calls of both the original service (and the mock extending it) was marked as private.
Had this not been a problem, I belive what I was attempting to do should be possible, ref http://symfony.com/doc/2.8/cookbook/bundles/override.html#services-configuration
Sorry to waste your time.

Related

Symfony bundle config parameters not available in listener?

I have a bundle that has a listener which I have configured:
class Configuration implements ConfigurationInterface
{
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder ()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('mybundle_name');
$rootNode
->children()
->scalarNode('name')->defaultValue('value')
->end()
;
return $treeBuilder;
}
}
I also have a listener which has a few services injected into, primarily doctrine and the container parameters:
services:
app.router_subscriber:
class: MyBundle\EventSubscriber\RequestSubscriber
calls:
- [setEntityManager, ['#doctrine.orm.entity_manager']]
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
When I dump the $this->container I can see the parameters except my own defined above.
When I run
bin/console config:dump-reference MyBundle
I do see what I am expecting
What am I missing to have my bundle parameters get merged into the application parameters? I am seeing third party bundles listed but not my own bundle. I followed the docs as verbatim as I could so the conventions have been followed as far as I am aware...
EDIT | I haven't created a bundle config.yml file - I assumed the Configuraiton object did that for me - setting the schema and default values - which could be overridden by application configs (if desired). Do I need to specifcy a bundle config.yml and import into application something like this (Merge config files in symfony2)?
Ideas?
I wrote a couple blog posts showing how you can set bundle configuration defaults using YAML files, and a follow-up on how to automatically set bundle configuration values as container parameters. This was for Symfony2 and written in 2014, and the particular section of the Symfony documentation I link to disappeared from Symfony 2.3 onward, but the same concept still applies.
The main takeaway from those posts is that you can set your configuration values as container parameters in your bundle's Extension class via the load() method manually like so:
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter($this->getAlias().'.name', $config['name']);
}
Notice that you can call $this->getAlias() to get the bundle's root name (mybundle_name). Using the above call you would then have a parameter defined as mybundle_name.name which you could then override in your application's config.yml if need be.

Call a Command from a Controller not work

i have this code to run schema update command from controller i got help from symfony document
i have this code:
namespace AdminBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DefaultController extends Controller
{
/**
* #Route("/admin")
*/
public function indexAction(KernelInterface $kernel)
{
$application = new Application($kernal);
$input = new ArrayInput(array(
'command' => 'doctrine:schema:update'
));
$output = new NullOutput();
$application->run($input, $output);
return new Response("");
}
}
it's not work for me i get this error after open this url (http://127.0.0.1:8000/admin):
Controller "AdminBundle\Controller\DefaultController::indexAction()" requires that you provide a value for the "$kernel" 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.
how can i do?
Instead of injecting the KernelInterface $kernel directly into your action (I guess, you're not using it as a declared service), call your container directly for asking the kernel:
public function indexAction()
{
$kernel = $this->get('kernel');
$application = new Application($kernel); // btw: you have an typo in here ($kernal vs $kernel)
$input = new ArrayInput(array(
'command' => 'doctrine:schema:update'
));
$output = new NullOutput();
$application->run($input, $output);
// tip: use "204 No Content" to indicate "It's successful, and empty"
return new Response("", 204);
}
While Michael's answer works, it is not the preferred method in Symfony 3.3, which had several changes to dependency injection. Your code will actually work just fine with some changes to your services configuration.
As the documentation states, the Dependency Injection Container changed in Symfony 3.3, and by default your controllers are registered as services:
# app/config/services.yml
services:
# ...
# 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 allows you to autowire the kernel through arguments in your controller action method, like you tried. The reason yours isn't working is because your AdminBundle is likely not set up the way your AppBundle is by default in app/config/services.yml. To truly solve the issue in the way that Symfony 3.3 wants, you should add AdminBundle to your services configuration like so:
# app/config/services.yml
services:
# add this below your AppBundle\Controller definition
AdminBundle\Controller\:
resource: '../../src/AdminBundle/Controller'
public: true
tags: ['controller.service_arguments']
With that, you no longer have to call $this->get('kernel');, and your original code will work as you have it, with KernelInterface as a parameter to your action method.
Furthermore, you can extend the new AbstractController instead of the regular Controller, and then calls to $this->get() will not work anymore, which is the way Symfony is going.
So again while Michael's answer will work just fine, I would advise you to implement the answer I've given simply because Symfony 3.3 prefers that method going forward.

Service in symfony2 - how service file should look like?

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.

Injecting parameter based service into other service

I have a service which takes a driver to do the actual work. The driver itself is within the context of Symfony 2 is just another service.
To illustrate a simplified version:
services:
# The driver services.
my_scope.mailer_driver_smtp:
class: \My\Scope\Service\Driver\SmtpDriver
my_scope.mailer_driver_mock:
class: \My\Scope\Service\Driver\MockDriver
# The actual service.
my_scope.mailer:
class: \My\Scope\Service\Mailer
calls:
- [setDriver, [#my_scope.mailer_driver_smtp]]
As the above illustrates, I can inject any of the two driver services into the Mailer service. The problem is of course that the driver service being injected is hard coded. So, I want to parameterize the #my_scope.mailer_driver_smtp.
I do this by adding an entry to my parameters.yml
my_scope_mailer_driver: my_scope.mailer_driver_smtp
I can then use this in my config.yml and assign the parameter to the semantic exposed configuration [1]:
my_scope:
mailer:
driver: %my_scope_mailer_driver%
In the end, in the Configuration class of my bundle I set a parameter onto the container:
$container->setParameter('my_scope.mailer.driver', $config['mailer']['driver'] );
The value for the container parameter my_scope.mailer.driver now equals the my_scope.mailer_driver_smtp that I set in the parameters.yml, which is, as my understanding of it is correct, just a string.
If I now use the parameter name from the container I get an error complaining that there is no such service. E.g:
services:
my_scope.mailer:
class: \My\Scope\Service\Mailer
calls:
- [setDriver, [#my_scope.mailer.driver]]
The above will result in an error:
[Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]
The service "my_scope.mailer" has a dependency on a non-existent service "my_scope.mailer.driver"
The question now is, what is the correct syntax to inject this container parameter based service?
[1] http://symfony.com/doc/current/cookbook/bundles/extension.html
This question has a similar answer here
I think the best way to use this kind of definition is to use service aliasing.
This may look like this
Acme\FooBundle\DependencyInjection\AcmeFooExtension
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration;
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yml');
$alias = $config['mailer']['driver'];
$container->setAlias('my_scope.mailer_driver', $alias);
}
This will alias the service you've defined in my_scope.mailer.driver with my_scope.mailer_driver, which you can use as any other service
services.yml
services:
my_scope.mailer_driver:
alias: my_scope.mailer_driver_smtp # Fallback
my_scope.mailer_driver_smtp:
class: My\Scope\Driver\Smtp
my_scope.mailer_driver_mock:
class: My\Scope\Driver\Mock
my_scope.mailer:
class: My\Scope\Mailer
arguments:
- #my_scope.mailer_driver
With such a design, the service will change whenever you change the my_scope.mailer_driver parameter in your config.yml.
Note that the extension will throw an exception if the service doesn't exist.
With service container expression language you have access to the following two functions in config files:
service - returns a given service (see the example below);
parameter - returns a specific parameter value (syntax is just like service)
So to convert parameter name into a service reference you need something like this:
parameters:
my_scope_mailer_driver: my_scope.mailer_driver_smtp
services:
my_scope.mailer:
class: \My\Scope\Service\Mailer
calls:
- [setDriver, [#=service(parameter('my_scope_mailer_driver'))]]
At first I thought this was just a question of getting the # symbol passed in properly. But I tried assorted combinations and came to the conclusion that you can't pass an actual service as a parameter. Maybe someone else will chime in and show how to do this.
So then I figured is was just a question of using the service definition and passing it a reference. At first I tried this in the usual extension but the container does not yet contain all the service definitions.
So I used a compiler pass: http://symfony.com/doc/current/cookbook/service_container/compiler_passes.html
The Pass class looks like:
namespace Cerad\Bundle\AppCeradBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class Pass1 implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
// Set in the Extension: my_scope.mailer_driver_smtp
$mailerDriverId = $container->getParameter('my_scope.mailer.driver');
$def = $container->getDefinition('my_scope.mailer');
$def->addMethodCall('setDriver', array(new Reference($mailerDriverId)));
}
}
Take the calls section out of the service file and it should work. I suspect there is an easier way but maybe not.
#my_scope.mailer.driver needs to be a service but not defined as service. To retrieve string parameter named as my_scope.mailer.driver you need to wrap it with %: %my_scope.mailer.driver%.
So you need to pass #%my_scope.mailer.driver% as parameter to a service. Yml parser will replace %my_scope.mailer.driver% with the appropriate value of the parameter and only then it will be called as a service.

Symfony2-How to use access a service from outside of a controller

In my Symfony2 controller, this works fine:
$uploadManager = $this->get('upload.upload_manager');
but when I move it to a custom Listener:
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\UploadBundle\Upload\UploadManager;
class PersonChange
{
public function postRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
$uploadManager = $this->get('ep_upload.upload_manager');
echo "the upload dir is " . $uploadManager->getUploadDir();
}
}
I get an error:
Fatal error: Call to undefined method Acme\MainBundle\Listener\PersonChange::get() in /home/frank/...
I know I must need a use statement but don't know what to use.
Update: Defining controllers as services is no longer officially recommended in Symfony.
The get() method in the Controller class is just a helper method to get services from the container, and it was meant to get new Symfony2 developers up to speed faster. Once people get comfortable with the framework and dependency injection, it's recommended to define controllers as services and inject each required service explicitly.
Since your PersonChange class is not a controller and doesn't extend the Controller class, you don't have that get() helper method. Instead, you need to define your class as a service and inject needed services explicitly. Read the Service Container chapter for details.
As I ran into the exact same problem maybe I can help
What Elnur said is perfectly fine and I'll just try to pop up a real life example.
In my case I wanted to access
$lucenemanager = $this->get('ivory.lucene.manager')
Even by extending the controller I couldn't get it to work while the controller does access the container (I still did not understand why)
In config.yml my listener (searchindexer.listener) is declared as follow :
services:
searchindexer.listener:
class: ripr\WfBundle\Listener\SearchIndexer
arguments:
luceneSearch: "#ivory_lucene_search"
tags:
- { name: doctrine.event_listener, event: postPersist }
A service (ivory.lucene.search) is passed as argument in my service/listener.
Then in my class
protected $lucenemanager;
public function __construct($luceneSearch)
{
$this->lucenemanager = $luceneSearch;
}
Then you can use the get method against $this
An approach that always works, despite not being the best practice in OO
global $kernel;
$assetsManager = $kernel->getContainer()->get('acme_assets.assets_manager');‏
If you need to access a Service, define it in the class constructor:
class PersonChange{
protected $uploadManager;
public function __construct(UploadManager $uploadManager){
$this->uploadManager = $uploadManager;
}
// Now you can use $this->uploadManager.
}
Now you can pass the Service as argument when calling the class (example 1) or define the clas itself as a Service (recommended, example 2)
Example 1:
use Acme\PersonChange;
class appController{
function buzzAction(){
$uploadManager = $this->get('upload.upload_manager');
$personChange = new PersonChange($uploadManager);
Example 2 (better):
Define PersonChange as a Service itself, and define the other Service as an argument in services.yml file:
# app/config/services.yml
services:
upload.upload_manager:
class: AppBundle\uploadManager
PersonChange:
class: AppBundle\PersonChange
arguments: ['#upload.upload_manager']
In this way, you don't have to bother with the upload_manager service in the Controller, since it's implicitely passed as an argument for the constructor, so your Controller can be:
class appController{
function buzzAction(){
$personChange = $this->get('PersonChange');

Resources