Load Symfony subscriber from package - php

I am currently developing a library that will be used to intercept incoming exceptions, log them, and pass the to a separate micro-service for handling logs. So inside of the composer lib I am developing I need to have a event subscriber that will listen for exceptions and if the class that throws the exceptions extend our interface log it. Currently I have the interface
namespace LogLibrary;
interface ExceptionLoggerInterface
{
}
And the event subscriber
final class ExceptionSubscriber implements EventSubscriberInterface
{
public function __construct()
{
$this->logger = new LoggerService();
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::EXCEPTION => ['logException', 0],
];
}
public function logException(ResponseEvent|ExceptionEvent $event): void
{
dd($event);
$exception = $event->getThrowable();
if ($exception instanceof ExceptionLoggerInterface) {
$this->logger->logError($exception);
}
}
}
And finally on any class from where I have to catch exception I have this
class UpdatePinCodeController implements ExceptionLoggerInterface
{...}
The problem is to actually make the subscriber work. I have the default doctribe setup, autowire is true, I followed the docs extensively. Any help on how to register the subscriber without actually writing anything in the services.yaml file?

Related

Adding events to Laminas

I am trying to add event for Laminas Framework that will fire when \Laminas\Mvc\MvcEvent::EVENT_DISPATCH is triggered. But absolutelly nothing happends, like this triggers not exists. What am I doing wrong?
This is the code under the module\Application\src\Module.php:
use Laminas\ModuleManager\ModuleManager;
use Laminas\Mvc\MvcEvent;
class Module
{
public function init(ModuleManager $moduleManager)
{
ini_set("display_errors", '1');
$eventManager = $moduleManager->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch']);
}
public function onDispatch(\Laminas\EventManager\Event $event)
{
var_dump('ok');die;
}
}
I think you need use another method in Module it's should be something like this:
use Laminas\Mvc\MvcEvent;
class Module
{
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch']);
}
public function onDispatch(MvcEvent $event)
{
var_dump('ok');
die;
}
}
In this case it onBootstrap. Hope help you
On init you'll need to get the shared event manager from the module manager:
<?php
use Laminas\ModuleManager\Feature\InitProviderInterface;
use Laminas\ModuleManager\ModuleManagerInterface;
use Laminas\Mvc\Application;
use Laminas\Mvc\MvcEvent;
final class Module implements InitProviderInterface
{
public function init(ModuleManagerInterface $manager): void
{
$sharedEventManager = $manager->getEventManager()->getSharedManager();
$sharedEventManager->attach(
Application::class,
MvcEvent::EVENT_DISPATCH,
function () {
var_Dump('dispatch from init');
}
);
}
}
The SharedEventManager is usually (or should be) shared between all event manager instances. This makes it possible to call or create events from other event manager instances. To differentiate between event names an identifier is used (so you can have more then one event with the same name). All MvcEvents belong to the Laminas\Mvc\Application identifier. Laminas\ModuleManager\ModuleManager has it's own EventManager instance, that is why you'll need to add the event to the SharedEventManager (init() is called by the ModuleManager and Laminas\ModuleManager\ModuleEvent is used).
onBootstrap() will be called by Laminas\Mvc\Application, that why you get the correct EventManager instance there.
As #Dimitry suggested: you should add that event in onBootstrap() as the dispatching process is part of the application and not the module manager. In init() you should only add bootstrap events.
And btw: you should use the Laminas\ModuleManager\Feature\* interfaces to make your application a bit more robust to future updates.

Symfony: Best place to catch exception (CQRS/DDD)

