in PHP, which Exception codes are mapped to http status codes? - php

I'm developing a (CakePHP) web application, thereby creating own exception classes.
At the moment I try to create a locked exception, that shall return HTTP status code 423 (Locked):
<?php
namespace App\Exceptions;
use Cake\Network\Exception\HttpException;
class MeasuringPointLockedException extends HttpException{
/**
* Constructor
*
* #param string $message If no message is given 'Locked' will be the message
*/
public function __construct($message = 'Locked'){
parent::__construct($message, 422);
}
}
?>
Unfortunately, at some point my code 423 is consumed and replaced by 500 (internal server error). I noticed, that only some codes are replaced, others (like 400, 404, 422) are passed through.
Note: HttpException is an extension of PHP builtin exception.
In between, I noticed, that response code 423 is intended for WebDAV services, but:
Is there any documentation, which codes are passed through? How could I achieve a status = 423 upon throwing (and not catching) such an exception?

You can seen a bunch of http exceptions in here:
[https://book.cakephp.org/3/en/development/errors.html1
Here is also a good example of how to make this implementation:
use Cake\Core\Exception\Exception;
class MissingWidgetException extends Exception
{
// Set your message here
protected $_messageTemplate = 'Your message';
// Set your status code here
protected $_defaultCode = 404;
}
throw new MissingWidgetException(['widget' => 'Pointy']);

Related

How to avoid logging expected exceptions that are correctly converted to status codes by Api-Platform?

In some of routes for our Api-Platform project, exceptions are thrown for some common error conditions.
E.g. while calling POST /orders, NewOrderHandler can throw either of this two if appropriate:
NotEnoughStock
NotEnoughCredit
All these exceptions belong to a DomainException hierarchy.
These exceptions are correctly converted to status code 400 on the response by using the exception_to_status configuration, and the response includes the appropriate error message. So far so good.
exception_to_status:
App\Order\NotEnoughStock: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST
App\Order\NotEnoughCredit: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST
The only problem is that the exception is still logged as a CRITICAL error, being treated as an "uncaught exception". This gets logged even in production.
I would have expected that by being converted to a correct status code (e.g. !== 500) these exceptions would be treated as "handled", and thus would not pollute the logs.
Throwing exceptions from the handler is convenient, because it helps deal with transactionality and automatically generates the appropriate error response message. It works for web and console.
Shouldn't these transactions be treated as handled? Is it necessary to create another exception listener to deal with this? And if creating an exception listener, how to do it so it doesn't interfere with Api-Platform error normalization?
There is a simple answer: handling an exception is not catching an exception.
Even if you convert your exception to a 400 error, your exception is still uncaught... that's why symfony logs it and this is done here.
If you don't want to log any DomainException, just override the logException() method in order to skip logging if it's an instance of DomainException.
Here is an exemple:
namespace App\EventListener;
use Symfony\Component\HttpKernel\EventListener\ErrorListener;
class ExceptionListener extends ErrorListener
{
protected function logException(\Exception $exception, string $message): void
{
if ($exception instanceof DomainException) {
return;
}
parent::logException($exception, $message);
}
}
Finally you need to tell Symfony to use this class instead of the Symfony one. Since there is no class parameter for the exception_listener service definition I recommend to use a compiler pass in order to replace the class.
namespace App;
use App\EventListener\ExceptionListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('exception_listener');
$definition->setClass(ExceptionListener::class);
}
}
See Bundle override for more details.
Alternatively, just decorate the exception_listener service with your own and no compiler pass is needed:
App\EventListener\ExceptionListener:
decorates: 'exception_listener'
I tested it on a dummy application and I got:
Apr 11 21:36:11 |CRITI| REQUES Uncaught PHP Exception App\Exception\DomainException: "This is no more logged" at D:\www\campagne\src\DataPersister\StationDataPersister.php line 53
Apr 11 23:36:12 |WARN | SERVER POST (400) /api/stations
You could implement your own log activation strategy:
This code is based on the HttpCode activation strategy
namespace App\Log
use App\Exception\DomainException;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Activation strategy for logs
*/
class LogActivationStrategy extends ErrorLevelActivationStrategy
{
public function __construct()
{
parent::__construct('error');
}
public function isHandlerActivated(array $record): bool
{
$isActivated = parent::isHandlerActivated($record);
if ($isActivated && isset($record['context']['exception'])) {
$exception = $record['context']['exception'];
// This is a domain exception, I don't log it
return !$exception instanceof DomainException;
// OR if code could be different from 400
if ($exception instanceof DomainException) {
// This is a domain exception
// You log it when status code is different from 400.
return 400 !== $exception->getStatusCode();
}
}
return $isActivated;
}
}
We also need to tell Monolog to use our ActivationStrategy
monolog:
handlers:
main:
type: fingers_crossed
action_level: info
handler: nested
activation_strategy: App\Log\LogActivationStrategy
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
Now my log contains only :
Apr 11 23:41:07 |WARN | SERVER POST (400) /api/stations
Like #yivi, I'm not in love of my solution, because each time application will try to log something, you loose time in this function... And this method do not change the log, it removes it.
While in Monolog, when using the fingers_crossed log handler, will let you exclude from logging requests that respond with certain statuses, it will only do so if the exception is an instance of HttpException:
I got around this by implementing a subscriber to convert the exception into an BadRequestHttpException.
final class DomainToHttpExceptionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): iterable
{
return [ KernelEvents::EXCEPTION => 'convertException'];
}
public function convertException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
if ($exception instanceof DomainException) {
$event->setThrowable(
new BadRequestHttpException(
$exception->getMessage(),
$exception
)
);
}
}
}
This coupled with this monolog configuration does the trick:
monolog:
handlers:
fingers:
type: fingers_crossed
action_level: warning
excluded_http_codes:
- 404
- 400
I got this from this answer on a GitHub issue. It works, but I'm not in love the solution. Hopefully some other answer will come to improve on this.

