How to set a error handler safety net in Symfony 4? - php

PHP offers the possibility to set customer error handlers with set_error_handler("customError",E_USER_WARNING); (for example).
How and where can I set some default error handlers for a Symfony 4 application to make sure I catch all unhandled errors?
I have noticed the Debug component which mentions ErrorHandler::register();. Is this the safety net I am looking for? If yes, where should I put that error handler registration call in my code? Should I modify the index.php page?

Do the kernel events may be a solution to your issue ?
http://symfony.com/doc/current/event_dispatcher.html
You can check out all requests made on your kernel or controllers and even stop propagation of them.

In your index.php, you can use it like below:
$app->error(function (\Exception $e, $code) use ($app) {
$errors = [
[
'status' => $code,
'detail' => $e->getMessage()
]
];
if ($app['debug'] === true) {
$errors['file'] = $e->getFile();
$errors['line'] = $e->getLine();
}
return new JsonResponse(['errors' => $errors]);
});

Related

It is posible to stop event listener propagation when first listener fails? Laravel Lumen

I have one event with two listeners. I need to stop listener propagation when the first listener fails. For example:
Event: RegisterUserEvent
Listeners: StoreUserListener and SendVerificationEmailListener
If, for any reason, the user can't be stored in database, I want that SendVerificationEmailListener doesn't execute.
I'm I using Lumen 8.x and the events are processed with Redis.
Part of my code is (Registering Events and Listeners):
`protected $listen = [
RegisterReservationEvent::class => [
RegisterReservationInDatabase::class,
SendReservationEmail::class
],
];`
First listener executed:
`public function handle(RegisterReservationEvent $event): bool
{
try {
if (formReservationExists($event->formId, $event->guestReservationId)) {
throw new FormException("Reservation {$event->guestReservationId} already registered in form {$event->formId}");
}
$data = [
'form_id' => $event->formId,
'guest_reservation_id' => $event->guestReservationId,
'token' => Str::uuid()->toString(),
'status' => 'ready_to_send',
];
$reservation = FormReservation::create($data);
if ($reservation === null) {
throw new FormException("Error saving reservation {$event->guestReservationId} in form {$event->formId}");
}
} catch (Exception $e) {
dump($e->getMessage());
return false;
}
}`
And the listener that I don't want to execute is:
`public function handle(RegisterReservationEvent $event)
{
dump('Executed for ' . $event->guestReservationId);
}`
But it was executed anyway..
I am using Redis to process the listeners.
And this is the result when queue runs
Thanks.
I don't know about lumen however in laravel according to their documentation.
Sometimes, you may wish to stop the propagation of an event to other listeners. You may do so by returning false from your listener's handle method.
Maybe it is the same for lumen.
source
Edit: just found the same in lumen's documentation.
Stopping The Propagation Of An Event
Sometimes, you may wish to stop the propagation of an event to other listeners. You may do so using by returning false from your listener's handle method.

Implement slack with laravel logs