I've a personal application. I use design pattern CQRS/DDD for a API.
Schema:
User --> Controller (dispatch command) --> Command handler --> some services...
In my Rest API controller
$this->dispatch($cmd);
If a throw a exception in services or specification classes for example, ok, I've a listener to catch exception and create JSON response error.
But if I want to develop an interface module with TWIG, I think I will not use my listener because I don't want a JSON response.
Should I used try/catch in my controller of my new interface module ?
SomeController extends AbstractController
{
public function getObject($id)
{
try {
$this->dispatch($cmd);
catch(SomeException $ex) {
$this->render(....)
}
}
}
Where is the best place to catch exception for TWIG ?
Thanks.
Edit:
#Cid
if (some conditions && $form->handleRequest($request)->isValid()) --> My handler don't return bool or values.
Imagine this code. Imagine I want share a service between an API and web view app.
class ApiController
{
public function register()
{
$this->dispatch($cmd);
}
}
class WebController
{
public function register()
{
$this->dispatch($cmd);
}
}
class SomeHandler implements CommandHandlerInterface
{
/** #required */
public RegisterService $service;
public function __invoke(SomeCommand $command)
{
$this->service->register($command->getEmail())
}
}
class RegisterService
{
public function register(string $email)
{
// Exception here
}
}
So, I think the best place to handle Exception is EventSubscriber, see here: https://symfony.com/doc/current/reference/events.html#kernel-exception
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
public function onKernelException(ExceptionEvent $event)
{
$exception = $event->getThrowable();
$response = new Response();
// setup the Response object based on the caught exception
$event->setResponse($response);
// you can alternatively set a new Exception
// $exception = new \Exception('Some special exception');
// $event->setThrowable($exception);
}

Refactoring a Controller in symfony to adapt to hexagonal architecture

I have create a controller that creates a Owner record into database. Everything was done on the CreateOwnerController like this and working properly:
class CreateOwnerController extends Controller
{
public function executeAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$owner = new Owner($request->request->get("name"));
$em->persist($owner);
$em->flush();
return new Response('Added',200);
}
}
Now,In order to refactor that I have created an interface that defines the OwnerRepository:
interface OwnerRepositoryInterface {
public function save(Owner $owner);
}
And a OwnerRepository that implements this interface:
class OwnerRepository extends EntityRepository implements OwnerRepositoryInterface {
public function save(Owner $owner) {
$this->_em->persist($owner);
$this->_em->flush();
}
}
Then I have Created for the application layer a CreateOwnerUseCase Class that receives a OwnerRepository and executes a method to save in into OwnerRepository:
class CreateOwnerUseCase {
private $ownerRepository;
public function __construct(OwnerRepositoryInterface $ownerRepository) {
$this->ownerRepository = $ownerRepository;
}
public function execute(string $ownerName) {
$owner = new Owner($ownerName);
$this->ownerRepository->save($owner);
}
}
Ok, i'm spliting the initial Controller intro layer Domain / Aplication / Framework layers.
On the CreateOwnerController now i have instantiated that Use Case and passed as parameter the OwnerRepository like this:
class CreateOwnerController extends Controller {
public function executeAction(Request $request) {
$createOwnerUseCase = new CreateOwnerUseCase(new OwnerRepository());
$createOwnerUseCase->execute($request->request->get("name"));
return new Response('Added',200);
}
}
But it fails when Make the request to create new Owner:
Warning: Missing argument 1 for Doctrine\ORM\EntityRepository::__construct(), called in /ansible/phpexercises/Frameworks/mpweb-frameworks-symfony/src/MyApp/Bundle/AppBundle/Controller/CreateOwnerController.php
It happens on OwnerRepository passed as parameter. It wants an $em and Mapped Class... What is the meaning of this mapped Class? How solve this error?
This answer is for Symfony 3.3+/4+.
You need to register your repository as a service. Instead of extending it 3rd party code, you should use composition over inheritance.
final class OwnerRepository implements OwnerRepositoryInterface
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function save(Owner $owner)
{
$this->entityManager->persist($owner);
$this->entityManager->flush();
}
}
And register it as a service:
# app/config/services.yml
services:
App\Repository\:
# for location app/Repository
resource: ../Repository
You might need to tune paths a bit, to make that work.
To get more extended answer, see How to use Repository with Doctrine as Service in Symfony

Symfony - Event not being dispatched

