I need to inject the requestStack in an event subscriber but it looks like I'm doing something wrong (when the class gets constructed) but I'm not sure what it is.
First I'm subscribing to onLineItemAdded then I want to set a new field in the payload but there is an error regarding the passing of RequestStack $requestStack. should be there any extra XML config that I need to add in order for this to work properly?
<?php declare(strict_types=1);
namespace Test\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
class TestCartSubscriber implements EventSubscriberInterface
{
private RequestStack $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public static function getSubscribedEvents(): array
{
// Return the events to listen to as array like this: <event to listen to> => <method to execute>
return [
BeforeLineItemAddedEvent::class => 'onLineItemAdded'
];
}
public function onLineItemAdded(BeforeLineItemAddedEvent $event): void
{
// $lineItem = $event->getLineItem();
$items = $this->requestStack->getCurrentRequest()->get('lineItems');
print_r($items);exit;
}
}
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Test\Subscriber\TestCartSubscriber">
<tag name="kernel.event_subscriber"/>
</service>
</services>
</container>
You have to inject the request stack in the XML:
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Test\Subscriber\TestCartSubscriber">
<argument type="service" id="request_stack"/>
<tag name="kernel.event_subscriber"/>
</service>
</services>
</container>
See the Symfony documentation for more information on how the constructor injection works.
Related
At beginning, I used repository.
But, after some code reviews on github, I'm interesting to use ObjectManager (to alleviate controllers, and also by curiosity ^^).
The problem is I didn't see some good tutorial about it. Even tutorials I saw was to initialize a service by an object manager but not to create one.
In the FriendsOfSymfony github, we could see an example for that but I don't really understand how to initialize the service. I have this error "Cannot instantiate interface Doctrine\Common\Persistence\ObjectManager" when I initialize my manager service like this:
<?xml version="1.0" ?>
http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="md_mechanical.entity.enginemanager.class">MD\MechanicalBundle\Entity\EngineManager</parameter>
</parameters>
<services>
<service id="md_mechanical.enginemanager.default" class="%md_mechanical.entity.enginemanager.class%">
<argument type="service" id="md_mechanical.object_manager" />
<argument>%md_engine.engine.class%</argument>
</service>
<!-- The factory is configured in the DI extension class to support more Symfony versions -->
<service id="md_mechanical.object_manager" class="Doctrine\Common\Persistence\ObjectManager">
<argument>%fos_user.model_manager_name%</argument>
</service>
</services>
thanks in advance for your help
Cannot instantiate interface Doctrine\Common\Persistence\ObjectManager means you are trying to instantiate an interface, which is not possible.
You have to create an object which implements this interface, and define all functions
use Doctrine\Common\Persistence\ObjectManager as ObjectManager;
class MyObjectManager implements ObjectManager
{
public function __construct(/* some params here */)
{
// Construct your manager here
}
public function find($className, $id)
{
// Do stuff
}
public function persist($object)
{
// Do stuff
}
public function remove($object)
{
// Do stuff
}
public function merge($object)
{
// Do stuff
}
public function clear($objectName = null)
{
// Do stuff
}
public function detach($object)
{
// Do stuff
}
public function refresh($object)
{
// Do stuff
}
public function flush()
{
// Do stuff
}
public function getRepository($className)
{
// Do stuff
}
public function getClassMetadata($className)
{
// Do stuff
}
public function getMetadataFactory()
{
// Do stuff
}
public function initializeObject($obj)
{
// Do stuff
}
public function contains($object)
{
// Do stuff
}
}
Then declare it as a service
<services>
<service id="myObjectManager" class="%myObjectManager.class%">
<argument>...</argument>
</service>
# Use your brand new object manager
<service id="md_mechanical.enginemanager.default" class="%md_mechanical.entity.enginemanager.class%">
<argument type="service" id="myObjectManager" />
<argument>%md_engine.engine.class%</argument>
</service>
</services>
You should have a look at Doctrine\ORM\EntityManager and Doctrine\ORM\EntityManagerInterface, it may help you.
I have registered my controller as a service so I can have my repository injected into my controller. This all seems to work fine, except that now when i try to return the view it bugs on returning data.
It gives an error and tries to load fos_rest.view_handler:
Error: Call to a member function get() on a non-object
The get is being called in the symfony2 controller class on $this->container->get($id). For some reason the ContainerInterface is not being injected in the ContainerAware anymore when I use my controller as a service.
Has anyone experienced this problem before? How can I make sure the same container gets injected?
This is how I declared my class as a service:
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme.users.apibundle.controller.user_controller" class="Acme\Users\ApiBundle\Controller\UserController">
<argument type="service" id="acme.users.user_repository"/>
</service>
</services>
</container>
And this is my controller:
class UserController extends FOSRestController
{
private $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public function indexAction()
{
$users = $this->repository->findAll();
$view = $this->view($users, 200)
->setTemplate("MyBundle:Users:getUsers.html.twig")
->setTemplateVar('users');
return $this->handleView($view);
}
}
You need to inject the container into your controller using a call so that it is available in the handleView method.
Change your config to..
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme.users.apibundle.controller.user_controller" class="Acme\Users\ApiBundle\Controller\UserController">
<argument type="service" id="acme.users.user_repository"/>
<!-- inject the container via the setContainer method -->
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
</services>
Given the the following class definition
class CacheSubscriber implements SubscriberInterface
{
public function __construct(
CacheStorageInterface $cache,
callable $canCache
) {
$this->storage = $cache;
$this->canCache = $canCache;
}
I want to define this class as a service in Symfony2 DIC.
While it is relatively clear what to do with the first argument
<service id="nsp_generic.guzzle.subscriber.cache" class="GuzzleHttp\Subscriber\Cache\CacheSubscriber">
<argument type="service" id="nsp_generic.redis.cache"/>
<!-- ??? -->
</service>
How can I define and inject the second argument?
UPDATE
User meckhardt pushed this into the right direction.
Helper class
<?php
namespace Nsp\GenericBundle\Service\Cache;
class CacheConfig {
public static function canCache() {
return true;
}
}
Service defintion
<service id="nsp_generic.guzzle.subscriber.cache" class="GuzzleHttp\Subscriber\Cache\CacheSubscriber">
<argument type="service" id="nsp_generic.guzzle.subscriber.cache.storage"/>
<argument type="collection">
<argument>Nsp\GenericBundle\Service\Cache\CacheConfig</argument>
<argument>canCache</argument>
</argument>
</service>
Thanks Martin!
Try
<argument type="collection">
<argument>ClassName::staticMethod</argument>
</argument>
or
<argument type="collection">
<argument type="service" id="serviceId" />
<argument>instanceMethodName</argument>
</argument>
http://php.net/manual/de/language.types.callable.php
Maybe create an dependency injection factory like in this example: docs
which will return your callable.
I have a parameter which should represent array of services in my services.xml file:
<parameters>
<parameter key="decorators.all" type="collection">
<parameter type="service" id="decorator1" />
<parameter type="service" id="decorator2" />
<parameter type="service" id="decorator3" />
</parameter>
</parameters>
<services>
<service id="decorator1" class="\FirstDecorator" />
<service id="decorator2" class="\SecondDecorator" />
<service id="decorator3" class="\ThirdDecorator" />
</services>
Now I want to inject this collection to another service as an array of services:
<services>
<service id="notifications_decorator" class="\NotificationsDecorator">
<argument>%decorators.all%</argument>
</service>
</services>
But it doesn't work. Can't understand why. What am I missing?
So, you inject array of parameters no array of services. You can inject service by service via:
<services>
<service id="notifications_decorator" class="\NotificationsDecorator">
<argument type="service" id="decorator1"/>
<argument type="service" id="decorator2"/>
<argument type="service" id="decorator3"/>
</service>
</services>
Or (in my opinion better way) tag decorators services and inject them to notifications_decorator during compilation passes.
UPDATE: Working with Tagged Services
In your case you have to modify your services like this:
<services>
<service id="decorator1" class="\FirstDecorator">
<tag name="acme_decorator" />
</service>
<service id="decorator2" class="\SecondDecorator">
<tag name="acme_decorator" />
</service>
<service id="decorator3" class="\ThirdDecorator">
<tag name="acme_decorator" />
</service>
</services>
Additionaly you should remove decorators.all parameter from <parameters> section. Next, you have to add sth like addDectorator function for \NotificationsDecorator:
class NotificationsDecorator
{
private $decorators = array();
public function addDecorator($decorator)
{
$this->decorators[] = $decorator;
}
// more code
}
It would be great if you write some interface for decorator's and add this as type of $decorator for addDecorator function.
Next, you have to write own compiler pass and ask them about tagged services and add this services to another one (simillarly to doc):
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class DecoratorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('notifications_decorator')) {
return;
}
$definition = $container->getDefinition('notifications_decorator');
$taggedServices = $container->findTaggedServiceIds('acme_decorator');
foreach ($taggedServices as $id => $attributes) {
$definition->addMethodCall(
'addDecorator',
array(new Reference($id))
);
}
}
}
Finally, you should add your DecoratorCompilerPass to Compiler in your bundle class like:
class AcmeDemoBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new DecoratorCompilerPass());
}
}
Good luck!
Little bit different approach with tagged services (or whatever you need) and CompilerPassInterface using array of services instead of method calls.
Here are the differences from #NHG answer:
<!-- Service definition (factory in my case) -->
<service id="example.factory" class="My\Example\SelectorFactory">
<argument type="collection" /> <!-- list of services to be inserted by compiler pass -->
</service>
CompilerPass:
/*
* Used to build up factory with array of tagged services definition
*/
class ExampleCompilerPass implements CompilerPassInterface
{
const SELECTOR_TAG = 'tagged_service';
public function process(ContainerBuilder $container)
{
$selectorFactory = $container->getDefinition('example.factory');
$selectors = [];
foreach ($container->findTaggedServiceIds(self::SELECTOR_TAG) as $selectorId => $tags) {
$selectors[] = $container->getDefinition($selectorId);
}
$selectorFactory->replaceArgument(0, $selectors);
}
}
in yaml you can do:
app.example_conditions:
class: AppBundle\Example\Conditions
arguments:
[[ "#app.example_condition_1", "#app.example_condition_2", "#app.example_condition_3", "#app.example_condition_4" ]]
and in AppBundle\Example\Conditions you receive the array...
Symfony 5.3+ and php 8.0+
class NotificationsDecorator
{
private $decorators;
public function __construct(
#[TaggedIterator('app.notifications_decorators')] iterable $decorators
) {
$this->decorators = $decorators;
}
}
#[Autoconfigure(tags: ['app.notifications_decorators'])]
interface DecoratorInterface
{
}
class FirstDecorator implements DecoratorInterface
{
}
Services.xml file:
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="task.task_history_insertion" class="Acme\Bundle\EventListener\TaskHistoryInsertion">
<argument type="service" id="service_container" />
<tag name="doctrine.event_listener" event="postPersist" method="postPersist"/>
</service>
</services>
</container>
TaskHistoryInsertion.php
class TaskHistoryInsertion implements EventSubscriber
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getSubscribedEvents()
{
return array(
Event::postPersist
);
}
public function postPersist(LifecycleEventArgs $args)
{
//not being called
}
}
Any ideas on why postPersist isn't being called after persisting?
Make sure you use the right tag for your service. You need to use doctrine.event_subscriber :
<service id="task.task_history_insertion" class="Acme\Bundle\EventListener\TaskHistoryInsertion">
<argument type="service" id="service_container" />
<tag name="doctrine.event_subscriber"/>
</service>
You are mixing event subscriber and event listener!
I would go for a event listener:
Remove
implements EventSubscriber
and
public function getSubscribedEvents()
{
return array(
Event::postPersist
);
}
Make sure you use
use Doctrine\ORM\Event\LifecycleEventArgs;
and that the services.xml gets loaded in src/Acme/Bundle/DependencyInjection/AcmeExtension.php.
Clear the cache and it should work.
The official documentation can be found at
http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html
When you want to implement eventListener - you should name the method in your listener class exactly like an event. In your example - you should have public method called postPersist. And the listener class shouldn't implement EventSubscriber.
There is a link that can give you much clearer picture about this topic http://docs.doctrine-project.org/en/latest/reference/events.html