phplumenfastroute

PHP Fastroute - Handle 404s


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

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:

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?


Solution

  • 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:

    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;
    }