Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v5 #3370

Merged
merged 8 commits into from
Feb 1, 2025
Merged

Add v5 #3370

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ExceptionLoggingMiddleware` for custom error logging.
- `ExceptionHandlingMiddleware` delegates exceptions to a custom error handler.
- `ErrorHandlingMiddleware` converts errors into `ErrorException` instances that can then be handled by the `ExceptionHandlingMiddleware` and `ExceptionLoggingMiddleware`.
- New custom error handlers using a new `ExceptionHandlerInterface`. See new `ExceptionHandlingMiddleware`.
- New `JsonExceptionRenderer` generates a JSON problem details (rfc7807) response
- New `XmlExceptionRenderer` generates a XML problem details (rfc7807) response
- New custom error handlers using the new `ExceptionLoggingMiddleware` middleware.
- New `JsonExceptionRenderer` generates JSON error response.
- New `XmlExceptionRenderer` generates XML error response.
- New `BasePathMiddleware` for dealing with Apache subdirectories.
- New `HeadMethodMiddleware` ensures that the response body is empty for HEAD requests.
- New `JsonRenderer` utility class for rendering JSON responses.
Expand All @@ -33,11 +33,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New `CorsMiddleware` for handling CORS requests.
- Support to build a custom middleware pipeline without the Slim App class. See new `ResponseFactoryMiddleware`
- New media type detector
- New Config class and ConfigInterface
- New ContainerFactoryInterface and PhpDiContainerFactory class

### Changed

* Require PHP 8.2 or 8.3. News versions will be supported after a review and test process.
* Require PHP 8.2 or newer. News versions will be supported after a review and test process.
* Migrated all tests to PHPUnit 11
* Update GitHub action and build settings
* Improve DI container integration. Make the DI container a first-class citizen. Require a PSR-11 package.
Expand Down
101 changes: 53 additions & 48 deletions Slim/Builder/AppBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@

namespace Slim\Builder;

use DI\Container;
use Psr\Container\ContainerInterface;
use RuntimeException;
use Slim\App;
use Slim\Container\DefaultDefinitions;
use Slim\Container\HttpDefinitions;
use Slim\Container\PhpDiContainerFactory;
use Slim\Interfaces\ContainerFactoryInterface;

/**
* This class is responsible for building and configuring a Slim application with a dependency injection (DI) container.
Expand All @@ -24,8 +25,6 @@
* Key functionalities include:
* - Building the Slim `App` instance with configured dependencies.
* - Customizing the DI container with user-defined service definitions or a custom container factory.
* - Setting up middleware in a specified order.
* - Configuring application settings.
*/
final class AppBuilder
{
Expand All @@ -35,9 +34,9 @@ final class AppBuilder
private array $definitions = [];

/**
* @var callable|null Factory function for creating a custom DI container
* @var ContainerFactoryInterface|null Factory function for creating a custom DI container
*/
private $containerFactory = null;
private ?ContainerFactoryInterface $containerFactory = null;

/**
* The constructor.
Expand All @@ -46,8 +45,8 @@ final class AppBuilder
*/
public function __construct()
{
$this->addDefinitions(DefaultDefinitions::class);
$this->addDefinitions(HttpDefinitions::class);
$this->addDefinitionsClass(DefaultDefinitions::class);
$this->addDefinitionsClass(HttpDefinitions::class);
}

/**
Expand All @@ -61,83 +60,89 @@ public function build(): App
}

/**
* Creates and configures the DI container.
* Sets the service definitions for the DI container.
*
* If a custom container factory is set, it will be used to create the container;
* otherwise, a default container with the provided definitions will be created.
* @param array $definitions An array of service definitions
*
* @return ContainerInterface The configured DI container
* @return self The current instance
*/
private function buildContainer(): ContainerInterface
public function addDefinitions(array $definitions): self
{
return $this->containerFactory
? call_user_func($this->containerFactory, $this->definitions)
: new Container($this->definitions);
$this->definitions = array_merge($this->definitions, $definitions);

return $this;
}

/**
* Sets the service definitions for the DI container.
*
* The method accepts either an array of definitions or the name of a class that provides definitions.
* If a class name is provided, its definitions are added to the existing ones.
* @param string $class A definition provider class name
*
* @throws RuntimeException
*
* @return self The current instance
*/
public function addDefinitionsClass(string $class): self
{
$definitions = call_user_func(new $class());

if (!is_array($definitions)) {
throw new RuntimeException('Definition file should return an array of definitions');
}

$this->addDefinitions($definitions);

return $this;
}

/**
* Sets the service definitions for the DI container.
*
* @param array|string $definitions An array of service definitions or a class name providing them
* @param string $file A service definitions provider file
*
* @throws RuntimeException
*
* @return self The current AppBuilder instance for method chaining
* @return self The current instance
*/
public function addDefinitions(array|string $definitions): self
public function addDefinitionsFile(string $file): self
{
if (is_string($definitions)) {
if (class_exists($definitions)) {
$definitions = (array)call_user_func(new $definitions());
} else {
$definitions = require $definitions;

if (!is_array($definitions)) {
throw new RuntimeException('Definition file should return an array of definitions');
}
}
$definitions = require $file;

if (!is_array($definitions)) {
throw new RuntimeException('Definition file should return an array of definitions');
}

$this->definitions = array_merge($this->definitions, $definitions);
$this->addDefinitions($definitions);

return $this;
}

/**
* Sets a custom factory for creating the DI container.
*
* @param callable $factory A callable that returns a configured DI container
* @param ContainerFactoryInterface $containerFactory A DI container factory
*
* @return self The current AppBuilder instance for method chaining
* @return self The current instance
*/
public function setContainerFactory(callable $factory): self
public function setContainerFactory(ContainerFactoryInterface $containerFactory): self
{
$this->containerFactory = $factory;
$this->containerFactory = $containerFactory;

return $this;
}

/**
* Sets application-wide settings in the DI container.
*
* This method allows the user to configure various settings for the Slim application,
* by passing an associative array of settings.
* Creates and configures the DI container.
*
* @param array $settings An associative array of application settings
* If a custom container factory is set, it will be used to create the container;
* otherwise, a default container with the provided definitions will be created.
*
* @return self The current AppBuilder instance for method chaining
* @return ContainerInterface The configured DI container
*/
public function setSettings(array $settings): self
private function buildContainer(): ContainerInterface
{
$this->addDefinitions(
[
'settings' => $settings,
]
);
$this->containerFactory = $this->containerFactory ?? new PhpDiContainerFactory();

return $this;
return $this->containerFactory->createContainer($this->definitions);
}
}
41 changes: 0 additions & 41 deletions Slim/Configuration/Config.php

This file was deleted.

70 changes: 0 additions & 70 deletions Slim/Container/DefaultDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,10 @@
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Slim\Configuration\Config;
use Slim\Emitter\ResponseEmitter;
use Slim\Error\Handlers\ExceptionHandler;
use Slim\Error\Renderers\HtmlExceptionRenderer;
use Slim\Error\Renderers\JsonExceptionRenderer;
use Slim\Error\Renderers\PlainTextExceptionRenderer;
use Slim\Error\Renderers\XmlExceptionRenderer;
use Slim\Interfaces\ConfigurationInterface;
use Slim\Interfaces\ContainerResolverInterface;
use Slim\Interfaces\EmitterInterface;
use Slim\Interfaces\ExceptionHandlerInterface;
use Slim\Interfaces\RequestHandlerInvocationStrategyInterface;
use Slim\Media\MediaType;
use Slim\Media\MediaTypeDetector;
use Slim\Middleware\BodyParsingMiddleware;
use Slim\Middleware\ExceptionHandlingMiddleware;
use Slim\Middleware\ExceptionLoggingMiddleware;
use Slim\RequestHandler\MiddlewareRequestHandler;
use Slim\Routing\Router;
use Slim\Routing\Strategies\RequestResponse;
Expand All @@ -52,23 +39,6 @@ final class DefaultDefinitions
public function __invoke(): array
{
return [
BodyParsingMiddleware::class => function (ContainerInterface $container) {
$mediaTypeDetector = $container->get(MediaTypeDetector::class);
$middleware = new BodyParsingMiddleware($mediaTypeDetector);

return $middleware
->withDefaultMediaType('text/html')
->withDefaultBodyParsers();
},

Config::class => function (ContainerInterface $container) {
return new Config($container->has('settings') ? (array)$container->get('settings') : []);
},

ConfigurationInterface::class => function (ContainerInterface $container) {
return $container->get(Config::class);
},

ContainerResolverInterface::class => function (ContainerInterface $container) {
return $container->get(ContainerResolver::class);
},
Expand All @@ -77,46 +47,6 @@ public function __invoke(): array
return new ResponseEmitter();
},

ExceptionHandlingMiddleware::class => function (ContainerInterface $container) {
$handler = $container->get(ExceptionHandlerInterface::class);

return (new ExceptionHandlingMiddleware())->withExceptionHandler($handler);
},

ExceptionHandlerInterface::class => function (ContainerInterface $container) {
// Default exception handler
$exceptionHandler = $container->get(ExceptionHandler::class);

// Settings
$displayErrorDetails = (bool)$container->get(ConfigurationInterface::class)
->get('display_error_details', false);

$exceptionHandler = $exceptionHandler
->withDisplayErrorDetails($displayErrorDetails)
->withDefaultMediaType(MediaType::TEXT_HTML);

return $exceptionHandler
->withoutHandlers()
->withHandler(MediaType::APPLICATION_JSON, JsonExceptionRenderer::class)
->withHandler(MediaType::TEXT_HTML, HtmlExceptionRenderer::class)
->withHandler(MediaType::APPLICATION_XHTML_XML, HtmlExceptionRenderer::class)
->withHandler(MediaType::APPLICATION_XML, XmlExceptionRenderer::class)
->withHandler(MediaType::TEXT_XML, XmlExceptionRenderer::class)
->withHandler(MediaType::TEXT_PLAIN, PlainTextExceptionRenderer::class);
},

ExceptionLoggingMiddleware::class => function (ContainerInterface $container) {
// Default logger
$logger = $container->get(LoggerInterface::class);
$middleware = new ExceptionLoggingMiddleware($logger);

// Read settings
$logErrorDetails = (bool)$container->get(ConfigurationInterface::class)
->get('log_error_details', false);

return $middleware->withLogErrorDetails($logErrorDetails);
},

LoggerInterface::class => function () {
return new NullLogger();
},
Expand Down
23 changes: 23 additions & 0 deletions Slim/Container/PhpDiContainerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/**
* Slim Framework (https://slimframework.com).
*
* @license https://github.com/slimphp/Slim/blob/5.x/LICENSE.md (MIT License)
*/

declare(strict_types=1);

namespace Slim\Container;

use DI\Container;
use Psr\Container\ContainerInterface;
use Slim\Interfaces\ContainerFactoryInterface;

final class PhpDiContainerFactory implements ContainerFactoryInterface
{
public function createContainer(array $definitions = []): ContainerInterface
{
return new Container($definitions);
}
}
Loading