PHP Fastroute - Handle 404s

518 views Asked by At

In my application I'm using FastRoute and I would like to have different types of 404 responses:

  • When a call is made to a not existing endpoint starting with /api then the application should return a JSON 404 response.
  • When a call is made to a not existing endpoint then the application should return a generic 404.

To get a generic 404 response I did as per the documentation:

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        // ... 404 Not Found
        break;

But I'm having trouble finding a way to get a 404 JSON response in case of a not existing /api endpoint.
What I achieved so this:

$router->addGroup('/api', function (RouteCollector $r) {
    $r->post('/login', 'ApiController@login');
    $r->addRoute('*', '[/{str}]', 'ApiController@notFound');
});

For example:

  • when calling /api/not-existing-endpoint it returns a JSON 404 response (so it's OK).
  • when calling /api/not-existing-endpoint/aaa/bbb/ccc it does not catch it and it returns a generic 404.

How can fix the pattern of the route $r->addRoute('*', '[/{str}]', 'ApiController@notFound'); in order to also catch the nested URIs like /api/aaa/bbb/ccc/ddd?

1

There are 1 answers

1
vee On BEST ANSWER

use route path with regular expression.

$r->addRoute('*', '[/{any:.*}]', 'ApiController@notFound');
My full code.
require 'vendor/autoload.php';


$dispatcher = \FastRoute\simpleDispatcher(function(\FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/hello[/]', function() {
        echo 'hello';
    });
    $r->addRoute('GET', '/hello/{name}', function($attribute) {
        echo 'hello ' . $attribute['name'];
    });
    $r->addGroup('/api', function (\FastRoute\RouteCollector $r) {
        $r->addRoute('GET', '/hello[/]', function() {
            header('Content-type: application/json');
            echo json_encode(['msg' => 'hello API']);
        });
        $r->addRoute('*', '[/{any:.*}]', function() {
            header('Content-type: application/json');
            echo json_encode(['msg' => 'not found']);
        });
    });
});

// Fetch method and URI from somewhere
// for dynamic subfolder. https://github.com/nikic/FastRoute/issues/110
$base  = dirname($_SERVER['PHP_SELF']);
ltrim($base, '/') ? $_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'], strlen($base)) : '';
// end dynamic subfolder.
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

// Strip query string (?foo=bar) and decode URI
if (false !== $pos = strpos($uri, '?')) {
    $uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        // ... 404 Not Found
        http_response_code(404);
        echo 'not found';
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        http_response_code(405);
        echo 'method not allowed';
        break;
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        // ... call $handler with $vars
        $handler($vars);
        break;
}

Tested:

  • https://localhost/api => JSON 'not found'
  • https://localhost/api/hello => JSON 'hello API'
  • https://localhost/api/notfound => JSON 'not found'
  • https://localhost/api/notfound/notfound => JSON 'not found'
  • https://localhost/notfound => HTML 'not found'

Alternative

I would recommend you to use 404 route to handle this instead. It is better and easier to manage your code in the future because it will not mixed with normal route.

switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        // ... 404 Not Found
        http_response_code(404);
        $expUri = explode('/', $uri);
        if ($expUri[1] === 'api') {
            // if first url segment is api. please check this again in your application that it is [1] or [0] and correct it.
            header('Content-type: application/json');
            echo json_encode(['msg' => 'not found']);
        } else {
            echo 'not found';
        }
        break;
    // ...
}
My full code.
<?php
require 'vendor/autoload.php';


$dispatcher = \FastRoute\simpleDispatcher(function(\FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/hello[/]', function() {
        echo 'hello';
    });
    $r->addRoute('GET', '/hello/{name}', function($attribute) {
        echo 'hello ' . $attribute['name'];
    });
    $r->addGroup('/api', function (\FastRoute\RouteCollector $r) {
        $r->addRoute('GET', '/hello[/]', function() {
            header('Content-type: application/json');
            echo json_encode(['msg' => 'hello API']);
        });
    });
});

// Fetch method and URI from somewhere
// for dynamic subfolder. https://github.com/nikic/FastRoute/issues/110
$base  = dirname($_SERVER['PHP_SELF']);
ltrim($base, '/') ? $_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'], strlen($base)) : '';
// end dynamic subfolder.
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

// Strip query string (?foo=bar) and decode URI
if (false !== $pos = strpos($uri, '?')) {
    $uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::NOT_FOUND:
        // ... 404 Not Found
        http_response_code(404);
        $expUri = explode('/', $uri);
        if ($expUri[1] === 'api') {
            // if first url segment is api. please check this again in your application that it is [1] or [0] and correct it.
            header('Content-type: application/json');
            echo json_encode(['msg' => 'not found']);
        } else {
            echo 'not found';
        }
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        $allowedMethods = $routeInfo[1];
        // ... 405 Method Not Allowed
        http_response_code(405);
        echo 'method not allowed';
        break;
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        // ... call $handler with $vars
        $handler($vars);
        break;
}