phpslimslim-4

Slim4 Route Args are not working as intended


So I am trying to create a slim4 solution but my route args are not being passed through the attributes to the controller. I am at a bit of a loose end with how I can resolve it. Can anyone point anything out or have any advice?

<?php

declare(strict_types=1);

namespace UMA\DoctrineDemo\DI;

ini_set('memory_limit', '256M');

session_start();

use Doctrine\ORM\EntityManager;
use Nyholm\Psr7;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\App;
use Slim\Exception\HttpNotFoundException;
use Slim\Factory\AppFactory;
use Slim\Middleware\ContentLengthMiddleware;
use Slim\Routing\RouteCollectorProxy;

// Services
use UMA\DoctrineDemo\Service\ClientContext;
use UMA\DIC\Container;
use UMA\DIC\ServiceProvider;
use UMA\DoctrineDemo\Listeners\TableNamePrefix;

use UMA\DoctrineDemo\Middleware\ApiKeyValidation;
use UMA\DoctrineDemo\Action\HomePage\HomePage;

// Actions
use UMA\DoctrineDemo\Action\Email\Email;
use UMA\DoctrineDemo\Action\Sms\SMS;
use UMA\DoctrineDemo\Action\Address\GetCities;
use UMA\DoctrineDemo\Action\Address\GetStates;
use UMA\DoctrineDemo\Action\Address\GetCountries;

final readonly class Slim implements ServiceProvider
{
    /**
     * {@inheritdoc}
     */
    public function provide(Container $container): void
    {
        $this->registerClientContext($container);
        $this->registerEntityManagerServices($container);
        $this->registerMiddleware($container);
        $this->registerApp($container);
    }

    /**
     * Register ClientContext service.
     */
    private function registerClientContext(Container $container): void
    {
        
        $container->set(ClientContext::class, fn() => new ClientContext());

    }

    /**
    * Register services that depend on EntityManager.
    */
    private function registerEntityManagerServices(Container $container): void
    {
        $entityServices = [
            Email::class,
            SMS::class,
            HomePage::class,
            GetCities::class,
            GetStates::class,
            GetCountries::class,
        ];

        foreach ($entityServices as $service) {
            $container->set(
                $service,
                fn(ContainerInterface $c): RequestHandlerInterface => new $service($c->get(EntityManager::class))
            );
        }

        // Register the TableNamePrefix as an event listener
        $container->get(EntityManager::class)
            ->getEventManager()
            ->addEventListener(
                [\Doctrine\ORM\Events::loadClassMetadata],
                new TableNamePrefix($container->get(ClientContext::class))
            );
    }
    
    private function registerMiddleware(Container $container): void
    {
        $container->set(AppTokenMiddleware::class, function ($c) {
            // Get the ClientContext from the container
            $clientContext = $c->get(ClientContext::class);
            
            // Pass ClientContext to the AppTokenMiddleware constructor
            return new AppTokenMiddleware($clientContext, []);
        });
    }

    private function registerApp(Container $container): void
    {
        $container->set(App::class, fn(ContainerInterface $c) => $this->createApp($c));
    }

    private function createApp(ContainerInterface $container): App
    {
        $app = AppFactory::create(null, $container);
        $this->addMiddleware($app);
        $this->defineRoutes($app);

        return $app;
    }

    private function addMiddleware(App $app): void
    {
        $app->addRoutingMiddleware($app);
        $this->addCustomMiddleware($app);
        $this->addBodyParsingMiddleware($app);
        $this->addErrorHandlingMiddleware($app);
        $this->addCorsMiddleware($app);
    }

    private function addCustomMiddleware(App $app): void
    {

        $exclude = [
            '/api/v1/app-token',
        ];

        //$app->add(new JwtMiddleware($exclude));
        //$app->add(new ApiKeyValidation($exclude));
        $app->add(new ContentLengthMiddleware());

    }

    private function addBodyParsingMiddleware(App $app): void
    {
        $app->addBodyParsingMiddleware();
    }

    private function addErrorHandlingMiddleware(App $app): void
    {
        $app->addErrorMiddleware(true, true, true);
        error_reporting(E_ALL & E_NOTICE & E_WARNING & E_DEPRECATED);
    }

    private function addCorsMiddleware(App $app): void
    {
        $app->add(function (ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
            $response = $handler->handle($request);
            return $response
                ->withHeader('Access-Control-Allow-Origin', 'http://localhost:3000')
                ->withHeader('Access-Control-Allow-Credentials', 'true')
                ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization, App_Token, App, Accept-Language, xsrf-token')
                ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
        });
    }

    private function defineRoutes(App $app): void
    {
        $routeCollector = $app->getRouteCollector();

        // Log to check that we're in the method
        error_log('Defining routes...');

        // Home Page
        $app->get('/', HomePage::class);

        // API Routes
        $app->group('/api', function (RouteCollectorProxy $api) {
            $api->group('/v1', function (RouteCollectorProxy $api) {
                    error_log('Defining address routes...');
                    $api->group('/address', function (RouteCollectorProxy $address) {

                        $address->get('/city/{countryId}', GetCities::class);
                        $address->get('/state[/{countryId}]', GetStates::class);
                        $address->get('/country', GetCountries::class);

                    });

                    $api->group('/email', function (RouteCollectorProxy $email) {

                        $email->post('', Email::class);

                    });

                    $api->group('/sms', function (RouteCollectorProxy $sms) {

                        $sms->post('', SMS::class);

                    });
                    
                $api->get('/debug[/{testId}]', function (ServerRequestInterface $request, ResponseInterface $response) {
                        $attributes = $request->getAttributes();
                        error_log(print_r($attributes, true));
                        $response->getBody()->write(json_encode($attributes));
                        return $response->withHeader('Content-Type', 'application/json');
                    });

            });

            // Handle options requests
            $api->options('/{routes:.+}', function (ServerRequestInterface $request): ResponseInterface {
                return new Psr7\Response(200, ['Content-Type' => 'application/json'], '');
            });
        });
    }
}


The debug endpoint is returning the attribute just fine, however when I return the attribute in the controller non are listed.

I have gone through multiple variations of route args and strategies but I am at a loss.

Route Definition

  $app->group('/api', function (RouteCollectorProxy $api) {
            $api->group('/v1', function (RouteCollectorProxy $api) {
                    error_log('Defining address routes...');
                    $api->group('/address', function (RouteCollectorProxy $address) {

                        $address->get('/city/{countryId}', GetCities::class);
                        $address->get('/state[/{countryId}]', GetStates::class);
                        $address->get('/country', GetCountries::class);

                    });

                    $api->group('/email', function (RouteCollectorProxy $email) {

                        $email->post('', Email::class);

                    });

                    $api->group('/sms', function (RouteCollectorProxy $sms) {

                        $sms->post('', SMS::class);

                    });

                $api->get('/debug[/{testId}]', function (ServerRequestInterface $request, ResponseInterface $response) {
                        $attributes = $request->getAttributes();
                        error_log(print_r($attributes, true));
                        $response->getBody()->write(json_encode($attributes));
                        return $response->withHeader('Content-Type', 'application/json');
                    });

            });

'''


Solution

  • Slim 4 passes the arguments from route in third parameter of handler instead of injecting them into request object.

    So for example your debug route handler should look like this:

    $api->get('/debug[/{testId}]', function (ServerRequestInterface $request, ResponseInterface $response, array $args) {
        error_log(print_r($args, true));
        $response->getBody()->write(json_encode($args));
        return $response->withHeader('Content-Type', 'application/json');
    });
    

    Or the __invoke() methods in your classes used as handler should be defined like this:

    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        array $args
    ): ResponseInterface