I just Implement Slack for logs in Laravel as follow:
in logging.php
'default' => env('LOG_CHANNEL', 'slack'),
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL', 'https://hooks.slack.com/services/xxxx/xx'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
],
In Handler.php I just add:
public function report(Throwable $exception)
{
Log::channel('slack')->critical($exception);
}
whenever I visit any route link in my app I got this error in slack!
Symfony\Component\HttpKernel\Exception\NotFoundHttpException in /Users/Ali/Documents/Sites/asu-admin/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php:43
Stack trace:
#0 /Users/Ali/Documents/Sites/asu-admin/vendor/laravel/framework/src/Illuminate/Routing/CompiledRouteCollection.php(144): Illuminate\Routing\AbstractRouteCollection->handleMatchedRoute(Object(Illuminate\Http\Request), NULL)
#1 /Users/Ali/Documents/Sites/asu-admin/vendor/laravel/framework/src/Illuminate/Routing/Router.php(647): Illuminate\Routing\CompiledRouteCollection->match(Object(Illuminate\Http\Request))
#2 /Users/Ali/Documents/Sites/asu-admin/vendor/laravel/framework/src/Illuminate/Routing/Router.php(636): Illuminate\Routing\Router->findRoute(Object(Illuminate\Http\Request))
I just clear route clear and route:cache and everything related but still, every visit to any route this exception pushed to slack!
The reality of the matter is those exceptions were probably always there. Here's an excerpt of the built-in error handler of Laravel:
protected $internalDontReport = [
AuthenticationException::class,
AuthorizationException::class,
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
MultipleRecordsFoundException::class,
RecordsNotFoundException::class,
SuspiciousOperationException::class,
TokenMismatchException::class,
ValidationException::class,
];
and this is used in
protected function shouldntReport(Throwable $e)
{
$dontReport = array_merge($this->dontReport, $this->internalDontReport);
return ! is_null(Arr::first($dontReport, function ($type) use ($e) {
return $e instanceof $type;
}));
}
therefore in order for you to properly report exceptions the same way you need to do something like:
public function report(Throwable $exception)
{
if ($this->shoudntReport($exception) { return; }
Log::channel('slack')->critical($exception);
}
As a sidenote 404 exceptions happen all the time. When I load a page the browser will often try different things out like loading a favicon or a page manifest.json of finding the search.xml and various things like this sometimes because we add the metadata in our template and then forget to add the actual files and other times because the browser tries to be "smart" about it.
If you want to eliminate these exceptions you need to first figure out which page is not found. It probably isn't the route action itself otherwise you'd know from the response.

PHP SDK not sending errors to Sentry when invoked from IBM Cloud Functions

I am using Serverless framework to deploy my PHP code as IBM Cloud Function.
Here is the code from the action PHP file:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
}
And this is the serverless.yml file:
service: cloudfunc
provider:
name: openwhisk
runtime: php
package:
individually: true
exclude:
- "**"
include:
- "vendor/**"
functions:
test-sentry:
handler: actions/test-sentry.main
annotations:
raw-http: true
events:
- http:
path: /test-sentry
method: post
resp: http
package:
include:
- actions/test-sentry.php
plugins:
- serverless-openwhisk
When I test the action handler from my local environment(NGINX/PHP Docker containers) the errors are being sent to Sentry.
But when I try to invoke the action from IBM Cloud nothing appears in the Sentry console.
Edit:
After some time trying to investigate the source of the problem I saw that its related with the async nature of sending the http request to Sentry(I have other libraries that make HTTP/TCP connections to Loggly, RabbitMQ, MySQL and they all work as expected):
vendor/sentry/sentry/src/Transport/HttpTransport.php
in the send method where the actual http request is being sent:
public function send(Event $event): ?string
{
$request = $this->requestFactory->createRequest(
'POST',
sprintf('/api/%d/store/', $this->config->getProjectId()),
['Content-Type' => 'application/json'],
JSON::encode($event)
);
$promise = $this->httpClient->sendAsyncRequest($request);
//The promise state here is "pending"
//This line here is being logged in the stdout of the invoked action
var_dump($promise->getState());
// This function is defined in-line so it doesn't show up for type-hinting
$cleanupPromiseCallback = function ($responseOrException) use ($promise) {
//The promise state here is "fulfilled"
//This line here is never logged in the stdout of the invoked action
//Like the execution never happens here
var_dump($promise->getState());
$index = array_search($promise, $this->pendingRequests, true);
if (false !== $index) {
unset($this->pendingRequests[$index]);
}
return $responseOrException;
};
$promise->then($cleanupPromiseCallback, $cleanupPromiseCallback);
$this->pendingRequests[] = $promise;
return $event->getId();
}
The requests that are registered asynchronously are sent in the destructor of the HttpTransport instance or when PHP shuts down as a shutdown function is registered. In OpenWhisk we never shut down as we run in a never-ending loop until the Docker container is killed.
Update: You can now call $client-flush() and don't need to worry about reflection.
main() now looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$client->flush();
return [
'body' => ['result' => 'ok']
];
}
Original explanation:
As a result, to make this work, we need to call the destructor of the $transport property of the Hub's $client. Unfortunately, this private, so the easiest way to do this is to use reflection to make it visible and then call it:
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
This will make the $transport property visible so that we can retrieve it and call its destructor which will in turn call cleanupPendingRequests() that will then send the requests to sentry.io.
The main() therefore looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
return [
'body' => ['result' => 'ok']
];
}
Incidentally, I wonder if this Sentry SDK works with Swoole?
Function runtimes are "paused" between requests by the platform. This means any background processes will be blocked if they aren't finished when the function returns.
It looks like the asynchronous HTTP request doesn't get a chance to complete before the runtime pauses.
You will need to find some way to block returning from the function until that request is completed. If the Sentry SDK has some callback handler or other mechanism to be notified when messages have been sent, you could use that?

