In my controller after response I have to do some work. What is better to use:
1. Listen to kernel.terminate event
or
2. Dispatch my custom event
?
Why kernel.terminate?
As you can see, by calling $kernel->terminate after sending the
response, you will trigger the kernel.terminate event where you can
perform certain actions that you may have delayed in order to return
the response as quickly as possible to the client (e.g. sending
emails).
But on the other hand is it ok to check every request in my subscriber?
kernel.terminate happens after the response is sent, and can be useful for some "heavy" operations you can perform after the client has received the response. There are a few downsides however, mainly that if something goes wrong, there is no way to give the appropriate feedback to the user (for example to try again or to report a problem). Additionally, not all errors may be logged (see https://github.com/symfony/symfony/issues/19078).
Since you want to publish jobs to a Gearman queue, I would suggest avoiding using kernel.terminate, since typically publishing a job does not involve significant resources, and should be possible to do before sending the response. So you could trigger your custom event, or perhaps even avoid the event dispatcher completely by doing a more explicit call in your controller.
You won't be able to have your own event doing work after the response without using kernel.terminate. Because this is the only action that may occur after the response. We can confirm this by having a look at the front controller app.php:
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
As a note, kernel.terminate will work only if you use PHP-FPM. Otherwise, no solution outside of using some message queue.
Finally, a common pattern is to dynamically add a listener on kernel.terminate. From inside your controller, assuming you need to call my_service:
$myService = $this->get('my_service');
$this->get('event_dispatcher')->addListener('kernel.terminate', function (Event $event) use (myService) {
$myService->doSomething();
});
Related
I am wondering how to deal with this. I have a webhook endpoint which responds to a webhook call from Github.
It starts a long running process in where it clones the repository from which the webhook call was made.
/**
* The webhook endpoint.
*
* #param Request $request
* #return mixed
* #throws \Exception
*/
public function webhook(Request $request)
{
// The type of GitHub event that we receive.
$event = $request->header('X-GitHub-Event');
$url = $this->createCloneUrl();
$this->cloneGitRepo($url);
return new Response('Webhook received succesfully', 200);
}
The problem with this is that Github gives an error when the response is not provided soon enough.
We couldn’t deliver this payload: Service Timeout
This is rightfully so because my cloneGitRepo method is simply blocking the execution of the response and it is taking too long.
How can I still deliver a response to acknowledge to Github that the webhook call has been made successfully and start my long running process?
I am using Laravel for all of this with Redis, maybe something can be accomplished there? I am open to all suggestions.
What you're looking for is a queued job. Laravel makes this very easy with Laravel Queues.
With queues, you setup a queue driver (database, redis, Amazon SQS, etc), and then you have one to many queue workers that are continuously running. When you put a job on the queue from your webhook method, it will be picked up by one of your queue workers and run in a separate process. The act of dispatching a queued job to a queue is very quick, though, so your webhook method will return quickly while the real work is being done by the queue worker.
The linked documentation has all the details, but the general process will be:
Setup a queue connection. You mention you're already using redis, I would start with that.
Use php artisan make:job CloneGitRepo to create a CloneGitRepo job class.
It should implement the Illuminate\Contracts\Queue\ShouldQueue interface so that Laravel knows to send this job to a queue when it is dispatched.
Make sure to define properties on the class for any data you pass into the constructor. This is necessary so the worker can rebuild the job correctly when it is pulled off the queue.
The queue worker will call the handle() method to process the job. Any dependencies can be type hinted here and they will be injected from the IoC container.
To dispatch the job to the queue, you can either use the global dispatch() helper function, or call the static dispatch() method on the job itself.
dispatch(new CloneGitRepo($url));
CloneGitRepo::dispatch($url);
So, your webhook would look like:
public function webhook(Request $request)
{
// The type of GitHub event that we receive.
$event = $request->header('X-GitHub-Event');
$url = $this->createCloneUrl();
CloneGitRepo::dispatch($url);
return new Response('Webhook received succesfully', 200);
}
I am testing a controller action using a functional test in Symfony. In this test I am doing something like this:
$client->request(
'PUT',
'/api/nodes/',
$data
);
Afterwards I would like to test if a certain event has been dispatched. I already tried to enable the profiler previously (and set the config accordingly) and check the data in the EventDataCollector:
$client->enableProfiler();
$client->request(
'PUT',
'/api/nodes/' . $data[0]['id'] . '?webspace=sulu_io&language=en',
$data[0]
);
/** #var EventDataCollector $eventDataCollector */
$eventDataCollector = $client->getProfile()->getCollector('events');
This works as expected, but the problem is that the $eventDataCollector only contains data about the events for which some listeners have actually been executed. Fortunately there is an event listener executed in this specific case, but I would like that to work also without any event listeners attached, since I can't say for sure that this situation will continue to be like that.
So my question is if there is a way to test if a event is dispatched, which is save, even if there wasn't a event listener attached.
You could register an event listener/subscriber in your test environment only. Its sole purpose would be to enable you to inspect if the event was fired.
Yagni. Functional tests should be based on the specifications, e.g. sending some data to PUT /api/nodes/ HTTP/1.1 should result with something (ideally) valuable for API consumers. Some data manipulations, I suppose. The test should confirm the output matches expectations for specific data permutations.
Event listening is an internal implementation of your black box and is not subject of functional testing. It should be tested in isolation. Enabling profiler, you basically change the system under test, and end up testing something that only partially related to the production code.
What is the correct way to stop code execution after sending Response headers, but without using exit()?
I know the script SHOULD return a Response, but how can I force it to be returned from outside of a Controller, for example from a service?
Lets say my service's method does return a Response this way:
return RedirectResponse($url)->send();
But in another place it can return other things. So what I can do is to check what it does actually return:
$result = $myService->doSomething();
if ($result instanceof RedirectResponse) {
return $result;
}
What I want to achieve is to avoid checking result type in every place where I use my service, BUT I would like to see the returned response in Profiler/logs (if I use exit() I can't).
Is there a way to force kernel terminate?
EDIT:
Actually the service is used in event before any controller, so I want to do redirect before any controller execution. So maybe a way to omit controller execution?
A controller is a PHP callable you create that takes information from the HTTP request and creates and returns an HTTP response (as a Symfony Response object).
The only concern for a Controller is to process a request and return a response. Having a Service handle a Response object is probably a bad design choice.
In any case, you could just die/exit your controller and use Kernel events to hook in the Request/Response flow and inspect the returned Response. Probably the terminate event is the right choice http://symfony.com/doc/current/components/http_kernel/introduction.html
Ok, I found a way to do it. All I had to do is to terminate the kernel before exit - it does dispatch all after-response events (like profiling, logging etc.).
$response = new RedirectResponse($url);
$response->send();
$kernel->terminate($request, $response);
exit();
If anyone would found better way do to this, please answer so I can switch the mark.
How would I be able to subscribe to events in a way such that a second round of bootstrapping would be possible in one of my modules, making sure all modules have had their onBootstrap() methods already called?
I have already tried subscribing to the same onBootstrap() event from within my Module's onBootstrap(), but with a lower priority. That didn't work; apparently the events that are to be triggered are determined before triggering any, and therefore you cannot subscribe to the same event that is currently being triggered and expect it to work.
I also wanted to try to subscribe to loadModules.post within init(), and then subscribe to EVENT_BOOTSTRAP, but I realized I couldn't find any way to access $mvcEvent, and in turn, $application, and in turn, the application event manager where the subscription needs to take place on.
I had to do exactly this just the other day.
You're right that you can't attach in the onBootstrap event, you need to attach a listener in the init method instead, and you need to use the shared manager to listen to the MvcEvent being triggered by the application
public function init(ModuleManager $modules)
{
// attach to the end of the bootstrap event
$modules->getEventManager()
->getSharedManager()
->attach('Zend\Mvc\Application', MvcEvent::EVENT_BOOTSTRAP, function ($e) {
// do something after everything else has bootstrapped
}, -1000);
}
I have been using ZF2 for a few months now and I am confused about how the controller redirect is supposed to work. Is it supposed to immediately stop processing the current action, and send the redirect request? Or is it supposed to complete processing of the current action, then perform the redirect?
Pretty fundamental question, huh?
Back in ZF1, I am pretty sure the redirection took place immediately (unlike forward(), which was stored up until the current action was completed). I assumed it was the same case in ZF2 and so far that has been my experience, however today suddenly I find that controllers are storing the redirect up, and sending it at the end of the current action.
Here's an example:
public function testAction()
{
$this->redirect()->toUrl('/info');
echo 'Hello';
die();
}
In this case, the action will echo 'Hello' and then die.
I think that this is probably the normal course of events and that I have just (by sheer fluke) not noticed it before today. I just want to be sure though, before I go back and alter all my controllers. (The alternative explanation is that somewhere in my config I am destroying/overriding the redirect plugin).
In Zend Framework 2.*, execution is never halted (except for a particular upload progress handler and some locations in the console component).
Therefore, you have to stop your controller from dispatching manually:
public function testAction()
{
return $this->redirect()->toUrl('/info');
echo 'Hello'; // will never be executed
}
To be more precise, as of this callback (used when triggering a Zend\Mvc\MvcEvent::EVENT_DISPATCH), any listener to the dispatch event of an application that returns a ResponseInterface causes a "short-circuit" to the application finish event.
Short-circuiting (in the Zend\Mvc\Application) basically causes subsequent events to be skipped and forces the application to directly trigger the Zend\Mvc\MvcEvent::EVENT_FINISH event, therefore echoing the response (happens in a listener of the finish event).
In this particular controller action, the call to the $this->redirect()->toUrl('...') helper produces a Zend\Http\Response, and since we directly return it, the short-circuit is triggered.