How to throw an exception in your Extension?

In an Extbase extension, the need may arise to inform the user about an error or exception.
In my case, I have to parse some data from a potentially bad source. So the extension has to validate this data. And if the data is invalid, it needs to throw an exception that then can be handled by TYPO3.
However, I can only find information about how the exception and error handlers works, but no information on how to correctly throw an exception from inside an extension.
So what is the intended way to throw an exception from inside an Extbase extension?
Expected result
If I produce a syntax error, TYPO3 displays a message similar to this:
(Taken from the core API reference.)
That is what I would expect a correctly thrown error or exception to look like.
What I tried
Edit: I tried throwing an error like this:
throw new \Exception('Invalid data');
However, all the frontend displays is
Oops, an error occurred! Code: 20160721101726b5339896
Another possible way to produce an error:
$GLOBALS['TSFE']->pageNotFoundAndExit('Invalid data');
However, this shows a Page Not Found error instead of the expected exception.
You implicitly asked 2 questions:
How do I correctly throw an exception in my code?
I think, that was correct, what you did: Just use PHP \Exception or a suitable exception inherited from \Exception:
throw new \UnexpectedValueException('Invalid data');
Once an exception has been thrown, how do I see more information?
This has already been answered quite well: https://stackoverflow.com/a/34067853/2444812
On a development system:
set configuration preset "debug"
Add TypoScript on start page: config.contentObjectExceptionHandler = 0
see Error and ExceptionHandling Example
On a production system:
You usually do not want to see full stack traces in the frontend. That is why config.contentObjectExceptionHandler is usually set to default, which only shows Oops, an error occurred! Code: 20160721101726b5339896 on the rendered page. Using this code, you can look in the logs (if things are logged and what is logged where always depends on the configuration of the logging system):
sys_log : see "Log" in the backend
logfile: var/logs/*.log (see Logging with TYPO3). May be typo3temp/logs on older versions and typo3temp/var/logs/ on non-Composer systems.
namespace VendorName\ExtensionName\Controller;
abstract class ActionController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* #var string
*/
protected $entityNotFoundMessage = 'The requested entity could not be found.';
/**
* #var string
*/
protected $unknownErrorMessage = 'An unknown error occurred. The wild monkey horde in our basement will try to fix this as soon as possible.';
/**
* #param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
* #param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response
* #return void
* #throws \Exception
* #override \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
*/
public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response) {
try {
parent::processRequest($request, $response);
}
catch(\Exception $exception) {
// If the property mapper did throw a \TYPO3\CMS\Extbase\Property\Exception, because it was unable to find the requested entity, call the page-not-found handler.
$previousException = $exception->getPrevious();
if (($exception instanceof \TYPO3\CMS\Extbase\Property\Exception) && (($previousException instanceof \TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException) || ($previousException instanceof \TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException))) {
$GLOBALS['TSFE']->pageNotFoundAndExit($this->entityNotFoundMessage);
}
throw $exception;
}
}
/**
* #return void
* #override \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
*/
protected function callActionMethod() {
try {
parent::callActionMethod();
}
catch(\Exception $exception) {
// This enables you to trigger the call of TYPO3s page-not-found handler by throwing \TYPO3\CMS\Core\Error\Http\PageNotFoundException
if ($exception instanceof \TYPO3\CMS\Core\Error\Http\PageNotFoundException) {
$GLOBALS['TSFE']->pageNotFoundAndExit($this->entityNotFoundMessage);
}
// $GLOBALS['TSFE']->pageNotFoundAndExit has not been called, so the exception is of unknown type.
\VendorName\ExtensionName\Logger\ExceptionLogger::log($exception, $this->request->getControllerExtensionKey(), \VendorName\ExtensionName\Logger\ExceptionLogger::SEVERITY_FATAL_ERROR);
// If the plugin is configured to do so, we call the page-unavailable handler.
if (isset($this->settings['usePageUnavailableHandler']) && $this->settings['usePageUnavailableHandler']) {
$GLOBALS['TSFE']->pageUnavailableAndExit($this->unknownErrorMessage, 'HTTP/1.1 500 Internal Server Error');
}
// Else we append the error message to the response. This causes the error message to be displayed inside the normal page layout. WARNING: the plugins output may gets cached.
if ($this->response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response) {
$this->response->setStatus(500);
}
$this->response->appendContent($this->unknownErrorMessage);
}
}
}
Here is an article that explains that.
However like most of articles about TYPO3 Programming is in German ;-)
http://nerdcenter.de/extbase-fehlerbehandlung/

