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/
Related
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']);
So I want to cover all posible and unexpected errors (like 401, 404, 500) with just one view. I want the same view to show up on all possible errors. I came up with a solution - to copy/paste the same code and just name the views with different error codes. But that seems stiff and wrong. Is there a better way of achieving this?
In the file app/Exceptions/Handler.php you can change what happens when an exception is thrown. In particular there's a render method in there that you can use to catch all the exceptions in an application.
public function render($request, Exception $e)
{
// Handle your error here. Perhaps you can show a view
// when a condition is met. Anything that isn't caught
// here will be handled by Laravel with the
// parent::render call below
return parent::render($request, $e);
}
The parent::render($request, $e) is where Laravel would normally show it's exception/oops page. So by overriding this method you can catch all application errors, including 404, 401 etc.
A cleaner way to achieve this effect is by modifying Laravel's exception handler.
Modify App\Exceptions\Handler to catch every error and return your shared custom error page.
/**
* 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)
{
if ($e instanceof NotFoundHttpException) {
return response()->view('errors.custom', [], 404);
}
return parent::render($request, $e);
}
Some customization may be required to fully meet exactly what & how you want data passed to your shared custom view.
Pass the error code to your view on the handler, and display the code on your page, use a switch to handle all the messages depending on the error code.
You can create one unique view (default to 404 error), use try catch in your code to capture other errors and call to this view with parameters so you can change the 404 default error to other error.
I am on Laravel 5.1.
Whenever there is an exception, Laravel usually shows an exception on the page. I'd like to add a custom 404 page with a human readable error report and email an admin with the entire error dump. I would also like to have the email nicely formatted in HTML (like on the error page).
How can I do this?
So, the first part for displaying fancy 404 comes with adding 404.blade.php to resources/views/errors.
Regarding emailing the error_dump, this requires a way to catch the error events. Here's how to handle that. In the file App\Exceptions\Handler.php, add the email code to your report() method
/**
* 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)
{
if ($e instanceof NotFoundHttpException) {
//Send email here
}
return parent::report($e);
}
I am new in zendframework. I am using apigility for rest services and ApiProblemListner to return the response if any error occures.
I have one function in model and this function just through an exception using php exception to use in catch block
I am using model function as the utility function in controller to catch those Exception. While catching exception I am using as
try{
imageUploade(); // this function in model and throwing exception if any error
}catch(\Exception $e){
return new ApiProblemResponse(new ApiProblem(500 , $e->getMessage()));
}
if imageUploade() throw an exception if the image size is more then I am able to catch the exception in catch block. I tried echo $e->getMessage(); and its printing the exception bt if I use new ApiProblem(500 , $e->getMessage()) it is not retuning the json error response with the 500 message. It is returning nothing. even it is not showing any error.
Seems like it is unable to render the error with this class. I am not sure if any event needs to add.
I have tried to search for documents but unable to find it.
Thanks in advance.
Normally it should work if you return an ApiProblemResponse straight from a controller action.
Are you sure your Api-Problem module is active?
Try once like this:
<?php
Application\Namespace;
use ZF\ApiProblem\ApiProblem;
use ZF\ApiProblem\ApiProblemResponse;
class IndexController
{
/**
* #return ApiProblemResponse
*/
public function indexAction()
{
return new ApiProblemResponse(new ApiProblem(500, 'test'));
}
}
If that doesn't work then I think your Api-Problem module is not running or the exception is never caught.
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.