Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom exception handler #5355

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions system/CodeIgniter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Closure;
use CodeIgniter\Debug\Timer;
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\CustomExceptionHandlerInterface;
use CodeIgniter\Exceptions\FrameworkException;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\CLIRequest;
Expand Down Expand Up @@ -394,23 +395,27 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
}
}

$returned = $this->startController();
try {
$returned = $this->startController();

// Closure controller has run in startController().
if (! is_callable($this->controller)) {
$controller = $this->createController();
// Closure controller has run in startController().
if (! is_callable($this->controller)) {
$controller = $this->createController();

if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
throw PageNotFoundException::forMethodNotFound($this->method);
}
if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
throw PageNotFoundException::forMethodNotFound($this->method);
}

// Is there a "post_controller_constructor" event?
Events::trigger('post_controller_constructor');
// Is there a "post_controller_constructor" event?
Events::trigger('post_controller_constructor');

$returned = $this->runController($controller);
} else {
$this->benchmark->stop('controller_constructor');
$this->benchmark->stop('controller');
$returned = $this->runController($controller);
} else {
$this->benchmark->stop('controller_constructor');
$this->benchmark->stop('controller');
}
} catch (CustomExceptionHandlerInterface $e) {
$returned = $e->renderResponse($this->request);
}

// If $returned is a string, then the controller output something,
Expand Down
23 changes: 23 additions & 0 deletions system/Exceptions/CustomExceptionHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Exceptions;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

/**
* Interface for implementing a global custom exception handler
*/
interface CustomExceptionHandlerInterface
{
public function renderResponse(RequestInterface $request): ResponseInterface;
}
17 changes: 17 additions & 0 deletions tests/_support/Controllers/Popcorn.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
namespace Tests\Support\Controllers;

use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Config\Services;
use CodeIgniter\Controller;
use CodeIgniter\Exceptions\CustomExceptionHandlerInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Exception;
use RuntimeException;

/**
Expand Down Expand Up @@ -89,4 +94,16 @@ public function echoJson()
{
return $this->response->setJSON($this->request->getJSON());
}

public function customException()
{
throw new class () extends Exception implements CustomExceptionHandlerInterface {
public function renderResponse(RequestInterface $request): ResponseInterface
{
return Services::response()
->setStatusCode(400)
->setBody('an exception thrown');
}
};
}
}
23 changes: 23 additions & 0 deletions tests/system/CodeIgniterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace CodeIgniter;

use CodeIgniter\Config\Services;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Router\RouteCollection;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockCodeIgniter;
Expand Down Expand Up @@ -425,4 +426,26 @@ public function testRunDefaultRoute()

$this->assertStringContainsString('Welcome to CodeIgniter', $output);
}

public function testCustomExceptionHandler()
{
$_SERVER['REQUEST_URI'] = '/exception';

// Inject mock router.
$routes = Services::routes(false);
$routes->add('exception', '\Tests\Support\Controllers\Popcorn::customException');

$router = Services::router($routes, Services::request(), false);
Services::injectMock('router', $router);

ob_start();
$this->codeigniter->useSafeOutput(true)->run();
ob_get_clean();

/** @var ResponseInterface $response */
$response = $this->getPrivateProperty($this->codeigniter, 'response');

$this->assertSame('an exception thrown', $response->getBody());
$this->assertSame(400, $response->getStatusCode());
}
}
29 changes: 29 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,32 @@ forcing a redirect to a specific route or URL::
redirect code to use instead of the default (``302``, "temporary redirect")::

throw new \CodeIgniter\Router\Exceptions\RedirectException($route, 301);


Custom Exception Handler
------------------------
iRedds marked this conversation as resolved.
Show resolved Hide resolved

You can use your own exception handler which will be called globally.

Your exception must implement ``\CodeIgniter\Exception\CustomExceptionHandlerInterface``::

namespace App\Exceptions;
use App\Config\Services;
use CodeIgniter\Exception\CustomExceptionHandlerInterface
iRedds marked this conversation as resolved.
Show resolved Hide resolved
use Exception;

class MyException extends Exception implements CustomExceptionHandlerInterface
{
public function renderResponse(RequestInterface $request): ResponseInterface
{
return Services::response()->setBody($this->getMessage());
}
}

Now, if an exception is thrown in any part of the application, it will be handled.::

if (someCondition) {
throw new MyException('Something wrong')
}

.. note:: Of course, if not caught by the try..catch block defined earlier.