Module API
Slick
is a modular framework where nearly all features are implemented as modules. Some modules are enabled by default and cannot be disabled, while others may need to be enabled to set up their dependencies in the dependency container, console commands, HTTP middlewares, and default settings.
This module provides the API needed to create a Slick module.
Slick Module interface
This is the base interface and the minimum implementation required for a module. You need to specify what needs to be done when enabling and disabling the module, including any services and default settings. Additionally, it is necessary to provide a module name and description to be displayed when the user lists available modules.
The system will search for PHP classes suffixed with Module
to link with during the request bootstrap process. For example, let’s create a simple module called acme
that will perform some tasks:
namespace My\Acme;
use Slick\ModuleApi\Infrastructure\AbstractModule;
use Slick\ModuleApi\Infrastructure\SlickModuleInterface;
final readonly class AcmeModule extends AbstractModule implements SlickModuleInterface
{
public function description(): string
{
return "Provides awesome ACME features.";
}
}
That’s the mininal needed to define a slick module.
Module settings
The SlickModuleInterface::settings()
method is called during the request bootstrap when the application is configuring global settings. You can use this method to provide default configuration settings for your module. The helper functions importSettingsFile()
and mergeArrays()
can be used to merge default settings with settings from a specific file.
Let’s assume you want the user to have a dedicated settings file for your module. By convention, these files should be placed in the config/modules
directory of your application.
namespace My\Acme;
use Slick\ModuleApi\Infrastructure\AbstractModule;
use Slick\ModuleApi\Infrastructure\SlickModuleInterface;
final readonly class AcmeModule extends AbstractModule implements SlickModuleInterface
{
use function Slick\ModuleApi\importSettingsFile;
// ...
/**
* @return array<string, mixed>
*/
public function settings(): array
{
$file = APP_ROOT . '/config/modules/acme.php';
$default = [
"enabled_feature" => true,
"logging" => false,
"key" => "test"
];
return importSettingsFile($file, $default);
}
}
This strategy allows you to define default configuration settings for your module, which can also be overridden by the applications using it.
Module services
Services are typically interface implementations that can be used as dependencies in a given controller or command. Modules can define these services so that the dependency injection container can create and inject them when needed.
You can also have a default list of services and use the helper functions importSettingsFile()
and mergeArrays()
to retrieve and merge service definitions:
namespace My\Acme;
use Slick\ModuleApi\Infrastructure\AbstractModule;
use Slick\ModuleApi\Infrastructure\SlickModuleInterface;
final readonly class AcmeModule extends AbstractModule implements SlickModuleInterface
{
use function Slick\ModuleApi\importSettingsFile;
// ...
/**
* @return array<string, mixed>
*/
public function services(): array
{
$file = APP_ROOT . '/config/modules/acme.php';
$default = [
AcmeInterface::class => ObjectDefinition
];
return importSettingsFile($file, $default);
}
}
Web application module
Web applications are managed using a PSR-15 HTTP middleware stack request handler. Therefore, the main goal is to add middlewares that can handle requests and provide the features your module offers.
Similarly, you can define an associative array of middlewares to be added during the request bootstrap process. The key difference is that you need to specify the position where you want each middleware to be.
Let’s imagine our Acme module will add a middleware that adds a special X-Test: acme
header to the dispatched response. First, create the middleware.
namespace My\Acme\Infrastructure\Http;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class AcmeMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
return $response->withHeader("X-Test", "acme");
}
}
Now we need to configure the middleware in the HTTP handler stack so that all requests pass through our middleware:
namespace My\Acme;
use My\Acme\Infrastructure\Http\AcmeMiddleware;
use Slick\ModuleApi\Infrastructure\AbstractModule;
use Slick\ModuleApi\Infrastructure\FrontController\MiddlewareHandler;
use Slick\ModuleApi\Infrastructure\FrontController\MiddlewareHandlerInterface;
use Slick\ModuleApi\Infrastructure\FrontController\MiddlewarePosition;
use Slick\ModuleApi\Infrastructure\FrontController\Position;
use Slick\ModuleApi\Infrastructure\FrontController\WebModuleInterface;
final readonly class AcmeModule extends AbstractModule implements WebModuleInterface;
{
...
/**
* @return array<MiddlewareHandlerInterface>
*/
public function middlewareHandlers(): array
{
return [
new MiddlewareHandler(
'acme',
new MiddlewarePosition(Position::Top),
AcmeMiddleware::class
)
];
}
}
The above code creates a MiddlewareHandler
that will hold the necessary configuration for placing an instance of our middleware in the HTTP handler stack. We need to provide a name, a position, and the middleware class name.
Middleware position
The following table describes the possible position values you have to to define where the middleware will be placed during the application bootstrap process:
Position | Description | Needs reference? |
---|---|---|
Position::Top | Place middleware in the beginning of the stack | No |
Position::Bottom | Place middleware in the end of the stack | No |
Position::Before | Place middleware before the one given in refernce | Yes |
Position::After | Place middleware after the one given in refernce | Yes |
A reference
should be provided when using the Position::Before
and Position::After
arguments to determine the exact position for placing the middleware. If the reference is not found in the stack, the middleware will be placed at the top or bottom, respectively.
...
public function middlewareHandlers(): array
{
// With a reference middleware name
$position = new MiddlewarePosition(Position::Before, 'dispatcher');
return [
new MiddlewareHandler(
'acme',
$position,
AcmeMiddleware::class
)
];
}
Use the following console command to view the current HTTP handler stack configuration:
bin/console stack
Middleware instance
When retrieving the middleware instance during the application bootstrap process, it is verified if the provided instance is a string
(which will be used as a key to retrieve the middleware from the dependency injection container), a callable
(which will be executed), or an object that should be an instance of the MiddlewareInterface
.
Console application module
Slick uses the Symfony Console component to create a command-line interface for its applications. Your module can add commands to be available when the bin/console
application runs:
namespace My\Acme;
use My\Acme\UserInterface\Console\AcmeCommand;
use Slick\Di\ContainerInterface;
use Slick\ModuleApi\Infrastructure\AbstractModule;
use Slick\ModuleApi\Infrastructure\Console\ConsoleModuleInterface;
final readonly class AcmeModule extends AbstractModule implements ConsoleModuleInterface
{
...
public function configureConsole(Application $cli, ContainerInterface $container): void
{
$cli->addCommand($container->get(AcmeCommand::class));
}
}
OnEnable
and OnDisable
events
In some cases, you may need to perform operations such as copying a file or creating a class or directory, which are only done once you enable a given module. In other cases, you may need to clean up files that are no longer needed when a module is disabled or perform tasks on both enable and disable actions.
Slick will call SlickModuleInterface::onEnable()
and SlickModuleInterface::onDisable()
when these operations are performed, allowing you to properly configure your module.
These methods share a similar signature and are called with an associative array context argument. In the context, you have a ContainerInterface
instance under the container
key. In the SlickModuleInterface::onDisable()
method, the context also includes a purge
flag under the purge
key. This flag is set when the user disables the module with the --purge
option, allowing you to perform a more thorough cleanup of your module setup.