Return JSON for exception with octoberCMS - php

I have some custom routes
Route::get('/files/', [
'as' => 'read',
'uses' => 'Myname\MyPlugin\Http\Controllers\FilesController#read'
]);
Somewhere in my class I have a function to validate a path
private function getPath()
{
$path = Input::get('path');
if (!$path)
{
throw new MyException('parameter is missing. path required', 400);
}
return base_path().'/'.$path;
}
I have set a custom error handler with a JSOM but it's the error handler of OctoberCMS that render the error in HTML format.
Do you know a way to replace default error handler of OctoberCMS by a custom one ?
Thanks

Just found the anwser in the documentation : https://octobercms.com/docs/services/error-log#exception-handling
October provide App:error to manage Exception in your plugin.
App::error(function(MyException $exception) {
//do what you want here
}
Don't forger to create a custom Exception for your plugin. If you use the generic Exception, you'll catch all the Exceptions.

I am posting an alternative solution here for a custom error handler because of this questions's visibility on Google.
I had problems using App:error due to this issue on Github: https://github.com/octobercms/october/issues/3416
Rather than using October's App::error for a custom error handling, try the following:
Create a custom error handler that inherit's from October's error handler. For example, create the following class in plugins/{AUTHOR}/{PLUGIN}/classes/CustomHandler.php (assuming you are developing a plugin for OctoberCMS). Override the render method of the handler.
<?php
namespace {AUTHOR}\{PLUGIN}\Classes; //<-- CHANGE ME OBVIOUSLY FOR YOUR PLUGIN
use October\Rain\Foundation\Exception\Handler;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Response;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException; // As an example exception you want to handle ...
/* Custom error handler which replaces the default error handler for OctoberCMS. */
class CustomHandler extends Handler
{
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
/* Custom JSON response for ModelNotFoundException exceptions. */
if($exception instanceof ModelNotFoundException){
return Response::json(['error' => 'A record was not found for the resource that was requested.'], 404);
}
/* The rest of this code is just the 'default' code from OctoberCMS' error handler. */
/* The only change is the code above this comment where I handle a specific exception in a unique way.*/
/* i.e. I decided to return JSON for the error rather than an HTML error page (in debug mode). */
if (!class_exists('Event')) {
return parent::render($request, $exception);
}
$statusCode = $this->getStatusCode($exception);
$response = $this->callCustomHandlers($exception);
if (!is_null($response)) {
return Response::make($response, $statusCode);
}
if ($event = Event::fire('exception.beforeRender', [$exception, $statusCode, $request], true)) {
return Response::make($event, $statusCode);
}
return parent::render($request, $exception);
}
}
Then, in your plugin registration file Plugin.php, add a boot method with the following code:
<?php namespace {AUTHOR}\{PLUGIN};
use System\Classes\PluginBase;
use {AUTHOR}\{PLUGIN}\Classes\CustomHandler; //<-- IMPORTANT
use Illuminate\Contracts\Debug\ExceptionHandler; //<-- IMPORTANT
class Plugin extends PluginBase
{
/**
* #var array Plugin dependencies
*/
public $require = [];
public function registerComponents()
{
}
public function registerSettings()
{
}
public function boot(){
/* Replace the default error handler of OctoberCMS to return JSON format responses instead. */
/* Also, this is used in order to control the responses for certain types of errors/exceptions. */
/* We are going about it this way because App::error (as mentioned in the documentation for OctoberCMS), */
/* was not returning the response properly presumably because of a bug. Argh... */
$this->app->bind(
ExceptionHandler::class,
CustomHandler::class
);
}
}
Since, the new CustomHandler.php was added to the classes directory, the new class should already be picked up. No need for composer dump-autoload -o or changes to the composer.json.
That is it. When the ModelNotFoundException occurs, the response given to the web browser will be JSON that you wrote (from the custom error handler we created).
Relevant links:
https://blog.sarav.co/registering-custom-exception-handler-laravel-5/
https://laravel.com/docs/5.8/container#binding
https://octobercms.com/forum/post/is-it-posible-to-bring-back-the-app-directory-for-native-laravel

Related

Symfony, redirect to (or render) 404 custom error page with 404 status return code

In symfony how can I redirect to a custom 404 error page from a controller when I don't find an element in the database?
For example:
if ( empty($db_result) ) {
/* DO REDIRECT */
} else {
return $this->render('default/correct.html.twig');
}
Try this way:
if ( is_null($db_result) ) {
throw $this->createNotFoundException();
} else {
return $this->render('default/correct.html.twig');
}
If the route provides a unique value (often an ID, but it could be a username or simple text), then the SensioFrameworkExtraBundle ParamConverter can fetch an entity (a database record) automatically.
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/blog/{id}")
* #ParamConverter("post", class="SensioBlogBundle:Post")
*/
public function showAction(Post $post)
{
}
That is so common there is an even easier and quicker way, by using the method signature on its own:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route("/blog/{id}")
*/
public function showAction(Post $post)
{
// $post will be the database record - if it exists
}
In a 'prod' environment (the live server), if there is no record in the 'post' table, id:1 (for example) when you visit /blog/1, then the framework will make a 404 error for you.
If you want to do the search by yourself (or its more complex), you can make a 'NotFoundHttpException', or use the controller shortcut to do so:
public function indexAction(/* parameters, like Request, or maybe $id */)
{
// retrieve the object from database
$product = ...;
if (!$product) {
throw $this->createNotFoundException('The product does not exist');
}
return $this->render(...);
}