how to make PHPUnit stop intercepting exceptions?

I am working with a internal framework where every exception is catched by an error handler and returned in a proper JSON error response, suitable for a RESTFul API.
Then I have a suite of tests, which are API tests, that are mainly testing that the API returns the proper JSON responses with the expected error codes.
For every test, the global variables are modified (and then restored) to emulate a different HTTP request. I do it that way to avoid the overload of doing cURL tests (through Guzzle or similar), and cause under the CLI environment, the code does not know the server's url.
<?php
// ... example, part of a base ApiTestCase class:
// Override globals (should be backed up by PHPUnit)
$_SERVER['REQUEST_METHOD'] = $request->method;
$_SERVER['QUERY_STRING'] = http_build_query($request->parameters);
$_SERVER['PATH_INFO'] = $request->path;
$_SERVER['REQUEST_URI'] = $request->path . ($_SERVER['QUERY_STRING'] ? '?' : '') . $_SERVER['QUERY_STRING'];
$_SERVER['REQUEST_TIME'] = time();
$_SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
$_SERVER['HTTP_COOKIE'] = '';
// Set headers, cookies and parameters
foreach ($request->headers as $k => $v) {
$_SERVER['HTTP_' . strtoupper(str_replace('-', '_', trim($k)))] = $v;
}
if ($_SERVER['HTTP_COOKIE']) {
$GLOBALS['_COOKIE'] = http_parse_cookie($_SERVER['HTTP_COOKIE']);
} else {
$GLOBALS['_COOKIE'] = [];
}
$GLOBALS['_REQUEST'] = $request->parameters;
$responseBody = $app->start();
$response->httpCode = http_response_code();
$response->body = $responseBody ? #json_decode($responseBody) : null;
$response->headers = headers_list();
(I know that changing globals this way is not nice, and the framework should not rely on globals directly, but I have still to deal with legacy code.)
Then here comes the problem: when I try to test JSON error responses: PHPUnit intercepts the thrown exception (before the handler I mentioned in the beginning), so the framework has no chance to convert it to JSON and return the proper response.
I tried to find something in the PHPUnit manual to disable the PHPUnit error handler with no luck.
What could I do in this case? Thanks
Just to be clear, it sounds like we're not actually talking about catching exceptions here; we're talking about using PHP's set_error_handler() to intercept a fatal error before it terminates the program. This will deal with both errors and uncaught exceptions.
One thing you will not be able to do is let those errors and exceptions fall through to your error handler function -- as you've already found out, phpUnit does its own error handling that you can't override (because it's kinda fundamental to how phpUnit works).
What you're going to have to do is tell phpUnit what kind of exception or error you're expecting; your test will then pass or fail according to whether the error occurs. You won't be running the error handler, but in truth, you shouldn't need to; you can test function that separately if you need to. For error conditions, you don't need to see that the error handler produces the right output every time, just that an error occurs that will trigger the handler.
For regular PHP exceptions, you can use phpUnit's #expectedException annotation above your test function, like so:
/**
* #expectedException YourExpectedExceptionClass
*/
function testThisWillThrowAnException() {
....
}
If the PHP code is expected to produce a PHP error (ie an error, not an exception), then you would use the same idea, but phpUnit provides a helper classname for the error: PHPUnit_Framework_Error. So your code would look like this:
/**
* #expectedException PHPUnit_Framework_Error
*/
function testThisWillProduceAPHPError() {
....
}
In either case, your test will pass if the expected error/exception occurs.
You can also test for specific exception messages and codes, in case the exception class itself isn't sufficient information for you to know whether the test has done what you want it to do. See the phpUnit manual page for annotations for more info.
The example above is also correct, mine only provide Exceptions as assertions and gives you knowladge of Exceptions Works.
/**
* #dataProvider fixturesProvider // its just example
*/
public function testDataIsWrong($fixtures)
{
try
{
//Some Code
$this->fail('Exception');
}
catch(Exception $ex)
{
$this->assertEquals($ex,'Exception');
}
}
This also provide in your code possibility ,that You can Test false or inncorect data and assert it is incorrect.
The only solution I implemented that solves my problem is to not delegate the exception handler the responsibility to build and send the API error responses, but catch exceptions in the top level of your application.
In the catch I have an exception-to-error-response converter that takes care of that (or re-throws the exception when convenient), so the errors that are not critical (like the ones producing HTTP 4xx responses) are not popping up in the PHPUnit tests anymore.
My PHPUnit tests are also now able to deal with PSR-7 HTTP Response objects, instead of capturing the output buffer.

Handling 500 Internal Server Error from DomDocument in Laravel 5

The library I wrote for Laravel uses DomDocument.
I use this library under my Controller, and its namespace is app/Services/Verify/. The library gets initialized and used when I put it some inputs into a form.
When the library fails, Laravel would fail the way it would - returning the following message: Whoops, looks like something went wrong.
I use the following regex to validate the URL on the client-side - 'our_team_link' => 'required|url|regex:/^http:\/\/www\.ugcleague\.com\/team_page\.cfm\?clan_id=\d+$/'
Below is my DomXPath code for the above URL.
// Generate our team's HTML file
$this->ourTeamHTML = new \DomDocument;
$this->ourTeamHTML->loadHTMLFile($this->ourTeamURL);
Most of the time the web app works just fine. However, there are cases where even if they type in a URL that is valid by the regex, there are indeed URLs that don't exist (that still get past the regex), and an error like the following gets returned:
PHP Warning: DOMDocument::loadHTMLFile(http://www.ugcleague.com/team_page.cfm?clan_id=8831111118): failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error
in /Users/loop/Code/laravel/app/Services/Verify/ScrapeUGC.php on line 49
Warning: DOMDocument::loadHTMLFile(http://www.ugcleague.com/team_page.cfm?clan_id=8831111118): failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error
in /Users/loop/Code/laravel/app/Services/Verify/ScrapeUGC.php on line 49
Laravel would be linked to the Whoops, looks like something went wrong page.
This confuses the user. I would like to instead return a more descriptive error, that would say, perhaps, "this team does not exist."
You could handle this in Laravel app/Exceptions/Hnadler.php
NB: I have looked in the option of using DOMException handler which is available in PHP, however the error message you are getting in not really and exception by an I/O Warning.
This what PHP native DomException looks like:
/**
* DOM operations raise exceptions under particular circumstances, i.e.,
* when an operation is impossible to perform for logical reasons.
* #link http://php.net/manual/en/class.domexception.php
*/
class DOMException extends Exception {
/**
* #var
* (PHP 5)<br/>
* An integer indicating the type of error generated
* #link http://php.net/manual/en/class.domexception.php#domexception.props.code
*/
public $code;
}
So I came up with this because we can not use DomException to dictate this error since its not an Exception, you can add this in your app/Exceptions/Handler.php
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that should not be reported.
*
* #var array
*/
protected $dontReport = [
\Symfony\Component\HttpKernel\Exception\HttpException::class,
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $e
* #return void
*/
public function report(Exception $e)
{
return parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
$message = $e->getMessage();
if (str_contains($message, 'DOMDocument::loadHTMLFile(): I/O warning')) {
return redirect($request->fullUrl())->with('error', "This team does not exist");
}
//We could also handle DomException like so, but not Dom warning
if ($e instanceof \DomException){
return redirect($request->fullUrl())->with('error', "Your friendly message here");
}
}
}
NB: Be careful when modifying Handler.php, you might start having blank pages instead of Laravel whoops or errors if you don't know what you are doing. You can make a backup somewhere if you are unsure.
There is a php function you will want to use to disable libxml errors.
libxml_use_internal_errors(true);
Now when you are done processing your script, you will need to get any errors generated by using...
$errors = libxml_get_errors();
That would return an array so you can now check it with...
if(count($errors) > 0) {
echo 'This team does not exist';
} else {
echo 'Successful';
}
// Be sure to clear errors
libxml_clear_errors();
You will most likely need to loop through that array and process each error to be sure it's the specific error you are looking for. When working with DOMDocument, you are bound to get a bunch of notices/warnings every time it finds something it doesn't like about invalid HTML/XML.

Symfony2 catch PDO errors when booting Kernel

I am using Symfony 2.4 and am trying to create a more powerful exceptions handler that, on certain PDO / Doctrine exceptions, changes the status code of the response from 500 to 503 to display a different custom error message than our standard (in other words, it returns the error503.html.twig template rather than error500.html.twig). So far, I have created a custom Exceptions controller that extends the TwigBundle ExceptionController, I have changed the Twig exception parameter in config.yml, and I am able to catch any and all exceptions that are thrown once Symfony calls handle(...) in HttpKernel.php:185 (so it's really the second time that handle is called -- this time being on the HttpKernel rather than the AppKernel). I'll refrain from posting all that code, and instead direct the reader here to learn more about my method if they are unfamiliar. All of that code is working just fine -- I am able to modify any applications that are thrown within my application, so you can assume that I'm using the aforementioned approach properly.
The issue I am running into is that in addition to catching exceptions that are thrown within Symfony, I also want to also be able to catch exceptions that are thrown before the HttpKernel's handle method is called (an example being a PDO Access Denied exception that is thrown from improper database credentials). To give you a more specific rundown, in app_dev.php, you have:
$response = $kernel->handle($request);
which calls:
/**
* {#inheritdoc}
*
* #api
*/
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
if (false === $this->booted) {
$this->boot();
}
return $this->getHttpKernel()->handle($request, $type, $catch);
}
Now, if an exception gets thrown in $this->boot(), it doesn't look like it gets caught anywhere, and because of that, I can't see any way of gracefully handling said exception in Symfony. It's only if the exception gets thrown within the try / catch block contained in $this->getHttpKernel()->handle($request, $type, $catch) that it will be caught and gracefully handled using Symfony code. Am I wrong about that? Does anyone know of an approach to handling exceptions that are thrown in this context that utilizes Symfony? My apologies in advance if this has already been answered elsewhere.
I ran into a similar problem, I didn't see a neat way around this but was able to get nice error pages for my specific problem simply by generating a Response object and sending that. I placed the following in some code which was called by boot()
try {
someExceptionFunction();
} catch (Exception $e) {
$response = new Response('<html><body>'.$e->getMessage().'</body></html>');
$response->send();
exit;
}
You could easily add some more logic to the catch block, catching different exceptions. It's not as clean/abstract as it could be, but since the entire framework fails to boot I don't know of any option you could use it to parse an error page.
Hope this helps

Categories