Laravel test connection to SOAP WDSL and exception handling

I use the laravel framework and I want to check if a connection to Soap server was successful or not, without the app dying with fatal error.
Both this:
$this->client = #new SoapClient("http://some.url/test.wsdl");
$this->session = $this->client->login("username", "password");
if (is_soap_fault($this->session)) {
return "Error";
}
And this:
try {
$this->client = #new SoapClient("http://some.url/test.wsdl");
$this->session = $this->client->login("username", "password");
} catch (SoapFault $e) {
return "Error";
}
Result in a fatal error:
Symfony \ Component \ Debug \ Exception \ FatalErrorException
SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://some.url/test.wsdl' : failed to load external entity "http://some.url/test.wsdl"
Thanks
I struggled with this issue today as well. The problem is the Laravel error handler is interpreting this catchable error as a fatal error, and aborting the program as a result.
To counter this, you need to intercept the error prior to Laravel's internal error handler. This method varies depending on your Laravel version:
Laravel 4.*
Go to your globals.php file. This should be in your app\start\ folder.
Add the following code (Thanks dmgfjaved):
App::fatal(function($exception)
{ //If SOAP Error is found, we don't want to FATALLY crash but catch it instead
if(strpos($exception->getMessage(), 'SOAP-ERROR') !== FALSE)
{
return '';
}
});
Laravel 5.*
There is no globals.php file. All IoC calls are handled via ServiceProviders. Go to app\Providers\AppServiceProvider.php.
Find the render() function.
Add the following code before the return parent::render($request, $e);
if(strpos($e->getMessage(), 'SOAP-ERROR') !== false)
{
return false;
}
This will remove the SoapFault error type from your error handler. Remember to catch the SoapFault as Laravel won't!
Try this:
try {
$this->client = #new SoapClient("http://some.url/test.wsdl");
$this->session = $this->client->login("username", "password");
} catch (\Throwable $e) {
return "Error";
}
The solution is to actually ask the Soap client to throw a SoapFault instead of reporting an E_ERROR.
When the Soap client reports an E_ERROR, there is nothing for you to catch.
To fix this initialise you SoapClient like this:
$clientOptions = array(
'exceptions' => true,
);
try {
$client = new \SoapClient("foo.wsdl", $clientOptions);
} catch (\SoapFault $e) {
// Do what you need to do!;
}
try {
$result = $client->__soapCall($method, $data);
} catch (\SoapFault $e) {
// Do what you need to do!;
}
This is how I got soap to work in Laravel 5.1
clean install laravel 5.1
install artisaninweb/laravel-soap
create a controller SoapController.php
<?php
namespace App\Http\Controllers;
use Artisaninweb\SoapWrapper\Facades\SoapWrapper;
class SoapController extends Controller {
public function demo()
{
// Add a new service to the wrapper
SoapWrapper::add(function ($service) {
$service
->name('currency')
->wsdl('http://currencyconverter.kowabunga.net/converter.asmx?WSDL')
->trace(true);
});
$data = [
'CurrencyFrom' => 'USD',
'CurrencyTo' => 'EUR',
'RateDate' => '2014-06-05',
'Amount' => '1000'
];
// Using the added service
SoapWrapper::service('currency', function ($service) use ($data) {
var_dump($service->getFunctions());
var_dump($service->call('GetConversionAmount', [$data])->GetConversionAmountResult);
});
}
}
Create a route in your routes.php
Route::get('/demo', ['as' => 'demo', 'uses' => 'SoapController#demo']);
#Adam Link provided a good hint, but in Laravel 5.1, it appear stha tthere is not longer a render method in AppServiceProvider.
Instead, it has been moved to app\Exceptions\Handler.php

