Symfony event subscriber doesn't react to dispatch - php

I'm using a service with an autowired dispatcher to dispatch an event. In order to test events, I've added an event subscriber just before dispatching the event. However, the registered subscriber isn't logging the method I would expect it to do.
The event:
<?php
namespace App\Event;
use Symfony\Component\EventDispatcher\Event;
class GameStartEvent extends Event {
}
The subscriber:
<?php
namespace App\Event;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class GameStartSubscriber implements EventSubscriberInterface {
use LoggerAwareTrait;
public function __construct(LoggerInterface $logger) {
$this->setLogger($logger);
}
public static function getSubscribedEvents() {
return [
"game.started" => [
[
'logEvent',
],
]
];
}
public function logEvent(GameStartEvent $event): void {
$this->logger->info("the game has started");
}
}
And this is the used service that is supposed to dispatch events the event registration is supposed to happen somewhere else in the future. I just did it here for testing:
<?php
namespace App\Service;
use App\Event\GameStartEvent;
use App\Event\GameStartSubscriber;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class GameCacheService {
private $eventDispatcher;
private $logger;
public function __construct(EventDispatcherInterface $eventDispatcher, LoggerInterface $logger) {
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
}
// some other methods...
function startGame() {
// some code...
$gameStartSubscriber = new GameStartSubscriber($this->logger);
$this->eventDispatcher->addSubscriber($gameStartSubscriber);
$this->eventDispatcher->dispatch("game.started", new GameStartEvent());
}
}
After calling the service method, the specified logger message isn't written down.

Replace
`$this->eventDispatcher->dispatch("game.started", new GameStartEvent());`
with
`$this->eventDispatcher->dispatch("game.started", $event);`
You seem to have at least your Event in App/Entity which is not autowired by default and shouldn't be. Have a look at your services.yml, it should be excluded from Autowiring. Move your stuff to something like App/Event

Related

Symfony doesn't load my custom validator defined as service

I'm trying to inject EntityManagerInterface in my custom validator, but I have this error:
Attempted to load class "validator_check_client_id" from the global namespace.
Did you forget a "use" statement?
My code is:
CheckClientId.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class CheckClientId extends Constraint{
public $message = 'Test';
public function validatedBy(){
return 'validator_check_client_id';
}
}
CheckClientIdValidator.php
namespace App\Validator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CheckClientIdValidator extends ConstraintValidator{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager) {
$this->entityManager = $entityManager;
}
public function validate($value, Constraint $constraint){
//Todo
return;
}
}
services.yaml
validator.check_client_id:
class: App\Validator\CheckClientIdValidator
autowire: false
arguments:
$entityManager: '#doctrine.orm.entity_manager'
tags:
- {
name: validator.constraint_validator,
alias: validator_check_client_id,
}
Should I do an extra step to register my service? I think that symfony cannot find it because it is not properly registered.
I also tried to do what this answer says symfony validator as service not working, but I get a similar error:
Attempted to load class "app.validator.blog.slug_unique" from the global namespace.
Did you forget a "use" statement?
Do you have any idea what is happening?
Thank you for your help!
Thanks to autowire and autoconfig, all you really have to do is to get your class names right. No changes to services.yaml. Here is a working example using a fresh 5.2 project.
# src/Validator/CheckClientId.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
/** #Annotation */
class CheckClientId extends Constraint
{
public $message = 'CheckClientId';
}
# =============================
# src/Validator/CheckClientIdValidator
namespace App\Validator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class CheckClientIdValidator extends ConstraintValidator
{
public function __construct(private EntityManagerInterface $entityManager)
{
}
public function validate($value, Constraint $constraint)
{
dump('validate ' . $value);
throw new UnexpectedValueException($value, 'string');
}
}
# ===============================
# Test Entity
# src/Entity/Client.php
namespace App\Entity;
use App\Validator as AcmeAssert;
class Client
{
/** #AcmeAssert\CheckClientId */
public $id = 42;
}
# ====================================
# Test Command
# src/Command/ValidateCommand
namespace App\Command;
use App\Entity\Client;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ValidateCommand extends Command
{
protected static $defaultName = 'test:validator';
public function __construct(private ValidatorInterface $validator)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$client = new Client();
$errors = $this->validator->validate($client);
echo $errors;
return Command::SUCCESS;
}
}
Get the test command working and move on from there.

Symfony - using twig in onKernelRequest kills session

