wondernetwork / slim-kernel
Requires
- php: ^7.4|^8.0
- ext-json: *
- dusank/knapsack: ^10.0
- php-di/php-di: ^6.4
- php-di/slim-bridge: ^3.4
- slim/psr7: ^1.6
- slim/slim: ^4.14
- symfony/console: ^5.4
- symfony/finder: ^5.4
Requires (Dev)
- bnf/phpstan-psr-container: ^1.0
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^9.6
README
This package aims to provide a set of configuration helpers for Slim v4 microframework.
By using the KernelBuilder
, one can easily bootstrap the Slim\App
service,
along with a DI\Container
dependency injection container, declaratively define
a bunch of services, register symfony console commands, middlewares, routes, etc.
See basic usage to get an overview how to use it.
Installation
composer require wondernetwork/slim-kernel
Basic Usage
use WonderNetwork\SlimKernel\KernelBuilder; use WonderNetwork\SlimKernel\ServiceFactory\SymfonyConsoleServiceFactory; use WonderNetwork\SlimKernel\StartupHook\RoutesStartupHook; // configure the container: $container = KernelBuilder::start( // all the paths will be calculated relative to this one __DIR__.'/..', ) // just add inline definitions if you’d like ->add([ 'environment' => $environment ]) // specify a directory or directories to search definition files in ->glob( // include all *.php files in some `services` folder __DIR__.'/../services/*.php', // overrides for specific environment, given we have $environment defined: __DIR__."/../services/{$environment}/*.php", ) // register more advanced definition providers implementing the ServiceFactory interface ->register( new SymfonyConsoleServiceFactory( // for example, this provider registers a symfony application // and adds commands matching this glob pattern: __DIR__.'/../src/Cli/**/*Command.php', ), ) ->onStartup( // automatically add routes new RoutesStartupHook(__DIR__.'/../app/routes/*.php'), ) // only pass a path for prod environments, null otherwise ->useCache(__DIR__.'/../.cache') ->build(); // get the slim application from the container $app = $container->get(Slim\App::class) $app->run();
Declarative service definitions
As mentioned in the basic usage section, the KernelBuilder
has a glob()
method,
which accepts a list of glob patterns that will be used to build the container. Each
of the matched php files needs to return either of:
- An array or other iterable of
key => value
pairs. See PHP DI documentation for details - A
ServiceFactory
instance (see below)
Example:
// given it’s configured: // $kernelBuilder->glob('app/services/*.php'); // next, in app/services/some.php: use Psr\Container\ContainerInterface; return [ 'foo.scalar.dependency' => 10, FooService::class => fn (ContainerInterface $container) => new FooService( $container->get(FooDependency::class), $container->get('foo.scalar.dependency'), ), ];
On Startup hooks
You might want some initialization code to run on each request and each invocation
of a CLI command. This is best place to setup some global error handlers, boot some
static properties, etc. To do so, pass an object implementing StartupHook
to the
onStartup()
method
Routes Startup Hook
WonderNetwork\SlimKernel\StartupHook\RouteStartupHook
If you would like to get your routes registered in a declarative manner,
use this startup hook to point to files containing your route definitions.
Each of the files matched by the glob pattern needs to return a closure,
which takes Slim\App
, or more precisely Slim\Interfaces\RouteCollectorProxyInterface
.
This means you can use it for more than routes: for example global middlewares.
The closures will be evaluated on a Slim Application fetched from the container.
$kernelBuilder->onStartup( new \WonderNetwork\SlimKernel\StartupHook\RoutesStartupHook( __DIR__.'/../routes/*.php', ), ); // app/routes/some.php use Psr\Http\Message\ResponseInterface; return static function (Slim\App $app) { $app->get('/hello/{message}', function (ResponseInterface $response, string $message) { return $response->withHeader("X-Hello", $message); }); }
Error handling
The default error handling middleware is added. It’s configured to silently log
errors with details, but do not display the details in the response. You can change
that behavour, for example if you’d like to have more verbose logging in certain
environments, by overriding the ErrorMiddlewareConfiguration
service:
// somewhere in app/services/test/errors.php use WonderNetwork\SlimKernel\SlimExtension\ErrorMiddlewareConfiguration as Configuration; return [ Configuration::class => static fn () => Configuration::verbose(); ];
Beside the factory methods (either silent()
or verbose()
) you can also just create()
and
set each individual setting using with*()
and without*()
methods.
Service Factory
WonderNetwork\SlimKernel\ServiceFactory
This represents a more advanced service definition than a simple array,
by passing in a ServiceBuilder
instance. It’s helpful to use it when
you’d like to autowire a bunch of files matching a pattern, or handle
a list of configuration files with some post-processing.
You can also add service factories directly using the KernelBuilder::register()
method.
See the following list of provided Service Factories for inspiration:
Slim Applcation Service Factory
This service factory is built-in and gets registered automatically.
It defines the Slim\App
service by using the PHP DI Slim Bridge.
Symfony Command Service Factory
WonderNetwork\SlimKernel\ServiceFactory\SymfonyConsoleServiceFactory
This service factory will find all files matching a specified glob pattern, autowire them in the container, and then register a Symfony console application with all these commands added to it.
Note
Make sure that your command classes are autoloaded using a valid PSR-4
configuration in your composer.json
file
// app/services/cli.php return new WonderNetwork\SlimKernel\ServiceFactory\SymfonyConsoleServiceFactory( // glob to find command files __DIR__.'/../../src/Cli/**/*Command.php', // name for your symfony console application 'acme v1.0', );
Convenience methods to access strongly typed input argumets
// HTTP $requestParams = WonderNetwork\SlimKernel\Http\RequestParams::of($serverRequest); $requestParams->post->requireString('userId'); $requestParams->query->int('limit', 10); $requestParams->server->bool('HTTPS'); // route arguments are all string, but you can still cast them to ints $routeArguments = WonderNetwork\SlimKernel\Http\RouteArgument::of($serverRequest); $routeArguments->string('token'); $routeArguments->int('limit', 10); // CLI $cliParams = WonderNetwork\SlimKernel\Cli\InputParams::ofInput($input); $cliParams->options $cliParams->arguments
This package is aimed at accessing trusted payloads. In other words, it does not aim at validating an unknown payload, but rather asserts that the structure is correct, so that we can use semantic methods to get strong type guarantees. The package does not try to handle any situation -- except for basic type casting, it throws if it encounters some unexpected input.
-
Request Params: created from
RequestInterface
and represent request body, query and server params. Each field is returned as an Array Accessor -
Input Params: created from
InputInterface
and represent command line arguments and options. Each field is returned as an Array Accessor -
Route Argument: created from
RequestInterface
and represent the arguments matched by slim routing. Returns a single Array Accessor
Array Accessor
string(key, default = '')
- gets the interpolated string value of given field
- returns default on missing and null values
- throws on non-scalar values
maybeString(key)
see above, but returns null as the defaultrequireString(key)
see above, but throws as the defaultint(key, default = 0)
similar to string, casts numeric strings to intsmaybeInt()
andrequireInt()
similarly to string methodsbool(key)
interpolates1
and0
to booleanmaybeBool()
andrequireBool()
see abovearray(key)
- returns a mixed array on given key
- returns an empty array if key does not exist
- throws if key exists but does not explicitly contain an array
maybeArray(key)
see above, but returns null by defaultat(key)
- returns an array accessor representing a nested structure
- uses null object pattern, so returns an empty accessor if the key does not exist or is not an array
allString()
,allInt()
,allBool()
- ensures all items of payload match a given type
- returns an array of scalars of that type (
string[]
,int[]
,bool[]
)
Fingers Crossed CLI handler
For all those pesky cron jobs, where on one hand you’d like to silence the output
because it causes noisy emails for no reason, but at the same time you don’t want
to lose context when something bad happens. Wrap your commands in a
FingersCrossedHandler
like so:
public function execute(InputInterface $input, OutputInterface $output): int { return FingersCrossedHandler::of($input, $output)->run( function (InputParams $input, FingersCrossedOutput $output) { $output->write("Hello world!") return 1; }, ); }
The output will be silenced except for the following situatios:
- You increase the output verbosity using the
-v
flag or thesetVerbosity()
method - The command returns a non-zero exit code
- The command throws
In the former case, the output will be written in realtime, and in the two latter ones you can expect it writtein in bulk at the end.