how to deattach global event in cakephp 3 - php

created a global beforeFind() listener for my find queries but need to detach it for some requests..
$this->eventManager()->off() in controller is not working. i.e. not deattach the event.
In my bootstrap.php file :
$modelListerner = new DeletedListener(); //my custom listerner
EventManager::instance()->on(
$modelListerner
);

You cannot locally detach a global listener, you have to detach it globally, ie via
EventManager::getInstance()->off(/* ... */);
However you may want to consider whether passing options to the finder may be a better solution, so that your controllers do not have to know about listeners and the like, but just execute the find calls as needed, like
$Table->find('all', ['doThisAndThat' => false]);
and your listener could then act accordingly.
Quote from the docs
[...] Any options that are not in this list will be passed to beforeFind listeners where they can be used to modify the query object.
Cookbook > Database Access & ORM > Retrieveing Data & Result Sets > Using Finders to Load Data

Related

Test if Eloquent Observer`s method fires an event

The app I am working on fires an event, when one of the Eloquent model attributes is updated.
The Eloquent model is called Job and it is supposed to fire JobCustomerTotalAmountDueChanged when the duration attribute is updated. I have the following code in the JobObserver:
public function saved(Job $job)
{
if ($job->isDirty('duration')) {
event(new JobCustomerTotalAmountDueChanged($job));
}
}
When I try to test it using Event::fake, Eloquent events are not being fired, which means that the code in saved method is never execured. From what I see the assertDispatched and assertNotDispatched methods are only available for faked events. Is there a way to assert that a particular event is/is not fired without Event::fake?
The solution turned out to be very easy after all:
Laravel lets you specify which events should be faked, as an argument to fake method. In my example:
Event::fake([JobCustomerTotalAmountDueChanged::class])
All of the other events are being triggered and handled. Also, you can make assertions only with reference to events passed as argument to fake method. If you don't pass any argument, Laravel will try to 'fake' every event in the app.

Add data return in all actions with #Template annotation

I need to return default data in all actions in my Symfony project.
For example search form in side bar, viewers counter etc...
So i need to return some default data in all actions
return array(
'form' => $form->createView(),
'short_search' => $shortSearch->createView(),
);
I found Add data to return of all actions in a Symfony controller solution, but it fails when I'm using #Template annotation.
Of course i can call render function from twig, but it seems like it's not fast and good idea.
What component I should override in this case???
The Controllers section of the Symfony Best Practices document advises against using the #Template() annotation, so the easy fix to your problem would be to simply not use #Template().
The reason overriding the base Controller's render method doesn't work is because you're not actually calling it, and neither is the framework. Instead, the SensioFrameworkExtraBundle #Template annotation works by installing an event listener for KernelEvents::VIEW (kernel.view) and (after having used a different event to guess the template name, if necessary), directly uses the templating service to render the response.
In the generic case, what you can do instead is install an event listener on kernel.view with a higher priority, and using the GetResponseForControllerResultEvent event provided to add in your parameters. This event listener might look something like
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$parameters = $event->getControllerResult();
//modify parameters
$event->setControllerResult($parameters);
}
with whatever services necessary to get the additional parameters passed in via dependency injection. You may also want to look at the implementation for #Template's TemplateListener for reference.
The Symfony Cookbook has more information on how to set up event listeners.
In your specific case, you're probably going to be generating your $form and $shortSearch entirely from within that event handler, so at the least, your event handler is going to need at least the form service injected.
In my opinion, this is all largely more trouble than it's worth, and it would be better to just remove the #Template annotation instead. (As a bonus, you'll get a minor performance boost, especially if you disable the annotations entirely, because you won't have the overhead of calling those event listeners on every request.)

Symfony2 impersonate listener with parameters injection

i'm currently working on a custom logic impersonate listenener.
i overrided the Symfony\Component\Security\Http\Firewall\SwitchUserListener because i would like to perform call to some entities repositories ( not User entity ) before authorize the switch event :
for example , i would like to authorize the switch if and only if the user to impersonate has already gave rights to user requesting for the switch.
is it possible to inject new parameters such as doctrine service or some arrays values to an overrided listener ?
the call to my custom SwitchUserListenener :
in services.yml
parameters:
security.authentication.switchuser_listener.class: acme\appBundle\EventListener\SwitchUserListener
Your solution may be here:
https://github.com/symfony/symfony/blob/2.6/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php#L597
The service security.authentication.switchuser_listener defined by Symfony is an abstract service. It is not actually the service being used as the listener, but it is the same class.
Go up in the code a bit to:
https://github.com/symfony/symfony/blob/2.6/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php#L233
foreach ($firewalls as $name => $firewall) {
list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds);
This is the name of the firewall you created where the user switch will be possible.
Now you have a couple of options to override the listener.
You can continue to override the class parameter, then manipulate the arguments as Symfony is already doing:
$listener->replaceArgument(1, new Reference($userProvider));
$listener->replaceArgument(3, $id);
$listener->replaceArgument(6, $config['parameter']);
$listener->replaceArgument(7, $config['role']);
Or more simply, compile additional method calls in your extension.
The other solution, would be to create a CompilerPass which systematically deletes the original listeners and replaces them with your own. This might be achievable in your own bundles extension provided it is loaded after the SecurityBundle.
$container->removeDefinition($id);
EDIT:
Another solution, perhaps more simple than the above, is to create another event listener.
One which fires on the request. The important thing here is the listener must have a higher priority to the listeners you are overriding (I used listener(s) because there are potentially more than one switch user listener).
The tricky part will be getting a list of the overridden listeners IDs, because you are going to need to loop over them and call your own methods to inject your new dependencies after the listeners are instantiated, but before they triggered.

ZF2: Where does the controller return/result get manipulated by the MVC?

I am working on a RESTful API in Zend Framework 2. I use the AbstractRestfulController to handle requests. In that controller I want to directly return an array or object (of a custom model class), not a JsonModel. That stuff and the conversion of the result to arrays (as a preparation for the JSON encoding) should happen later automatically.
I tried to catch the MvcEvent::EVENT_DISPATCH event to manipulate the $e->getResult()value but when I return an associative array in the controller I get a ViewModel in the result instead of the plain data passed in by the controller method.
I tried to create a custom view strategy by implementing the ListenerAggregateInterface class. In the ViewEvent::EVENT_RESPONSE event catch, I dumped the $e->getResult() value and got the already encoded result. Also here, I need the plain result to prepare it for encoding.
Where can I hook in to manipulate the controller's plain return value before encoding to JSON? Where does ZF2 generally manipulate the returned value in the MVC lifecycle?
Thank you in advance!
I don't have so much time to investigate in this issue, but my guess is that you may omit to stop event propagation when you attached a listener to MvcEvent::EVENT_DISPATCH. So what you set as the event result is probably overridden later in the dispatch process.
Try to attach your callback with a higher priority and stop the propagation ($e->stopPropagation() from the callback), since the event manager won't stop the propagation by itself until one or the other callback returns a Zend\StdLib\ResponseInterface object.
Anyway, I know you probably have good reasons trying to "hack" the dispatch process this way, but I tend to think that there could be even better reasons to adapt your code to fit the default process ;)
Here, zf2 creates a ViewModel in case an assoc array is detected in the result, registered at priority -80. According to this documentation page this is the first event catch changing the result returned by the controller.
What I did wrong is attaching my MvcEvent::EVENT_DISPATCH to the $application->getEventManager() instead of $application->getEventManager()->getSharedManager() (like zf2 does). I don't understand yet what the difference is, but it works.
Here is how I registered the event in the onBootstrap method:
$application->getEventManager()->getSharedManager()->attach(
__NAMESPACE__,
MvcEvent::EVENT_DISPATCH,
array($this, 'createJsonModelFromResult'),
-10
// priority of -10 because at priority 1 the mvc
// runs the actual controller method
);
At the end of the createJsonModelFromResult method I also added $e->stopPropagation(); because we already found the matching ViewModel.
Now the $e->getResult() value is exactly the same as returned in my controller actions and I can prepare it for JSON encoding. Cool!
I have no idea how you want to return an array or Object. Usually people make a DTO (Data Transfer Object), serialize it, and then send it. In this method the DTO at the end converts to JSON.
What format do you want to use in response? String (serialized DTO), XML, or something else? You can create a class extending ViewModel and serialize your array for your format.
Anyway, you do not need to get MVC_EVENT.

Where to put custom initialization code in Symfony2?

Let's say I've two services:
get('my_first_service')
get('my_second_service')
I want to do something like this:
$this->get('my_first_service')->doSomething($this->get('my_second_service'));
This is some initialization code that must be active everywhere in the project. As far as I see I have a few options (both services originates from the vendor-dir (haven't writtem them myself)):
Add the above code (with some modifications) in app.php and/or app_dev.php
Add the above code to every controller action
Create a service with the services as arguments and add the above code in the constructor
The first option seems a little bit "unusual" as I don't link editing the app.php file for something link this. The second option is too inefficient as I need to do this for dozens of actions. The third action seems better but I still need to call this service from somewhere in order to execute the constructor right?
I have the feeling I'm missing a possibility... there must be a better place for this kind of initialization code right?
If you really want to do execute your code before EVERY action, you can listen for the event kernel.controller.
Check this example in the cookboook.
Your listener is a service so you can inject both of your services if needed.
That looks very similar to setter injection. You could add doSomething as a call, with my_second_service as a parameter. That way when you get the first_service, the method will be called automatically, with the correct parameter passed in, and saves you from an ugly hack.
I assume you are already defining the services in configuration.
Eg in YAML:
services:
my_second_service:
# ...
my_first_service:
class: MyFirstServiceClassName
calls:
- [ doSomething, [ #my_second_service ] ]
If doSomething is returning a different object that you need to use, then it sounds like a factory, which you can also configure. Read through this.

Categories