I have a strange issue in symfony 4.3 (also tested it in 4.2 - same behaviour) - I am using an EventListener to process a request - heres the code:
<?php
namespace App\EventListener;
use App\Entity\Company;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;
class ShopListener implements EventSubscriberInterface
{
/** #var EntityManagerInterface */
protected $em;
/** #var Environment */
protected $twig;
public function __construct(EntityManagerInterface $entityManager, Environment $twig)
{
$this->em=$entityManager;
$this->twig=$twig;
}
public function onKernelRequest(RequestEvent $event)
{
if($event->isMasterRequest()===false) {
return;
}
/** #var Request $request */
$request=$event->getRequest();
$subDomain=$request->attributes->get('domain');
if($subDomain===null) {
return;
}
$company=$this->em->getRepository(Company::class)->findOneBy([
'subDomain' => $subDomain,
]);
if($company instanceof Company && $company->shopIsOnline()) {
$request->attributes->set('company',$company);
return;
}
$event->setResponse(
new Response($this->twig->render('page/shop_not_found.html.twig'),404)
);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest',0],
];
}
}
After registering that listener, $request->getSession() is always null in my controller (toolbar also notices, that there is no session registered). When deregistering it, the session is there, but the logic in the listener is skipped. I have tried to play around with the priority to ensure, there's no other listener which interferes.
It seems, that already registering that event kills the session (even if onKernelRequest is empty), which is hard to believe.
What am I missing?
Session is created by Symfony\Component\FrameworkBundle\EventListener\SessionListener listener, on kernel.request event too (priority of 128).
This event has a specific behavior: if a listener sets a Response, "the process skips directly to the kernel.response event" to quote the documentation. I would suspect it could causes issues.
Try setting your listener a priority < 0 (I'm getting you tried many), and please checks the order the profiler "Events" section (/_profiler/latest?panel=events).
Found the solution - problem is the injection of the twig-environment in the constructor - without twig everything works as expected. I guess, loading the twig-environment at this stage does something to the session (like loading it too early).
I moved the listener to onKernelController and modified it:
<?php
namespace App\EventListener;
use App\Entity\Company;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;
class ShopListener implements EventSubscriberInterface
{
/** #var EntityManagerInterface */
protected $em;
/** #var Environment */
protected $twig;
public function __construct(EntityManagerInterface $entityManager, Environment $twig)
{
$this->em=$entityManager;
$this->twig=$twig;
}
public function onKernelController(ControllerEvent $controllerEvent)
{
if($controllerEvent->isMasterRequest()===false) {
return;
}
/** #var Request $request */
$request=$controllerEvent->getRequest();
$subDomain = $request->attributes->get('domain');
if($subDomain===null) {
return;
}
$company=$this->em->getRepository(Company::class)->findOneBy([
'subDomain' => $subDomain,
]);
if($company instanceof Company && $company->shopIsOnline()) {
$request->attributes->set('company',$company);
return;
}
$controllerEvent->setController(
function() {
return new Response($this->twig->render('page/shop_not_found.html.twig'),404);
}
);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => ['onKernelController',128],
];
}
}

Laravel - Event Subscriber Enqueue - When Subscribers Methods are in different class