Zend 2: Route constraints log specific error

I try to log if user type wrong url parameter for a route with
'constraints' => array('personalnumber' => '[0-9]*')
$error = $e->getError();
if ($error == Application::ERROR_ROUTER_NO_MATCH) {
$url = $e->getRequest()->getUriString();
$sm->get('Zend\Log\RouteLogger')->warn('Url could not match to routing: ' . $url);
}
Can I get a specific error like: Value for Parameter "id" must type integer?
That won't be so easy. You would have to build your own functionality to find out the exact details on why the route didn't match.
Route matching is checked using the RouteInterface::match method from the corresponding class. For example for segment routes this method can be found in the Zend\Router\Http\Segment class on line 359-404.
If there is no match, the class returns null/void. Details on why the route didn't match is not part of the response, so you would have to do such in depth analysis yourself and write your own custom error response.
Such a solution could be to do manually validate the person number (for example by isolating it from the request url) when the dispatch error event is triggered and return your own custom response before the default 404 response.
<?php
namespace Application;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Router\Http\RouteMatch;
class Module{
public function onBootstrap(MvcEvent $event)
{
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'validatePersonNumber'), 1000);
}
public function validatePersonNumber(MvcEvent $event)
{
if ($event->getError() !== Application::ERROR_ROUTER_NO_MATCH) {
// Not a 404 error
return;
}
$request = $event->getRequest();
$controller = $event->getController();
if($controller !== 'Application\Expected\ControllerName'){
// not a controller for person number route
return;
}
$url = $request->getRequestUri();
$personNumber = ''; //...manually isolate the person number from the request...
/** #var Response $response */
$response = $event->getResponse();
$response->setStatusCode(404);
$viewModel = $event->getViewModel();
$viewModel->setTemplate('error/404');
$event->setViewModel($viewModel);
$event->stopPropagation(true);
if (strlen($personNumber) !== 12) {
$viewModel->setVariable('message', 'A person number should have 12 characters');
}
if(...additional check...){
$viewModel->setVariable('message', 'Some other message');
}
}
}
To make things prettier you could consider moving all this into a Listener class (instead of polluting your module.php file) and you could also consider the 404 code here. Most likely there is a more suitable status code for such validation response.
Note: This is not a completely finished example, it needs more work!

How to override bundle resources in micro-kernel Symfony?

I've got micro-kernel Symfony project with custom catalog structure.
I used this: https://github.com/ikoene/symfony-micro
How can I override e.g. Twig Resources (Exception views)?
Cookbook says that I should create a directory called TwigBundle in my Resources directory.
I made \AppBundle\Resources\TwigBundle\views\Exception directory. Overriding view does not seem to work.
Thanks for using the microkernel setup. Here's how to override exception views.
1. Create a custom ExceptionController
First off, we're gonna create our own ExceptionController which extends the base ExceptionController. This will allow us to overwrite the template path.
<?php
namespace AppBundle\Controller\Exception;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseExceptionController;
use Symfony\Component\HttpFoundation\Request;
class ExceptionController extends BaseExceptionController
{
/**
* #param Request $request
* #param string $format
* #param int $code
* #param bool $showException
*
* #return string
*/
protected function findTemplate(Request $request, $format, $code, $showException)
{
$name = $showException ? 'exception' : 'error';
if ($showException && 'html' == $format) {
$name = 'exception_full';
}
// For error pages, try to find a template for the specific HTTP status code and format
if (!$showException) {
$template = sprintf('AppBundle:Exception:%s%s.%s.twig', $name, $code, $format);
if ($this->templateExists($template)) {
return $template;
}
}
// try to find a template for the given format
$template = sprintf('#Twig/Exception/%s.%s.twig', $name, $format);
if ($this->templateExists($template)) {
return $template;
}
// default to a generic HTML exception
$request->setRequestFormat('html');
return sprintf('#Twig/Exception/%s.html.twig', $showException ? 'exception_full' : $name);
}
}
2. Create the error templates
Create templates for the different error codes:
error.html.twig
error403.html.twig
error404.html.twig
In this example, the exception templates would be placed in AppBundle/Resources/views/Exception/
3. Override the default ExceptionController
Now let's point to our new exception controller in the configuration.
twig:
exception_controller: app.exception_controller:showAction
I really like your solution, but I found another way how to do it without custom exception controller.
I realized that automatical additional check for overrided templates happens in directory Resources in the directory when you store your kernel class.
So, for structure in your repo it's:
/Resources/TwigBundle/views/Exception/
Finally I changed a little bit the directory structure to have a 'app' directory with kernel file inside. Just like in the default Symfony project.

show custom exceptions in kohana 3.3 template controller

