Category: Slim Framework

Configuration in Slim Framework

Configuration in Slim Framework is nice and simple: the App's constructor takes a configuration array for the DI container;

Setting up

The settings sub-array is used to hold the settings of your application:

Slim comes with a number of settings that you can change. The most important is displayErrorDetails. This defaults to false, but if you set it to true, then it will display the details of any exceptions when rendering an error page. Ensure that this is set to false in production!

You can put any other settings you want to in the configuration, under any name you like as I have done with the logger key, which contains information about how to configure a Monolog instance.

Retrieving settings

The settings are stored in the DI container so you can access them via the settings key in container factories. For example, I can set up a container factory for to create my Monolog instance like this:

Slim's default container is Pimple, so we use the array notation to register a new service called 'logger' in this case. The logger settings are retrieved using $settings = $c->get('settings')['logger'].

Separate file for configuration

You should use a separate file for your configuration. This is most easily done using require. Firstly, we create config.php and return the configuration array from it:

config.php:

As require returns whatever the included file returns, we load like this:

Environment variables using dotenv

Consider using environment variables for the differences between servers. These can be configured as part of your apache/nginx setup or you can use dotenv.

In this case, create a .env file and add it to .gitignore:

.env:

We can use these environment variables in our configuration file via getenv:

config.php:

We then load using:

Multiple configuration files

You may also want to split your configuration into multiple files which are then merged together with files loaded later overriding the settings from previous files.

One use case is to have a local.config.php that's not in git that contains per-server configuration to be merged with your master configuration in config.php.

For example, your live configuration should turn off display of errors and maybe set a different logging level:

local.config.php:

Merging the two arrays is a little complicated as array_merge_recursive doesn't do what you expect and will result in the 'displayErrorDetails' key becoming an array with two elements. You could write your own merge method, but it's easier to use zend-stdlib's merge method:

Ini/Yaml/JSON/XML configuration

If you want to use something other than PHP arrays, then use zend-config along with glob. In this case, you place all your configuration files in a single directory, such as config/ and then name them with .global.{type} and .local.{type} to control order.

For example, to load the configuration files global.yaml, db.global.yaml and then local.yaml in that order:

One useful feature of zend-config is that you can mix and match between formats, so local.yaml could be local.ini and it would still work.

Summary

As you can see, using configuration with Slim is very easy; all the choices come down to how you want to organise your configuration so that you can manage the differences between environments easily.

Testing Slim Framework actions

To test a Slim Framework action, you need a request and a response object and mock whatever is in the action. This is one way to do this.

Consider this simple echo action that returns the query parameters to you as a JSON encoded string:

This is one of those useful API endpoints that your users can use to check that everything is working as expected.

The action under test

The code for this endpoint is a class called EchoAction which looks like this:

and it is registered with the Slim App like so:

Testing

Testing it isn't too complex as there are no dependencies on the EchoAction class itself, so we just have instantiate the class, invoke it and write an test.

For a URL of the form /echo?foo=bar, the core test code is:

Creating the request and response

Creating the $response is easy as the constructor parameters all have defaults:

The $request is a little more complex as it's constructor signature looks like this:

However, Slim actually creates a Slim\Http\Request using the static createFromEnvironment() factory method, which takes a Slim\Http\Environment instance. Roughly, it does this:

Setting up a $_SERVER array with the relevant elements can be a little tiresome in testing though. Fortunately, Josh needed to test Slim itself, so the Environment object has a handy static method called mock() that does this for us.

We use it like this:

As you can see, mock() takes an array which contains $_SERVER keys that we wish to set up for our particular test. Usually we set the REQUEST_METHOD, REQUEST_URI and, if we need it, QUERY_STRING. We need QUERY_STRING as this is the key in $_SERVER that Request uses to determine the query parameters for the request.

Putting it all together

Hence, our completed test looks like this:

All in all, it's not too complicated to test a Slim action and Environment::mock() makes it easy to set up particular test cases.

Improved error handling in Slim 3.2.0

We released Slim 3.2.0 yesterday which includes a number of minor bug fixes since 3.1.0 and also a few nice improvements around the way we handle errors.

Writing to the error log

Slim has a simple exception handler implemented that displays an error page that looks like this:

Slim error 1

It's not very informative, is it? That's intentional as we don't want to leak information on a live website. To display the error information you need to enable the displayErrorDetails setting like this:

Not too difficult, if you know the setting! If you don't then you're staring at a blank page and have no idea what to do next.

To help solve this Slim 3.2.0 now writes the error information to the PHP error log when the displayErrorDetails is disabled. The built in PHP web server writes the error log to stderr, so I see this in my terminal:

Slim error 2

As you can see, all the information needed to find the issue is there, so the developer can get on with her day and solve the problem at hand.

PHP 7 errors

One of the new features of PHP 7 is that it can throw Error exceptions which can then be caught and processed as you would with a standard Exception. However, Slim 3's error handler is type hinted on Exception and so doesn't catch these new Errors.