This is the first time ever I am working with creating custom event dispatcher and subscriber so I am trying to wrap my head around it and I cant seem to find out why my custom event is not being dispatched.
I am following the documentation and in my case I need to dispatch an event as soon as someone registers on the site.
so inside my registerAction() I am trying to dispatch an event like this
$dispatcher = new EventDispatcher();
$event = new RegistrationEvent($user);
$dispatcher->dispatch(RegistrationEvent::NAME, $event);
This is my RegistrationEvent class
namespace AppBundle\Event;
use AppBundle\Entity\User;
use Symfony\Component\EventDispatcher\Event;
class RegistrationEvent extends Event
{
const NAME = 'registration.complete';
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function getUser(){
return $this->user;
}
}
This is my RegistrationSubscriber class
namespace AppBundle\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class RegistrationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => array(
array('onKernelResponsePre', 10),
array('onKernelResponsePost', -10),
),
RegistrationEvent::NAME => 'onRegistration'
);
}
public function onKernelResponsePre(FilterResponseEvent $event)
{
// ...
}
public function onKernelResponsePost(FilterResponseEvent $event)
{
// ...
}
public function onRegistration(RegistrationEvent $event){
var_dump($event);
die;
}
}
After doing this, I was hoping that the registration process would stop at the function onRegistration but that did not happen, I then looked at the Events tab of the profiler and I do not see my Event listed their either.
What am I missing here? A push in right direction will really be appreciated.
Update:
I thought i need to register a service for the custom event so I added the following code inside services.yml
app.successfull_registration_subscriber:
class: AppBundle\Event\RegistrationSubscriber
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: kernel.event_subscriber}
Inside the Event tab of profiler I do see my custom event being listed but it still does not dispatch.
By creating your own EventDispatcher instance you dispatch an event that can never be listened to by other listeners (they are not attached to this dispatcher instance). You need to use the event_dispatcher service to notify all listeners you have tagged with the kernel.event_listener and kernel.event_subscriber tags:
// ...
class RegistrationController extends Controller
{
public function registerAction()
{
// ...
$this->get('event_dispatcher')->dispatch(RegistrationEvent::NAME, new RegistrationEvent($user););
}
}
Duplicate of dispatcher doesn't dispatch my event symfony
With auto-wiring, it is now better to inject the EventDispatcherInterface
<?php
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
//...
class DefaultController extends Controller
{
public function display(Request $request, EventDispatcherInterface $dispatcher)
{
//Define your event
$event = new YourEvent($request);
$dispatcher->dispatch(YourEvent::EVENT_TO_DISPATCH, $event);
}
}

Zend framework 2 translator in model

How to get translator in model?
Inside view we can get translator using this code
$this->translate('Text')
Inside controller we can get translator using this code
$translator=$this->getServiceLocator()->get('translator');
$translator->translate("Text") ;
But how to get translator in model?
I'd tried so many ways to get service locator in models
2 of those
1)Using MVC events
$e=new MvcEvent();
$sm=$e->getApplication()->getServiceManager();
$this->translator = $sm->get('translator');
if i pring $sm it is showing null. but it works fine in Model.php onBootstrap
2)Created one model which implements ServiceLocatorAwareInterface
SomeModel.php
<?php
namespace Web\Model;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class SomeModel implements ServiceLocatorAwareInterface
{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $locator)
{
$this->services = $locator;
}
public function getServiceLocator()
{
return $this->services;
}
}
and used that inside my model
$sl = new SomeModel();
$sm=$sl->getServiceManager();
var_dump($sm); exit;
$this->translator = $sm->get('translator');
this is also printing null.
If you don't need the servicemanager instance in your model, simply inject translator instance to it.
For example:
// Your model's constructor
class MyModel {
// Use the trait if your php version >= 5.4.0
use \Zend\I18n\Translator\TranslatorAwareTrait;
public function __construct( $translator )
{
$this->setTranslator( $translator );
}
public function modelMethodWhichNeedsToUseTranslator()
{
// ...
$text = $this->getTranslator()->translate('lorem ipsum');
// ...
}
}
When you creating your model first time on service or controller level
class someClass implements ServiceLocatorAwareInterface {
public function theMethodWhichCreatesYourModelInstance()
{
// ...
$sm = $this->getServiceLocator();
$model = new \Namespace\MyModel( $sm->get('translator') )
// ...
}
}
If you need to instantiate your model (new MyModel();) on multiple methods/classes, consider to writing a factory for it.
Here is a nice article about Dependency Injection and PHP by Ralph Schindler for more detailed comments about this approach.
For your Model class to be ServiceLocatorAware, you not only need to implement the interface, you also need to make your model a service of the service manager, and fetch the model from there.
Add your model to the service manager, since it doesn't appear to need any constructor params, it's invokable, so you can add it to the invokables array in service manager config. You can do that by using the getServiceConfig() method in your Module class...
class Module
{
public function getServiceConfig()
{
return array(
'invokables' => array(
'SomeModel' => 'Fully\Qualified\ClassName\To\SomeModel',
),
);
}
}
Then, instead of calling the new keyword to create your model instance, you fetch it from the service manager, for instance, by calling the getServiceLocator() method in a controller action...
public function fooAction()
{
$sm = $this->getServiceLocator();
$model = $sm->get('SomeModel');
}
When your model is fetched from the service manager, a service initializer will look to see if it implements the ServiceLocatorAwareInterface and automatically call setServiceLocator() if it does, passing it an instance of the service manager.

Categories