Upgrading LAC to Slim 4
Wednesday, October 28th, 2020 | Programming
Last month, I published an upgrade guide to Slim 4 covering the changes that most people will need to make.
I recently upgraded the Leeds Anxiety Clinic and this project has some further complexities, so this post will elaborate on all of the changes I made, in case my original blog post did not cover some of the issues that you are also running into.
Things to do in advance
The old service layer calls no longer work:
$db = $this->ci->db;
And need to be updated to:
$db = $this->ci->get('db');
However, Slim 3’s service container already supports this syntax, so you can go ahead and update your code in advance.
If you are passing the container into controller functions, you need to typecast it.
use Psr\Container\ContainerInterface;
class Controller {
__construct(ContainerInterface $ci) {}
}
If you pull the request out from the service container, you cannot do that in Slim 4. For example, if you have a render helper that relies on the request being in the container, you will have to start manually passing that into your render help from the page route closure.
Dependencies
You will want all of these:
"slim/slim": "4.*",
"slim/psr7": "1.*",
"slim/http": "1.*",
"php-di/php-di": "^6.1",
I did wonder if I could remove my other PSR-7 library that I use for Mailgun, nyholm/psr7, but the answer is no.
Instanciating the app
We now create the container and then the app, and you can recursively pass in the container.
$container = new \DI\Container;
$container->set('serviceA', function() use ($container) {
return new ServiceA($container->get('serviceB'));
});
\Slim\Factory\AppFactory::setContainer($container);
$app = \Slim\Factory\AppFactory::create();
Throwing not found exceptions
If you manually throw not found exceptions or other HTTP exceptions, Slim 3 has them located here:
Slim\Exception\NotFoundException
Slim 4 moves it to here:
Slim\Exception\HttpNotFoundException
Error handling
If you want Slim 4’s default error handling, you need:
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
And if you want to handle HTTP errors (such as not found), you can use:
$errorMiddleware->setErrorHandler(
Slim\Exception\HttpNotFoundException::class,
function (Psr\Http\Message\ServerRequestInterface $request) use ($container) {
$controller = new App\Controller\ExceptionController($container);
return $controller->notFound($request);
});
You can also set your own error handling:
$errorMiddleware->setDefaultErrorHandler(
function (Psr\Http\Message\ServerRequestInterface $request,
Throwable $exception) use ($container) {
$controller = new App\Controller\ExceptionController($container);
return $controller->error($request, $exception);
});
This means you will need to create your own response object in any exception controller you use.
public function notFound(Request $request)
{
$response = new \Slim\Psr7\Response;
return $this->render($response, 'not-found.html');
}
Custom middleware
The way we pass through middleware has changed. The old way:
public function __invoke($request, $response, $next)
{
$response = $next($request, $response);
return $response;
}
The new way:
public function __invoke($request, $handler)
{
return $handler->handle($request);
}
That change alone probably won’t do what you want it to do out-of-the-box, so you might need to do some reading up and adapt it to your specific middleware depending on function.
Custom middleware wants to go towards the bottom after you define your routes, but before the error middleware.
Last month, I published an upgrade guide to Slim 4 covering the changes that most people will need to make.
I recently upgraded the Leeds Anxiety Clinic and this project has some further complexities, so this post will elaborate on all of the changes I made, in case my original blog post did not cover some of the issues that you are also running into.
Things to do in advance
The old service layer calls no longer work:
$db = $this->ci->db;
And need to be updated to:
$db = $this->ci->get('db');
However, Slim 3’s service container already supports this syntax, so you can go ahead and update your code in advance.
If you are passing the container into controller functions, you need to typecast it.
use Psr\Container\ContainerInterface; class Controller { __construct(ContainerInterface $ci) {} }
If you pull the request out from the service container, you cannot do that in Slim 4. For example, if you have a render helper that relies on the request being in the container, you will have to start manually passing that into your render help from the page route closure.
Dependencies
You will want all of these:
"slim/slim": "4.*", "slim/psr7": "1.*", "slim/http": "1.*", "php-di/php-di": "^6.1",
I did wonder if I could remove my other PSR-7 library that I use for Mailgun, nyholm/psr7, but the answer is no.
Instanciating the app
We now create the container and then the app, and you can recursively pass in the container.
$container = new \DI\Container; $container->set('serviceA', function() use ($container) { return new ServiceA($container->get('serviceB')); }); \Slim\Factory\AppFactory::setContainer($container); $app = \Slim\Factory\AppFactory::create();
Throwing not found exceptions
If you manually throw not found exceptions or other HTTP exceptions, Slim 3 has them located here:
Slim\Exception\NotFoundException
Slim 4 moves it to here:
Slim\Exception\HttpNotFoundException
Error handling
If you want Slim 4’s default error handling, you need:
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
And if you want to handle HTTP errors (such as not found), you can use:
$errorMiddleware->setErrorHandler( Slim\Exception\HttpNotFoundException::class, function (Psr\Http\Message\ServerRequestInterface $request) use ($container) { $controller = new App\Controller\ExceptionController($container); return $controller->notFound($request); });
You can also set your own error handling:
$errorMiddleware->setDefaultErrorHandler( function (Psr\Http\Message\ServerRequestInterface $request, Throwable $exception) use ($container) { $controller = new App\Controller\ExceptionController($container); return $controller->error($request, $exception); });
This means you will need to create your own response object in any exception controller you use.
public function notFound(Request $request) { $response = new \Slim\Psr7\Response; return $this->render($response, 'not-found.html'); }
Custom middleware
The way we pass through middleware has changed. The old way:
public function __invoke($request, $response, $next) { $response = $next($request, $response); return $response; }
The new way:
public function __invoke($request, $handler) { return $handler->handle($request); }
That change alone probably won’t do what you want it to do out-of-the-box, so you might need to do some reading up and adapt it to your specific middleware depending on function.
Custom middleware wants to go towards the bottom after you define your routes, but before the error middleware.