Symfony 5 Messenger not auto configuring message handler - symfony

I am new to Symfony framework and am trying to set up a project with Messenger (https://symfony.com/doc/current/messenger.html). My understanding is after setting up the handler class with “implements MessageHandlerInterface” the handler should be available for use but it does not seem to be working for me. I have tried several different things including setting up a new project from scratch. I am using Symfony 5.0.4.
I set up the project like this:
symfony new --full testMessenger
composer require messenger
php bin/console make:controller
Then I made a new messenger and handler and called it from the controller. Doing that I get the exception: “No handler for message "App\Message\Message".”
php bin/console debug:messenger
Output:
Messenger
=========
messenger.bus.default
---------------------
The following messages can be dispatched:
-----------------------------------------------------  
 Symfony\Component\Mailer\Messenger\SendEmailMessage   
     handled by mailer.messenger.message_handler       
 Symfony\Component\Notifier\Message\ChatMessage        
     handled by chatter.messenger.chat_handler         
 Symfony\Component\Notifier\Message\SmsMessage         
     handled by texter.messenger.sms_handler           
-----------------------------------------------------  
My code is basically the same as the samples in the message handler documentation, but I will add it here.
// src/Message.php
<?php
namespace App\Message;
class Message
{
private $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
// src/MessageHandler.php
<?php
namespace App\MessageHandler;
use App\Message\Message;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class SmsNotificationHandler implements MessageHandlerInterface
{
public function __invoke(Message $message)
{
// ... do some work - like sending an SMS message!
}
}
// src/Controller/MessageController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use App\Message\Message;
class MessageController extends AbstractController
{
/**
* #Route("/message", name="message")
*/
public function index()
{
$this->dispatchMessage(new Message('Look! I created a message!'));
return $this->render('message/index.html.twig', [
'controller_name' => 'MessageController',
]);
}
}
//config/packages/messenger.yaml
framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
# async: '%env(MESSENGER_TRANSPORT_DSN)%'
# failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
# Route your messages to the transports
# 'App\Message\YourMessage': async

I've struggled a little with similar case, so I hope this will help someone:
So I had autowiring on and multiple buses defined like this:
final class QueryBus implements QueryBusInterface
{
use HandleTrait {
HandleTrait::handle as messengerHandle;
}
public function __construct(MessageBusInterface $messageBus)
{
$this->messageBus = $messageBus;
}
public function handle(object $query): mixed
{
return $this->messengerHandle($query);
}
}
final class CommandBus implements CommandBusInterface
{
use HandleTrait;
public function __construct(
MessageBusInterface $messageBus
) {
$this->messageBus = $messageBus;
}
public function dispatch(object $command)
{
return $this->handle($command);
}
}
// messenger.yaml
framework:
messenger:
default_bus: command_bus
buses:
command_bus: ~
query_bus: ~
And I got the same error for when dispatching a query "No handler for message..."
Eventually what helped was:
Turn off autowiring in Bus implementation directories (by excluding paths in services.yaml.
App\:
resource: '../src/'
exclude:
- '../src/Bus/'
Manually define the services:
// services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
App\src\Bus\CommandBus:
arguments:
- '#command_bus'
App\src\Bus\QueryBus:
arguments:
- '#query_bus'
So actually it seems it was autowiring issue - failing to autowire 2 implementations of same Interface (the MessageBusInterface). The actual error of missing service was hiding under the messenger component.

Related

How to override translator in symfony 5.2

I'm trying to override translator class in Symfony 5.2. I tried this:
# config/services.yaml
services:
# ....
App\Translator:
decorates: translator
and this (App\Translator implements TranslatorInterface):
# config/services.yaml
services:
# ....
App\Translator:
arguments:
$translator: '#translator'
Symfony\Contracts\Translation\TranslatorInterface: '#App\Translator'
both methods work well in PHP code, but in development mode in the twig, translator service is still DataCollectorTranslator. So in twig templates the translator service remains not overridden. How can I fix it?
It's possible I am not understanding the question. If something works in one mode but not another then sometimes just deleting the var/cache directory and building a new cache with bin/console cache:clear might work.
Decorating services can be a bit interesting sometimes. I created a fresh 5.2 project and then added:
# src/Translation/Translation.php
namespace App\Translation;
use JetBrains\PhpStorm\Pure;
use Symfony\Component\Translation\MessageCatalogueInterface;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Bundle\FrameworkBundle\Translation\Translator as BaseTranslator;
class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
{
// Uses PHP8 constructor promotion
public function __construct(private BaseTranslator $translator)
{
}
#[Pure]
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
//return $this->translator->trans($id,$parameters,$domain,$locale);
return strtoupper($id); // Verify calling this class
}
public function getCatalogue(string $locale = null): MessageCatalogueInterface
{
return $this->translator->getCatalogue($locale);
}
#[Pure]
public function getLocale(): string
{
return $this->translator->getLocale();
}
public function setLocale(string $locale)
{
$this->translator->setLocale($locale);
}
}
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
App\Translation\Translator:
decorates: translator
# index.html.twig
<li>{{ 'Hello' | trans }}</li>
You can disregard the Pure stuff as well as some of the PHP8 stuff. I was using this as a PHP8 test as well.
But it all seems to work as advertised.

How to globally define configuration of Serializer used in API Platform

In order to user the #MaxDepth annotation in entities, the enable_max_depth property has to be set explicitly in serializer context (e.g. in config of the #ApiPlatform annotation), so on entity level, so for each entity
Is there a way to define this property enable_max_depth=true for all entities of the project ? Something we could find in api-platform.yaml and which will look like that :
api-platform:
serializer:
enable_max_depth: true
There is no such global option for now (it can be worth adding it, PR welcome).
However, you can register a SerializerContextBuilder to add this context entry automatically for all resources:
<?php
namespace App\Serializer;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
final class MaxDepthContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
public function __construct(SerializerContextBuilderInterface $decorated)
{
$this->decorated = $decorated;
}
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
$context['enable_max_depth'] = true;
return $context;
}
}
Then register this new class as a service decorator:
# api/config/services.yaml
services:
# ...
'App\Serializer\MaxDepthContextBuilder':
decorates: 'api_platform.serializer.context_builder'
autoconfigure: false
autowire: true

