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