To resolve this, Slim 3.2 ships with a new PHP 7 error handler which works exactly like the current exception handler but it catch Error. Here's an example (with displayErrorDetails enabled!):

Slim error 3

To sum up

I'm very happy to have more robust error handling in Slim as I think good errors as key for usability and makes Slim that much easier and enjoyable to use. If you find any error situations in Slim that you feel could be improved, please raise an issue.

PSR-7 file uploads in Slim 3

Handling file uploads in Slim 3 is reasonably easy as it uses the PSR-7 Request object, so let's take a look.

The easiest way to get a Slim framework project up and running is to use the Slim-Skeleton to create a project:

and then you can cd into the directory and run the PHP built-in web server using:

Displaying the form

We can now create a simple form, firstly by setting up the / route in src/routes.php:

The view script, templates/index.phtml contains the form:

Handling the upload

We now need to write the route that handles the uploaded file. This goes in src/routes.php:

The file upload in $_FILES is available from the $request's getUploadedFiles() method. This returns an array keyed by the name of the <input> element. In this case, that's newfile.

The $newfile object is a instance of PSR-7's UploadedFileInterface. Typical usage is to check that there is no error and then move the file to somewhere else. This is done like this:

There's also other useful methods such as getClientMediaType() and getSize() if you need them.

Conclusion

As you can see, dealing with file uploads within a PSR-7 request is really easy!

Running Phan against Slim 3

Having installed Phan, I decided to use it against the upcoming Slim 3 codebase.

Phan needs a list of files to scan, and the place I started was with Lorna's article on Generating a file list for Phan.

I started by removing all the developer dependencies:

and then built the file list for the vendor and Slim directories. I started by using Lorna's grep statement, but that found too many non-PHP files in vendor, so I ended up with:

I then reorganised the list to put interfaces and traits at the top as order matters & then ran Phan:

A lot of issues have been found – most of them due to missing dependent classes & interfaces, so I ignored those when working through the list. Next time, I'll add the relevant vendor files to see if that catches anything else.

The remaining issues found were all type errors related to the docblock saying one thing and the code doing another. These will need tidying up.

It was an interesting exercise and I encourage you to run Phan or a similar tool over your own codebase.

Slim 3.0 RC2 Released

I released Slim 3 RC 2 today after a longer than expected gap from RC1. The gap was caused by two things:

  • Real LifeTM was busy for all the core team members
  • People test RCs, but not betas!

Obviously, the important one here was testing. Thank your everyone who tested RC1!

As a result, a number of important issues were raised after RC1 was released which we had to address. Two key BC-breaking ones involved potential security issues around trusting X-Forwarded headers:

  • #1559Request::getIp() has been removed
  • #1570 – Inspection of X-Forwarded-Proto and X-Forwarded-Host headers in Uri have been removed

As these are related to the headers, the obvious solution to was to replace with middleware. I created rka-ip-address-middleware and rka-scheme-and-host-detection-middleware to provide the same functionality in a more controlled manner, though I'm sure other alternatives will arise as more people write PSR-7 middleware.

We also made a few improvements. My favourite one is with the way errors are handled when the Accept header is JSON or XML; our response is now in the correct format! This is very important for APIs and it is important to us that out-of-the-box, we are a good API citizen. Note that the error handler's determination of the current accept header is simplistic and so is not exposed to your application. Please use Ryan Szrama's NegotiationMiddleware for your application's needs.

In this cycle we fixed a lot of issues, and are happy with the state of Slim 3. So happy, in fact, that we intend to release final next week unless someone finds a show-stopper!

If you have any interest in Slim or micro-frameworks, please test this release and report any issues that you find. We'd appreciate it!

Improved error handling in Slim 3 RC1

From RC1 of Slim 3, we have improved our error handling. We've always had error handling for HTML so that when an exception occurs, you get a nice error page that looks like this:

Slim3 error page

However, if you're writing an API that sends and expects JSON, then it still sends back HTML:

Slim3 old json error

At least we set the right Content-Type and status code!

However, this isn't really good enough. We should send back JSON if the client has asked for JSON. Until RC1, the only way to do this was to register your own error handler:

which provides the correct output:

Slim 3 custom json error

However, we can do better than this and do it for you. Starting with RC1, Slim will now output JSON (or XML) when an error occurs if the client's Accept header is application/json (or application/xml) and it will also provide all the previous exception too. This is much nicer and also works for the two other error handlers: notFound and notAllowed.

Slim 3 rc1 error json

Finally, Note that you should never use our default errorHandler in production as it leaks too much data! We'll try and fix this before 3.0 final, though.

Slim-Csrf with Slim 3

In addition to the core Slim framework, we also ship a number of add-ons that are useful for specific types of problems. One of these is Slim-Csrf which provides CSRF protection.