How to configure symfony2/3 to handle different domains serving different views?

Is it possible to configure symfony2/3 to handle more than 1 domain with different views?
For example I have site1.com and site2.com, I would create a site1 and site2 folders inside app/Resources/views and serve a different set of templates depending on the domain.
Models and controllers should be in common so site1.com/mypage and site2.com/mypage should serve the same content with different layout.
Any suggestion or best practice related to it is welcome.
Thanks
Check for the host in your controller :
namespace Acme\FooBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
class DemoController
{
public function showAction(Request $request)
{
switch($request->getHost())
{
case 'site1.com':
return $this->render('site1/show.html.twig');
break;
case 'site2.com':
return $this->render('site2/show.html.twig');
break;
default:
return $this->render('default/show.html.twig');
}
}
}
EDIT : Something more generic
Create a onKernelRequest listener :
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class DomainRequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$host = explode('.',$request->getHost());
$request->request->attributes->set('_domain',$host[0]);
}
}
Add this listener in services.yml :
app.listener.domain_request:
class: AppBundle\EventListener\DomainRequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
Then you can use the '_domain' routing parameter in all your controllers :
return $this->render($request->attributes->get('_domain').'/show.html.twig');
Not tested, but I expect the following should work. You'll want to register a kernel request listener that uses the Twig loader service (responsible for locating the templates) and registers a path based on the request's hostname.
Create a request listener:
<?php
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class RegisterTwigPathSubscriber implements EventSubscriberInterface
{
private $loader;
public function __construct(\Twig_Loader_Filesystem $loader)
{
$this->loader = $loader;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => 'registerTwigPath'
];
}
public function registerTwigPath(GetResponseEvent $event)
{
$host = $event->getRequest()->getHost();
$path = '...'; // determine path based on hostname
$this->loader->addPath($path, 'Theme'); // the second argument is a namespace for templates located under this folder and can be chosen
}
}
Register the event listener:
services:
register_twig_path_listener:
class: RegisterTwigPathSubscriber
arguments: ["#twig.loader"]
tags: [{ name: kernel.event_subscriber }]
Now to reference the template:
return $this->render('#Theme/path/to/actual/template.html.twig');