Hello Good Developers,
I've tried using solution from This Question but it didn't solve my problem hence I am asking it as a separate question.
TL;DR - I want my subscriber Listeners to get executed in a queue asynchronously. My Subscriber calls method from different class on for each Event. I tried to add implements ShouldQueue in my SubscriberClass | My Listener Class | renamed method to handle in Each Class but nothing works. they are still executed synchronously without queue.
I've a Event Subscriber Class: TesterAPI. I added it in EventServiceProvider subscribe array.
I've an Event Class SourceAPIEvents
class SourceAPIEvents
{
const PROJECT_CREATED = 'project.created';
const PROJECT_PAUSED = 'project.paused';
const PROJECT_RESUME = 'project.resume';
const PROJECT_CLOSED = 'project.closed';
public $project;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Project $project)
{
$this->project = $project;
}
}
TesterAPI - Subscriber
class TesterAPI implements ShouldQueue
{
/**
* Register the listeners for the subscriber.
*
* #param \Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
SourceAPIEvents::PROJECT_CREATED,
'App\Listeners\Internal\Project\SourceAPI\Tester\ProjectCreate#handle'
);
$events->listen(
SourceAPIEvents::PROJECT_RESUME,
'App\Listeners\Internal\Project\SourceAPI\Tester\ProjectResume#handle'
);
$events->listen(
SourceAPIEvents::PROJECT_PAUSED,
'App\Listeners\Internal\Project\SourceAPI\Tester\ProjectPause#handle'
);
$events->listen(
SourceAPIEvents::PROJECT_CLOSED,
'App\Listeners\Internal\Project\SourceAPI\Tester\ProjectClose#handle'
);
}
}
Here are my Listener Classes:
class ProjectCreate extends TesterBase implements ShouldQueue
{
public function handle(SourceAPIEvents $sourceAPIEvent)
{
//Do Something
}
}
class ProjectPause extends TesterBase implements ShouldQueue
{
public function handle(SourceAPIEvents $sourceAPIEvent)
{
//Do Something
}
}
class ProjectResume extends TesterBase implements ShouldQueue
{
public function handle(SourceAPIEvents $sourceAPIEvent)
{
//Do Something
}
}
I am calling these events like this
event(SourceAPIEvents::PROJECT_CREATED, new SourceAPIEvents($project));
event(SourceAPIEvents::PROJECT_RESUME, new SourceAPIEvents($project));
Help me understand what I am doing wrong.

Injecting Services into a Service

I am trying to inject public services like entityManager in the constructor of a service I created but I keep having this error :
Too few arguments to function App\Services\BillingInterface::__construct(), 0 passed in /var/www/.../src/Controller/TestController.php on line 144 and exactly 1 expected.
In my controllers, services are correctly injected in different methods but in the service I created it's not injected in the constructor.
I didn't change anything in the services.yaml as the documentation says autowire is automatic in Symfony 4.2
PS : I recently updated from Symfony 4.1 to 4.2 and I'm not sure but I think it worked before.
Maybe a library didn't updated correctly but I don't find any errors.
Here are the informations for the service
Service code :
#/src/Services/BillingInterface
namespace App\Services;
use Doctrine\ORM\EntityManagerInterface;
class BillingInterface {
private $em;
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
}
Controller code :
namespace App\Controller;
use App\Services\BillingInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController {
public function teest(EntityManagerInterface $entityManager)
{
$billing = new BillingInterface();
}
}
And If I instantiate BillingInterface with $entityManager parameter of Controller, it works but I would like it injected directly in the BillingInterface class constructor.
And finally, here is what is written in Symfony's documentation :
// src/Service/MessageGenerator.php
// ...
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function getHappyMessage()
{
$this->logger->info('About to find a happy message!');
// ...
}
}
Link : https://symfony.com/doc/current/service_container.html
Chapter : Injecting Services/Config into a Service
So, I don't know what's wrong with my Service.
Thank you for your answers.
Since your BillingInterface is a service - you need to use its instance that is provided by Symfony container instead of attempting to instantiate it by yourself. Your controller needs to inject this service in order to be able to use it:
namespace App\Controller;
use App\Services\BillingInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
/**
* #var BillingInterface
*/
private $billing;
/**
* #param BillingInterface $billing
*/
public function __construct(BillingInterface $billing)
{
$this->billing = $billing;
}
public function teest(EntityManagerInterface $entityManager)
{
// Use $this->billing ...
}
}

Where should I put model saving event listener in laravel 5.1

The Laravel docs say I should put the model events in the EventServiceProvider boot() method like this.
public function boot(DispatcherContract $events)
{
Raisefund::saved(function ($project) {
//do something
});
}
But I have many models that I want to listen to.
So I was wondering if it is the right way to put it all in the EventServiceProvider.
Yes that's correct, the EventServiceProvider is the best place for it.
However you can create Observers to keep it clean. I will give you a quick example.
EventServiceProvider
<?php
namespace App\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use App\Models\Users;
use App\Observers\UserObserver;
/**
* Event service provider class
*/
class EventServiceProvider extends ServiceProvider
{
/**
* Boot function
*
* #param DispatcherContract $events
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
Users::observe(new UserObserver());
}
}
UserObserver
<?php
namespace App\Observers;
/**
* Observes the Users model
*/
class UserObserver
{
/**
* Function will be triggerd when a user is updated
*
* #param Users $model
*/
public function updated($model)
{
}
}
The Observer will be the place where the saved, updated, created, etc.. functions will be executed.
More information about Observers: http://laravel.com/docs/5.0/eloquent#model-observers
You can register listener callbacks in your models boot method, e.g.:
class User extends Eloquent {
protected static function boot() {
parent::boot();
static::deleting(function ($user) {
// deleting listener logic
});
static::saving(function ($user) {
// saving listener logic
});
}
}

Categories