I wish to show custom exception pages in my application. In documentation is written:
"Note: We can also use HMVC to issue a sub-request to another page rather than generating the Response in the HTTP_Exception itself." The problem is i don't know how to do it.
Code which I am using is:
class HTTP_Exception_404 extends Kohana_HTTP_Exception_404 {
/**
* Generate a Response for the 404 Exception.
*
* The user should be shown a nice 404 page.
*
* #return Response
*/
public function get_response()
{
$view = View::factory('errors/404');
// Remembering that `$this` is an instance of HTTP_Exception_404
$view->message = $this->getMessage();
$response = Response::factory()
->status(404)
->body($view->render());
return $response;
}
}
It is working but i need to show the message in currently used template.
Regards
You shouldn't need to bother with sub-requests if all you want is to use the template. Here's the easy way:
class HTTP_Exception_404 extends Kohana_HTTP_Exception_404 {
public function get_response()
{
$view = View::factory('errors/404');
// Remembering that `$this` is an instance of HTTP_Exception_404
$view->message = $this->getMessage();
// Wrap it in our layout
$layout = $layout = View::factory('template');
$layout->body = $view->render();
$response = Response::factory()
->status(404)
->body($layout->render());
return $response;
}
}
If you really must use a sub-request, just do the sub-request normally:
class HTTP_Exception_404 extends Kohana_HTTP_Exception_404 {
public function get_response()
{
$request = Request::factory('route_to_error_page_action')
->method(Request::POST)
->post(array('message' => $this->getMessage()));
$response = $request->execute();
// If you didn't return the 404 code from the sub-request, uncomment this:
$response->status(404);
return $response;
}
}

Overriding Kohana_Exception::_handler() for Production - 3.3

I am using Kohana 3.3 and I read in this forum post.
It says that to prevent stack traces from being shown to the end users, I need to override Kohana_Exception::_handler() to do something different with the errors that percolate up. Does that mean overriding Kohana_Exception and adding the following function?
public static function _handler(Exception $e)
{
try
{
// Log the exception
Kohana_Exception::log($e);
// Generate the response
//instead of below line:
//$response = Kohana_Exception::response($e);
$response = //what do I do here, return a 404 page or custom 500 page?
return $response;
}
//rest of function
}
If so, what do I return there?
EDIT:
bootstrap.php
/**
* Attach the file write to logging. Multiple writers are supported.
*/
Kohana::$log->attach(new Log_File(APPPATH.'logs'));
/**
* Attach a file reader to config. Multiple readers are supported.
*/
Kohana::$config->attach(new Config_File);
/**
* Attach customer error handler if we are in production
*/
if(Kohana::$environment == Kohana::PRODUCTION || Kohana::$environment == Kohana::STAGING)
{
set_exception_handler(array('My_Exception', 'handler'));
throw new Exception('text'); //this works, if removed however my exception handler does not do anything
}
My_Exception.php (in classes/My/Exception.php)
<?php
class My_Exception extends Kohana_Exception
{
public static function handler(Exception $e)
{
try
{
// Log the exception
Kohana_Exception::log($e);
// Let's try and load an error View
$class = get_class($e);
if($class == 'HTTP_Exception_404')
{
$view = View::factory('errors/404');
$view->set('error_code', 404);
}
else
{
$view = View::factory('errors/general');
$view->set('error_code', $e->getCode()); // alternatively use $e->getCode()
$view->set('error_message', $e->getMessage()); // alternatively use $e->getMessage();
}
// Let's render the output and send it to the browser
$response = $view->render();
echo $response;
}
catch (Exception $e)
{
/**
* Things are going *really* badly for us, We now have no choice
* but to bail. Hard.
*/
// Clean the output buffer if one exists
ob_get_level() AND ob_clean();
// Set the Status code to 500, and Content-Type to text/plain.
header('Content-Type: text/plain; charset='.Kohana::$charset, TRUE, 500);
// This will output the Exceptiones error message
// You may want to display something else
echo $e->getMessage();
exit(1);
}
}
}
I have actually investigated quite a bit further this issue and rewritten my asnwer from scratch now that I have a more complete understanding of Kohana's behaviour in this area.
To achieve what you're after you need to do two things:
Change the default error View (in APPPATH/bootstrap.php):
/**
* Change default error View
*/
if(Kohana::$environment == Kohana::PRODUCTION || Kohana::$environment == Kohana::STAGING)
{
Kohana_Exception::$error_view = 'errors/general';
}
Note that your template file has to use the same (and only those) variable names as Kohana's native one, i.e.:
$class
$code
$message
$file
$line
$trace
2. Create custom HTTP error pages following the tutorial you linked to in the comments.
By following these steps you assure that:
You have your own view for all Kohana's error pages.
You can have custom views for different HTTP errors.
You don't have to override Kohana's default Exception Handler (which, as this exercise proved, isn't exactly simple to implement).
I have tested the above approach and it worked like a charm for me.
I just set the Kohana_Exception::$error_view at the bootstrap file, and created the corresponding view, nothing else was necessary, all errors have redirected auto-magically...

Categories