This is middleware that sets a token in the session for every request that you can then set as an hidden input field on a form. When the form is submitted, the middleware checks that the value in the form field matches the value stored in the session. If they match, then the all is okay, but if they don't then an error is raised.

For the simplest use case, you need start the session and add the middleware:

Then, from within a given route callable, you can create your form and add two hidden fields: one for the token's name and one for its value:

If you run this in a browser and view the source, you'll see something like this:

Slim csrf view source

Refresh and you see different values for the csrf_name and csrf_value fields, which means that the user can have multiple tabs open and submit without any issues.

For testing, I created a simple route callable:

Pressing form's submit button will result in the display of "Passed CSRF check.". If you then refresh and confirm the post, you'll see "Failed CSRF check!" and the HTTP status code will be 400.

Customising the CSRF failure

It's likely that you'll want to customise the CSRF failure display as a plaint text error message isn't very user friendly! To change this, supply a callable to the Guard class which has the same signature as middleware: `
function($request, $response, $next). The middleware must return a Response.

This allows you to supply a custom error page:

As the failure callable has the middleware signature, you can also set a flag into $request and then deal with the CSRF failure later. The failure callable would look something like this:

Now, your route callable can decide what to do:

This is very powerful and remarkably easy to set up.

Summary

The flexibility of the failure callable allows you to handle a CSRF validation failure in the most appropriate way for your application and is a very powerful feature of this middleware.

As it's PSR-7 compliant, you can use the middleware independently of Slim with any PSR-7 middleware dispatch system that uses the middleware signature of function($request, $response, $next) where a Response is returned.

Using abstract factories with Slim 3

In my Slim 3 skeleton, I chose to put each action into its own class so that its dependencies are injected into the constructor. We then register each action with the DI Container like this:

In this case, HomeAction requires a view and a logger in order to operate. This is quite clear and easy. However, it requires you manually register each action class with the DI Container.

Bruno Skvorc said this on Twitter:

In short, this seems ULTRA wasteful if I want ALL my controllers to get view and logger.

Slim 3's default DI container is Pimple, which is one of the simpler DI Containers out there. To solve Bruno's problem, my initial though was to use Zend\ServiceManager's abstract factories feature.

Fortunately, Slim 3 supports container-interop and I've already written RKA\ZsmSlimContainer which integrates Zend\ServiceManager with Slim 3. Once we have ZSM in place it's all quite easy!

We need to register our own abstract factory with Zend\ServiceManager. An abstract factory requires two methods to be implemented: canCreateServiceWithName and createServiceWithName. The method names are fairly self-explantory.

In our case, canCreateServiceWithName needs to return true if the requested class name ends in "Action" and then createServiceWithName simply creates the class with the two required dependencies. It looks like this:

app/src/ActionAbstractFactory.php:

We then register the abstract factory with the DI container:

The end result is that we don't need to register every Action with the DI Container individually as long as they all have the same constructor signature. This is obviously a simplistic example, but the principle applies to any situation where you need to create different classes in the same vein.

If you want to see this in action, I've created a sample project on GitHub.

Replacing Pimple in a Slim 3 application

One feature of Slim 3 is that the DI container is loosely coupled to the core framework. This is done in two ways:

  1. The App composes the container instance rather than extending from it.
  2. Internally, App depends on the container implementing the container-interop interface.

You can see this decoupling in the way that you instantiate a Slim 3 application:

Slim 3 ships with Pimple by default, but my preference is for Zend\ServiceManager, so I decided to integrate ServiceManager into Slim 3.

RKA\ZsmSlimContainer is the result of this work.

Usage

To use RKA\ZsmSlimContainer, simply add it via composer:

and then update your index.php:

As you can see, due to the decoupling, the change required is very small and everything still works! The only thing that doesn't work is Pimple\ServiceProviderInterface's register() method as that is tightly coupled to the Pimple container itself.

Implementation

To implement this, I extended Zend\ServiceManager and in the constructor registered Slim's default services. This is easy enough to do with ServiceManager's setFactory method. For example:

In this example, the callableResolver service needs to return a new instance every time it is retrieved from the container, so I pass false in as the third parameter to setFactory as it will default to shared otherwise.

This was the minimum effort required, but I wanted to be able to use code that had been written for Pimple (as far as possible), such as Twig-View. To do this, I implemented ArrayAccess as that's how Pimple works. Implementing offsetGet, offsetExists and offsetUnset was easy as I simply had to call ServiceManager's get, has and unregisterService methods respectively, but offsetSet required a bit more work.

When setting a service via array access, you can assign settings, closures or instances:

In ServiceManager, you would use different methods, such as setService or setFactory.

I needed to detect what was intended and call the correct internal method, so I came up with this:

I also support the invokable feature of ServiceManager, where if the value is a class name string, then ServiceManager can instantiate when required. This would be used like this:

 

All in all, I was pleased at how the decoupled nature of Slim 3 made it easy to replace Pimple with my preferred DI container. I particularly like how it is seamless enough that I can continue to use Twig-View.