symfony2 logger in profiler

I looked for this info in this forum and many others but I can't understand how to log messages in the profiler.
I attempt to log messages from my controller.
CalendarController.php :
<?php
namespace DJU\CalendarBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Psr\Log\LoggerInterface;
class CalendarController extends Controller
{
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function getweekAction() {
$this->logger->info("this is a test message");
}
}
According to what I've read in the symfony doc, I've also modified the config.yml
app/config/config.yml
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
console:
type: console
bubble: false
verbosity_levels:
VERBOSITY_VERBOSE: INFO
VERBOSITY_VERY_VERBOSE: DEBUG
channels: ["!doctrine"]
console_very_verbose:
type: console
bubble: false
verbosity_levels:
VERBOSITY_VERBOSE: NOTICE
VERBOSITY_VERY_VERBOSE: NOTICE
VERBOSITY_DEBUG: DEBUG
channels: ["doctrine"]
This is a simple copy paste from the config_dev.yml.
No log appears in the profiler.
Any suggestion?
Thank you
If you're using constructor in controller class then you should use controller as service to inject logger there.
Normally if you want to call logger service from controller you can simply get it from container:
class CalendarController extends Controller
{
public function getweekAction() {
$this->get('logger')->info("this is a test message");
}
}
what version of symfony? if you're on symfony 2.6 or higher you can just do dump('some log'); and it will appear in the profiler.
http://symfony.com/blog/new-in-symfony-2-6-vardumper-component

Symfony Custom Error Page By Overriding ExceptionController

what I am trying to do is to have custom error page, not only will they be extending the base layout but also I want extra up selling content in those pages too so changing templates only is not an option
regardless of the reason (404 Not Found or just missing variable) I would like to show my template and my content instead
I have spent hours trying to get this going with no luck
app/console --version
Symfony version 2.5.6 - app/dev/debug
I tried some resources, but couldn't get it working. The name a few:
http://symfony.com/doc/current/reference/configuration/twig.html
http://symfony.com/doc/current/cookbook/controller/error_pages.html
I'm running in dev with no debug, see app_dev.php below:
$kernel = new AppKernel('dev', false);
following the tutorials i got these extra bits
app/config/config.yml
twig:
exception_controller: SomethingAppBundle:Exception:show
in my bundle
<?php
namespace Something\AppBundle\Controller;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ExceptionController extends Controller
{
public function showAction( FlattenException $error, DebugLoggerInterface $debug)
{
print_r($error);
}
}
but my error controller does not get executed,
I am on purpose causing error by trying to echo undefined variable in different controller, since it should handle error from entire application
At the beginning you need to create action in the controller:
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ErrorController extends Controller
{
public function notFoundAction()
{
return $this->render('error/404.html.twig');
}
}
Then you need to create a Listener:
<?php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class NotFoundHttpExceptionListener
{
private $controller_resolver;
private $request_stack;
private $http_kernel;
public function __construct($controller_resolver, $request_stack, $http_kernel)
{
$this->controller_resolver = $controller_resolver;
$this->request_stack = $request_stack;
$this->http_kernel = $http_kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->getException() instanceof NotFoundHttpException) {
$request = new \Symfony\Component\HttpFoundation\Request();
$request->attributes->set('_controller', 'AppBundle:Error:notFound');
$controller = $this->controller_resolver->getController($request);
$path['_controller'] = $controller;
$subRequest = $this->request_stack->getCurrentRequest()->duplicate(array(), null, $path);
$event->setResponse($this->http_kernel->handle($subRequest, HttpKernelInterface::MASTER_REQUEST)); // Simulating "forward" in order to preserve the "Not Found URL"
}
}
}
Now register the service:
#AppBundle/Resources/config/services.yml
services:
kernel.listener.notFoundHttpException:
class: AppBundle\EventListener\NotFoundHttpExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: -10 }
arguments: [ #controller_resolver, #request_stack, #http_kernel ]
Not tested this, but rather it should work;)
EDIT:
Tested, it works. On the rendered page, you have a session, so you have access to app.user, his cart, and other matters related to the session.

Resources