How to return a JsonModel error from a dispatch event in Zend Framework 2?

WHAT I'M TRYING TO DO
I have a PHP application using the Zend Framework 2 and it has a "visual" side (where it returns nice HTML web pages) and an API side (where it simply returns JSON). When a request is sent to our server it's routed through the onBootstrap( \Zend\Mvc\MvcEvent ) function in Module.php. The bootstrap function does some general setup and checks (is the user signed in...etc.) and then attaches a dispatch function for \Zend\Mvc\Controller\AbstractRestfulController's (our API controllers) and another dispatch function for \Zend\Mvc\Controller\AbstractActionController(our "Visual" controllers).
In the dispatch functions more session specific stuff is checked and setup and occasionally an error can occur. If an error occurs in the \Zend\Mvc\Controller\AbstractActionController's dispatch function then it simply returns the error message to the view and the view displays it to the user. If an error occurs in the \Zend\Mvc\Controller\AbstractRestfulController's dispatch function I want it to return a JsonModel with the error information and an appropriate response header (400,404...etc).
WHAT'S HAPPENING
When an error occurs in the \Zend\Mvc\Controller\AbstractRestfulController's dispatch function the response header is set but the body isn't. On top of that the action is still routed to the controller so if the controller specifies a new response header then that overrides the previous one.
Here's an excerpt of my code:
public function onBootstrap( \Zend\Mvc\MvcEvent $event ) {
...
$event_manager = $event->getApplication()->getEventManager();
$shared_manager = $event_manager->getSharedManager();
...
// Dispatch event for AbstractRestfulController calls
$shared_manager->attach('Zend\Mvc\Controller\AbstractRestfulController', 'dispatch', function($event) {
...
try {
$organization = $organization_interface->get($id);
} catch(Exception $e) {
$event->getResponse()->setStatusCode($e->getCode());
return new JsonModel(array(
'error' => $e->getMessage(),
));
}
...
}, 100);
...
}
So I know that the returned JsonModel won't work because it's being returned from the dispatch function and not the controller. What I want to know is an "easy" way to send my JsonModel as the response AND stop the framework from running the routed action in the controller.
Any and all help is appreciated. Thanks!
If you are looking for the string to be returned from that JSON Model then this should be enough -
It is obvious to get the action getting dispatch and not the JSON Model, so try this -
instead of -
return new JsonModel(array(
'error' => $e->getMessage(),
));
write
$view = new JsonModel(array(
'error' => $e->getMessage(),
));
echo $view->serialize();
http_response_code($e->getCode()); //Added the line of code as per suggested in the comment by B1NARY
exit();
This will return the JSON string.
Let us know if this is not what you are looking for.
This is, for me, a better zf2ish solution :
$this->response->setStatusCode(Response::STATUS_CODE_401);
$viewModel = new JsonModel(['error' => 'Unauthorized', 'error_description' => $exception->getMessage()]);
$event->setViewModel($viewModel);
$event->stopPropagation(true);
return $viewModel;

Categories