Troubleshooting PHP Slim Routing

The Slim PHP framework is a great place to start up a back end for a website. I’m sure I haven’t mastered it yet, but there’s good documentation, and it seems to go pretty fast. My big issue this time was trying to figure out why it wouldn’t route correctly. Here’s my notes for figuring it out…

So, first, I started with the skeleton Slim project. It’s an easy install with:

composer create-project slim/slim-skeleton nehemiah-backend

I can get to it two ways. On my machine, I can run the PHP server with the command:

composer start

Then, I can view the web service with: http://localhost:8080/users

I also have the same directory shared to a virtual machine with nginx installed. I should be able to get to the same thing with the following URL: http://digitaleagle.phillips.hom/nehemiah/nehemiah-backend/public/users

Troubleshooting with Stacktrace

One of my challenges was finding the files and where the code was getting called. This little snippet will print in the log the function name and the file where the code is getting called. You can play with the index to go up as many levels as you like:

            $skptemp = debug_backtrace()[0];
            error_log("SkpTemp: called by: " . $skptemp['function']);
            error_log("SkpTemp: called by: " . $skptemp['file'] . " -- " . $skptemp['line']);

In the nginx log or the terminal window where you ran “composer start”, you should see something like this:

[Sat Nov 23 10:27:21 2019] SkpTemp: called by: Slim\Routing\{closure}
[Sat Nov 23 10:27:21 2019] SkpTemp: called by: /home/skp/app/vwh/nehemiah/nehemiah-backend/vendor/nikic/fast-route/src/functions.php -- 25

Note: I put the “skptemp” in there to remind myself to come back and remove these debugging statements.

What routes does it see?

The next step is to print out in the log what the routes are. I found the best place was to put it in the FastRouteDispatcher class. You can find that at vendor/slim/slim/Slim/Routing/FastRouteDispatcher.php. I put two “skptemp” log lines in the createDispatcher().

Here’s what I ended up with:

    /**
     * @return FastRouteDispatcher
     */
    protected function createDispatcher(): FastRouteDispatcher
    {
        if ($this->dispatcher) {
            return $this->dispatcher;
        }

        $routeDefinitionCallback = function (FastRouteCollector $r) {
            $basePath = $this->routeCollector->getBasePath();
            error_log("SkpTemp ... Base Path $basePath");

            foreach ($this->routeCollector->getRoutes() as $route) {
                error_log("SkpTemp ... Adding fast route: ". $basePath . $route->getPattern());
                $r->addRoute($route->getMethods(), $basePath . $route->getPattern(), $route->getIdentifier());
            }
        };

What is the URI it is mapping?

Just knowing the routes doesn’t help as much. I need to know what it is looking for. I added an “skptemp” log line into the RouteResolver class in the computeRoutingResults() function. The file is vendor/slim/slim/Slim/Routing/RouteResolver.php:

    /**
     * @param string $uri Should be $request->getUri()->getPath()
     * @param string $method
     * @return RoutingResults
     */
    public function computeRoutingResults(string $uri, string $method): RoutingResults
    {
        $uri = rawurldecode($uri);
        if ($uri === '' || $uri[0] !== '/') {
            $uri = '/' . $uri;
        }
        error_log("SkpTemp ... computeRoutingResults method = $method; uri = $uri");
        return $this->dispatcher->dispatch($method, $uri);
    }

Also, I added a line into FastRouteDispatcher (/vaendor/slim/Slim/Routing/FastRouteDispatcher.php). I put it in the routingResults() function.

    private function routingResults(string $httpMethod, string $uri): array
    {
        error_log("SkpTemp ... routing results $httpMethod   $uri");
        if (isset($this->staticRouteMap[$httpMethod][$uri])) {
            return [self::FOUND, $this->staticRouteMap[$httpMethod][$uri], []];
        }

        if (isset($this->variableRouteData[$httpMethod])) {
            $result = $this->dispatchVariableRoute($this->variableRouteData[$httpMethod], $uri);
            if ($result[0] === self::FOUND) {
                return [self::FOUND, $result[1], $result[2]];
            }
        }

        return [self::NOT_FOUND, null, []];
    }

Getting the Base Path right

Originally, I left the “public” off the base path. So, looking in the nginx log (/var/log/nginx/error.log), I see this:

2019/11/23 16:49:37 [error] 342#342: *263 FastCGI sent in stderr: "PHP message: -----------------------------------------
PHP message:    Loading service
PHP message: -----------------------------------------
PHP message: SkpTemp ... computeRoutingResults method = GET; uri = /nehemiah/nehemiah-backend/public/users
PHP message: SkpTemp ... Base Path /nehemiah/nehemiah-backend
PHP message: SkpTemp ... Adding fast route: /nehemiah/nehemiah-backend/
PHP message: SkpTemp ... Adding fast route: /nehemiah/nehemiah-backend/users
PHP message: SkpTemp ... Adding fast route: /nehemiah/nehemiah-backend/users/{id}
PHP message: SkpTemp ... Adding fast route: /nehemiah/nehemiah-backend/files
PHP message: SkpTemp ... Adding fast route: /nehemiah/nehemiah-backend/files/{id}
PHP message: SkpTemp ... routing results GET   /nehemiah/nehemiah-backend/public/users
PHP message: SkpTemp ... routing results *   /nehemiah/nehemiah-backend/public/users" while reading response header from upstream, client: 192.168.56.1, server: _, request: "GET /nehemiah/nehemiah-backend/public/users HTTP/1.1", upstream: "fastcgi://unix:/run/php/php7.2-fpm.sock:", host: "digitaleagle.phillips.hom

I found I could set the base path like this so it would work through Nginx or from the PHP server either way:

// set the base path
if(strpos($_SERVER["REQUEST_URI"], "nehemiah-backend")) {
	$app->getRouteCollector()->setBasePath("/nehemiah/nehemiah-backend/public");
}

The other key part of making this work is the configuration on Nginx:

    location /nehemiah/nehemiah-backend/ {
        try_files $uri/xx /nehemiah/nehemiah-backend/public/index.php;
    }

Leave a Comment

Your email address will not be published. Required fields are marked *