Advanced SlimPHP Tutorial

Published Oct 19, 2017

swag-sticker-text.png

Well since my previous post on Slim seems to be well received here is a more advanced post. In this post I will be describing how I use Slim in the workplace and what I have found works really well.

Container

Pimple is a great container however I have found the lack of auto-wiring to be a bit of a pain so I do prefer to use PHP-DI via (http://php-di.org/doc/frameworks/slim.html). I use PHP-DI with a custom application class that looks like this:

<?php
namespace MyApp;

use DI\ContainerBuilder;

class App extends \DI\Bridge\Slim\App
{
    protected function configureContainer(ContainerBuilder $builder)
    {
        $dotenv = new \Dotenv\Dotenv(__DIR__.'/../config');
        $dotenv->load();

        $builder->addDefinitions(__DIR__ . '/../config/di-container.php');
    }
}

The di-container.php file is really simple and contains a PHP array that holds all of the "root" dependencies for a project. Typically this is limited to: Doctrine, Twig, Mailgun, Command Bus, Event Dipatcher, Redis ... etc. Having all of the "root" dependencies it saves me having to write all of the wiring code for my domain (which can be huge... Current project has 250+ Domain classes). If you have different configs that you need you can add infrastucture in your APP class to load different di-configs depending upon your environment.

This comes at a cost; performance... so just note that it's not free on average maybe 5ms will be added to your request.

A minimal PHP-DI di-container.php:

return [
  'settings.displayErrorDetails' => true, //This should be turned on for Dev and false for production!
];

Container Resolution

In Slim we support the Anonymous function style for route actions, but I suggest that no-one use them in production. Instead I have always recommended people use Action classes. Paul Jones wrote a nice article on how to use Action classes in Slim 3 Action-Domain-Responder.

Having said that as people start to use Slim and understand how PSR-4 works, I recommend that everyone use the class name for resolution of services inside the container. This is pretty much required when using PHP-DI however it's not really written anywhere. The class constant is very powerful especially when it comes to maintaining applications as if you move or refactor class names then PHPStorm is able to do all the things for you and you don't waste time changing strings!

$app->get('/user/{userId}', GetUserAction::class);

Remember Action classes must implement public function __invoke(Request $request, Response $response) and if you are using PHP-DI then the signature changes to include the userId param! public function __invoke($userId, Request $request, Response $response) I find this very very handy!

Route Groups

Route groups are a convienence feature which ends up being a URI builder. It is a very easy way to map middleware to entire blocks of routes and saves having to type out ->add(Middleware) everywhere.

$app->group('/api', function () {
  //The context inside here $this is actually the RouterInstance
  
    //User REST
    $this->group('/user', function () {
      $this->get('/{userId}', GetUserAction::class);
      $this->post('', CreateUserAction::class);
      $this->put('/{userId}', UpdateUserAction::class);
      $this->delete('/{userId}', DeleteUserAction::class);
    })->add(AclUserApiCheck::class);
    
    //Order REST
    $this->group('/order', function () {
      $this->get('/{order}', GetOrderAction::class);
      $this->post('', CreateOrderAction::class);
      $this->put('/{order}', UpdateOrderAction::class);
      $this->delete('/{order}', DeleteOrderAction::class);
    })->add(AclOrderApiCheck::class);
})->add(AclApiCheck::class);

I think this style ends up being more readable and is easier to trace when your routes file has a lot of route definitions.

Folder Structure

I personally use the following structure however there is absolutely no required structure for your application:

  • config/
  • templates/
  • src/
  • tests/
  • intl/
  • cil/
  • public/
  • logs/

Unit Testing Controllers/Actions

I have found that it's easier if you pass in an actual Slim object rather than a mocked one. The following code is a great place to start!

public function requestFactory()
    {
        $env = Environment::mock();
        $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123');
        $headers = Headers::createFromEnvironment($env);
        $cookies = [
            'user' => 'john',
            'id' => '123',
        ];
        $serverParams = $env->all();
        $body = new RequestBody();
        $uploadedFiles = UploadedFile::createFromEnvironment($env);
        $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
        return $request;
    }

If you have any questions please feel free to reach out to me! I can be found on this platform, Twitter or on SlimPHP's (Slack Channel / IRC Channel)

Happy Coding!

Discover and read more posts from Glenn Eggleton
get started
Enjoy this post?

Leave a like and comment for Glenn