diff --git a/.cs.php b/.cs.php index 00e0f6f7d..57627b692 100644 --- a/.cs.php +++ b/.cs.php @@ -9,64 +9,68 @@ [ '@PSR1' => true, '@PSR2' => true, - '@Symfony' => true, - 'psr_autoloading' => true, - // custom rules - 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], // psr-5 - 'phpdoc_to_comment' => false, - 'no_superfluous_phpdoc_tags' => false, + 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], 'array_indentation' => true, 'array_syntax' => ['syntax' => 'short'], + 'blank_line_between_import_groups' => true, 'cast_spaces' => ['space' => 'none'], - 'concat_space' => ['spacing' => 'one'], + 'class_definition' => [ + 'space_before_parenthesis' => true, + ], 'compact_nullable_type_declaration' => true, - 'declare_equal_normalize' => ['space' => 'single'], + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'declare_strict_types' => false, + 'echo_tag_syntax' => ['format' => 'long'], + 'fully_qualified_strict_types' => true, + 'function_declaration' => [ + 'closure_fn_spacing' => 'none', + ], 'general_phpdoc_annotation_remove' => [ 'annotations' => [ 'author', 'package', ], ], + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => null, + 'import_functions' => null, + ], 'increment_style' => ['style' => 'post'], 'list_syntax' => ['syntax' => 'short'], - 'echo_tag_syntax' => ['format' => 'long'], - 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], - 'phpdoc_align' => false, - 'phpdoc_no_empty_return' => false, - 'phpdoc_order' => true, // psr-5 - 'phpdoc_no_useless_inheritdoc' => false, - 'protected_to_private' => false, - 'yoda_style' => false, 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => ['class', 'function', 'const'], - ], - 'single_line_throw' => false, - 'declare_strict_types' => false, - 'blank_line_between_import_groups' => true, - 'fully_qualified_strict_types' => true, 'no_null_property_initialization' => false, + 'no_superfluous_phpdoc_tags' => false, + 'no_unused_imports' => true, 'nullable_type_declaration_for_default_null_value' => false, 'operator_linebreak' => [ 'only_booleans' => true, 'position' => 'beginning', ], - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => null, - 'import_functions' => null + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => ['class', 'function', 'const'], ], - 'class_definition' => [ - 'space_before_parenthesis' => true, + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], + 'phpdoc_align' => false, + 'phpdoc_no_empty_return' => false, + 'phpdoc_no_useless_inheritdoc' => false, + 'phpdoc_order' => true, + 'phpdoc_to_comment' => false, + 'protected_to_private' => false, + 'psr_autoloading' => true, + 'single_line_throw' => false, + 'trailing_comma_in_multiline' => [ + 'after_heredoc' => true, + 'elements' => ['array_destructuring', 'arrays', 'match'], + ], + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, ], - 'declare_equal_normalize' => false, - 'phpdoc_summary' => false, - 'phpdoc_add_missing_param_annotation' => false, - 'no_useless_concat_operator' => false, - 'fully_qualified_strict_types' => false, - 'trailing_comma_in_multiline' => ['elements' => ['arrays']], - ] + ], ) ->setFinder( PhpCsFixer\Finder::create() @@ -74,5 +78,5 @@ ->in(__DIR__ . '/tests') ->name('*.php') ->ignoreDotFiles(true) - ->ignoreVCS(true) + ->ignoreVCS(true), ); diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4c1ef00e0..247555fbb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,14 +16,14 @@ jobs: strategy: fail-fast: false matrix: - php: [ 8.2, 8.3, 8.4 ] + php: [ 8.2, 8.3, 8.4, 8.5 ] include: - - php: 8.2 + - php: 8.4 analysis: true steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 @@ -37,9 +37,6 @@ jobs: - name: Coding standards run: composer cs:check - - name: Code sniffer - run: composer sniffer:check - - name: Static analysis run: composer stan diff --git a/CHANGELOG.md b/CHANGELOG.md index 667fb8c68..d07d251b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### New Features -- New `AppBuilder` to create a Slim App instance for different scenarios. Replaces the `AppFactory`. - Unified DI container resolution. All the factory logic has been removed and moved to the DI container. This reduces the internal complexity by delegating the building logic into the DI container. - Provide FIFO (first in, first out) middleware order support. LIFO is not supported anymore. - Optimized internal routing concept for better separation of concern and flexibility. @@ -73,7 +72,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `Slim/Builder/AppBuilder.php`: Introduced to replace `Slim/Factory/AppFactory.php`. - `Slim/Container/CallableResolver.php`: New implementation of the Callable Resolver. - `Slim/Container/DefaultDefinitions.php`: Default container definitions. - `Slim/Handlers/ExceptionHandler.php`: New Exception Handler for better error handling. @@ -115,7 +113,6 @@ New files for routing, middleware, and factories, including: - `Slim/CallableResolver.php` - `Slim/Handlers/ErrorHandler.php` -- `Slim/Factory/AppFactory.php` and related `Psr17` factories. - `Slim/Interfaces/AdvancedCallableResolverInterface.php` - `Slim/Interfaces/RouteCollectorInterface.php`, - `RouteCollectorProxyInterface.php`, diff --git a/README.md b/README.md index ca8c8ab82..4c1837e0d 100644 --- a/README.md +++ b/README.md @@ -56,16 +56,15 @@ Then create file `public/index.php`. use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Middleware\EndpointMiddleware; use Slim\Middleware\ExceptionHandlingMiddleware; use Slim\Middleware\RoutingMiddleware; require __DIR__ . '/../vendor/autoload.php'; -// Instantiate App using the builder -$builder = new AppBuilder(); -$app = $builder->build(); +// Instantiate the App +$app = AppFactory::build(); // Add middleware $app->add(ExceptionHandlingMiddleware::class); diff --git a/Slim/App.php b/Slim/App.php index 7d9f0ba3e..bd98ad9c5 100644 --- a/Slim/App.php +++ b/Slim/App.php @@ -16,13 +16,16 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Interfaces\EmitterInterface; -use Slim\Interfaces\RouteCollectionInterface; +use Slim\Interfaces\RouterInterface; use Slim\Interfaces\ServerRequestCreatorInterface; -use Slim\RequestHandler\MiddlewareRequestHandler; +use Slim\Middleware\EndpointMiddleware; +use Slim\Middleware\ErrorExceptionMiddleware; +use Slim\Middleware\HtmlExceptionMiddleware; +use Slim\Middleware\JsonExceptionMiddleware; +use Slim\Middleware\RoutingMiddleware; use Slim\Routing\Route; use Slim\Routing\RouteCollectionTrait; use Slim\Routing\RouteGroup; -use Slim\Routing\Router; /** * App @@ -31,11 +34,9 @@ * running the application. It provides methods for defining routes, adding middleware, and managing * the application's lifecycle, including handling HTTP requests and emitting responses. * - * @template TContainerInterface of (ContainerInterface|null) - * * @api */ -class App implements RequestHandlerInterface, RouteCollectionInterface +class App implements RequestHandlerInterface { use RouteCollectionTrait; @@ -64,7 +65,7 @@ class App implements RequestHandlerInterface, RouteCollectionInterface /** * The router instance for handling route definitions and matching. */ - private Router $router; + private RouterInterface $router; /** * The emitter instance for sending the HTTP response to the client. @@ -84,14 +85,12 @@ public function __construct(ContainerInterface $container) $this->container = $container; $this->serverRequestCreator = $container->get(ServerRequestCreatorInterface::class); $this->requestHandler = $container->get(RequestHandlerInterface::class); - $this->router = $container->get(Router::class); + $this->router = $container->get(RouterInterface::class); $this->emitter = $container->get(EmitterInterface::class); } /** * Get the dependency injection container. - * - * @return ContainerInterface The DI container instance */ public function getContainer(): ContainerInterface { @@ -101,7 +100,7 @@ public function getContainer(): ContainerInterface /** * Define a new route with the specified HTTP methods and URI pattern. * - * @param array $methods The HTTP methods the route should respond to + * @param array $methods The HTTP methods the route should respond to * @param string $path The URI pattern for the route * @param callable|string $handler The route handler callable or controller method * @@ -125,22 +124,9 @@ public function group(string $path, callable $handler): RouteGroup return $this->router->group($path, $handler); } - /** - * Get the base path used for routing. - * - * @return string The base path used for routing - */ - public function getBasePath(): string - { - return $this->router->getBasePath(); - } - /** * Set the base path used for routing. - * - * @param string $basePath The base path to use for routing - * - * @return self The current App instance for method chaining + * @param string $basePath */ public function setBasePath(string $basePath): self { @@ -149,8 +135,17 @@ public function setBasePath(string $basePath): self return $this; } + /** + * Get the base path used for routing. + */ + public function getBasePath(): string + { + return $this->router->getBasePath(); + } + /** * Add a new middleware to the stack. + * @param MiddlewareInterface|callable|string $middleware */ public function add(MiddlewareInterface|callable|string $middleware): self { @@ -161,10 +156,7 @@ public function add(MiddlewareInterface|callable|string $middleware): self /** * Add a new middleware to the application's middleware stack. - * - * @param MiddlewareInterface $middleware The middleware to add - * - * @return self The current App instance for method chaining + * @param MiddlewareInterface $middleware */ public function addMiddleware(MiddlewareInterface $middleware): self { @@ -173,16 +165,37 @@ public function addMiddleware(MiddlewareInterface $middleware): self return $this; } + /** + * Add routing middleware. + * + * @return self + */ + public function addRoutingMiddleware(): self + { + return $this + ->add(RoutingMiddleware::class) + ->add(EndpointMiddleware::class); + } + + /** + * Add set of default error handling middleware. + * + * @return self + */ + public function addErrorMiddleware(): self + { + return $this + ->add(ErrorExceptionMiddleware::class) + ->add(HtmlExceptionMiddleware::class) + ->add(JsonExceptionMiddleware::class); + } + /** * Run the Slim application. * * This method traverses the application's middleware stack, processes the incoming HTTP request, * and emits the resultant HTTP response to the client. - * - * @param ServerRequestInterface|null $request The HTTP request to handle. - * If null, it creates a request from globals. - * - * @return void + * @param ?ServerRequestInterface $request */ public function run(?ServerRequestInterface $request = null): void { @@ -190,9 +203,7 @@ public function run(?ServerRequestInterface $request = null): void $request = $this->serverRequestCreator->createServerRequestFromGlobals(); } - $response = $this->handle($request); - - $this->emitter->emit($response); + $this->emitter->emit($this->handle($request)); } /** @@ -200,15 +211,10 @@ public function run(?ServerRequestInterface $request = null): void * * This method processes the request through the application's middleware stack and router, * returning the resulting HTTP response. - * - * @param ServerRequestInterface $request The HTTP request to handle - * - * @return ResponseInterface The HTTP response + * @param ServerRequestInterface $request */ public function handle(ServerRequestInterface $request): ResponseInterface { - $request = $request->withAttribute(MiddlewareRequestHandler::MIDDLEWARE, $this->router->getMiddlewareStack()); - return $this->requestHandler->handle($request); } } diff --git a/Slim/Builder/AppBuilder.php b/Slim/Builder/AppBuilder.php deleted file mode 100644 index 1c4aa0be8..000000000 --- a/Slim/Builder/AppBuilder.php +++ /dev/null @@ -1,148 +0,0 @@ -addDefinitionsClass(DefaultDefinitions::class); - $this->addDefinitionsClass(HttpDefinitions::class); - } - - /** - * Builds the Slim application instance using the configured DI container. - * - * @return App The fully built Slim application instance - */ - public function build(): App - { - return $this->buildContainer()->get(App::class); - } - - /** - * Sets the service definitions for the DI container. - * - * @param array $definitions An array of service definitions - * - * @return self The current instance - */ - public function addDefinitions(array $definitions): self - { - $this->definitions = array_merge($this->definitions, $definitions); - - return $this; - } - - /** - * Sets the service definitions for the DI container. - * - * @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 string $file A service definitions provider file - * - * @throws RuntimeException - * - * @return self The current instance - */ - public function addDefinitionsFile(string $file): self - { - $definitions = require $file; - - if (!is_array($definitions)) { - throw new RuntimeException('Definition file should return an array of definitions'); - } - - $this->addDefinitions($definitions); - - return $this; - } - - /** - * Sets a custom factory for creating the DI container. - * - * @param ContainerFactoryInterface $containerFactory A DI container factory - * - * @return self The current instance - */ - public function setContainerFactory(ContainerFactoryInterface $containerFactory): self - { - $this->containerFactory = $containerFactory; - - return $this; - } - - /** - * Creates and configures 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. - * - * @return ContainerInterface The configured DI container - */ - private function buildContainer(): ContainerInterface - { - $this->containerFactory = $this->containerFactory ?? new PhpDiContainerFactory(); - - return $this->containerFactory->createContainer($this->definitions); - } -} diff --git a/Slim/Container/ContainerResolver.php b/Slim/Container/ContainerResolver.php index 33054f496..74b42ebee 100644 --- a/Slim/Container/ContainerResolver.php +++ b/Slim/Container/ContainerResolver.php @@ -18,18 +18,30 @@ use function is_array; /** - * This class is responsible for resolving dependencies or services from a PSR-11 compatible DI container. - * It can handle resolving strings, arrays, callables, and objects. If the provided identifier is a string, - * it can also interpret Slim's notation (e.g., "service:method") or the standard "::" notation for static - * method calls. + * Resolves identifiers into services or callables using a PSR-11 DI container. * - * The primary use case for this class is to provide a way to retrieve or resolve services and callables from - * a container by processing the given identifier. + * Supports: + * - Service names (strings) + * - Slim notation: "service:method" + * - PHP notation: "Class::method" + * - Arrays like ["service", "method"] + * - Callables or objects directly + * + * Returned results can be: + * - A callable + * - An object fetched from the container + * - A callable bound to the container (closures) + * + * This is used internally by Slim to resolve route callables, middleware, and + * other handler definitions. */ final class ContainerResolver implements ContainerResolverInterface { private ContainerInterface $container; + /** + * Regex matching Slim-style "service:method" callables. + */ private string $callablePattern = '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!'; public function __construct(ContainerInterface $container) @@ -42,30 +54,51 @@ public function __construct(ContainerInterface $container) */ public function resolve(callable|object|array|string $identifier): mixed { - if (is_object($identifier) || is_callable($identifier)) { + // Already an object, no further resolution needed + if (is_object($identifier)) { return $identifier; } - // The callable is a container entry name - if (is_string($identifier)) { - $identifier = $this->processStringNotation($identifier); + // Bind callable to container + if (is_callable($identifier)) { + return $this->bindToContainer($identifier); } + // ClassName::methodName or Slim notation ClassName:methodName + if (is_string($identifier) && preg_match($this->callablePattern, $identifier, $matches)) { + $identifier = [$matches[1], $matches[2]]; + } + + // Resolve as a container entry name if (is_string($identifier)) { return $this->container->get($identifier); } - // The callable is an array whose first item is a container entry name - // e.g. ['some-container-entry', 'methodToCall'] - if (is_string($identifier[0] ?? null)) { + // Array callable notation: ['service-id', 'method'] + // @phpstan-ignore-next-line + if (is_string($identifier[0])) { // Replace the container entry name by the actual object - $identifier[0] = $this->container->get($identifier[0]); + $service = $this->container->get($identifier[0]); + + if (!is_object($service) && !is_string($service)) { + throw new RuntimeException( + sprintf( + 'Container entry "%s" must resolve to an object or class name.', + $identifier[0], + ), + ); + } + + $method = (string)($identifier[1] ?? ''); - if (!method_exists($identifier[0], (string)$identifier[1])) { - throw new RuntimeException(sprintf('The method "%s" does not exists', $identifier[1])); + if (!method_exists($service, $method)) { + throw new RuntimeException(sprintf('The method "%s" does not exist', $method)); } + + return [$service, $method]; } + // @phpstan-ignore-next-line return $identifier; } @@ -74,39 +107,24 @@ public function resolve(callable|object|array|string $identifier): mixed */ public function resolveCallable(callable|array|string $identifier): callable { - $callable = $this->resolve($identifier); + if (is_string($identifier)) { + $identifier = $this->resolve($identifier); + } - if (is_callable($callable)) { - return $callable; + if (is_callable($identifier)) { + return $this->bindToContainer($identifier); } // Unrecognized stuff, we let it fail throw new RuntimeException( - sprintf('The definition "%s" is not a callable.', implode(':', (array)$identifier)) + sprintf('The definition "%s" is not a callable.', implode(':', (array)$identifier)), ); } /** - * {@inheritdoc} + * Bind closures to the container to allow `$this` access. + * @param callable $callable */ - public function resolveRoute(callable|array|string $identifier): callable - { - $callable = $this->resolveCallable($identifier); - - return $this->bindToContainer($callable); - } - - private function processStringNotation(string $toResolve): string|array - { - // Resolve Slim notation - $matches = null; - if (preg_match($this->callablePattern, $toResolve, $matches)) { - return $matches ? [$matches[1], $matches[2]] : [$toResolve, null]; - } - - return $toResolve; - } - private function bindToContainer(callable $callable): callable { if (is_array($callable) && $callable[0] instanceof Closure) { @@ -115,7 +133,7 @@ private function bindToContainer(callable $callable): callable if ($callable instanceof Closure) { $callable = $callable->bindTo($this->container) ?? throw new RuntimeException( - 'Unable to bind callable to DI container.' + 'Unable to bind callable to DI container.', ); } diff --git a/Slim/Container/GuzzleDefinitions.php b/Slim/Container/Definition/GuzzleDefinitions.php similarity index 90% rename from Slim/Container/GuzzleDefinitions.php rename to Slim/Container/Definition/GuzzleDefinitions.php index 4466a9381..6525861c3 100644 --- a/Slim/Container/GuzzleDefinitions.php +++ b/Slim/Container/Definition/GuzzleDefinitions.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; use GuzzleHttp\Psr7\HttpFactory; use GuzzleHttp\Psr7\ServerRequest; @@ -19,11 +19,12 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; +use Slim\Interfaces\DefinitionsInterface; use Slim\Interfaces\ServerRequestCreatorInterface; -final class GuzzleDefinitions +final class GuzzleDefinitions implements DefinitionsInterface { - public function __invoke(): array + public function getDefinitions(): array { return [ ServerRequestFactoryInterface::class => function (ContainerInterface $container) { diff --git a/Slim/Container/HttpDefinitions.php b/Slim/Container/Definition/HttpDefinitions.php similarity index 56% rename from Slim/Container/HttpDefinitions.php rename to Slim/Container/Definition/HttpDefinitions.php index ba29b92be..663aa8dfb 100644 --- a/Slim/Container/HttpDefinitions.php +++ b/Slim/Container/Definition/HttpDefinitions.php @@ -8,25 +8,34 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; use GuzzleHttp\Psr7\ServerRequest; use HttpSoft\Message\RequestFactory; use Nyholm\Psr7\Factory\Psr17Factory; use RuntimeException; use Slim\Http\Factory\DecoratedServerRequestFactory; +use Slim\Interfaces\DefinitionsInterface; use Slim\Psr7\Factory\ServerRequestFactory; /** - * Selects the appropriate PSR-17 implementations based on the available libraries. + * Selects the appropriate PSR-7/PSR-17 implementation based on + * which libraries are available at runtime. */ -final class HttpDefinitions +final class HttpDefinitions implements DefinitionsInterface { /** - * @var callable + * Callable used to check whether a class exists. + * + * @var callable(string): bool */ private $classExists = 'class_exists'; + /** + * Mapping of PSR-17/PSR-7 factory classes to their DI definition providers. + * + * @var array + */ private array $classes = [ DecoratedServerRequestFactory::class => SlimHttpDefinitions::class, ServerRequestFactory::class => SlimPsr7Definitions::class, @@ -35,18 +44,19 @@ final class HttpDefinitions RequestFactory::class => HttpSoftDefinitions::class, ]; - public function __invoke(): array + public function getDefinitions(): array { foreach ($this->classes as $factory => $definitionClass) { if (call_user_func($this->classExists, $factory)) { - return call_user_func(new $definitionClass()); + /** @var DefinitionsInterface $definitionClass */ + return (new $definitionClass())->getDefinitions(); } } throw new RuntimeException( - 'Could not detect any PSR-17 ResponseFactory implementations. ' . - 'Please install a supported implementation. ' . - 'See https://github.com/slimphp/Slim/blob/5.x/README.md for a list of supported implementations.' + 'Could not detect any PSR-17 ResponseFactory implementations. ' + . 'Please install a supported implementation. ' + . 'See https://github.com/slimphp/Slim/blob/5.x/README.md for a list of supported implementations.', ); } } diff --git a/Slim/Container/HttpSoftDefinitions.php b/Slim/Container/Definition/HttpSoftDefinitions.php similarity index 91% rename from Slim/Container/HttpSoftDefinitions.php rename to Slim/Container/Definition/HttpSoftDefinitions.php index b2e333a4e..6be959226 100644 --- a/Slim/Container/HttpSoftDefinitions.php +++ b/Slim/Container/Definition/HttpSoftDefinitions.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; use HttpSoft\Message\ResponseFactory; use HttpSoft\Message\ServerRequestFactory; @@ -23,11 +23,12 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; +use Slim\Interfaces\DefinitionsInterface; use Slim\Interfaces\ServerRequestCreatorInterface; -final class HttpSoftDefinitions +final class HttpSoftDefinitions implements DefinitionsInterface { - public function __invoke(): array + public function getDefinitions(): array { return [ ServerRequestFactoryInterface::class => function (ContainerInterface $container) { diff --git a/Slim/Container/LaminasDiactorosDefinitions.php b/Slim/Container/Definition/LaminasDefinitions.php similarity index 91% rename from Slim/Container/LaminasDiactorosDefinitions.php rename to Slim/Container/Definition/LaminasDefinitions.php index 373b89d68..73bfde2ad 100644 --- a/Slim/Container/LaminasDiactorosDefinitions.php +++ b/Slim/Container/Definition/LaminasDefinitions.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; use Laminas\Diactoros\ResponseFactory; use Laminas\Diactoros\ServerRequestFactory; @@ -22,11 +22,12 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; +use Slim\Interfaces\DefinitionsInterface; use Slim\Interfaces\ServerRequestCreatorInterface; -final class LaminasDiactorosDefinitions +final class LaminasDefinitions implements DefinitionsInterface { - public function __invoke(): array + public function getDefinitions(): array { return [ ServerRequestFactoryInterface::class => function (ContainerInterface $container) { diff --git a/Slim/Container/NyholmDefinitions.php b/Slim/Container/Definition/NyholmDefinitions.php similarity index 92% rename from Slim/Container/NyholmDefinitions.php rename to Slim/Container/Definition/NyholmDefinitions.php index 463f17682..5c4e29c20 100644 --- a/Slim/Container/NyholmDefinitions.php +++ b/Slim/Container/Definition/NyholmDefinitions.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7Server\ServerRequestCreator; @@ -19,11 +19,12 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; +use Slim\Interfaces\DefinitionsInterface; use Slim\Interfaces\ServerRequestCreatorInterface; -final class NyholmDefinitions +final class NyholmDefinitions implements DefinitionsInterface { - public function __invoke(): array + public function getDefinitions(): array { return [ ServerRequestFactoryInterface::class => function (ContainerInterface $container) { diff --git a/Slim/Container/DefaultDefinitions.php b/Slim/Container/Definition/SlimDefinitions.php similarity index 55% rename from Slim/Container/DefaultDefinitions.php rename to Slim/Container/Definition/SlimDefinitions.php index 13239cc6d..00a3f2344 100644 --- a/Slim/Container/DefaultDefinitions.php +++ b/Slim/Container/Definition/SlimDefinitions.php @@ -8,35 +8,36 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; -use FastRoute\DataGenerator\GroupCountBased; -use FastRoute\RouteCollector; -use FastRoute\RouteParser\Std; use Psr\Container\ContainerInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Slim\Container\ContainerResolver; use Slim\Emitter\ResponseEmitter; use Slim\Interfaces\ContainerResolverInterface; +use Slim\Interfaces\DefinitionsInterface; use Slim\Interfaces\EmitterInterface; use Slim\Interfaces\RequestHandlerInvocationStrategyInterface; -use Slim\RequestHandler\MiddlewareRequestHandler; +use Slim\Interfaces\RouterInterface; +use Slim\Interfaces\UrlGeneratorInterface; use Slim\Routing\Router; -use Slim\Routing\Strategies\RequestResponse; +use Slim\Routing\UrlGenerator; +use Slim\Strategy\RequestResponse; /** - * This class provides the default dependency definitions for a Slim application. It implements the - * `__invoke()` method to return an array of service definitions that are used to set up the Slim - * framework’s core components, including the application instance, middleware, request and response - * factories, and other essential services. + * Provides service definitions for the Slim core components. * - * This class ensures that the Slim application can be properly instantiated with the necessary - * components and services. + * The returned definitions include services for routing, dispatching, + * resolving handlers, emitting responses, generating URLs. + * + * These defaults allow a Slim application to be instantiated with all + * essential framework services ready for use. */ -final class DefaultDefinitions +final class SlimDefinitions implements DefinitionsInterface { - public function __invoke(): array + public function getDefinitions(): array { return [ ContainerResolverInterface::class => function (ContainerInterface $container) { @@ -51,16 +52,20 @@ public function __invoke(): array return new NullLogger(); }, + RequestHandlerInvocationStrategyInterface::class => function (ContainerInterface $container) { + return $container->get(RequestResponse::class); + }, + RequestHandlerInterface::class => function (ContainerInterface $container) { - return $container->get(MiddlewareRequestHandler::class); + return $container->get(Router::class); }, - RequestHandlerInvocationStrategyInterface::class => function (ContainerInterface $container) { - return $container->get(RequestResponse::class); + RouterInterface::class => function (ContainerInterface $container) { + return $container->get(Router::class); }, - Router::class => function () { - return new Router(new RouteCollector(new Std(), new GroupCountBased())); + UrlGeneratorInterface::class => function (ContainerInterface $container) { + return $container->get(UrlGenerator::class); }, ]; } diff --git a/Slim/Container/SlimHttpDefinitions.php b/Slim/Container/Definition/SlimHttpDefinitions.php similarity index 91% rename from Slim/Container/SlimHttpDefinitions.php rename to Slim/Container/Definition/SlimHttpDefinitions.php index f7287c56d..4e35884eb 100644 --- a/Slim/Container/SlimHttpDefinitions.php +++ b/Slim/Container/Definition/SlimHttpDefinitions.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; @@ -21,17 +21,20 @@ use Slim\Http\Factory\DecoratedResponseFactory; use Slim\Http\Factory\DecoratedUriFactory; use Slim\Http\ServerRequest; +use Slim\Interfaces\DefinitionsInterface; use Slim\Interfaces\ServerRequestCreatorInterface; use Slim\Psr7\Factory\ServerRequestFactory; -final class SlimHttpDefinitions +final class SlimHttpDefinitions implements DefinitionsInterface { /** - * @var callable + * Callable used to check whether a class exists. + * + * @var callable(string): bool */ private $classExists = 'class_exists'; - public function __invoke(): array + public function getDefinitions(): array { $that = $this; @@ -47,13 +50,18 @@ public function __construct(ServerRequestFactory $serverRequestFactory) $this->serverRequestFactory = $serverRequestFactory; } + /** + * @param array $serverParams + * @param string $method + * @param mixed $uri + */ public function createServerRequest( string $method, $uri, - array $serverParams = [] + array $serverParams = [], ): ServerRequestInterface { return new ServerRequest( - $this->serverRequestFactory->createServerRequest($method, $uri, $serverParams) + $this->serverRequestFactory->createServerRequest($method, $uri, $serverParams), ); } }; @@ -93,7 +101,7 @@ public function createServerRequestFromGlobals(): ServerRequestInterface return $responseFactory ?? throw new RuntimeException( 'Could not detect any PSR-17 ResponseFactory implementations. ' . 'Please install a supported implementation. ' . - 'See https://github.com/slimphp/Slim/blob/5.x/README.md for a list of supported implementations.' + 'See https://github.com/slimphp/Slim/blob/5.x/README.md for a list of supported implementations.', ); }, StreamFactoryInterface::class => function (ContainerInterface $container) use ($that) { diff --git a/Slim/Container/SlimPsr7Definitions.php b/Slim/Container/Definition/SlimPsr7Definitions.php similarity index 91% rename from Slim/Container/SlimPsr7Definitions.php rename to Slim/Container/Definition/SlimPsr7Definitions.php index 926f093e4..157127a06 100644 --- a/Slim/Container/SlimPsr7Definitions.php +++ b/Slim/Container/Definition/SlimPsr7Definitions.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Container; +namespace Slim\Container\Definition; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; @@ -17,6 +17,7 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; +use Slim\Interfaces\DefinitionsInterface; use Slim\Interfaces\ServerRequestCreatorInterface; use Slim\Psr7\Factory\ResponseFactory; use Slim\Psr7\Factory\ServerRequestFactory; @@ -24,9 +25,9 @@ use Slim\Psr7\Factory\UploadedFileFactory; use Slim\Psr7\Factory\UriFactory; -final class SlimPsr7Definitions +final class SlimPsr7Definitions implements DefinitionsInterface { - public function __invoke(): array + public function getDefinitions(): array { return [ ServerRequestFactoryInterface::class => function (ContainerInterface $container) { diff --git a/Slim/Container/PhpDiContainerFactory.php b/Slim/Container/DiContainerFactory.php similarity index 50% rename from Slim/Container/PhpDiContainerFactory.php rename to Slim/Container/DiContainerFactory.php index 5c2e20b69..f1c3ca450 100644 --- a/Slim/Container/PhpDiContainerFactory.php +++ b/Slim/Container/DiContainerFactory.php @@ -12,12 +12,20 @@ use DI\Container; use Psr\Container\ContainerInterface; +use Slim\Container\Definition\HttpDefinitions; +use Slim\Container\Definition\SlimDefinitions; use Slim\Interfaces\ContainerFactoryInterface; -final class PhpDiContainerFactory implements ContainerFactoryInterface +final class DiContainerFactory implements ContainerFactoryInterface { public function createContainer(array $definitions = []): ContainerInterface { - return new Container($definitions); + return new Container( + array_replace( + (new SlimDefinitions())->getDefinitions(), + (new HttpDefinitions())->getDefinitions(), + $definitions + ) + ); } } diff --git a/Slim/Container/MiddlewareResolver.php b/Slim/Container/MiddlewareResolver.php deleted file mode 100644 index 6321ee919..000000000 --- a/Slim/Container/MiddlewareResolver.php +++ /dev/null @@ -1,105 +0,0 @@ -container = $container; - $this->containerResolver = $containerResolver; - } - - /** - * Resolve the middleware stack. - * - * @param array $queue - * - * @return array - */ - public function resolveStack(array $queue): array - { - foreach ($queue as $key => $value) { - $queue[$key] = $this->resolveMiddleware($value); - } - - return $queue; - } - - /** - * Add a new middleware to the stack. - * - * @throws RuntimeException - */ - private function resolveMiddleware(MiddlewareInterface|callable|string|array $middleware): MiddlewareInterface - { - $middleware = $this->containerResolver->resolve($middleware); - - if ($middleware instanceof MiddlewareInterface) { - return $middleware; - } - - if (is_callable($middleware)) { - return $this->addCallable($middleware); - } - - throw new RuntimeException('A middleware must be an object or callable that implements "MiddlewareInterface".'); - } - - /** - * Add a (non-standard) callable middleware to the stack - * - * @throws RuntimeException - */ - private function addCallable(callable $middleware): MiddlewareInterface - { - if ($middleware instanceof Closure) { - /** @var Closure $middleware */ - $middleware = $middleware->bindTo($this->container) ?? throw new RuntimeException( - 'Unable to bind middleware to DI container.' - ); - } - - return new class ($middleware) implements MiddlewareInterface { - /** - * @var callable - */ - private $middleware; - - public function __construct(callable $middleware) - { - $this->middleware = $middleware; - } - - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler, - ): ResponseInterface { - return ($this->middleware)($request, $handler); - } - }; - } -} diff --git a/Slim/Emitter/ResponseEmitter.php b/Slim/Emitter/ResponseEmitter.php index 4941ff074..fb368fda9 100644 --- a/Slim/Emitter/ResponseEmitter.php +++ b/Slim/Emitter/ResponseEmitter.php @@ -35,6 +35,7 @@ public function __construct(int $responseChunkSize = 4096) /** * Send the response the client. + * @param ResponseInterface $response */ public function emit(ResponseInterface $response): void { @@ -81,13 +82,15 @@ private function emitStatusLine(ResponseInterface $response): void 'HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatusCode(), - $response->getReasonPhrase() + $response->getReasonPhrase(), ); header($statusLine, true, $response->getStatusCode()); } /** * Emit Body + * + * @param ResponseInterface $response */ private function emitBody(ResponseInterface $response): void { @@ -125,6 +128,8 @@ private function emitBody(ResponseInterface $response): void /** * Asserts response body is empty or status code is 204, 205 or 304. + * + * @param ResponseInterface $response */ public function isResponseEmpty(ResponseInterface $response): bool { diff --git a/Slim/Factory/AppFactory.php b/Slim/Factory/AppFactory.php new file mode 100644 index 000000000..16fbd8dc0 --- /dev/null +++ b/Slim/Factory/AppFactory.php @@ -0,0 +1,64 @@ + $definitions Optional container definitions. + * + * @return App + */ + public static function create(array $definitions = []): App + { + $containerBuilder = static::$containerFactory ?? new DiContainerFactory(); + + return $containerBuilder->createContainer($definitions)->get(App::class); + } + + /** + * Create an application using an existing container instance. + * + * @param ContainerInterface $container + * + * @return App + */ + public static function createFromContainer(ContainerInterface $container): App + { + return new App($container); + } +} diff --git a/Slim/Interfaces/ContainerFactoryInterface.php b/Slim/Interfaces/ContainerFactoryInterface.php index 36bb17fe9..4a0e89199 100644 --- a/Slim/Interfaces/ContainerFactoryInterface.php +++ b/Slim/Interfaces/ContainerFactoryInterface.php @@ -12,7 +12,17 @@ use Psr\Container\ContainerInterface; +/** + * Factory interface for creating a service container. + */ interface ContainerFactoryInterface { + /** + * Create a container instance. + * + * @param array $definitions The service definitions. + * + * @return ContainerInterface + */ public function createContainer(array $definitions = []): ContainerInterface; } diff --git a/Slim/Interfaces/ContainerResolverInterface.php b/Slim/Interfaces/ContainerResolverInterface.php index 00115f7d3..64e9ee6e0 100644 --- a/Slim/Interfaces/ContainerResolverInterface.php +++ b/Slim/Interfaces/ContainerResolverInterface.php @@ -15,27 +15,19 @@ interface ContainerResolverInterface /** * Resolve the given $identifier to an object. * - * @param callable|object|array|string $identifier + * @param callable|object|array|string $identifier * * @return mixed An object or callable */ public function resolve(callable|object|array|string $identifier): mixed; /** - * Resolve the given $identifier to an object or callable. + * Resolve the given $identifier to a callable that is bounded to the container. * - * @param callable|string|array $identifier + * @param callable|string|array $identifier * * @return callable A callable */ public function resolveCallable(callable|array|string $identifier): callable; - /** - * Resolve the given $identifier to a callable that is bounded to the container. - * - * @param callable|string|array $identifier - * - * @return callable A callable - */ - public function resolveRoute(callable|array|string $identifier): callable; } diff --git a/Slim/Interfaces/DefinitionsInterface.php b/Slim/Interfaces/DefinitionsInterface.php new file mode 100644 index 000000000..d7f0c4859 --- /dev/null +++ b/Slim/Interfaces/DefinitionsInterface.php @@ -0,0 +1,24 @@ + + */ + public function getDefinitions(): array; +} diff --git a/Slim/Interfaces/RequestHandlerInvocationStrategyInterface.php b/Slim/Interfaces/RequestHandlerInvocationStrategyInterface.php index 3b051b142..0299bdef1 100644 --- a/Slim/Interfaces/RequestHandlerInvocationStrategyInterface.php +++ b/Slim/Interfaces/RequestHandlerInvocationStrategyInterface.php @@ -24,7 +24,7 @@ interface RequestHandlerInvocationStrategyInterface * @param callable $callable the callable to invoke using the strategy * @param ServerRequestInterface $request the request object * @param ResponseInterface $response the response object - * @param array $routeArguments The route's placeholder arguments + * @param array $routeArguments The route's placeholder arguments * * @return ResponseInterface The response from the callable */ diff --git a/Slim/Interfaces/RouteCollectionInterface.php b/Slim/Interfaces/RouteCollectionInterface.php index 5c3126a59..667e487df 100644 --- a/Slim/Interfaces/RouteCollectionInterface.php +++ b/Slim/Interfaces/RouteCollectionInterface.php @@ -7,23 +7,102 @@ use Slim\Routing\Route; use Slim\Routing\RouteGroup; +/** + * Collection interface for defining and grouping HTTP routes. + * + * Each method registers a route for one or more HTTP methods + * and returns the corresponding Route instance. + */ interface RouteCollectionInterface { + /** + * Register a GET route. + * + * @param string $path Route path. + * @param callable|string $handler Route handler or controller action. + * + * @return Route + */ public function get(string $path, callable|string $handler): Route; + /** + * Register a POST route. + * + * @param string $path + * @param callable|string $handler + * + * @return Route + */ public function post(string $path, callable|string $handler): Route; + /** + * Register a PUT route. + * + * @param string $path + * @param callable|string $handler + * + * @return Route + */ public function put(string $path, callable|string $handler): Route; + /** + * Register a PATCH route. + * + * @param string $path + * @param callable|string $handler + * + * @return Route + */ public function patch(string $path, callable|string $handler): Route; + /** + * Register a DELETE route. + * + * @param string $path + * @param callable|string $handler + * + * @return Route + */ public function delete(string $path, callable|string $handler): Route; + /** + * Register an OPTIONS route. + * + * @param string $path + * @param callable|string $handler + * + * @return Route + */ public function options(string $path, callable|string $handler): Route; + /** + * Register a route for any HTTP method. + * + * @param string $path + * @param callable|string $handler + * + * @return Route + */ public function any(string $path, callable|string $handler): Route; + /** + * Register a route with multiple HTTP methods. + * + * @param list $methods List of HTTP methods. + * @param string $path Route path. + * @param callable|string $handler Route handler. + * + * @return Route + */ public function map(array $methods, string $path, callable|string $handler): Route; + /** + * Register a group of routes under a common path prefix. + * + * @param string $path Base path for the group. + * @param callable $handler Function receiving the RouteGroup. + * + * @return RouteGroup + */ public function group(string $path, callable $handler): RouteGroup; } diff --git a/Slim/Interfaces/RouterInterface.php b/Slim/Interfaces/RouterInterface.php new file mode 100644 index 000000000..f10c9eb66 --- /dev/null +++ b/Slim/Interfaces/RouterInterface.php @@ -0,0 +1,53 @@ + $methods + * @param string $path + * @param callable|string $handler + * + * @throws InvalidArgumentException + */ + public function map(array $methods, string $path, callable|string $handler): Route; + + public function group(string $path, callable $handler): RouteGroup; + + public function getRouteCollector(): RouteCollector; + + public function setBasePath(string $basePath): void; + + public function getBasePath(): string; + + /** + * @return array + */ + public function getMiddleware(): array; + + public function add(MiddlewareInterface|callable|string $middleware): Router; + + public function addMiddleware(MiddlewareInterface $middleware): Router; +} diff --git a/Slim/Middleware/BasePathMiddleware.php b/Slim/Middleware/BasePathMiddleware.php index 77699b8c9..6f9e1ec27 100644 --- a/Slim/Middleware/BasePathMiddleware.php +++ b/Slim/Middleware/BasePathMiddleware.php @@ -14,28 +14,28 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\App; +use Slim\Interfaces\RouterInterface; use Slim\Routing\RouteContext; final class BasePathMiddleware implements MiddlewareInterface { - private App $app; + private RouterInterface $router; private string $phpSapi; /** * The constructor. * - * @param App $app The Slim app instance + * @param RouterInterface $router The router * @param string $phpSapi The type of interface between web server and PHP * * Supported: 'apache2handler' * Not supported: 'cgi', 'cgi-fcgi', 'fpm-fcgi', 'litespeed', 'cli-server' */ - public function __construct(App $app, string $phpSapi = PHP_SAPI) + public function __construct(RouterInterface $router, string $phpSapi = PHP_SAPI) { $this->phpSapi = $phpSapi; - $this->app = $app; + $this->router = $router; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -48,13 +48,14 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $request = $request->withAttribute(RouteContext::BASE_PATH, $basePath); - $this->app->setBasePath($basePath); + $this->router->setBasePath($basePath); return $handler->handle($request); } /** * Return basePath for most common webservers, such as Apache. + * @param ServerRequestInterface $request */ private function getBasePathByRequestUri(ServerRequestInterface $request): string { diff --git a/Slim/Middleware/CorsMiddleware.php b/Slim/Middleware/CorsMiddleware.php index 21d6d0d14..4b0a7e018 100644 --- a/Slim/Middleware/CorsMiddleware.php +++ b/Slim/Middleware/CorsMiddleware.php @@ -18,13 +18,44 @@ final class CorsMiddleware implements MiddlewareInterface { + /** + * @var ResponseFactoryInterface Factory used to create PSR-7 responses. + */ private ResponseFactoryInterface $responseFactory; + + /** + * @var int|null Max-Age value for CORS preflight caching (in seconds). + */ private ?int $maxAge = null; + + /** + * @var array|null List of allowed origins, or null to allow all. + */ private ?array $allowedOrigins = null; + + /** + * @var bool Whether to include Access-Control-Allow-Credentials. + */ private bool $allowCredentials = false; + + /** + * @var array Allowed HTTP methods for CORS requests. + */ private array $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; + + /** + * @var array Allowed request headers. Use ['*'] to allow all. + */ private array $allowedHeaders = ['*']; + + /** + * @var array Headers exposed to the browser via Access-Control-Expose-Headers. + */ private array $exposedHeaders = []; + + /** + * @var bool Whether to cache OPTIONS responses when possible. + */ private bool $useCache = true; public function __construct(ResponseFactoryInterface $responseFactory) @@ -34,7 +65,7 @@ public function __construct(ResponseFactoryInterface $responseFactory) public function process( ServerRequestInterface $request, - RequestHandlerInterface $handler + RequestHandlerInterface $handler, ): ResponseInterface { $origin = $request->getHeaderLine('Origin'); @@ -61,7 +92,7 @@ public function process( if (!empty($this->allowedMethods)) { $response = $response->withHeader( 'Access-Control-Allow-Methods', - implode(', ', $this->allowedMethods) + implode(', ', $this->allowedMethods), ); } @@ -69,7 +100,7 @@ public function process( if (!empty($this->allowedHeaders)) { $response = $response->withHeader( 'Access-Control-Allow-Headers', - implode(', ', $this->allowedHeaders) + implode(', ', $this->allowedHeaders), ); } @@ -77,7 +108,7 @@ public function process( if (!empty($this->exposedHeaders)) { $response = $response->withHeader( 'Access-Control-Expose-Headers', - implode(', ', $this->exposedHeaders) + implode(', ', $this->exposedHeaders), ); } @@ -99,6 +130,7 @@ public function process( /** * Set the Access-Control-Max-Age header value in seconds. * Set to null to disable the header. + * @param ?int $maxAge */ public function withMaxAge(?int $maxAge): self { @@ -109,8 +141,14 @@ public function withMaxAge(?int $maxAge): self } /** - * Set allowed origins. Null means allow all (*). - * Pass an array of strings to specify allowed origins. + * Set the allowed origins for CORS. + * + * Passing `null` allows all origins (`*`). + * Passing a list of strings restricts the allowed origins. + * + * @param array|null $origins List of allowed origins, or null to allow all. + * + * @return self */ public function withAllowedOrigins(?array $origins = null): self { @@ -122,6 +160,7 @@ public function withAllowedOrigins(?array $origins = null): self /** * Set whether to allow credentials. + * @param bool $allow */ public function withAllowCredentials(bool $allow): self { @@ -132,7 +171,13 @@ public function withAllowCredentials(bool $allow): self } /** - * Set allowed methods. + * Set the allowed HTTP methods for CORS. + * + * Each method will be normalized to uppercase. + * + * @param array $methods List of HTTP methods. + * + * @return self */ public function withAllowedMethods(array $methods): self { @@ -143,7 +188,13 @@ public function withAllowedMethods(array $methods): self } /** - * Set allowed headers. + * Set the allowed request headers for CORS. + * + * Use ['*'] to allow all headers. + * + * @param array $headers List of allowed request header names. + * + * @return self */ public function withAllowedHeaders(array $headers): self { @@ -154,7 +205,12 @@ public function withAllowedHeaders(array $headers): self } /** - * Set exposed headers. + * Set the headers that should be exposed to the browser via + * the Access-Control-Expose-Headers response header. + * + * @param array $headers List of header names to expose. + * + * @return self */ public function withExposedHeaders(array $headers): self { @@ -166,6 +222,7 @@ public function withExposedHeaders(array $headers): self /** * Set whether to use cache control headers. + * @param bool $useCache */ public function withCache(bool $useCache): self { @@ -177,6 +234,7 @@ public function withCache(bool $useCache): self /** * Check if origin is allowed. + * @param string $origin */ private function isOriginAllowed(string $origin): bool { diff --git a/Slim/Middleware/EndpointMiddleware.php b/Slim/Middleware/EndpointMiddleware.php index c02b703a9..9868d96ea 100644 --- a/Slim/Middleware/EndpointMiddleware.php +++ b/Slim/Middleware/EndpointMiddleware.php @@ -2,7 +2,6 @@ namespace Slim\Middleware; -use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -10,11 +9,10 @@ use RuntimeException; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; -use Slim\Interfaces\ContainerResolverInterface; -use Slim\Interfaces\RequestHandlerInvocationStrategyInterface; -use Slim\RequestHandler\MiddlewareRequestHandler; +use Slim\Routing\PipelineRunner; use Slim\Routing\Route; use Slim\Routing\RouteContext; +use Slim\Routing\RouteInvoker; use Slim\Routing\RoutingResults; /** @@ -25,21 +23,16 @@ */ final class EndpointMiddleware implements MiddlewareInterface { - private ContainerResolverInterface $containerResolver; - private ResponseFactoryInterface $responseFactory; - private RequestHandlerInterface $requestHandler; - private RequestHandlerInvocationStrategyInterface $invocationStrategy; + private RouteInvoker $routeInvoker; + + private PipelineRunner $pipelineRunner; public function __construct( - ContainerResolverInterface $callableResolver, - ResponseFactoryInterface $responseFactory, - RequestHandlerInterface $requestHandler, - RequestHandlerInvocationStrategyInterface $invocationStrategy, + RouteInvoker $routeInvoker, + PipelineRunner $pipelineRunner, ) { - $this->containerResolver = $callableResolver; - $this->responseFactory = $responseFactory; - $this->requestHandler = $requestHandler; - $this->invocationStrategy = $invocationStrategy; + $this->routeInvoker = $routeInvoker; + $this->pipelineRunner = $pipelineRunner; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -49,7 +42,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if (!$routingResults instanceof RoutingResults) { throw new RuntimeException( - 'An unexpected error occurred while handling routing results. Routing results are not available.' + 'An unexpected error occurred while handling routing results. Routing results are not available.', ); } @@ -79,82 +72,38 @@ private function handleFound( RoutingResults $routingResults, ): ResponseInterface { $route = $routingResults->getRoute() ?? throw new RuntimeException('Route not found.'); - $response = $this->responseFactory->createResponse(); - - $middlewares = $this->getRouteMiddleware($route); - - // Add route handler middleware - $containerResolver = $this->containerResolver; - $invocationStrategy = $this->invocationStrategy; - - $middlewares[] = function () use ( - $request, - $response, - $routingResults, - $containerResolver, - $invocationStrategy - ) { - // Get handler - $actionHandler = $routingResults->getRoute()->getHandler(); - $vars = $routingResults->getRouteArguments(); - $actionHandler = $containerResolver->resolveRoute($actionHandler); - - // Invoke action handler - return call_user_func($invocationStrategy, $actionHandler, $request, $response, $vars); - }; - - return $this->invokeMiddlewareStack($request, $response, $middlewares); - } - private function invokeMiddlewareStack( - ServerRequestInterface $request, - ResponseInterface $response, - array $middlewares, - ): ResponseInterface { - // Tunnel the response object through the route/group specific middleware stack - $middlewares[] = - new class ($response) implements MiddlewareInterface { - private ResponseInterface $response; - - public function __construct(ResponseInterface $response) - { - $this->response = $response; - } - - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler, - ): ResponseInterface { - return $this->response; - } - }; - - $request = $request->withAttribute(MiddlewareRequestHandler::MIDDLEWARE, $middlewares); - - return $this->requestHandler->handle($request); + // Collect route specific middleware + $pipeline = $this->collectRouteMiddleware($route); + + // Invoke the route/group specific middleware stack + $pipeline[] = $this->routeInvoker->withHandler( + $route->getHandler(), + $routingResults->getRouteArguments(), + ); + + return $this->pipelineRunner->withPipeline($pipeline)->handle($request); } - private function getRouteMiddleware(Route $route): array + /** + * @param Route $route + * @return array List of middleware + */ + private function collectRouteMiddleware(Route $route): array { $middlewares = []; // Append group specific middleware from all parent route groups $group = $route->getRouteGroup(); + while ($group) { - $middlewareStack = $group->getMiddlewareStack(); - foreach ($middlewareStack as $middleware) { - $middlewares[] = $middleware; - } + // Prepend group middleware so outer groups come first + $middlewares = array_merge($group->getMiddleware(), $middlewares); $group = $group->getRouteGroup(); } - $middlewares = array_reverse($middlewares); - // Append endpoint specific middleware - $routeMiddlewares = $route->getMiddlewareStack(); - foreach ($routeMiddlewares as $routeMiddleware) { - $middlewares[] = $routeMiddleware; - } - - return $middlewares; + // Append endpoint-specific middleware + return array_merge($middlewares, $route->getMiddleware()); } + } diff --git a/Slim/Middleware/ErrorExceptionMiddleware.php b/Slim/Middleware/ErrorExceptionMiddleware.php index 9fa81e6af..e37542569 100644 --- a/Slim/Middleware/ErrorExceptionMiddleware.php +++ b/Slim/Middleware/ErrorExceptionMiddleware.php @@ -16,6 +16,10 @@ final class ErrorExceptionMiddleware implements MiddlewareInterface { /** + * Process. + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler * @throws ErrorException */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -28,7 +32,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } throw new ErrorException($message, 0, $code, $file, $line); - }, E_ALL); + }); try { $response = $handler->handle($request); diff --git a/Slim/Middleware/ExceptionLoggingMiddleware.php b/Slim/Middleware/ExceptionLoggingMiddleware.php index f4010ec5b..6a91be9da 100644 --- a/Slim/Middleware/ExceptionLoggingMiddleware.php +++ b/Slim/Middleware/ExceptionLoggingMiddleware.php @@ -30,10 +30,6 @@ public function __construct(LoggerInterface $logger) $this->logger = $logger; } - /** - * @throws Throwable - * @throws ErrorException - */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { try { @@ -62,6 +58,11 @@ public function withLogErrorDetails(bool $logErrorDetails): self return $clone; } + /** + * @param Throwable $exception + * @param ServerRequestInterface $request + * @return array{exception?: Throwable, request?: ServerRequestInterface} + */ private function getContext(Throwable $exception, ServerRequestInterface $request): array { $context = []; diff --git a/Slim/Middleware/ExceptionMiddlewareTrait.php b/Slim/Middleware/ExceptionMiddlewareTrait.php index 54346dd42..f582d2740 100644 --- a/Slim/Middleware/ExceptionMiddlewareTrait.php +++ b/Slim/Middleware/ExceptionMiddlewareTrait.php @@ -27,6 +27,9 @@ trait ExceptionMiddlewareTrait private bool $displayErrorDetails = false; + /** + * @var array|int[] + */ private array $mimeTypes = [self::DEFAULT_TYPE => 1]; public function __construct(ResponseFactoryInterface $responseFactory) @@ -111,7 +114,7 @@ private function createResponse(Throwable $exception, string $payload, string $c * * @param string|null $accept The value of the 'Accept' header * - * @return array An array of normalized media types from the 'Accept' header + * @return array An array of normalized media types from the 'Accept' header */ public function parseAcceptHeader(?string $accept): array { diff --git a/Slim/Middleware/FormUrlEncodedBodyParserMiddleware.php b/Slim/Middleware/FormBodyParserMiddleware.php similarity index 93% rename from Slim/Middleware/FormUrlEncodedBodyParserMiddleware.php rename to Slim/Middleware/FormBodyParserMiddleware.php index af6b6d1b7..96267188c 100644 --- a/Slim/Middleware/FormUrlEncodedBodyParserMiddleware.php +++ b/Slim/Middleware/FormBodyParserMiddleware.php @@ -9,7 +9,7 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -final class FormUrlEncodedBodyParserMiddleware implements MiddlewareInterface +final class FormBodyParserMiddleware implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { diff --git a/Slim/Middleware/HtmlExceptionMiddleware.php b/Slim/Middleware/HtmlExceptionMiddleware.php index 78becbce2..bd76d784d 100644 --- a/Slim/Middleware/HtmlExceptionMiddleware.php +++ b/Slim/Middleware/HtmlExceptionMiddleware.php @@ -55,7 +55,7 @@ private function renderExceptionFragment(Throwable $exception): string { $html = sprintf( '
Type: %s
', - $this->escapeHtml(get_class($exception)) + $this->escapeHtml(get_class($exception)), ); $code = $exception instanceof ErrorException ? $exception->getSeverity() : $exception->getCode(); @@ -63,17 +63,17 @@ private function renderExceptionFragment(Throwable $exception): string $html .= sprintf( '
Message: %s
', - $this->escapeHtml($exception->getMessage()) + $this->escapeHtml($exception->getMessage()), ); $html .= sprintf( '
File: %s
', - $this->escapeHtml($exception->getFile()) + $this->escapeHtml($exception->getFile()), ); $html .= sprintf( '
Line: %s
', - $this->escapeHtml((string)$exception->getLine()) + $this->escapeHtml((string)$exception->getLine()), ); $html .= '

Trace

'; @@ -85,29 +85,29 @@ private function renderExceptionFragment(Throwable $exception): string private function renderHtmlBody(string $title = '', string $html = ''): string { return sprintf( - '' . - '' . - ' ' . - ' ' . - ' ' . - ' %s' . - ' ' . - ' ' . - ' ' . - '

%s

' . - '
%s
' . - ' Go Back' . - ' ' . - '', + '' + . '' + . ' ' + . ' ' + . ' ' + . ' %s' + . ' ' + . ' ' + . ' ' + . '

%s

' + . '
%s
' + . ' Go Back' + . ' ' + . '', $this->escapeHtml($title), $this->escapeHtml($title), - $html + $html, ); } diff --git a/Slim/Middleware/JsonBodyParserMiddleware.php b/Slim/Middleware/JsonBodyParserMiddleware.php index 6eef03fd0..3bea6214f 100644 --- a/Slim/Middleware/JsonBodyParserMiddleware.php +++ b/Slim/Middleware/JsonBodyParserMiddleware.php @@ -52,6 +52,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /** * Check whether the content type is JSON or has a +json structured suffix. + * + * @param string $contentType */ private function isJsonMediaType(string $contentType): bool { diff --git a/Slim/Middleware/JsonExceptionMiddleware.php b/Slim/Middleware/JsonExceptionMiddleware.php index 5306059e9..19255aede 100644 --- a/Slim/Middleware/JsonExceptionMiddleware.php +++ b/Slim/Middleware/JsonExceptionMiddleware.php @@ -45,6 +45,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface * * @see https://php.net/manual/function.json-encode.php * @see https://php.net/manual/json.constants.php + * @param int $options */ public function withJsonOptions(int $options): self { @@ -68,6 +69,10 @@ private function createPayload(Throwable $exception): string return (string)json_encode($payload, $this->jsonOptions); } + /** + * @param Throwable $exception + * @return array{type: string, code: mixed, message: string, file: string, line: int} + */ private function formatExceptionFragment(Throwable $exception): array { $code = $exception instanceof ErrorException ? $exception->getSeverity() : $exception->getCode(); diff --git a/Slim/Middleware/MethodOverrideMiddleware.php b/Slim/Middleware/MethodOverrideMiddleware.php index 4b9d72db6..56127327e 100644 --- a/Slim/Middleware/MethodOverrideMiddleware.php +++ b/Slim/Middleware/MethodOverrideMiddleware.php @@ -29,7 +29,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } elseif (strtoupper($request->getMethod()) === 'POST') { $body = $request->getParsedBody(); - if (is_array($body) && !empty($body['_METHOD'])) { + if (is_array($body) && !empty($body['_METHOD']) && is_string($body['_METHOD'])) { $request = $request->withMethod($body['_METHOD']); } diff --git a/Slim/Middleware/OutputBufferingMiddleware.php b/Slim/Middleware/OutputBufferingMiddleware.php index bde8c00a5..b1f4bc96c 100644 --- a/Slim/Middleware/OutputBufferingMiddleware.php +++ b/Slim/Middleware/OutputBufferingMiddleware.php @@ -29,11 +29,8 @@ final class OutputBufferingMiddleware implements MiddlewareInterface public const PREPEND = 'prepend'; - private StreamFactoryInterface $streamFactory; - - private string $style; - /** + * @param StreamFactoryInterface $streamFactory The stream factory * @param string $style Either "append" or "prepend" */ public function __construct(StreamFactoryInterface $streamFactory, string $style = 'append') @@ -46,7 +43,13 @@ public function __construct(StreamFactoryInterface $streamFactory, string $style } } + private StreamFactoryInterface $streamFactory; + + private string $style; + /** + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler * @throws Throwable */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface diff --git a/Slim/Middleware/RoutingArgumentsMiddleware.php b/Slim/Middleware/RoutingArgumentsMiddleware.php index e515a2bdd..b632e9e32 100644 --- a/Slim/Middleware/RoutingArgumentsMiddleware.php +++ b/Slim/Middleware/RoutingArgumentsMiddleware.php @@ -27,7 +27,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /* @var RoutingResults|null $routingResults */ $routingResults = $request->getAttribute(RouteContext::ROUTING_RESULTS); - if ($routingResults) { + if ($routingResults instanceof RoutingResults) { foreach ($routingResults->getRouteArguments() as $key => $value) { $request = $request->withAttribute($key, $value); } diff --git a/Slim/Middleware/RoutingMiddleware.php b/Slim/Middleware/RoutingMiddleware.php index 2ad1b11b2..127945612 100644 --- a/Slim/Middleware/RoutingMiddleware.php +++ b/Slim/Middleware/RoutingMiddleware.php @@ -15,10 +15,9 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Slim\Interfaces\RouterInterface; use Slim\Routing\RouteContext; -use Slim\Routing\Router; use Slim\Routing\RoutingResults; -use Slim\Routing\UrlGenerator; /** * Middleware for resolving routes. @@ -28,14 +27,11 @@ */ final class RoutingMiddleware implements MiddlewareInterface { - private Router $router; + private RouterInterface $router; - private UrlGenerator $urlGenerator; - - public function __construct(Router $router, UrlGenerator $urlGenerator) + public function __construct(RouterInterface $router) { $this->router = $router; - $this->urlGenerator = $urlGenerator; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -49,15 +45,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface // Determine base path $basePath = $request->getAttribute(RouteContext::BASE_PATH) ?? $this->router->getBasePath(); - $dispatcherUri = $uri; - if ($basePath) { + if (is_string($basePath)) { // Remove base path for the dispatcher - $dispatcherUri = substr($dispatcherUri, strlen($basePath)); - $dispatcherUri = $this->normalizePath($dispatcherUri); + $uri = $this->removeBasePath($uri, $basePath); } - $dispatcherUri = rawurldecode($dispatcherUri); - $routeInfo = $dispatcher->dispatch($httpMethod, $dispatcherUri); + $routeInfo = $dispatcher->dispatch($httpMethod, rawurldecode($uri)); $routeStatus = (int)$routeInfo[0]; $routingResults = null; @@ -67,7 +60,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $routeInfo[1], $request->getMethod(), $uri, - $routeInfo[2] + $routeInfo[2], ); } @@ -87,27 +80,19 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if ($routingResults) { $request = $request - ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults) - ->withAttribute(RouteContext::URL_GENERATOR, $this->urlGenerator); + ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); } return $handler->handle($request); } - private function normalizePath(string $path): string + private function removeBasePath(string $uri, string $basePath): string { - // If path is empty or just a slash, return single slash - if ($path === '' || $path === '/') { - return '/'; + // No base path configured + if (!$basePath || $basePath === '/') { + return $uri; } - // Ensure path starts with a slash - $path = '/' . ltrim($path, '/'); - - // Remove trailing slash unless it's the root path - $path = rtrim($path, '/'); - - // Replace multiple consecutive slashes with a single slash - return preg_replace('#/+#', '/', $path); + return '/' . ltrim(rtrim(substr($uri, strlen($basePath)), '/'), '/'); } } diff --git a/Slim/RequestHandler/MiddlewareRequestHandler.php b/Slim/RequestHandler/MiddlewareRequestHandler.php deleted file mode 100644 index c8efb0c70..000000000 --- a/Slim/RequestHandler/MiddlewareRequestHandler.php +++ /dev/null @@ -1,48 +0,0 @@ -resolver = $resolver; - } - - /** - * Handles the current entry in the middleware queue and advances. - */ - public function handle(ServerRequestInterface $request): ResponseInterface - { - /** @var array $middlewares */ - $middlewares = $request->getAttribute(self::MIDDLEWARE) ?: []; - $queue = $this->resolver->resolveStack($middlewares); - - reset($queue); - $runner = new Runner($queue); - - $request = $request->withoutAttribute(self::MIDDLEWARE); - - return $runner->handle($request); - } -} diff --git a/Slim/RequestHandler/Runner.php b/Slim/RequestHandler/Runner.php deleted file mode 100644 index ba2408c7a..000000000 --- a/Slim/RequestHandler/Runner.php +++ /dev/null @@ -1,66 +0,0 @@ -queue = $queue; - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - $middleware = current($this->queue); - - if (!$middleware) { - throw new RuntimeException('No middleware found. Add a response factory middleware.'); - } - - next($this->queue); - - if ($middleware instanceof MiddlewareInterface) { - return $middleware->process($request, $this); - } - - if ($middleware instanceof RequestHandlerInterface) { - return $middleware->handle($request); - } - - if (is_callable($middleware)) { - return $middleware($request, $this); - } - - throw new RuntimeException( - sprintf( - 'Invalid middleware queue entry "%s". Middleware must either be callable or implement %s.', - is_scalar($middleware) ? (string)$middleware : gettype($middleware), - MiddlewareInterface::class - ) - ); - } -} diff --git a/Slim/Routing/MiddlewareAwareTrait.php b/Slim/Routing/MiddlewareCollectionTrait.php similarity index 62% rename from Slim/Routing/MiddlewareAwareTrait.php rename to Slim/Routing/MiddlewareCollectionTrait.php index b07f5f8d3..52551eccd 100644 --- a/Slim/Routing/MiddlewareAwareTrait.php +++ b/Slim/Routing/MiddlewareCollectionTrait.php @@ -1,22 +1,28 @@ + * @var array */ private array $middleware = []; /** - * @return array + * @return array */ - public function getMiddlewareStack(): array + public function getMiddleware(): array { return $this->middleware; } diff --git a/Slim/Routing/PipelineRunner.php b/Slim/Routing/PipelineRunner.php new file mode 100644 index 000000000..0e6695197 --- /dev/null +++ b/Slim/Routing/PipelineRunner.php @@ -0,0 +1,84 @@ + + */ + private array $pipeline = []; + + public function __construct(ContainerResolverInterface $resolver) + { + $this->resolver = $resolver; + } + + /** + * @param array $pipeline + */ + public function withPipeline(array $pipeline): self + { + $clone = clone $this; + $clone->pipeline = array_values($pipeline); + + return $clone; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $entry = current($this->pipeline); + + if (!$entry) { + throw new RuntimeException('The middleware pipeline is empty.'); + } + + $entry = $this->resolver->resolve($entry); + + next($this->pipeline); + + if ($entry instanceof MiddlewareInterface) { + return $entry->process($request, $this); + } + + if ($entry instanceof RequestHandlerInterface) { + return $entry->handle($request); + } + + if (is_callable($entry)) { + return $entry($request, $this); + } + + throw new RuntimeException( + sprintf( + 'Invalid pipeline entry of type "%s". Expected one of: callable, %s, or %s.', + is_object($entry) ? $entry::class : gettype($entry), + MiddlewareInterface::class, + RequestHandlerInterface::class, + ), + ); + } +} diff --git a/Slim/Routing/Route.php b/Slim/Routing/Route.php index 865c7edf2..37dc8dd26 100644 --- a/Slim/Routing/Route.php +++ b/Slim/Routing/Route.php @@ -1,12 +1,18 @@ @@ -26,6 +32,9 @@ final class Route implements MiddlewareCollectionInterface /** * @param array $methods + * @param string $pattern + * @param callable|string $handler + * @param ?RouteGroup $group */ public function __construct(array $methods, string $pattern, callable|string $handler, ?RouteGroup $group = null) { @@ -57,6 +66,9 @@ public function getPattern(): string return $this->pattern; } + /** + * @return array + */ public function getMethods(): array { return $this->methods; diff --git a/Slim/Routing/RouteCollectionTrait.php b/Slim/Routing/RouteCollectionTrait.php index ff170c2c2..1168dd746 100644 --- a/Slim/Routing/RouteCollectionTrait.php +++ b/Slim/Routing/RouteCollectionTrait.php @@ -1,16 +1,20 @@ urlGenerator = $urlGenerator; $this->routingResults = $routingResults; $this->basePath = $basePath; } public static function fromRequest(ServerRequestInterface $request): self { - /* @var UrlGenerator|null $urlGenerator */ - $urlGenerator = $request->getAttribute(self::URL_GENERATOR); - /* @var RoutingResults|null $routingResults */ $routingResults = $request->getAttribute(self::ROUTING_RESULTS); /* @var string|null $basePath */ $basePath = $request->getAttribute(self::BASE_PATH); - if ($urlGenerator === null) { + if (!$routingResults instanceof RoutingResults) { throw new RuntimeException( - 'Cannot create RouteContext before routing has been completed. Add UrlGeneratorMiddleware to fix this.' + 'Cannot create RouteContext before routing has been completed. Add RoutingMiddleware to fix this.', ); } - if ($routingResults === null) { + if ($basePath !== null && !is_string($basePath)) { throw new RuntimeException( - 'Cannot create RouteContext before routing has been completed. Add RoutingMiddleware to fix this.' + sprintf('Invalid basePath attribute type: %s', gettype($basePath)), ); } - return new self($routingResults, $urlGenerator, $basePath); - } - - public function getUrlGenerator(): UrlGenerator - { - return $this->urlGenerator; + return new self($routingResults, $basePath); } public function getRoutingResults(): RoutingResults @@ -83,6 +69,9 @@ public function getRoute(): ?Route return $this->routingResults->getRoute(); } + /** + * @return array + */ public function getArguments(): array { return $this->routingResults->getRouteArguments(); diff --git a/Slim/Routing/RouteGroup.php b/Slim/Routing/RouteGroup.php index 1abb3ed54..7ba227b6c 100644 --- a/Slim/Routing/RouteGroup.php +++ b/Slim/Routing/RouteGroup.php @@ -1,5 +1,11 @@ prefix = sprintf('/%s', ltrim($prefix, '/')); + private RouteCollector $routeCollector; + + public function __construct( + string $prefix, + callable $callback, + RouteCollector $routeCollector, + ?RouteGroup $group = null, + ) { + $this->prefix = $prefix; $this->callback = $callback; - $this->router = $router; - $this->routeCollector = $router->getRouteCollector(); + $this->routeCollector = $routeCollector; $this->group = $group; } @@ -57,13 +64,12 @@ public function getRouteGroup(): ?RouteGroup /** * @param array $methods + * @param string $path + * @param callable|string $handler */ public function map(array $methods, string $path, callable|string $handler): Route { - $routePath = ($path === '' || $path === '/') ? $this->prefix : $this->prefix . sprintf( - '/%s', - ltrim($path, '/') - ); + $routePath = $this->prefix . $path; $route = new Route($methods, $routePath, $handler, $this); $this->routeCollector->addRoute($methods, $path, $route); @@ -72,8 +78,8 @@ public function map(array $methods, string $path, callable|string $handler): Rou public function group(string $path, callable $handler): RouteGroup { - $routePath = ($path === '/') ? $this->prefix : $this->prefix . sprintf('/%s', ltrim($path, '/')); - $routeGroup = new RouteGroup($routePath, $handler, $this->router, $this); + $routePath = $this->prefix . $path; + $routeGroup = new RouteGroup($routePath, $handler, $this->routeCollector, $this); $this->routeCollector->addGroup($path, $routeGroup); diff --git a/Slim/Routing/RouteInvoker.php b/Slim/Routing/RouteInvoker.php new file mode 100644 index 000000000..53bfcf422 --- /dev/null +++ b/Slim/Routing/RouteInvoker.php @@ -0,0 +1,72 @@ + */ + private array $args = []; + + public function __construct( + ResponseFactoryInterface $responseFactory, + RequestHandlerInvocationStrategyInterface $invocationStrategy, + ContainerResolverInterface $containerResolver, + ) { + $this->responseFactory = $responseFactory; + $this->invocationStrategy = $invocationStrategy; + $this->resolver = $containerResolver; + } + + /** + * Add handler. + * + * @param callable|string $handler + * @param array $args + * + * @return self + */ + public function withHandler(callable|string $handler, array $args = []): self + { + $clone = clone $this; + $clone->handler = $this->resolver->resolveCallable($handler); + $clone->args = $args; + + return $clone; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + if ($this->handler === null) { + throw new RuntimeException( + 'RouteInvokerMiddleware: no handler has been assigned. ' . + 'Use withHandler() before using this middleware.', + ); + } + + return ($this->invocationStrategy)( + $this->handler, + $request, + $this->responseFactory->createResponse(), + $this->args, + ); + } +} diff --git a/Slim/Routing/Router.php b/Slim/Routing/Router.php index ba42ebe3e..c3a943cb0 100644 --- a/Slim/Routing/Router.php +++ b/Slim/Routing/Router.php @@ -1,29 +1,46 @@ collector = $collector; + $this->collector = new RouteCollector(new Std(), new GroupCountBased()); + $this->pipelineRunner = $pipelineRunner; } /** * @param array $methods + * @param string $path + * @param callable|string $handler * - * @throws InvalidArgumentException + * @return Route */ public function map(array $methods, string $path, callable|string $handler): Route { @@ -42,7 +59,7 @@ public function map(array $methods, string $path, callable|string $handler): Rou public function group(string $path, callable $handler): RouteGroup { $routePattern = $this->normalizePath($path); - $routeGroup = new RouteGroup($routePattern, $handler, $this); + $routeGroup = new RouteGroup($routePattern, $handler, $this->getRouteCollector()); $this->collector->addGroup($routePattern, $routeGroup); return $routeGroup; @@ -63,11 +80,19 @@ public function getBasePath(): string return $this->basePath; } + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->pipelineRunner + ->withPipeline($this->getMiddleware()) + ->handle($request); + } + /** * Normalizes a path by ensuring: * - Starts with a forward slash * - No trailing slash (unless root path) * - No double slashes + * @param string $path */ private function normalizePath(string $path): string { @@ -83,6 +108,6 @@ private function normalizePath(string $path): string $path = rtrim($path, '/'); // Replace multiple consecutive slashes with a single slash - return preg_replace('#/+#', '/', $path); + return preg_replace('#/+#', '/', $path) ?? ''; } } diff --git a/Slim/Routing/RoutingResults.php b/Slim/Routing/RoutingResults.php index 962551bd0..77162292f 100644 --- a/Slim/Routing/RoutingResults.php +++ b/Slim/Routing/RoutingResults.php @@ -1,5 +1,11 @@ + * @var array */ private array $allowedMethods; /** - * @param array $routeArguments + * @param int $routeStatus + * @param ?Route $route + * @param string $method + * @param string $uri + * @param array $routeArguments + * @param array $allowedMethods */ public function __construct( int $routeStatus, diff --git a/Slim/Routing/Strategies/RequestResponseTypedArgs.php b/Slim/Routing/Strategies/RequestResponseTypedArgs.php deleted file mode 100644 index 150066283..000000000 --- a/Slim/Routing/Strategies/RequestResponseTypedArgs.php +++ /dev/null @@ -1,41 +0,0 @@ -invoker = $invoker; - } - - public function __invoke( - callable $callable, - ServerRequestInterface $request, - ResponseInterface $response, - array $routeArguments, - ): ResponseInterface { - $routeArguments['request'] = $request; - $routeArguments['response'] = $response; - - return $this->invoker->call($callable, $routeArguments); - } -} diff --git a/Slim/Routing/UrlGenerator.php b/Slim/Routing/UrlGenerator.php index 5d695ea39..dac4227b0 100644 --- a/Slim/Routing/UrlGenerator.php +++ b/Slim/Routing/UrlGenerator.php @@ -1,5 +1,11 @@ router->getRouteCollector()->getData(); $iterator = new RecursiveIteratorIterator( - new RecursiveArrayIterator($routes, RecursiveArrayIterator::CHILD_ARRAYS_ONLY) + new RecursiveArrayIterator($routes, RecursiveArrayIterator::CHILD_ARRAYS_ONLY), ); foreach ($iterator as $route) { @@ -88,6 +94,12 @@ private function getNamedRoute(string $name): Route throw new UnexpectedValueException('Named route does not exist for name: ' . $name); } + /** + * @param string $pattern + * @param array $data + * + * @return array + */ private function getSegments(string $pattern, array $data): array { $segments = []; diff --git a/Slim/Routing/Strategies/RequestHandler.php b/Slim/Strategy/RequestHandler.php similarity index 91% rename from Slim/Routing/Strategies/RequestHandler.php rename to Slim/Strategy/RequestHandler.php index b4942c171..d47dd082d 100644 --- a/Slim/Routing/Strategies/RequestHandler.php +++ b/Slim/Strategy/RequestHandler.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Routing\Strategies; +namespace Slim\Strategy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -23,7 +23,7 @@ public function __invoke( callable $callable, ServerRequestInterface $request, ResponseInterface $response, - array $routeArguments + array $routeArguments, ): ResponseInterface { return $callable($request); } diff --git a/Slim/Routing/Strategies/RequestResponse.php b/Slim/Strategy/RequestResponse.php similarity index 95% rename from Slim/Routing/Strategies/RequestResponse.php rename to Slim/Strategy/RequestResponse.php index e1e4dbebb..2283eb61d 100644 --- a/Slim/Routing/Strategies/RequestResponse.php +++ b/Slim/Strategy/RequestResponse.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Routing\Strategies; +namespace Slim\Strategy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/Slim/Routing/Strategies/RequestResponseArgs.php b/Slim/Strategy/RequestResponseArgs.php similarity index 95% rename from Slim/Routing/Strategies/RequestResponseArgs.php rename to Slim/Strategy/RequestResponseArgs.php index 66232944e..7e5cf768e 100644 --- a/Slim/Routing/Strategies/RequestResponseArgs.php +++ b/Slim/Strategy/RequestResponseArgs.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Routing\Strategies; +namespace Slim\Strategy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/Slim/Routing/Strategies/RequestResponseNamedArgs.php b/Slim/Strategy/RequestResponseNamedArgs.php similarity index 95% rename from Slim/Routing/Strategies/RequestResponseNamedArgs.php rename to Slim/Strategy/RequestResponseNamedArgs.php index c64a4a90a..b32aa0511 100644 --- a/Slim/Routing/Strategies/RequestResponseNamedArgs.php +++ b/Slim/Strategy/RequestResponseNamedArgs.php @@ -8,7 +8,7 @@ declare(strict_types=1); -namespace Slim\Routing\Strategies; +namespace Slim\Strategy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/composer.json b/composer.json index e6e752eae..d6b8ed82b 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "slack": "https://slimphp.slack.com/" }, "require": { - "php": "8.2.* || 8.3.* || 8.4.*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "ext-json": "*", "nikic/fast-route": "^1.3", "psr/container": "^2.0", @@ -77,13 +77,12 @@ "phpstan/phpstan": "^2", "phpunit/phpunit": "^11", "slim/http": "^1.3", - "slim/psr7": "^1.6", - "squizlabs/php_codesniffer": "^3.10" + "slim/psr7": "^1.6" }, "suggest": { - "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", - "ext-libxml": "Needed to support XML format in BodyParsingMiddleware", "ext-dom": "Needed to support XML format in XmlErrorFormatter", + "ext-libxml": "Needed to support XML format in BodyParsingMiddleware", + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v5/start/installation.html for more information." }, @@ -101,21 +100,12 @@ "sort-packages": true }, "scripts": { - "cs:check": [ - "@putenv PHP_CS_FIXER_IGNORE_ENV=1", - "php-cs-fixer fix --dry-run --format=txt --verbose --config=.cs.php --ansi" - ], - "cs:fix": [ - "@putenv PHP_CS_FIXER_IGNORE_ENV=1", - "php-cs-fixer fix --config=.cs.php --ansi --verbose" - ], - "sniffer:check": "phpcs --standard=phpcs.xml", - "sniffer:fix": "phpcbf --standard=phpcs.xml", + "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi --allow-unsupported-php-version=yes", + "cs:fix": "php-cs-fixer fix --config=.cs.php --ansi --verbose --allow-unsupported-php-version=yes", "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi", "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always --display-warnings --display-deprecations --no-coverage", "test:all": [ "@cs:check", - "@sniffer:check", "@stan", "@test" ], diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 0c1eddae1..000000000 --- a/phpcs.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - Slim coding standard - - - - - - - - - - - - Slim - tests - diff --git a/phpstan.neon b/phpstan.neon index 97e042c04..e9ff4bb72 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 8 paths: - Slim includes: diff --git a/tests/AppTest.php b/tests/AppTest.php index 1f5dca247..4e1134ef0 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -10,21 +10,18 @@ namespace Slim\Tests; -use DI\Container; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; use Slim\App; -use Slim\Builder\AppBuilder; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; -use Slim\Interfaces\ContainerFactoryInterface; +use Slim\Factory\AppFactory; use Slim\Interfaces\RequestHandlerInvocationStrategyInterface; use Slim\Interfaces\ServerRequestCreatorInterface; use Slim\Middleware\BasePathMiddleware; @@ -41,9 +38,10 @@ use Slim\Psr7\Stream; use Slim\Psr7\Uri; use Slim\Routing\RouteGroup; -use Slim\Routing\Strategies\RequestResponseArgs; -use Slim\Routing\Strategies\RequestResponseNamedArgs; +use Slim\Strategy\RequestResponseArgs; +use Slim\Strategy\RequestResponseNamedArgs; use Slim\Tests\Traits\AppTestTrait; +use SplQueue; use SplStack; use UnexpectedValueException; @@ -56,8 +54,7 @@ final class AppTest extends TestCase public function testAppWithExceptionAndErrorDetails(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( + $definitions = [ HtmlExceptionMiddleware::class => function ($container) { $responseFactory = $container->get(ResponseFactoryInterface::class); @@ -65,23 +62,22 @@ public function testAppWithExceptionAndErrorDetails(): void return $middleware->withErrorDetails(true); }, - ] - ); - $app = $builder->build(); + ]; + + $app = AppFactory::create($definitions); $app->add(HtmlExceptionMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $app->get('/', fn () => throw new UnexpectedValueException('Test exception message')); + $app->get('/', fn() => throw new UnexpectedValueException('Test exception message')); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $request = $request->withHeader( 'Accept', - 'text/html, application/xhtml+xml, application/xml;q=0.9, application/json , image/webp, */*;q=0.8' + 'text/html, application/xhtml+xml, application/xml;q=0.9, application/json , image/webp, */*;q=0.8', ); $response = $app->handle($request); @@ -94,33 +90,30 @@ public function testAppWithExceptionAndErrorDetails(): void public function testGetAppFromContainer(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); // should return the same instance $actual = $app->getContainer()->get(App::class); $this->assertSame($app, $actual); } - public function testGetContainer(): void + public function testGetContainerWithDefinitions(): void { - $factory = new class implements ContainerFactoryInterface { - public function createContainer(array $definitions = []): ContainerInterface - { - return new Container($definitions); - } - }; + $definitions = + [ + 'test' => function () { + return 'test-value'; + }, + ]; - $builder = new AppBuilder(); - $builder->setContainerFactory($factory); - $app = $builder->build(); + $app = AppFactory::create($definitions); $this->assertInstanceOf(ContainerInterface::class, $app->getContainer()); } public function testAppWithMiddlewareStack(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $app->add(BasePathMiddleware::class); $app->add(RoutingMiddleware::class); @@ -135,8 +128,8 @@ public function testAppWithMiddlewareStack(): void return $response->withHeader('X-Test', 'action'); }); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $response = $app->handle($request); @@ -146,11 +139,9 @@ public function testAppWithMiddlewareStack(): void public function testGetWithInvokableClass(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $action = new class { public function __invoke($request, $response, $args) @@ -161,8 +152,8 @@ public function __invoke($request, $response, $args) $app->get('/', $action::class); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $response = $app->handle($request); @@ -185,12 +176,11 @@ public static function lowerCaseRequestMethodsProvider(): array #[DataProvider('upperCaseRequestMethodsProvider')] public function testGetPostPutPatchDeleteOptionsMethods(string $method): void { - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest($method, '/'); $methodName = strtolower($method); @@ -206,12 +196,11 @@ public function testGetPostPutPatchDeleteOptionsMethods(string $method): void public function testAnyRoute(): void { - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('FOO', '/'); $app->any('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -240,12 +229,11 @@ public static function upperCaseRequestMethodsProvider(): array #[DataProvider('upperCaseRequestMethodsProvider')] public function testMapRoute(string $method): void { - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest($method, '/'); $app->map([$method], '/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -263,12 +251,11 @@ public function testRouteWithInternationalCharacters(): void { $path = '/новости'; - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', $path); $app->get($path, function (ServerRequestInterface $request, ResponseInterface $response) { @@ -310,12 +297,11 @@ public static function routePatternsProvider(): array #[DataProvider('routePatternsProvider')] public function testRoutePatterns(string $pattern, string $uri): void { - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', $uri); $app->get($pattern, function (ServerRequestInterface $request, ResponseInterface $response) { @@ -335,7 +321,7 @@ public function testRoutePatterns(string $pattern, string $uri): void public function testGroupClosureIsBoundToThisClass(): void { - $app = $this->createApp(); + $app = AppFactory::create(); $testCase = $this; $app->group('/foo', function () use ($testCase) { $testCase->assertSame($testCase, $this); @@ -347,12 +333,11 @@ public function testGroupClosureIsBoundToThisClass(): void */ public function testAddMiddlewareUsingDeferredResolution(): void { - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -368,7 +353,7 @@ public function testAddMiddlewareUsingDeferredResolution(): void public function testAddMiddlewareUsingClosure(): void { - $app = $this->createApp(); + $app = AppFactory::create(); $middleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) { $response = $handler->handle($request); @@ -377,11 +362,10 @@ public function testAddMiddlewareUsingClosure(): void }; $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -398,7 +382,7 @@ public function testAddMiddlewareUsingClosure(): void public function testAddMiddlewareOnRoute(): void { - $app = $this->createApp(); + $app = AppFactory::create(); $middleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) { $response = $handler->handle($request); @@ -414,8 +398,7 @@ public function testAddMiddlewareOnRoute(): void return $response; }; - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // This middleware should not be invoked $middleware3 = function (ServerRequestInterface $request, RequestHandlerInterface $handler) { @@ -426,16 +409,17 @@ public function testAddMiddlewareOnRoute(): void }; $app->add($middleware3); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); // Add two middlewares - $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $response->getBody()->write('_ROUTE1_'); + $app + ->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('_ROUTE1_'); - return $response; - }) + return $response; + }) ->add($middleware) ->add($middleware2); @@ -446,7 +430,7 @@ public function testAddMiddlewareOnRoute(): void public function testAddMiddlewareOnRouteGroup(): void { - $app = $this->createApp(); + $app = AppFactory::create(); $trace = new SplStack(); @@ -458,7 +442,7 @@ public function testAddMiddlewareOnRouteGroup(): void $outgoingMiddleware = function ( ServerRequestInterface $request, - RequestHandlerInterface $handler + RequestHandlerInterface $handler, ) use ($trace) { $response = $handler->handle($request); $response->getBody()->write('_OUTGOING_'); @@ -467,18 +451,17 @@ public function testAddMiddlewareOnRouteGroup(): void return $response; }; - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/api/users'); // Add middleware to group $app->group('/api', function (RouteGroup $group) use ($trace) { $group->get('/users', function ( ServerRequestInterface $request, - ResponseInterface $response + ResponseInterface $response, ) use ($trace) { $trace->push('_ROUTE1_'); $response->getBody()->write('_ROUTE1_'); @@ -496,36 +479,41 @@ public function testAddMiddlewareOnRouteGroup(): void 1 => '_ROUTE1_', 0 => '_AUTH_', ], - iterator_to_array($trace) + iterator_to_array($trace), ); } public function testAddMiddlewareOnTwoRouteGroup(): void { - $app = $this->createApp(); + $app = AppFactory::create(); - $trace = new SplStack(); + $trace = new SplQueue(); + + $app->add(function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($trace) { + $trace[] = '_GLOBAL_'; + + return $handler->handle($request); + }); $authMiddleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($trace) { - $trace->push('_AUTH_'); + $trace[] = '_AUTH_'; $response = $handler->handle($request); return $response; }; $usersMiddleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($trace) { - $trace->push('_USERS_'); + $trace[] = '_USERS_'; $response = $handler->handle($request); $response->getBody()->write('_USERS_'); return $response; }; - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/api/users/123'); // Add middleware to groups @@ -534,11 +522,11 @@ public function testAddMiddlewareOnTwoRouteGroup(): void $group->get( '/{id}', function (ServerRequestInterface $request, ResponseInterface $response) use ($trace) { - $trace->push('_ROUTE1_'); + $trace[] = '_ROUTE1_'; $response->getBody()->write('_ROUTE1_'); return $response; - } + }, ); })->add($usersMiddleware); })->add($authMiddleware); @@ -549,11 +537,12 @@ function (ServerRequestInterface $request, ResponseInterface $response) use ($tr $this->assertSame( [ - 2 => '_ROUTE1_', - 1 => '_USERS_', - 0 => '_AUTH_', + '_GLOBAL_', + '_AUTH_', + '_USERS_', + '_ROUTE1_', ], - iterator_to_array($trace) + iterator_to_array($trace), ); } @@ -561,12 +550,11 @@ public function testInvokeReturnMethodNotAllowed(): void { $this->expectException(HttpMethodNotAllowedException::class); - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('POST', '/'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -578,12 +566,11 @@ public function testInvokeReturnMethodNotAllowed(): void public function testInvokeWithMatchingRoute(): void { - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -599,12 +586,11 @@ public function testInvokeWithMatchingRoute(): void public function testInvokeWithMatchingRouteWithNamedParameterRequestResponseStrategy(): void { - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/hello/john'); $app->get( @@ -614,7 +600,7 @@ function (ServerRequestInterface $request, ResponseInterface $response, $args) { $response->getBody()->write("Hello {$args['name']}"); return $response; - } + }, ); $response = $app->handle($request); @@ -623,17 +609,15 @@ function (ServerRequestInterface $request, ResponseInterface $response, $args) { public function testInvokeWithMatchingRouteWithNamedParameterRequestResponseArgStrategy(): void { - $builder = new AppBuilder(); - $builder->addDefinitions([ - RequestHandlerInvocationStrategyInterface::class => fn () => new RequestResponseArgs(), - ]); - $app = $builder->build(); + $definitions = [ + RequestHandlerInvocationStrategyInterface::class => fn() => new RequestResponseArgs(), + ]; + $app = AppFactory::create($definitions); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/hello/john'); $app->get( @@ -642,7 +626,7 @@ function (ServerRequestInterface $request, ResponseInterface $response, $name) { $response->getBody()->write("Hello {$name}"); return $response; - } + }, ); $response = $app->handle($request); @@ -652,17 +636,17 @@ function (ServerRequestInterface $request, ResponseInterface $response, $name) { public function testInvokeWithMatchingRouteWithNamedParameterRequestResponseNamedArgsStrategy(): void { - $builder = new AppBuilder(); - $builder->addDefinitions([ - RequestHandlerInvocationStrategyInterface::class => fn () => new RequestResponseNamedArgs(), - ]); - $app = $builder->build(); + $definitions = + [ + RequestHandlerInvocationStrategyInterface::class => fn() => new RequestResponseNamedArgs(), + ]; - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create($definitions); + + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/hello/john'); $app->get( @@ -671,7 +655,7 @@ function (ServerRequestInterface $request, ResponseInterface $response, $name) { $response->getBody()->write("Hello {$name}"); return $response; - } + }, ); $response = $app->handle($request); @@ -683,12 +667,11 @@ public function testInvokeWithoutMatchingRoute(): void { $this->expectException(HttpNotFoundException::class); - $app = $this->createApp(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/nada'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -700,8 +683,6 @@ public function testInvokeWithoutMatchingRoute(): void public function testInvokeWithCallableRegisteredInContainer(): void { - $builder = new AppBuilder(); - $handler = new class { public function foo(ServerRequestInterface $request, ResponseInterface $response) { @@ -711,17 +692,16 @@ public function foo(ServerRequestInterface $request, ResponseInterface $response } }; - $builder->addDefinitions([ + $definitions = [ 'handler' => $handler, - ]); + ]; - $app = $builder->build(); + $app = AppFactory::create($definitions); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', 'handler:foo'); @@ -733,8 +713,6 @@ public function foo(ServerRequestInterface $request, ResponseInterface $response public function testInvokeWithCallableRegisteredInContainerAsFunction(): void { - $builder = new AppBuilder(); - $handler = new class { public function foo(ServerRequestInterface $request, ResponseInterface $response) { @@ -744,19 +722,18 @@ public function foo(ServerRequestInterface $request, ResponseInterface $response } }; - $builder->addDefinitions([ + $definitions = [ 'handler' => function () use ($handler) { return $handler; }, - ]); + ]; - $app = $builder->build(); + $app = AppFactory::create($definitions); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', 'handler:foo'); @@ -769,24 +746,22 @@ public function foo(ServerRequestInterface $request, ResponseInterface $response public function testInvokeWithNonExistentMethodOnCallableRegisteredInContainer(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The method "method_does_not_exist" does not exists'); - - $builder = new AppBuilder(); + $this->expectExceptionMessage('The method "method_does_not_exist" does not exist'); - $builder->addDefinitions([ + $definitions = [ 'handler' => new class { public function foo() { } }, - ]); - $app = $builder->build(); + ]; - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create($definitions); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $app->addRoutingMiddleware(); + + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', 'handler:method_does_not_exist'); @@ -795,14 +770,12 @@ public function foo() public function testInvokeFunctionName(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); // @codingStandardsIgnoreStart @@ -824,8 +797,7 @@ function handle($request, ResponseInterface $response) public function testAddMiddleware(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $container = $app->getContainer(); $routing = $container->get(RoutingMiddleware::class); @@ -834,8 +806,8 @@ public function testAddMiddleware(): void $app->addMiddleware($routing); $app->add($endpoint); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -850,11 +822,9 @@ public function testAddMiddleware(): void public function testGetBasePath(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $this->assertSame('', $app->getBasePath()); $app->setBasePath('/sub'); @@ -863,14 +833,12 @@ public function testGetBasePath(): void public function testRun(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -885,10 +853,8 @@ public function testRun(): void public function testRunWithoutPassingInServerRequest(): void { - $builder = new AppBuilder(); - - $builder->addDefinitions( - [ + $definitions + = [ ServerRequestCreatorInterface::class => function () { return new class implements ServerRequestCreatorInterface { public function createServerRequestFromGlobals(): ServerRequestInterface @@ -899,17 +865,17 @@ public function createServerRequestFromGlobals(): ServerRequestInterface new Headers(), [], [], - new Stream(fopen('php://memory', 'w+')) + new Stream(fopen('php://memory', 'w+')), ); } }; }, - ] - ); + ]; - $app = $builder->build(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create($definitions); + + // $app = $builder->build(); + $app->addRoutingMiddleware(); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Hello World'); @@ -924,14 +890,12 @@ public function createServerRequestFromGlobals(): ServerRequestInterface public function testHandleReturnsEmptyResponseBodyWithHeadRequestMethod(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(HeadMethodMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('HEAD', '/'); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -944,15 +908,13 @@ public function testHandleReturnsEmptyResponseBodyWithHeadRequestMethod(): void $this->assertEmpty((string)$response->getBody()); } - public function testInvokeSequentialProcessToAPathWithOptionalArgsAndWithoutOptionalArgs(): void + public function testPathWithOptionalArgs(): void { - $builder = new AppBuilder(); - $app = $builder->build(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app = AppFactory::create(); + $app->addRoutingMiddleware(); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/hello/friend'); $app->get('/hello[/{name}]', function (ServerRequestInterface $request, ResponseInterface $response, $args) { @@ -965,11 +927,30 @@ public function testInvokeSequentialProcessToAPathWithOptionalArgsAndWithoutOpti $this->assertSame('1', (string)$response->getBody()); // 2. test without value - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/hello'); $response = $app->handle($request); $this->assertSame('0', (string)$response->getBody()); } + + public function testAddErrorMiddleware(): void + { + $app = AppFactory::create() + ->addErrorMiddleware() + ->addRoutingMiddleware(); + + $request = $this + ->getServerRequestFactory($app) + ->createServerRequest('HEAD', '/') + ->withHeader('Accept', 'text/html'); + + $app->get('/', function () { + throw new RuntimeException('test message'); + }); + + $response = $app->handle($request); + $this->assertStringContainsString('Application Error', (string)$response->getBody()); + } } diff --git a/tests/Builder/AppBuilderTest.php b/tests/Builder/AppBuilderTest.php deleted file mode 100644 index 6c95ec57a..000000000 --- a/tests/Builder/AppBuilderTest.php +++ /dev/null @@ -1,140 +0,0 @@ - 'bar']; - } - }; - $builder->addDefinitionsClass($class::class); - $app = $builder->build(); - - $this->assertSame('bar', $app->getContainer()->get('foo')); - } - - public function testAddDefinitionsClassException(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Definition file should return an array of definitions'); - - $builder = new AppBuilder(); - $class = new class { - public function __invoke() - { - return null; - } - }; - $builder->addDefinitionsClass($class::class); - $app = $builder->build(); - - $this->assertSame('bar', $app->getContainer()->get('foo')); - } - - public function testAddDefinitionsFile(): void - { - $builder = new AppBuilder(); - $builder->addDefinitionsFile(__DIR__ . '/TestContainerDefinition.php'); - $app = $builder->build(); - - $this->assertSame('bar', $app->getContainer()->get('foo')); - } - - public function testAddDefinitionsFileError(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Definition file should return an array of definitions'); - - $builder = new AppBuilder(); - $builder->addDefinitionsFile(__DIR__ . '/TestContainerError.php'); - $app = $builder->build(); - - $this->assertSame('bar', $app->getContainer()->get('foo')); - } - - public function testSetContainerFactory(): void - { - $builder = new AppBuilder(); - $builder->setContainerFactory( - new class implements ContainerFactoryInterface { - public function createContainer(array $definitions = []): ContainerInterface - { - $defaults = (new DefaultDefinitions())->__invoke(); - $defaults = array_merge($defaults, (new HttpDefinitions())->__invoke()); - - $defaults['foo'] = 'bar'; - - return new Container($defaults); - } - } - ); - $app = $builder->build(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $response->getBody()->write($this->get('foo')); - - return $response; - }); - - $response = $app->handle($request); - $this->assertSame('bar', (string)$response->getBody()); - } - - public function testMiddlewareOrderFifo(): void - { - $builder = new AppBuilder(); - $app = $builder->build(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $response->getBody()->write('OK'); - - return $response; - }); - - $response = $app->handle($request); - $this->assertSame('OK', (string)$response->getBody()); - } -} diff --git a/tests/Container/ContainerResolverTest.php b/tests/Container/ContainerResolverTest.php index a314992b7..b586533ae 100644 --- a/tests/Container/ContainerResolverTest.php +++ b/tests/Container/ContainerResolverTest.php @@ -14,16 +14,17 @@ use Exception; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; -use Psr\Http\Message\ResponseFactoryInterface; -use Slim\Builder\AppBuilder; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; use Slim\Container\ContainerResolver; +use Slim\Factory\AppFactory; use Slim\Interfaces\ContainerResolverInterface; use Slim\Tests\Mocks\CallableTester; use Slim\Tests\Mocks\InvokableTester; -use Slim\Tests\Mocks\MiddlewareTester; use Slim\Tests\Mocks\RequestHandlerTester; use Slim\Tests\Traits\AppTestTrait; -use TypeError; final class ContainerResolverTest extends TestCase { @@ -35,23 +36,21 @@ public function testClosure(): void return true; }; - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable($test); + $callable = $resolver->resolve($test); $this->assertTrue($callable()); } public function testClosureContainer(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( - [ - 'ultimateAnswer' => fn () => 42, - ] - ); - $app = $builder->build(); + $definitions + = [ + 'ultimateAnswer' => fn() => 42, + ]; + $app = AppFactory::create($definitions); $container = $app->getContainer(); $that = $this; @@ -64,15 +63,14 @@ public function testClosureContainer(): void }; $resolver = $container->get(ContainerResolverInterface::class); - $callable = $resolver->resolveRoute($test); + $callable = $resolver->resolveCallable($test); $this->assertSame(42, $callable()); } public function testClosureFromCallable(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $container = $app->getContainer(); $that = $this; @@ -81,22 +79,22 @@ function () use ($that, $container) { $that->assertSame($container, $this); return 42; - } + }, ); $test = [$class, '__invoke']; $resolver = $container->get(ContainerResolverInterface::class); - $callable = $resolver->resolveRoute($test); + $callable = $resolver->resolveCallable($test); $this->assertSame(42, $callable()); } public function testFunctionName(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable(__NAMESPACE__ . '\testAdvancedCallable'); + $callable = $resolver->resolve(__NAMESPACE__ . '\testAdvancedCallable'); $this->assertTrue($callable()); } @@ -104,188 +102,189 @@ public function testFunctionName(): void public function testObjMethodArray(): void { $obj = new CallableTester(); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable([$obj, 'toCall']); + $callable = $resolver->resolve([$obj, 'toCall']); $this->assertSame(true, $callable()); } public function testSlimCallable(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable('Slim\Tests\Mocks\CallableTester:toCall'); + $callable = $resolver->resolve('Slim\Tests\Mocks\CallableTester:toCall'); $this->assertSame(true, $callable()); } public function testSlimCallableAsArray(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable([CallableTester::class, 'toCall']); + $callable = $resolver->resolve([CallableTester::class, 'toCall']); $this->assertSame(true, $callable()); } public function testContainer(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( - [ - 'callable_service' => fn () => new CallableTester(), - ] - ); - $app = $builder->build(); + $definitions + = [ + 'callable_service' => fn() => new CallableTester(), + ]; + $app = AppFactory::create($definitions); $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable('callable_service:toCall'); + $callable = $resolver->resolve('callable_service:toCall'); $this->assertSame(true, $callable()); } public function testResolutionToAnInvokableClassInContainer(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( - [ - 'an_invokable' => fn () => new InvokableTester(), - ] - ); - $app = $builder->build(); + $definitions + = [ + 'an_invokable' => fn() => new InvokableTester(), + ]; + $app = AppFactory::create($definitions); + $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable('an_invokable'); + $callable = $resolver->resolve('an_invokable'); $this->assertSame(true, $callable()); } public function testResolutionToAnInvokableClass(): void { - $builder = new AppBuilder(); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable(InvokableTester::class); + $app = AppFactory::create(); + $resolver = $app->getContainer()->get(ContainerResolverInterface::class); + $callable = $resolver->resolve(InvokableTester::class); $this->assertSame(true, $callable()); } - public function testResolutionToRequestHandler(): void + public function testResolutionToAPsrRequestHandlerClassWithCustomMethod(): void + { + $app = AppFactory::create(); + $resolver = $app->getContainer()->get(ContainerResolverInterface::class); + $callable = $resolver->resolve(RequestHandlerTester::class . ':custom'); + + $this->assertIsArray($callable); + $this->assertInstanceOf(RequestHandlerTester::class, $callable[0]); + $this->assertSame('custom', $callable[1]); + } + + public function testMethodNotFoundThrowException(): void { $this->expectException(Exception::class); - $this->expectExceptionMessage('The definition "Slim\Tests\Mocks\RequestHandlerTester" is not a callable'); + $this->expectExceptionMessage('The method "notFound" does not exist'); - $builder = new AppBuilder(); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); + $definitions + = [ + 'callable_service' => fn() => new CallableTester(), + ]; + $app = AppFactory::create($definitions); - $resolver->resolveCallable(RequestHandlerTester::class); + $resolver = $app->getContainer()->get(ContainerResolver::class); + $resolver->resolve('callable_service:notFound'); } - public function testObjRequestHandlerInContainer(): void + public function testFunctionNotFoundThrowException(): void { $this->expectException(Exception::class); - $this->expectExceptionMessage('The definition "a_requesthandler" is not a callable'); - - $builder = new AppBuilder(); - $builder->addDefinitions( - [ - 'a_requesthandler' => function ($container) { - return new RequestHandlerTester($container->get(ResponseFactoryInterface::class)); - }, - ] - ); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); + $this->expectExceptionMessage("No entry or class found for 'notFound'"); - $resolver->resolveCallable('a_requesthandler'); + $app = AppFactory::create(); + $resolver = $app->getContainer()->get(ContainerResolverInterface::class); + $resolver->resolve('notFound'); } - public function testResolutionToAPsrRequestHandlerClassWithCustomMethod(): void + public function testClassNotFoundThrowException(): void { - $builder = new AppBuilder(); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $callable = $resolver->resolveCallable(RequestHandlerTester::class . ':custom'); + $this->expectException(Exception::class); + $this->expectExceptionMessage("No entry or class found for 'Unknown'"); - $this->assertIsArray($callable); - $this->assertInstanceOf(RequestHandlerTester::class, $callable[0]); - $this->assertSame('custom', $callable[1]); + $app = AppFactory::create(); + $resolver = $app->getContainer()->get(ContainerResolverInterface::class); + $resolver->resolve('Unknown:notFound'); } - public function testObjMiddlewareClass(): void + public function testCallableClassNotFoundThrowException(): void { - $this->expectException(TypeError::class); - $this->expectExceptionMessage('must be of type callable|array|string'); + $this->expectException(Exception::class); + $this->expectExceptionMessage("No entry or class found for 'Unknown'"); - $obj = new MiddlewareTester(); - $builder = new AppBuilder(); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $resolver->resolveCallable($obj); + $app = AppFactory::create(); + $resolver = $app->getContainer()->get(ContainerResolverInterface::class); + $resolver->resolve(['Unknown', 'notFound']); } - public function testNotObjectInContainerThrowException(): void + public function testResolveStackWithFifoOrder() { - $this->expectException(Exception::class); - $this->expectExceptionMessage('The definition "callable_service" is not a callable'); + $app = AppFactory::create(); + $container = $app->getContainer(); + $resolver = $container->get(ContainerResolverInterface::class); - $builder = new AppBuilder(); - $builder->addDefinitions( - [ - 'callable_service' => fn () => 'NOT AN OBJECT', - ] - ); - $app = $builder->build(); + $middleware1 = $this->createCallableMiddleware(); + $middleware2 = $this->resolveMiddleware(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $resolver->resolveCallable('callable_service'); + $resolved1 = $resolver->resolve($middleware1); + $this->assertTrue(is_callable($resolved1)); + $resolved2 = $resolver->resolve($middleware2); + + $this->assertInstanceOf(MiddlewareInterface::class, $resolved2); } - public function testMethodNotFoundThrowException(): void + public function testResolveMiddlewareWithValidMiddleware() { - $this->expectException(Exception::class); - $this->expectExceptionMessage('The method "notFound" does not exists'); + $app = AppFactory::create(); + $container = $app->getContainer(); + $resolver = $container->get(ContainerResolverInterface::class); - $builder = new AppBuilder(); - $builder->addDefinitions( - [ - 'callable_service' => fn () => new CallableTester(), - ] - ); - $app = $builder->build(); + $middleware = $this->resolveMiddleware(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $resolver->resolveCallable('callable_service:notFound'); + $resolvedMiddleware = $resolver->resolve($middleware); + + $this->assertInstanceOf(MiddlewareInterface::class, $resolvedMiddleware); } - public function testFunctionNotFoundThrowException(): void + public function testUnresolvableWithSameResult(): void { - $this->expectException(Exception::class); - $this->expectExceptionMessage("No entry or class found for 'notFound'"); + $app = AppFactory::create(); + $container = $app->getContainer(); + $resolver = $container->get(ContainerResolverInterface::class); - $builder = new AppBuilder(); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $resolver->resolveCallable('notFound'); + $input = [[null]]; + $actual = $resolver->resolve($input); + + $this->assertEquals($input, $actual); } - public function testClassNotFoundThrowException(): void + private function createCallableMiddleware(): callable { - $this->expectException(Exception::class); - $this->expectExceptionMessage("No entry or class found for 'Unknown'"); + $response = $this->createMock(ResponseInterface::class); - $builder = new AppBuilder(); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $resolver->resolveCallable('Unknown:notFound'); + return function () use ($response): ResponseInterface { + return $response; + }; } - public function testCallableClassNotFoundThrowException(): void + private function resolveMiddleware(): MiddlewareInterface { - $this->expectException(Exception::class); - $this->expectExceptionMessage("No entry or class found for 'Unknown'"); + $response = $this->createMock(ResponseInterface::class); - $builder = new AppBuilder(); - $app = $builder->build(); - $resolver = $app->getContainer()->get(ContainerResolver::class); - $resolver->resolveCallable(['Unknown', 'notFound']); + return new class ($response) implements MiddlewareInterface { + private ResponseInterface $response; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ): ResponseInterface { + return $this->response; + } + }; } } diff --git a/tests/Container/GuzzleDefinitionsTest.php b/tests/Container/GuzzleDefinitionsTest.php index 7d1e201fe..2953c7d22 100644 --- a/tests/Container/GuzzleDefinitionsTest.php +++ b/tests/Container/GuzzleDefinitionsTest.php @@ -19,14 +19,14 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; -use Slim\Container\GuzzleDefinitions; +use Slim\Container\Definition\GuzzleDefinitions; use Slim\Interfaces\ServerRequestCreatorInterface; class GuzzleDefinitionsTest extends TestCase { public function testInvokeReturnsCorrectDefinitions() { - $definitions = (new GuzzleDefinitions())->__invoke(); + $definitions = (new GuzzleDefinitions())->getDefinitions(); $container = new Container($definitions); $this->assertTrue($container->has(ServerRequestFactoryInterface::class)); @@ -39,7 +39,7 @@ public function testInvokeReturnsCorrectDefinitions() public function testServerRequestFactoryInterface() { - $definitions = (new GuzzleDefinitions())->__invoke(); + $definitions = (new GuzzleDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class); @@ -49,7 +49,7 @@ public function testServerRequestFactoryInterface() public function testServerRequestCreatorInterface() { - $definitions = (new GuzzleDefinitions())->__invoke(); + $definitions = (new GuzzleDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestCreator = $container->get(ServerRequestCreatorInterface::class); @@ -60,7 +60,7 @@ public function testServerRequestCreatorInterface() public function testResponseFactoryInterface() { - $definitions = (new GuzzleDefinitions())->__invoke(); + $definitions = (new GuzzleDefinitions())->getDefinitions(); $container = new Container($definitions); $responseFactory = $container->get(ResponseFactoryInterface::class); @@ -70,7 +70,7 @@ public function testResponseFactoryInterface() public function testStreamFactoryInterface() { - $definitions = (new GuzzleDefinitions())->__invoke(); + $definitions = (new GuzzleDefinitions())->getDefinitions(); $container = new Container($definitions); $streamFactory = $container->get(StreamFactoryInterface::class); @@ -80,7 +80,7 @@ public function testStreamFactoryInterface() public function testUriFactoryInterface() { - $definitions = (new GuzzleDefinitions())->__invoke(); + $definitions = (new GuzzleDefinitions())->getDefinitions(); $container = new Container($definitions); $uriFactory = $container->get(UriFactoryInterface::class); @@ -90,7 +90,7 @@ public function testUriFactoryInterface() public function testUploadedFileFactoryInterface() { - $definitions = (new GuzzleDefinitions())->__invoke(); + $definitions = (new GuzzleDefinitions())->getDefinitions(); $container = new Container($definitions); $uploadedFileFactory = $container->get(UploadedFileFactoryInterface::class); diff --git a/tests/Container/HttpDefinitionsTest.php b/tests/Container/HttpDefinitionsTest.php index b30d04829..2fbe08811 100644 --- a/tests/Container/HttpDefinitionsTest.php +++ b/tests/Container/HttpDefinitionsTest.php @@ -15,7 +15,7 @@ use Psr\Http\Message\ServerRequestFactoryInterface; use ReflectionClass; use RuntimeException; -use Slim\Container\HttpDefinitions; +use Slim\Container\Definition\HttpDefinitions; class HttpDefinitionsTest extends TestCase { @@ -24,22 +24,21 @@ public function testInvokeThrowsRuntimeException() $this->expectException(RuntimeException::class); // Create a mock for the class_exists function - $classExistsMock = fn () => false; + $classExistsMock = fn() => false; $httpDefinitions = new HttpDefinitions(); // Use reflection to inject the mock callable into the $classExists property $reflection = new ReflectionClass($httpDefinitions); $classExistsProperty = $reflection->getProperty('classExists'); - $classExistsProperty->setAccessible(true); $classExistsProperty->setValue($httpDefinitions, $classExistsMock); - $httpDefinitions(); + $httpDefinitions->getDefinitions(); } public function testServerRequestFactoryInterface() { - $definitions = (new HttpDefinitions())->__invoke(); + $definitions = (new HttpDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class); diff --git a/tests/Container/HttpSoftDefinitionsTest.php b/tests/Container/HttpSoftDefinitionsTest.php index 5a26e490b..eab195b36 100644 --- a/tests/Container/HttpSoftDefinitionsTest.php +++ b/tests/Container/HttpSoftDefinitionsTest.php @@ -23,14 +23,14 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; -use Slim\Container\HttpSoftDefinitions; +use Slim\Container\Definition\HttpSoftDefinitions; use Slim\Interfaces\ServerRequestCreatorInterface; class HttpSoftDefinitionsTest extends TestCase { public function testInvokeReturnsCorrectDefinitions() { - $definitions = (new HttpSoftDefinitions())->__invoke(); + $definitions = (new HttpSoftDefinitions())->getDefinitions(); $container = new Container($definitions); $this->assertTrue($container->has(ServerRequestFactoryInterface::class)); @@ -43,7 +43,7 @@ public function testInvokeReturnsCorrectDefinitions() public function testServerRequestFactoryInterface() { - $definitions = (new HttpSoftDefinitions())->__invoke(); + $definitions = (new HttpSoftDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class); @@ -53,7 +53,7 @@ public function testServerRequestFactoryInterface() public function testServerRequestCreatorInterface() { - $definitions = (new HttpSoftDefinitions())->__invoke(); + $definitions = (new HttpSoftDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestCreator = $container->get(ServerRequestCreatorInterface::class); @@ -64,7 +64,7 @@ public function testServerRequestCreatorInterface() public function testResponseFactoryInterface() { - $definitions = (new HttpSoftDefinitions())->__invoke(); + $definitions = (new HttpSoftDefinitions())->getDefinitions(); $container = new Container($definitions); $responseFactory = $container->get(ResponseFactoryInterface::class); @@ -74,7 +74,7 @@ public function testResponseFactoryInterface() public function testStreamFactoryInterface() { - $definitions = (new HttpSoftDefinitions())->__invoke(); + $definitions = (new HttpSoftDefinitions())->getDefinitions(); $container = new Container($definitions); $streamFactory = $container->get(StreamFactoryInterface::class); @@ -84,7 +84,7 @@ public function testStreamFactoryInterface() public function testUriFactoryInterface() { - $definitions = (new HttpSoftDefinitions())->__invoke(); + $definitions = (new HttpSoftDefinitions())->getDefinitions(); $container = new Container($definitions); $uriFactory = $container->get(UriFactoryInterface::class); @@ -94,7 +94,7 @@ public function testUriFactoryInterface() public function testUploadedFileFactoryInterface() { - $definitions = (new HttpSoftDefinitions())->__invoke(); + $definitions = (new HttpSoftDefinitions())->getDefinitions(); $container = new Container($definitions); $uploadedFileFactory = $container->get(UploadedFileFactoryInterface::class); diff --git a/tests/Container/LaminasDiactorosDefinitionsTest.php b/tests/Container/LaminasDiactorosDefinitionsTest.php index 0f323b4c3..68d2f9fd1 100644 --- a/tests/Container/LaminasDiactorosDefinitionsTest.php +++ b/tests/Container/LaminasDiactorosDefinitionsTest.php @@ -23,14 +23,14 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; -use Slim\Container\LaminasDiactorosDefinitions; +use Slim\Container\Definition\LaminasDefinitions; use Slim\Interfaces\ServerRequestCreatorInterface; class LaminasDiactorosDefinitionsTest extends TestCase { public function testInvokeReturnsCorrectDefinitions() { - $definitions = (new LaminasDiactorosDefinitions())->__invoke(); + $definitions = (new LaminasDefinitions())->getDefinitions(); $container = new Container($definitions); $this->assertTrue($container->has(ServerRequestFactoryInterface::class)); @@ -43,7 +43,7 @@ public function testInvokeReturnsCorrectDefinitions() public function testServerRequestFactoryInterface() { - $definitions = (new LaminasDiactorosDefinitions())->__invoke(); + $definitions = (new LaminasDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class); @@ -53,7 +53,7 @@ public function testServerRequestFactoryInterface() public function testServerRequestCreatorInterface() { - $definitions = (new LaminasDiactorosDefinitions())->__invoke(); + $definitions = (new LaminasDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestCreator = $container->get(ServerRequestCreatorInterface::class); @@ -64,7 +64,7 @@ public function testServerRequestCreatorInterface() public function testResponseFactoryInterface() { - $definitions = (new LaminasDiactorosDefinitions())->__invoke(); + $definitions = (new LaminasDefinitions())->getDefinitions(); $container = new Container($definitions); $responseFactory = $container->get(ResponseFactoryInterface::class); @@ -74,7 +74,7 @@ public function testResponseFactoryInterface() public function testStreamFactoryInterface() { - $definitions = (new LaminasDiactorosDefinitions())->__invoke(); + $definitions = (new LaminasDefinitions())->getDefinitions(); $container = new Container($definitions); $streamFactory = $container->get(StreamFactoryInterface::class); @@ -84,7 +84,7 @@ public function testStreamFactoryInterface() public function testUriFactoryInterface() { - $definitions = (new LaminasDiactorosDefinitions())->__invoke(); + $definitions = (new LaminasDefinitions())->getDefinitions(); $container = new Container($definitions); $uriFactory = $container->get(UriFactoryInterface::class); @@ -94,7 +94,7 @@ public function testUriFactoryInterface() public function testUploadedFileFactoryInterface() { - $definitions = (new LaminasDiactorosDefinitions())->__invoke(); + $definitions = (new LaminasDefinitions())->getDefinitions(); $container = new Container($definitions); $uploadedFileFactory = $container->get(UploadedFileFactoryInterface::class); diff --git a/tests/Container/MiddlewareResolverTest.php b/tests/Container/MiddlewareResolverTest.php deleted file mode 100644 index 95f95b5d8..000000000 --- a/tests/Container/MiddlewareResolverTest.php +++ /dev/null @@ -1,126 +0,0 @@ -build(); - $container = $app->getContainer(); - $containerResolver = $container->get(ContainerResolverInterface::class); - - $middlewareResolver = new MiddlewareResolver( - $container, - $containerResolver - ); - - $middleware1 = $this->createCallableMiddleware(); - $middleware2 = $this->createMiddleware(); - - $queue = [$middleware1, $middleware2]; - - $resolvedStack = $middlewareResolver->resolveStack($queue); - - $this->assertCount(2, $resolvedStack); - $this->assertInstanceOf(MiddlewareInterface::class, $resolvedStack[0]); - $this->assertInstanceOf(MiddlewareInterface::class, $resolvedStack[1]); - - $request = $this->createMock(ServerRequestInterface::class); - $handler = $this->createMock(RequestHandlerInterface::class); - - $response = $resolvedStack[0]->process($request, $handler); - $this->assertInstanceOf(ResponseInterface::class, $response); - - $response = $resolvedStack[1]->process($request, $handler); - $this->assertInstanceOf(ResponseInterface::class, $response); - } - - public function testResolveMiddlewareWithValidMiddleware() - { - $builder = new AppBuilder(); - $app = $builder->build(); - $container = $app->getContainer(); - $containerResolver = $container->get(ContainerResolverInterface::class); - - $middlewareResolver = new MiddlewareResolver( - $container, - $containerResolver - ); - - $middleware = $this->createMiddleware(); - - $resolvedMiddleware = $middlewareResolver->resolveStack([$middleware]); - - $this->assertInstanceOf(MiddlewareInterface::class, $resolvedMiddleware[0]); - } - - public function testResolveStackWithException(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'A middleware must be an object or callable that implements "MiddlewareInterface".' - ); - - $builder = new AppBuilder(); - $app = $builder->build(); - $container = $app->getContainer(); - $containerResolver = $container->get(ContainerResolverInterface::class); - - $middlewareResolver = new MiddlewareResolver( - $container, - $containerResolver - ); - - $middlewareResolver->resolveStack([[null]]); - } - - private function createCallableMiddleware(): callable - { - $response = $this->createMock(ResponseInterface::class); - - return function () use ($response): ResponseInterface { - return $response; - }; - } - - private function createMiddleware(): MiddlewareInterface - { - $response = $this->createMock(ResponseInterface::class); - - return new class ($response) implements MiddlewareInterface { - private ResponseInterface $response; - - public function __construct(ResponseInterface $response) - { - $this->response = $response; - } - - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler, - ): ResponseInterface { - return $this->response; - } - }; - } -} diff --git a/tests/Container/NyholmDefinitionsTest.php b/tests/Container/NyholmDefinitionsTest.php index 63cc84b10..60c593c96 100644 --- a/tests/Container/NyholmDefinitionsTest.php +++ b/tests/Container/NyholmDefinitionsTest.php @@ -19,14 +19,14 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; -use Slim\Container\NyholmDefinitions; +use Slim\Container\Definition\NyholmDefinitions; use Slim\Interfaces\ServerRequestCreatorInterface; class NyholmDefinitionsTest extends TestCase { public function testInvokeReturnsCorrectDefinitions() { - $definitions = (new NyholmDefinitions())->__invoke(); + $definitions = (new NyholmDefinitions())->getDefinitions(); $container = new Container($definitions); $this->assertTrue($container->has(ServerRequestFactoryInterface::class)); @@ -39,7 +39,7 @@ public function testInvokeReturnsCorrectDefinitions() public function testServerRequestFactoryInterface() { - $definitions = (new NyholmDefinitions())->__invoke(); + $definitions = (new NyholmDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class); @@ -49,7 +49,7 @@ public function testServerRequestFactoryInterface() public function testServerRequestCreatorInterface() { - $definitions = (new NyholmDefinitions())->__invoke(); + $definitions = (new NyholmDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestCreator = $container->get(ServerRequestCreatorInterface::class); @@ -60,7 +60,7 @@ public function testServerRequestCreatorInterface() public function testResponseFactoryInterface() { - $definitions = (new NyholmDefinitions())->__invoke(); + $definitions = (new NyholmDefinitions())->getDefinitions(); $container = new Container($definitions); $responseFactory = $container->get(ResponseFactoryInterface::class); @@ -70,7 +70,7 @@ public function testResponseFactoryInterface() public function testStreamFactoryInterface() { - $definitions = (new NyholmDefinitions())->__invoke(); + $definitions = (new NyholmDefinitions())->getDefinitions(); $container = new Container($definitions); $streamFactory = $container->get(StreamFactoryInterface::class); @@ -80,7 +80,7 @@ public function testStreamFactoryInterface() public function testUriFactoryInterface() { - $definitions = (new NyholmDefinitions())->__invoke(); + $definitions = (new NyholmDefinitions())->getDefinitions(); $container = new Container($definitions); $uriFactory = $container->get(UriFactoryInterface::class); @@ -90,7 +90,7 @@ public function testUriFactoryInterface() public function testUploadedFileFactoryInterface() { - $definitions = (new NyholmDefinitions())->__invoke(); + $definitions = (new NyholmDefinitions())->getDefinitions(); $container = new Container($definitions); $uploadedFileFactory = $container->get(UploadedFileFactoryInterface::class); diff --git a/tests/Container/DefaultDefinitionsTest.php b/tests/Container/SlimDefinitionsTest.php similarity index 70% rename from tests/Container/DefaultDefinitionsTest.php rename to tests/Container/SlimDefinitionsTest.php index ca27d026f..af2f3114a 100644 --- a/tests/Container/DefaultDefinitionsTest.php +++ b/tests/Container/SlimDefinitionsTest.php @@ -25,28 +25,26 @@ use Psr\Http\Server\RequestHandlerInterface; use Psr\Log\LoggerInterface; use Slim\App; -use Slim\Builder\AppBuilder; -use Slim\Container\GuzzleDefinitions; -use Slim\Container\HttpDefinitions; -use Slim\Container\HttpSoftDefinitions; -use Slim\Container\LaminasDiactorosDefinitions; -use Slim\Container\NyholmDefinitions; -use Slim\Container\SlimHttpDefinitions; -use Slim\Container\SlimPsr7Definitions; +use Slim\Container\Definition\GuzzleDefinitions; +use Slim\Container\Definition\HttpDefinitions; +use Slim\Container\Definition\HttpSoftDefinitions; +use Slim\Container\Definition\LaminasDefinitions; +use Slim\Container\Definition\NyholmDefinitions; +use Slim\Container\Definition\SlimHttpDefinitions; +use Slim\Container\Definition\SlimPsr7Definitions; use Slim\Emitter\ResponseEmitter; +use Slim\Factory\AppFactory; use Slim\Interfaces\ContainerResolverInterface; use Slim\Interfaces\EmitterInterface; use Slim\Interfaces\RequestHandlerInvocationStrategyInterface; +use Slim\Middleware\RoutingMiddleware; use Slim\Psr7\Factory\ServerRequestFactory; -use Slim\RequestHandler\MiddlewareRequestHandler; -use Slim\Routing\Router; -use Slim\Routing\Strategies\RequestResponse; -final class DefaultDefinitionsTest extends TestCase +final class SlimDefinitionsTest extends TestCase { public function testApp(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $app = $container->get(App::class); $this->assertInstanceOf(App::class, $app); @@ -54,7 +52,7 @@ public function testApp(): void public function testContainerResolverInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $resolver = $container->get(ContainerResolverInterface::class); $this->assertInstanceOf(ContainerResolverInterface::class, $resolver); @@ -62,26 +60,25 @@ public function testContainerResolverInterface(): void public function testRequestHandlerInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $requestHandler = $container->get(RequestHandlerInterface::class); $this->assertInstanceOf(RequestHandlerInterface::class, $requestHandler); - $this->assertInstanceOf(MiddlewareRequestHandler::class, $requestHandler); } public function testServerRequestFactoryInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $requestFactory = $container->get(ServerRequestFactoryInterface::class); $this->assertInstanceOf(ServerRequestFactoryInterface::class, $requestFactory); } #[DataProvider('serverRequestFactoryDefinitionsProvider')] - public function testServerRequestFactoryInterfaceWithDefinitions(callable $definition, string $instanceOf): void + public function testServerRequestFactoryInterfaceWithDefinitions($definition, string $instanceOf): void { - $definitions = call_user_func(new HttpDefinitions()); - $definitions = array_merge($definitions, call_user_func($definition)); + $definitions = (new HttpDefinitions())->getDefinitions(); + $definitions = array_merge($definitions, (new $definition())->getDefinitions()); $container = new Container($definitions); $requestFactory = $container->get(ServerRequestFactoryInterface::class); @@ -95,7 +92,7 @@ public static function serverRequestFactoryDefinitionsProvider(): array return [ 'GuzzleDefinitions' => [new GuzzleDefinitions(), HttpFactory::class], 'HttpSoftDefinitions' => [new HttpSoftDefinitions(), HttpSoftServerRequestFactory::class], - 'LaminasDiactorosDefinitions' => [new LaminasDiactorosDefinitions(), LaminasServerRequestFactory::class], + 'LaminasDiactorosDefinitions' => [new LaminasDefinitions(), LaminasServerRequestFactory::class], 'NyholmDefinitions' => [new NyholmDefinitions(), Psr17Factory::class], 'SlimHttpDefinitions' => [new SlimHttpDefinitions(), ServerRequestFactoryInterface::class], 'SlimPsr7Definitions' => [new SlimPsr7Definitions(), ServerRequestFactory::class], @@ -104,7 +101,7 @@ public static function serverRequestFactoryDefinitionsProvider(): array public function testResponseFactoryInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $responseFactory = $container->get(ResponseFactoryInterface::class); $this->assertInstanceOf(ResponseFactoryInterface::class, $responseFactory); @@ -112,7 +109,7 @@ public function testResponseFactoryInterface(): void public function testStreamFactoryInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $streamFactory = $container->get(StreamFactoryInterface::class); $this->assertInstanceOf(StreamFactoryInterface::class, $streamFactory); @@ -120,7 +117,7 @@ public function testStreamFactoryInterface(): void public function testUriFactoryInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $uriFactory = $container->get(UriFactoryInterface::class); $this->assertInstanceOf(UriFactoryInterface::class, $uriFactory); @@ -128,7 +125,7 @@ public function testUriFactoryInterface(): void public function testUploadedFileFactoryInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $uploadedFileFactory = $container->get(UploadedFileFactoryInterface::class); $this->assertInstanceOf(UploadedFileFactoryInterface::class, $uploadedFileFactory); @@ -136,7 +133,7 @@ public function testUploadedFileFactoryInterface(): void public function testEmitterInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $emitter = $container->get(EmitterInterface::class); $this->assertInstanceOf(ResponseEmitter::class, $emitter); @@ -144,23 +141,23 @@ public function testEmitterInterface(): void public function testRouter(): void { - $container = (new AppBuilder())->build()->getContainer(); - $router = $container->get(Router::class); + $container = AppFactory::create()->getContainer(); + $router = $container->get(RoutingMiddleware::class); - $this->assertInstanceOf(Router::class, $router); + $this->assertInstanceOf(RoutingMiddleware::class, $router); } public function testRequestHandlerInvocationStrategyInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $invocationStrategy = $container->get(RequestHandlerInvocationStrategyInterface::class); - $this->assertInstanceOf(RequestResponse::class, $invocationStrategy); + $this->assertInstanceOf(RequestHandlerInvocationStrategyInterface::class, $invocationStrategy); } public function testLoggerInterface(): void { - $container = (new AppBuilder())->build()->getContainer(); + $container = AppFactory::create()->getContainer(); $logger = $container->get(LoggerInterface::class); $this->assertInstanceOf(LoggerInterface::class, $logger); diff --git a/tests/Container/SlimHttpDefinitionsTest.php b/tests/Container/SlimHttpDefinitionsTest.php index ad845c271..0189b229a 100644 --- a/tests/Container/SlimHttpDefinitionsTest.php +++ b/tests/Container/SlimHttpDefinitionsTest.php @@ -20,7 +20,7 @@ use Psr\Http\Message\UriFactoryInterface; use ReflectionClass; use RuntimeException; -use Slim\Container\SlimHttpDefinitions; +use Slim\Container\Definition\SlimHttpDefinitions; use Slim\Http\Factory\DecoratedResponseFactory; use Slim\Http\Factory\DecoratedUriFactory; use Slim\Interfaces\ServerRequestCreatorInterface; @@ -29,7 +29,7 @@ class SlimHttpDefinitionsTest extends TestCase { public function testInvokeReturnsCorrectDefinitions() { - $definitions = (new SlimHttpDefinitions())->__invoke(); + $definitions = (new SlimHttpDefinitions())->getDefinitions(); $container = new Container($definitions); $this->assertTrue($container->has(ServerRequestFactoryInterface::class)); @@ -42,7 +42,7 @@ public function testInvokeReturnsCorrectDefinitions() public function testServerRequestFactoryInterface() { - $definitions = (new SlimHttpDefinitions())->__invoke(); + $definitions = (new SlimHttpDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class); @@ -55,7 +55,7 @@ public function testServerRequestFactoryInterface() public function testServerRequestCreatorInterface() { - $definitions = (new SlimHttpDefinitions())->__invoke(); + $definitions = (new SlimHttpDefinitions())->getDefinitions(); $container = new Container($definitions); $serverRequestCreator = $container->get(ServerRequestCreatorInterface::class); @@ -66,7 +66,7 @@ public function testServerRequestCreatorInterface() public function testResponseFactoryInterface() { - $definitions = (new SlimHttpDefinitions())->__invoke(); + $definitions = (new SlimHttpDefinitions())->getDefinitions(); $container = new Container($definitions); $responseFactory = $container->get(ResponseFactoryInterface::class); @@ -80,7 +80,7 @@ public function testResponseFactoryInterface() public function testStreamFactoryInterface() { - $definitions = (new SlimHttpDefinitions())->__invoke(); + $definitions = (new SlimHttpDefinitions())->getDefinitions(); $container = new Container($definitions); $streamFactory = $container->get(StreamFactoryInterface::class); @@ -90,7 +90,7 @@ public function testStreamFactoryInterface() public function testUriFactoryInterface() { - $definitions = (new SlimHttpDefinitions())->__invoke(); + $definitions = (new SlimHttpDefinitions())->getDefinitions(); $container = new Container($definitions); $uriFactory = $container->get(UriFactoryInterface::class); @@ -104,7 +104,7 @@ public function testUriFactoryInterface() public function testUploadedFileFactoryInterface() { - $definitions = (new SlimHttpDefinitions())->__invoke(); + $definitions = (new SlimHttpDefinitions())->getDefinitions(); $container = new Container($definitions); $uploadedFileFactory = $container->get(UploadedFileFactoryInterface::class); @@ -122,10 +122,9 @@ public function testResponseFactoryInterfaceThrowsRuntimeExceptionWhenNoImplemen // Use reflection to inject the mock callable into the $classExists property $reflection = new ReflectionClass($definitions); $classExistsProperty = $reflection->getProperty('classExists'); - $classExistsProperty->setAccessible(true); - $classExistsProperty->setValue($definitions, fn () => false); + $classExistsProperty->setValue($definitions, fn() => false); - $container = new Container($definitions()); + $container = new Container($definitions->getDefinitions()); $container->get(ResponseFactoryInterface::class); } @@ -139,10 +138,9 @@ public function testStreamFactoryInterfaceThrowsRuntimeExceptionWhenNoImplementa // Use reflection to inject the mock callable into the $classExists property $reflection = new ReflectionClass($definitions); $classExistsProperty = $reflection->getProperty('classExists'); - $classExistsProperty->setAccessible(true); - $classExistsProperty->setValue($definitions, fn () => false); + $classExistsProperty->setValue($definitions, fn() => false); - $container = new Container($definitions()); + $container = new Container($definitions->getDefinitions()); $container->get(StreamFactoryInterface::class); } @@ -156,10 +154,9 @@ public function testUriFactoryInterfaceThrowsRuntimeExceptionWhenNoImplementatio // Use reflection to inject the mock callable into the $classExists property $reflection = new ReflectionClass($definitions); $classExistsProperty = $reflection->getProperty('classExists'); - $classExistsProperty->setAccessible(true); - $classExistsProperty->setValue($definitions, fn () => false); + $classExistsProperty->setValue($definitions, fn() => false); - $container = new Container($definitions()); + $container = new Container($definitions->getDefinitions()); $container->get(UriFactoryInterface::class); } @@ -173,10 +170,9 @@ public function testUploadedFileFactoryInterfaceThrowsRuntimeExceptionWhenNoImpl // Use reflection to inject the mock callable into the $classExists property $reflection = new ReflectionClass($definitions); $classExistsProperty = $reflection->getProperty('classExists'); - $classExistsProperty->setAccessible(true); - $classExistsProperty->setValue($definitions, fn () => false); + $classExistsProperty->setValue($definitions, fn() => false); - $container = new Container($definitions()); + $container = new Container($definitions->getDefinitions()); $container->get(UploadedFileFactoryInterface::class); } } diff --git a/tests/Container/SlimPsr7DefinitionsTest.php b/tests/Container/SlimPsr7DefinitionsTest.php index 4177b0f89..a702b5e06 100644 --- a/tests/Container/SlimPsr7DefinitionsTest.php +++ b/tests/Container/SlimPsr7DefinitionsTest.php @@ -18,7 +18,7 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UriFactoryInterface; -use Slim\Container\SlimPsr7Definitions; +use Slim\Container\Definition\SlimPsr7Definitions; use Slim\Interfaces\ServerRequestCreatorInterface; use Slim\Psr7\Factory\ResponseFactory; use Slim\Psr7\Factory\ServerRequestFactory; @@ -30,7 +30,7 @@ class SlimPsr7DefinitionsTest extends TestCase { public function testInvokeReturnsCorrectDefinitions() { - $definitions = (new SlimPsr7Definitions())->__invoke(); + $definitions = (new SlimPsr7Definitions())->getDefinitions(); $container = new Container($definitions); $this->assertTrue($container->has(ServerRequestFactoryInterface::class)); @@ -43,7 +43,7 @@ public function testInvokeReturnsCorrectDefinitions() public function testServerRequestFactoryInterface() { - $definitions = (new SlimPsr7Definitions())->__invoke(); + $definitions = (new SlimPsr7Definitions())->getDefinitions(); $container = new Container($definitions); $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class); @@ -53,7 +53,7 @@ public function testServerRequestFactoryInterface() public function testServerRequestCreatorInterface() { - $definitions = (new SlimPsr7Definitions())->__invoke(); + $definitions = (new SlimPsr7Definitions())->getDefinitions(); $container = new Container($definitions); $serverRequestCreator = $container->get(ServerRequestCreatorInterface::class); @@ -64,7 +64,7 @@ public function testServerRequestCreatorInterface() public function testResponseFactoryInterface() { - $definitions = (new SlimPsr7Definitions())->__invoke(); + $definitions = (new SlimPsr7Definitions())->getDefinitions(); $container = new Container($definitions); $responseFactory = $container->get(ResponseFactoryInterface::class); @@ -74,7 +74,7 @@ public function testResponseFactoryInterface() public function testStreamFactoryInterface() { - $definitions = (new SlimPsr7Definitions())->__invoke(); + $definitions = (new SlimPsr7Definitions())->getDefinitions(); $container = new Container($definitions); $streamFactory = $container->get(StreamFactoryInterface::class); @@ -83,7 +83,7 @@ public function testStreamFactoryInterface() public function testUriFactoryInterface() { - $definitions = (new SlimPsr7Definitions())->__invoke(); + $definitions = (new SlimPsr7Definitions())->getDefinitions(); $container = new Container($definitions); $uriFactory = $container->get(UriFactoryInterface::class); @@ -93,7 +93,7 @@ public function testUriFactoryInterface() public function testUploadedFileFactoryInterface() { - $definitions = (new SlimPsr7Definitions())->__invoke(); + $definitions = (new SlimPsr7Definitions())->getDefinitions(); $container = new Container($definitions); $uploadedFileFactory = $container->get(UploadedFileFactoryInterface::class); diff --git a/tests/Emitter/ResponseEmitterTest.php b/tests/Emitter/ResponseEmitterTest.php index 946e7dd29..989d43937 100644 --- a/tests/Emitter/ResponseEmitterTest.php +++ b/tests/Emitter/ResponseEmitterTest.php @@ -16,8 +16,8 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamFactoryInterface; use ReflectionClass; -use Slim\Builder\AppBuilder; use Slim\Emitter\ResponseEmitter; +use Slim\Factory\AppFactory; use Slim\Tests\Mocks\MockStream; use Slim\Tests\Mocks\SlowPokeStream; use Slim\Tests\Mocks\SmallChunksStream; @@ -57,7 +57,7 @@ public function tearDown(): void private function createResponse(int $statusCode = 200, string $reasonPhrase = ''): ResponseInterface { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); return $app->getContainer() ->get(ResponseFactoryInterface::class) @@ -77,8 +77,8 @@ public function testRespond(): void public function testRespondWithPaddedStreamFilterOutput(): void { - $builder = new AppBuilder(); - $streamFactory = $builder->build()->getContainer()->get(StreamFactoryInterface::class); + $app = AppFactory::create(); + $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); $availableFilter = stream_get_filters(); @@ -191,7 +191,7 @@ public function testResponseDoesNotReplacePreviouslySetSetCookieHeaders(): void public function testIsResponseEmptyWithNonEmptyBodyAndTriggeringStatusCode(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $body = $app->getContainer() ->get(StreamFactoryInterface::class) @@ -207,7 +207,7 @@ public function testIsResponseEmptyWithNonEmptyBodyAndTriggeringStatusCode(): vo public function testIsResponseEmptyDoesNotReadAllDataFromNonEmptySeekableResponse(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $body = $app->getContainer() ->get(StreamFactoryInterface::class) @@ -228,8 +228,8 @@ public function testIsResponseEmptyDoesNotReadAllDataFromNonEmptySeekableRespons public function testIsResponseEmptyDoesNotDrainNonSeekableResponseWithContent(): void { - $builder = new AppBuilder(); - $streamFactory = $builder->build()->getContainer()->get(StreamFactoryInterface::class); + $app = AppFactory::create(); + $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); $resource = popen('echo 12', 'r'); $body = $streamFactory->createStreamFromResource($resource); @@ -266,7 +266,7 @@ public function testIsResponseEmptyWithEmptyBody(): void public function testIsResponseEmptyWithZeroAsBody(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $body = $app->getContainer() ->get(StreamFactoryInterface::class) @@ -284,8 +284,8 @@ public function testIsResponseEmptyWithZeroAsBody(): void public function testWillHandleInvalidConnectionStatusWithADeterminateBody(): void { - $builder = new AppBuilder(); - $streamFactory = $builder->build()->getContainer()->get(StreamFactoryInterface::class); + $app = AppFactory::create(); + $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); $body = $streamFactory->createStreamFromResource(fopen('php://temp', 'r+')); $body->write('Hello!' . "\n"); @@ -310,8 +310,8 @@ public function testWillHandleInvalidConnectionStatusWithADeterminateBody(): voi public function testWillHandleInvalidConnectionStatusWithAnIndeterminateBody(): void { - $builder = new AppBuilder(); - $streamFactory = $builder->build()->getContainer()->get(StreamFactoryInterface::class); + $app = AppFactory::create(); + $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); $body = $streamFactory->createStreamFromResource(fopen('php://input', 'r+')); @@ -326,7 +326,6 @@ public function testWillHandleInvalidConnectionStatusWithAnIndeterminateBody(): $mirror = new ReflectionClass(ResponseEmitter::class); $emitBodyMethod = $mirror->getMethod('emitBody'); - $emitBodyMethod->setAccessible(true); $emitBodyMethod->invoke($responseEmitter, $response); $this->expectOutputString(''); diff --git a/tests/Exception/HttpExceptionTest.php b/tests/Exception/HttpExceptionTest.php index 4d5beedd4..4af13dc02 100644 --- a/tests/Exception/HttpExceptionTest.php +++ b/tests/Exception/HttpExceptionTest.php @@ -13,18 +13,18 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; +use Slim\Factory\AppFactory; use Slim\Tests\Traits\AppTestTrait; final class HttpExceptionTest extends TestCase { use AppTestTrait; - public function testHttpExceptionRequestReponseGetterSetters() + public function testHttpExceptionRequestResponseGetterSetters() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -37,7 +37,7 @@ public function testHttpExceptionRequestReponseGetterSetters() public function testHttpExceptionAttributeGettersSetters() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -53,7 +53,7 @@ public function testHttpExceptionAttributeGettersSetters() public function testHttpNotAllowedExceptionGetAllowedMethods() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) diff --git a/tests/Exception/HttpUnauthorizedExceptionTest.php b/tests/Exception/HttpUnauthorizedExceptionTest.php index 99bc3462e..2e37451cd 100644 --- a/tests/Exception/HttpUnauthorizedExceptionTest.php +++ b/tests/Exception/HttpUnauthorizedExceptionTest.php @@ -12,8 +12,8 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestFactoryInterface; -use Slim\Builder\AppBuilder; use Slim\Exception\HttpUnauthorizedException; +use Slim\Factory\AppFactory; use Slim\Tests\Traits\AppTestTrait; final class HttpUnauthorizedExceptionTest extends TestCase @@ -22,7 +22,7 @@ final class HttpUnauthorizedExceptionTest extends TestCase public function testHttpUnauthorizedException() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -35,7 +35,7 @@ public function testHttpUnauthorizedException() public function testHttpUnauthorizedExceptionWithMessage() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) diff --git a/tests/Factory/AppFactoryTest.php b/tests/Factory/AppFactoryTest.php new file mode 100644 index 000000000..5164c0fdc --- /dev/null +++ b/tests/Factory/AppFactoryTest.php @@ -0,0 +1,79 @@ +assertInstanceOf(App::class, AppFactory::create()); + } + + public function testSetContainerFactoryOverridesDefault(): void + { + $customFactory = $this->createMock(ContainerFactoryInterface::class); + + $mockApp = $this->createMock(App::class); + $mockContainer = $this->createMock(ContainerInterface::class); + $mockContainer->method('get')->with(App::class)->willReturn($mockApp); + $customFactory->method('createContainer')->willReturn($mockContainer); + + AppFactory::setContainerFactory($customFactory); + + $result = AppFactory::create(); + + $this->assertSame($mockApp, $result); + } + + public function testCreateFromContainerBuildsAppWithProvidedServices(): void + { + $container = $this->createMock(ContainerInterface::class); + $appMock = $this->createMock(App::class); + $serverRequestCreator = $this->createMock(ServerRequestCreatorInterface::class); + $handler = $this->createMock(RequestHandlerInterface::class); + $router = $this->createMock(RouterInterface::class); + $emitter = $this->createMock(EmitterInterface::class); + + // container->get(...) returns expected dependencies + $container->method('get')->willReturnCallback(function (string $id) use ( + $serverRequestCreator, + $handler, + $router, + $emitter, + $appMock + ) { + return match ($id) { + ServerRequestCreatorInterface::class => $serverRequestCreator, + RequestHandlerInterface::class => $handler, + RouterInterface::class => $router, + EmitterInterface::class => $emitter, + App::class => $appMock, + default => null, + }; + }); + + $app = AppFactory::createFromContainer($container); + + $this->assertInstanceOf(App::class, $app); + } +} diff --git a/tests/Middleware/BasePathMiddlewareTest.php b/tests/Middleware/BasePathMiddlewareTest.php index 5e86671c1..1865d341c 100644 --- a/tests/Middleware/BasePathMiddlewareTest.php +++ b/tests/Middleware/BasePathMiddlewareTest.php @@ -13,13 +13,10 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\App; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; +use Slim\Interfaces\RouterInterface; use Slim\Middleware\BasePathMiddleware; -use Slim\Middleware\EndpointMiddleware; -use Slim\Middleware\RoutingMiddleware; use Slim\Tests\Traits\AppTestTrait; final class BasePathMiddlewareTest extends TestCase @@ -28,24 +25,22 @@ final class BasePathMiddlewareTest extends TestCase public function testEmptyScriptName(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( + $definitions = [ BasePathMiddleware::class => function (ContainerInterface $container) { - $app = $container->get(App::class); + $router = $container->get(RouterInterface::class); - return new BasePathMiddleware($app, 'apache2handler'); + return new BasePathMiddleware($router, 'apache2handler'); }, - ] - ); - $app = $builder->build(); + ]; + $app = AppFactory::create($definitions); $app->add(BasePathMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function ($request, ResponseInterface $response) { - $basePath = $this->get(App::class)->getBasePath(); + /** @var ContainerInterface $this */ + $basePath = $this->get(RouterInterface::class)->getBasePath(); $response->getBody()->write('basePath: ' . $basePath); return $response; @@ -56,8 +51,8 @@ public function testEmptyScriptName(): void 'SCRIPT_NAME' => '', ]; - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/', $serverParams); $response = $app->handle($request); @@ -68,24 +63,21 @@ public function testEmptyScriptName(): void public function testScriptNameWithIndexPhp(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( + $definitions = [ BasePathMiddleware::class => function (ContainerInterface $container) { - $app = $container->get(App::class); + $router = $container->get(RouterInterface::class); - return new BasePathMiddleware($app, 'apache2handler'); + return new BasePathMiddleware($router, 'apache2handler'); }, - ] - ); - $app = $builder->build(); + ]; + $app = AppFactory::create($definitions); $app->add(BasePathMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function ($request, ResponseInterface $response) { - $basePath = $this->get(App::class)->getBasePath(); + $basePath = $this->get(RouterInterface::class)->getBasePath(); $response->getBody()->write('basePath: ' . $basePath); return $response; @@ -97,8 +89,8 @@ public function testScriptNameWithIndexPhp(): void 'SCRIPT_NAME' => '/index.php', ]; - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/', $serverParams); $response = $app->handle($request); @@ -109,24 +101,21 @@ public function testScriptNameWithIndexPhp(): void public function testScriptNameWithPublicIndexPhp(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( + $definitions = [ BasePathMiddleware::class => function (ContainerInterface $container) { - $app = $container->get(App::class); + $router = $container->get(RouterInterface::class); - return new BasePathMiddleware($app, 'apache2handler'); + return new BasePathMiddleware($router, 'apache2handler'); }, - ] - ); - $app = $builder->build(); + ]; + $app = AppFactory::create($definitions); $app->add(BasePathMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { - $basePath = $this->get(App::class)->getBasePath(); + $basePath = $this->get(RouterInterface::class)->getBasePath(); $response->getBody()->write('basePath: ' . $basePath); return $response; @@ -138,8 +127,8 @@ public function testScriptNameWithPublicIndexPhp(): void 'SCRIPT_NAME' => '/public/index.php', ]; - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/', $serverParams); $response = $app->handle($request); @@ -150,24 +139,21 @@ public function testScriptNameWithPublicIndexPhp(): void public function testSubDirectoryWithSlash(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( + $definitions = [ BasePathMiddleware::class => function (ContainerInterface $container) { - $app = $container->get(App::class); + $router = $container->get(RouterInterface::class); - return new BasePathMiddleware($app, 'apache2handler'); + return new BasePathMiddleware($router, 'apache2handler'); }, - ] - ); - $app = $builder->build(); + ]; + $app = AppFactory::create($definitions); $app->add(BasePathMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function ($request, ResponseInterface $response) { - $basePath = $this->get(App::class)->getBasePath(); + $basePath = $this->get(RouterInterface::class)->getBasePath(); $response->getBody()->write('basePath: ' . $basePath); return $response; @@ -177,8 +163,8 @@ public function testSubDirectoryWithSlash(): void 'REQUEST_URI' => '/slim-hello-world/', 'SCRIPT_NAME' => '/slim-hello-world/public/index.php', ]; - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/slim-hello-world/?key=value', $serverParams); $response = $app->handle($request); @@ -191,24 +177,21 @@ public function testSubDirectoryWithSlash(): void public function testSubDirectoryWithoutSlash(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( + $definitions = [ BasePathMiddleware::class => function (ContainerInterface $container) { - $app = $container->get(App::class); + $router = $container->get(RouterInterface::class); - return new BasePathMiddleware($app, 'apache2handler'); + return new BasePathMiddleware($router, 'apache2handler'); }, - ] - ); - $app = $builder->build(); + ]; + $app = AppFactory::create($definitions); $app->add(BasePathMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/foo', function ($request, ResponseInterface $response) { - $basePath = $this->get(App::class)->getBasePath(); + $basePath = $this->get(RouterInterface::class)->getBasePath(); $response->getBody()->write('basePath: ' . $basePath); return $response; @@ -219,8 +202,8 @@ public function testSubDirectoryWithoutSlash(): void 'SCRIPT_NAME' => '/slim-hello-world/public/index.php', ]; - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/slim-hello-world/foo?key=value', $serverParams); $response = $app->handle($request); @@ -233,24 +216,21 @@ public function testSubDirectoryWithoutSlash(): void public function testSubDirectoryWithFooPath(): void { - $builder = new AppBuilder(); - $builder->addDefinitions( - [ + $definitions + = [ BasePathMiddleware::class => function (ContainerInterface $container) { - $app = $container->get(App::class); + $router = $container->get(RouterInterface::class); - return new BasePathMiddleware($app, 'apache2handler'); + return new BasePathMiddleware($router, 'apache2handler'); }, - ] - ); - $app = $builder->build(); + ]; + $app = AppFactory::create($definitions); $app->add(BasePathMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/foo', function ($request, ResponseInterface $response) { - $basePath = $this->get(App::class)->getBasePath(); + $basePath = $this->get(RouterInterface::class)->getBasePath(); $response->getBody()->write('basePath: ' . $basePath); return $response; @@ -260,8 +240,8 @@ public function testSubDirectoryWithFooPath(): void 'REQUEST_URI' => '/slim-hello-world/foo', 'SCRIPT_NAME' => '/slim-hello-world/public/index.php', ]; - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/slim-hello-world/foo/?key=value', $serverParams); $response = $app->handle($request); diff --git a/tests/Middleware/ContentLengthMiddlewareTest.php b/tests/Middleware/ContentLengthMiddlewareTest.php index 4df444291..642e6bc6e 100644 --- a/tests/Middleware/ContentLengthMiddlewareTest.php +++ b/tests/Middleware/ContentLengthMiddlewareTest.php @@ -14,10 +14,8 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Middleware\ContentLengthMiddleware; -use Slim\Middleware\EndpointMiddleware; -use Slim\Middleware\RoutingMiddleware; use Slim\Tests\Traits\AppTestTrait; final class ContentLengthMiddlewareTest extends TestCase @@ -26,12 +24,10 @@ final class ContentLengthMiddlewareTest extends TestCase public function testAddsContentLength() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(new ContentLengthMiddleware()); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Body'); diff --git a/tests/Middleware/CorsMiddlewareTest.php b/tests/Middleware/CorsMiddlewareTest.php index b3e27cd65..dd034b3e2 100644 --- a/tests/Middleware/CorsMiddlewareTest.php +++ b/tests/Middleware/CorsMiddlewareTest.php @@ -6,21 +6,18 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestFactoryInterface; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Middleware\CorsMiddleware; -use Slim\Middleware\EndpointMiddleware; -use Slim\Middleware\RoutingMiddleware; class CorsMiddlewareTest extends TestCase { public function testDefaultConfiguration(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); // Add CORS middleware with default config $app->add(CorsMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Add test route $app->get('/test', function ($request, $response) { @@ -40,19 +37,18 @@ public function testDefaultConfiguration(): void $this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Headers')); $this->assertSame( 'GET, POST, PUT, PATCH, DELETE, OPTIONS', - $response->getHeaderLine('Access-Control-Allow-Methods') + $response->getHeaderLine('Access-Control-Allow-Methods'), ); $this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials')); } public function testDefaultConfigurationWithOrigin(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); // Add CORS middleware with default config $app->add(CorsMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Add test route $app->get('/test', function ($request, $response) { @@ -73,14 +69,14 @@ public function testDefaultConfigurationWithOrigin(): void $this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Headers')); $this->assertSame( 'GET, POST, PUT, PATCH, DELETE, OPTIONS', - $response->getHeaderLine('Access-Control-Allow-Methods') + $response->getHeaderLine('Access-Control-Allow-Methods'), ); $this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials')); } public function testPreflightRequest(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); // Configure CORS middleware $cors = $app->getContainer() @@ -90,8 +86,7 @@ public function testPreflightRequest(): void ->withMaxAge(3600); $app->add($cors); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Add test routes $app->get('/test', function ($request, $response) { @@ -120,7 +115,7 @@ public function testPreflightRequest(): void public function testDisallowedOrigin(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); // Configure CORS middleware $cors = $app->getContainer() @@ -129,8 +124,7 @@ public function testDisallowedOrigin(): void ->withAllowCredentials(true); $app->add($cors); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Add test route $app->get('/test', function ($request, $response) { @@ -153,7 +147,7 @@ public function testDisallowedOrigin(): void public function testCustomHeadersAndMethods(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); // Configure CORS middleware $cors = $app->getContainer() @@ -166,8 +160,7 @@ public function testCustomHeadersAndMethods(): void ->withCache(false); $app->add($cors); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Add test routes $app->get('/test', function ($request, $response) { @@ -201,7 +194,7 @@ public function testCustomHeadersAndMethods(): void public function testWildcardOriginWithCredentials(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); // Configure CORS middleware $cors = $app->getContainer() @@ -210,8 +203,7 @@ public function testWildcardOriginWithCredentials(): void ->withAllowCredentials(true); $app->add($cors); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Add test route $app->get('/test', function ($request, $response) { diff --git a/tests/Middleware/EndpointMiddlewareTest.php b/tests/Middleware/EndpointMiddlewareTest.php index e8ef4d0bb..84d494a27 100644 --- a/tests/Middleware/EndpointMiddlewareTest.php +++ b/tests/Middleware/EndpointMiddlewareTest.php @@ -14,20 +14,17 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; -use Slim\Middleware\EndpointMiddleware; -use Slim\Middleware\RoutingMiddleware; +use Slim\Factory\AppFactory; class EndpointMiddlewareTest extends TestCase { public function testProcessRouteFound(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Set up a route that will be found $app->get('/test', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -50,10 +47,9 @@ public function testProcessRouteNotFound(): void { $this->expectException(HttpNotFoundException::class); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -66,10 +62,9 @@ public function testProcessMethodNotAllowed(): void { $this->expectException(HttpMethodNotAllowedException::class); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Set up a route with POST method only $app->post('/test', function (ServerRequestInterface $request, ResponseInterface $response) { diff --git a/tests/Middleware/ErrorExceptionMiddlewareTest.php b/tests/Middleware/ErrorExceptionMiddlewareTest.php index 3935d9176..ea13d57f7 100644 --- a/tests/Middleware/ErrorExceptionMiddlewareTest.php +++ b/tests/Middleware/ErrorExceptionMiddlewareTest.php @@ -17,10 +17,8 @@ use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\Builder\AppBuilder; -use Slim\Middleware\EndpointMiddleware; +use Slim\Factory\AppFactory; use Slim\Middleware\ErrorExceptionMiddleware; -use Slim\Middleware\RoutingMiddleware; final class ErrorExceptionMiddlewareTest extends TestCase { @@ -42,7 +40,7 @@ public function testProcessHandlesError(): void error_reporting(E_USER_WARNING); // Instantiate the middleware with a custom error level - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $middleware = $app ->getContainer() ->get(ErrorExceptionMiddleware::class); @@ -55,15 +53,13 @@ public function testProcessHandlesErrorSilent(): void { error_reporting(E_USER_ERROR); - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $middleware = $app ->getContainer() ->get(ErrorExceptionMiddleware::class); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function ($request, $response) { trigger_error('Test warning', E_USER_WARNING); @@ -96,7 +92,7 @@ public function testProcessHandlesException(): void }); // Instantiate the middleware - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $middleware = $app->getContainer()->get(ErrorExceptionMiddleware::class); // Invoke the middleware process method @@ -114,7 +110,7 @@ public function testProcessReturnsResponse(): void ->method('handle') ->willReturn($response); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $middleware = $app->getContainer()->get(ErrorExceptionMiddleware::class); // Invoke the middleware process method and assert the response is returned diff --git a/tests/Middleware/ExceptionLoggingMiddlewareTest.php b/tests/Middleware/ExceptionLoggingMiddlewareTest.php index 53b6297ee..b25dad501 100644 --- a/tests/Middleware/ExceptionLoggingMiddlewareTest.php +++ b/tests/Middleware/ExceptionLoggingMiddlewareTest.php @@ -18,11 +18,9 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LogLevel; use RuntimeException; -use Slim\Builder\AppBuilder; -use Slim\Middleware\EndpointMiddleware; +use Slim\Factory\AppFactory; use Slim\Middleware\ErrorExceptionMiddleware; use Slim\Middleware\ExceptionLoggingMiddleware; -use Slim\Middleware\RoutingMiddleware; use Slim\Tests\Logging\TestLogger; class ExceptionLoggingMiddlewareTest extends TestCase @@ -31,15 +29,14 @@ public function testErrorExceptionIsLogged(): void { $this->expectException(ErrorException::class); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $logger = new TestLogger(); $middleware = new ExceptionLoggingMiddleware($logger); $app->add($middleware->withLogErrorDetails(true)); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Set up a route that throws an ErrorException $app->get('/error', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -68,15 +65,14 @@ public function testThrowableIsLogged(): void // Expect the RuntimeException to be thrown $this->expectException(RuntimeException::class); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $logger = new TestLogger(); $middleware = new ExceptionLoggingMiddleware($logger); $app->add($middleware->withLogErrorDetails(true)); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Set up a route that throws a generic Throwable $app->get('/throwable', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -109,7 +105,7 @@ public function testUserLevelErrorIsLogged(): void { $this->expectException(ErrorException::class); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); error_reporting(E_ALL); $logger = new TestLogger(); @@ -118,8 +114,7 @@ public function testUserLevelErrorIsLogged(): void $middleware = new ExceptionLoggingMiddleware($logger); $app->add($middleware->withLogErrorDetails(true)); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/error', function () { trigger_error('This is an error', E_USER_ERROR); diff --git a/tests/Middleware/FormBodyParserMiddlewareTest.php b/tests/Middleware/FormBodyParserMiddlewareTest.php new file mode 100644 index 000000000..0c79baeb9 --- /dev/null +++ b/tests/Middleware/FormBodyParserMiddlewareTest.php @@ -0,0 +1,108 @@ +createStream($body); + + $request = (new ServerRequestFactory()) + ->createServerRequest('POST', '/') + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withBody($stream); + + $middleware = new FormBodyParserMiddleware(); + $response = $middleware->process( + $request, + new class implements RequestHandlerInterface { + public function handle(ServerRequestInterface $request): ResponseInterface + { + $parsed = $request->getParsedBody(); + $response = new Response(); + $response->getBody()->write($parsed['foo'] . ',' . $parsed['baz']); + + return $response; + } + }, + ); + + $this->assertSame('bar,qux', (string)$response->getBody()); + } + + public function testSkipsParsingForNonFormContentType(): void + { + $stream = (new StreamFactory())->createStream('foo=bar&baz=qux'); + + $request = (new ServerRequestFactory()) + ->createServerRequest('POST', '/') + ->withHeader('Content-Type', 'text/plain') + ->withBody($stream); + + $middleware = new FormBodyParserMiddleware(); + $response = $middleware->process( + $request, + new class implements RequestHandlerInterface { + public function handle( + ServerRequestInterface $request, + ): ResponseInterface { + $parsed = $request->getParsedBody(); + $response = new Response(); + $response->getBody()->write($parsed === null ? 'no-parse' : 'parsed'); + + return $response; + } + }, + ); + + $this->assertSame('no-parse', (string)$response->getBody()); + } + + public function testSkipsParsingForEmptyBody(): void + { + $stream = (new StreamFactory())->createStream(''); + + $request = (new ServerRequestFactory()) + ->createServerRequest('POST', '/') + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withBody($stream); + + $middleware = new FormBodyParserMiddleware(); + $response = $middleware->process( + $request, + new class implements RequestHandlerInterface { + public function handle( + ServerRequestInterface $request, + ): ResponseInterface { + $parsed = $request->getParsedBody(); + $response = new Response(); + $response->getBody()->write(json_encode($parsed)); + + return $response; + } + }, + ); + + // empty + $this->assertSame('[]', (string)$response->getBody()); + } +} diff --git a/tests/Middleware/FormUrlEncodedBodyParserMiddlewareTest.php b/tests/Middleware/FormUrlEncodedBodyParserMiddlewareTest.php deleted file mode 100644 index f74259403..000000000 --- a/tests/Middleware/FormUrlEncodedBodyParserMiddlewareTest.php +++ /dev/null @@ -1,100 +0,0 @@ -createStream($body); - - $request = (new ServerRequestFactory()) - ->createServerRequest('POST', '/') - ->withHeader('Content-Type', 'application/x-www-form-urlencoded') - ->withBody($stream); - - $middleware = new FormUrlEncodedBodyParserMiddleware(); - $response = $middleware->process($request, new class implements RequestHandlerInterface { - public function handle( - ServerRequestInterface $request - ): ResponseInterface { - $parsed = $request->getParsedBody(); - $response = new Response(); - $response->getBody()->write($parsed['foo'] . ',' . $parsed['baz']); - - return $response; - } - }); - - $this->assertSame('bar,qux', (string)$response->getBody()); - } - - public function testSkipsParsingForNonFormContentType(): void - { - $stream = (new StreamFactory())->createStream('foo=bar&baz=qux'); - - $request = (new ServerRequestFactory()) - ->createServerRequest('POST', '/') - ->withHeader('Content-Type', 'text/plain') - ->withBody($stream); - - $middleware = new FormUrlEncodedBodyParserMiddleware(); - $response = $middleware->process($request, new class implements RequestHandlerInterface { - public function handle( - ServerRequestInterface $request - ): ResponseInterface { - $parsed = $request->getParsedBody(); - $response = new Response(); - $response->getBody()->write($parsed === null ? 'no-parse' : 'parsed'); - - return $response; - } - }); - - $this->assertSame('no-parse', (string)$response->getBody()); - } - - public function testSkipsParsingForEmptyBody(): void - { - $stream = (new StreamFactory())->createStream(''); - - $request = (new ServerRequestFactory()) - ->createServerRequest('POST', '/') - ->withHeader('Content-Type', 'application/x-www-form-urlencoded') - ->withBody($stream); - - $middleware = new FormUrlEncodedBodyParserMiddleware(); - $response = $middleware->process($request, new class implements RequestHandlerInterface { - public function handle( - ServerRequestInterface $request - ): ResponseInterface { - $parsed = $request->getParsedBody(); - $response = new Response(); - $response->getBody()->write(json_encode($parsed)); - - return $response; - } - }); - - // empty - $this->assertSame('[]', (string)$response->getBody()); - } -} diff --git a/tests/Middleware/HeadMethodMiddlewareTest.php b/tests/Middleware/HeadMethodMiddlewareTest.php index e1d232a40..ecd97bcca 100644 --- a/tests/Middleware/HeadMethodMiddlewareTest.php +++ b/tests/Middleware/HeadMethodMiddlewareTest.php @@ -14,20 +14,17 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; -use Slim\Middleware\EndpointMiddleware; +use Slim\Factory\AppFactory; use Slim\Middleware\HeadMethodMiddleware; -use Slim\Middleware\RoutingMiddleware; class HeadMethodMiddlewareTest extends TestCase { public function testHeadRequestResponseBodyIsEmpty(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $app->add(HeadMethodMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Set up a route that returns a non-empty body $app->get('/test', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -48,11 +45,10 @@ public function testHeadRequestResponseBodyIsEmpty(): void public function testGetRequestResponseBodyIsUnchanged(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $app->add(HeadMethodMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Set up a route that returns a non-empty body $app->get('/test', function (ServerRequestInterface $request, ResponseInterface $response) { diff --git a/tests/Middleware/MethodOverrideMiddlewareTest.php b/tests/Middleware/MethodOverrideMiddlewareTest.php index 8c54fcc87..d9a0bb6f2 100644 --- a/tests/Middleware/MethodOverrideMiddlewareTest.php +++ b/tests/Middleware/MethodOverrideMiddlewareTest.php @@ -16,10 +16,8 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\Builder\AppBuilder; -use Slim\Middleware\EndpointMiddleware; +use Slim\Factory\AppFactory; use Slim\Middleware\MethodOverrideMiddleware; -use Slim\Middleware\RoutingMiddleware; use Slim\Tests\Traits\AppTestTrait; final class MethodOverrideMiddlewareTest extends TestCase @@ -28,8 +26,7 @@ final class MethodOverrideMiddlewareTest extends TestCase public function testHeader() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = (function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -41,8 +38,7 @@ public function testHeader() $app->add($methodOverrideMiddleware); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->put('/', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Hello World'); @@ -62,8 +58,7 @@ public function testHeader() public function testBodyParam() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = (function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -75,8 +70,7 @@ public function testBodyParam() $app->add($methodOverrideMiddleware); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->put('/', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Hello World'); @@ -96,8 +90,7 @@ public function testBodyParam() public function testHeaderPreferred() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = (function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -109,8 +102,7 @@ public function testHeaderPreferred() $app->add($methodOverrideMiddleware); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->delete('/', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Hello World'); @@ -131,8 +123,7 @@ public function testHeaderPreferred() public function testNoOverride() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = (function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -144,8 +135,7 @@ public function testNoOverride() $app->add($methodOverrideMiddleware); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->post('/', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Hello World'); @@ -164,8 +154,7 @@ public function testNoOverride() public function testNoOverrideRewindEofBodyStream() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = (function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -177,8 +166,7 @@ public function testNoOverrideRewindEofBodyStream() $app->add($methodOverrideMiddleware); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->post('/', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Hello World'); diff --git a/tests/Middleware/OutputBufferingMiddlewareTest.php b/tests/Middleware/OutputBufferingMiddlewareTest.php index bf2e4cda0..54978e7c5 100644 --- a/tests/Middleware/OutputBufferingMiddlewareTest.php +++ b/tests/Middleware/OutputBufferingMiddlewareTest.php @@ -18,10 +18,8 @@ use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; -use Slim\Builder\AppBuilder; -use Slim\Middleware\EndpointMiddleware; +use Slim\Factory\AppFactory; use Slim\Middleware\OutputBufferingMiddleware; -use Slim\Middleware\RoutingMiddleware; use Slim\Tests\Traits\AppTestTrait; use function ob_get_contents; @@ -34,8 +32,8 @@ public function testStyleCustomValid() { $this->expectNotToPerformAssertions(); - $builder = new AppBuilder(); - $streamFactory = $builder->build()->getContainer()->get(StreamFactoryInterface::class); + $app = AppFactory::create(); + $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); new OutputBufferingMiddleware($streamFactory, OutputBufferingMiddleware::APPEND); new OutputBufferingMiddleware($streamFactory, OutputBufferingMiddleware::PREPEND); @@ -45,16 +43,15 @@ public function testStyleCustomInvalid() { $this->expectException(InvalidArgumentException::class); - $builder = new AppBuilder(); - $streamFactory = $builder->build()->getContainer()->get(StreamFactoryInterface::class); + $app = AppFactory::create(); + $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); new OutputBufferingMiddleware($streamFactory, 'foo'); } public function testAppend() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $responseFactory = $app->getContainer()->get(ResponseFactoryInterface::class); $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); @@ -71,8 +68,7 @@ public function testAppend() }; $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -85,8 +81,7 @@ public function testAppend() public function testPrepend() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $responseFactory = $app->getContainer()->get(ResponseFactoryInterface::class); $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); @@ -103,8 +98,7 @@ public function testPrepend() $app->add($outputBufferingMiddleware); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { return $response; @@ -121,8 +115,7 @@ public function testPrepend() public function testOutputBufferIsCleanedWhenThrowableIsCaught() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $streamFactory = $app->getContainer()->get(StreamFactoryInterface::class); @@ -137,8 +130,7 @@ public function testOutputBufferIsCleanedWhenThrowableIsCaught() $app->add($outputBufferingMiddleware); $app->add($middleware); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) { return $response; diff --git a/tests/Middleware/ResponseFactoryMiddlewareTest.php b/tests/Middleware/ResponseFactoryMiddlewareTest.php index e29845723..35a85a8f3 100644 --- a/tests/Middleware/ResponseFactoryMiddlewareTest.php +++ b/tests/Middleware/ResponseFactoryMiddlewareTest.php @@ -14,14 +14,14 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Middleware\ResponseFactoryMiddleware; class ResponseFactoryMiddlewareTest extends TestCase { public function testWithoutEndpointMiddleware(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $app->add(function ($request, $handler) { $response = $handler->handle($request); @@ -43,7 +43,7 @@ public function testWithoutEndpointMiddleware(): void public function testProcessReturnsResponseFromFactory(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $responseFactory = $app->getContainer()->get(ResponseFactoryInterface::class); diff --git a/tests/Middleware/RoutingArgumentsMiddlewareTest.php b/tests/Middleware/RoutingArgumentsMiddlewareTest.php index 27d5d8f9e..5b9c1634b 100644 --- a/tests/Middleware/RoutingArgumentsMiddlewareTest.php +++ b/tests/Middleware/RoutingArgumentsMiddlewareTest.php @@ -14,7 +14,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Middleware\EndpointMiddleware; use Slim\Middleware\RoutingArgumentsMiddleware; use Slim\Middleware\RoutingMiddleware; @@ -23,7 +23,7 @@ class RoutingArgumentsMiddlewareTest extends TestCase { public function testProcessAddsRoutingArgumentsToRequestAttributes(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $app->add(RoutingMiddleware::class); $app->add(RoutingArgumentsMiddleware::class); @@ -50,11 +50,10 @@ public function testProcessAddsRoutingArgumentsToRequestAttributes(): void public function testProcessNoRoutingArguments(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $app->add(RoutingArgumentsMiddleware::class); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Define a route without any arguments $app->get('/no-args', function (ServerRequestInterface $request, ResponseInterface $response) { diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index 9bcd28b4f..fbfe32924 100644 --- a/tests/Middleware/RoutingMiddlewareTest.php +++ b/tests/Middleware/RoutingMiddlewareTest.php @@ -13,24 +13,26 @@ use FastRoute\Dispatcher; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\Builder\AppBuilder; use Slim\Exception\HttpMethodNotAllowedException; use Slim\Exception\HttpNotFoundException; +use Slim\Factory\AppFactory; use Slim\Interfaces\UrlGeneratorInterface; use Slim\Middleware\EndpointMiddleware; +use Slim\Middleware\JsonBodyParserMiddleware; use Slim\Middleware\RoutingMiddleware; use Slim\Routing\RouteContext; use Slim\Routing\RoutingResults; +use Slim\Tests\Traits\AppTestTrait; final class RoutingMiddlewareTest extends TestCase { + use AppTestTrait; + public function testRouteIsStoredOnSuccessfulMatch() { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -44,10 +46,6 @@ public function testRouteIsStoredOnSuccessfulMatch() $test->assertNotNull($route); // routeParser is available - $urlGenerator = $request->getAttribute(RouteContext::URL_GENERATOR); - $test->assertNotNull($urlGenerator); - $test->assertInstanceOf(UrlGeneratorInterface::class, $urlGenerator); - return $handler->handle($request); }; @@ -55,8 +53,8 @@ public function testRouteIsStoredOnSuccessfulMatch() $app->add($middleware); $app->add(EndpointMiddleware::class); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', 'https://example.com:443/hello/foo'); $app->get('/hello/foo', function (ServerRequestInterface $request, ResponseInterface $response) { @@ -70,12 +68,32 @@ public function testRouteIsStoredOnSuccessfulMatch() $this->assertSame('Hello World', (string)$response->getBody()); } + public function testRouteWithMiddlewareAsString() + { + $app = AppFactory::create(); + + $app->addRoutingMiddleware(); + + $request = $this + ->getServerRequestFactory($app) + ->createServerRequest('GET', 'https://example.com:443/hello/foo'); + + $app->get('/hello/foo', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('Hello World'); + + return $response; + })->add(JsonBodyParserMiddleware::class); + + $response = $app->handle($request); + + $this->assertSame('Hello World', (string)$response->getBody()); + } + public function testRouteIsNotStoredOnMethodNotAllowed() { $this->expectException(HttpMethodNotAllowedException::class); - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -94,11 +112,6 @@ public function testRouteIsNotStoredOnMethodNotAllowed() $route = $routingResults->getRoute(); $test->assertNull($route); - // routeParser is available - $urlParser = $request->getAttribute(RouteContext::URL_GENERATOR); - $test->assertNotNull($urlParser); - $test->assertInstanceOf(UrlGeneratorInterface::class, $urlParser); - // Re-throw to keep the behavior consistent throw $exception; } @@ -114,8 +127,8 @@ public function testRouteIsNotStoredOnMethodNotAllowed() return $response; }); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/hello/foo'); $app->handle($request); @@ -125,8 +138,7 @@ public function testRouteIsNotStoredOnNotFound() { $this->expectException(HttpNotFoundException::class); - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $test = $this; $middleware = function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($test) { @@ -145,11 +157,6 @@ public function testRouteIsNotStoredOnNotFound() $route = $routingResults->getRoute(); $test->assertNull($route); - // routeParser is available - $urlGenerator = $request->getAttribute(RouteContext::URL_GENERATOR); - $test->assertNotNull($urlGenerator); - $test->assertInstanceOf(UrlGeneratorInterface::class, $urlGenerator); - // Re-throw to keep the behavior consistent throw $exception; } @@ -161,8 +168,8 @@ public function testRouteIsNotStoredOnNotFound() // No route is defined for '/hello/foo' - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/hello/foo'); $app->handle($request); @@ -170,15 +177,14 @@ public function testRouteIsNotStoredOnNotFound() public function testRoutingWithBasePath(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $app->setBasePath('/api'); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Define a route with arguments $app->get('/users/{id}', function (ServerRequestInterface $request, ResponseInterface $response, $args) { - $urlGenerator = RouteContext::fromRequest($request)->getUrlGenerator(); + $urlGenerator = $this->get(UrlGeneratorInterface::class); $url = $urlGenerator->relativeUrlFor('user.show', ['id' => $args['id']], ['page' => 2]); $response = $response->withHeader('X-relativeUrlFor', $url); @@ -189,8 +195,8 @@ public function testRoutingWithBasePath(): void return $response; })->setName('user.show'); - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) + $request = $this + ->getServerRequestFactory($app) ->createServerRequest('GET', '/api/users/123'); $response = $app->handle($request); diff --git a/tests/Mocks/MockMiddlewareWithoutInterface.php b/tests/Mocks/MockMiddlewareWithoutInterface.php deleted file mode 100644 index 2288b8bbc..000000000 --- a/tests/Mocks/MockMiddlewareWithoutInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -stream = $body; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; @@ -112,7 +113,7 @@ public function __construct($body = '') $this->uri = $this->getMetadata('uri'); } else { throw new InvalidArgumentException( - 'First argument to Stream::create() must be a string, resource or StreamInterface.' + 'First argument to Stream::create() must be a string, resource or StreamInterface.', ); } } @@ -164,7 +165,7 @@ public function detach() public function getSize(): ?int { - if (null !== $this->size) { + if ($this->size !== null) { return $this->size; } @@ -215,7 +216,7 @@ public function seek($offset, $whence = SEEK_SET): void if (fseek($this->stream, $offset, $whence) === -1) { throw new RuntimeException( 'Unable to seek to stream position ' - . $offset . ' with whence ' . var_export($whence, true) + . $offset . ' with whence ' . var_export($whence, true), ); } } @@ -279,7 +280,7 @@ public function getMetadata($key = null) return $key ? null : []; } - if (null === $key) { + if ($key === null) { return stream_get_meta_data($this->stream); } diff --git a/tests/Mocks/SlowPokeStream.php b/tests/Mocks/SlowPokeStream.php index ed57b6489..38bc13b7f 100644 --- a/tests/Mocks/SlowPokeStream.php +++ b/tests/Mocks/SlowPokeStream.php @@ -12,6 +12,7 @@ use Exception; use Psr\Http\Message\StreamInterface; +use Stringable; use function min; use function str_repeat; @@ -19,15 +20,12 @@ use const SEEK_SET; -class SlowPokeStream implements StreamInterface +class SlowPokeStream implements Stringable, StreamInterface { - public const CHUNK_SIZE = 1; - public const SIZE = 500; + private const CHUNK_SIZE = 1; + private const SIZE = 500; - /** - * @var int - */ - private $amountToRead; + private int $amountToRead; public function __construct() { diff --git a/tests/Mocks/SmallChunksStream.php b/tests/Mocks/SmallChunksStream.php index 7b36fac31..e0bf5a31d 100644 --- a/tests/Mocks/SmallChunksStream.php +++ b/tests/Mocks/SmallChunksStream.php @@ -12,21 +12,19 @@ use Exception; use Psr\Http\Message\StreamInterface; +use Stringable; use function min; use function str_repeat; use const SEEK_SET; -class SmallChunksStream implements StreamInterface +class SmallChunksStream implements Stringable, StreamInterface { public const CHUNK_SIZE = 10; public const SIZE = 40; - /** - * @var int - */ - private $amountToRead; + private int $amountToRead; public function __construct() { diff --git a/tests/RequestHandler/MiddlewareRequestHandlerTest.php b/tests/RequestHandler/MiddlewareRequestHandlerTest.php index 0d19721fe..14522d41d 100644 --- a/tests/RequestHandler/MiddlewareRequestHandlerTest.php +++ b/tests/RequestHandler/MiddlewareRequestHandlerTest.php @@ -11,39 +11,38 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; -use Slim\Builder\AppBuilder; -use Slim\Enums\MiddlewareOrder; +use Slim\Factory\AppFactory; use Slim\Middleware\ResponseFactoryMiddleware; -use Slim\RequestHandler\MiddlewareRequestHandler; +use Slim\Routing\Router; final class MiddlewareRequestHandlerTest extends TestCase { public function testHandleWithFunctionMiddlewareStack() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $middleware = [ + $app->add( function ($req, $handler) { $response = $handler->handle($req); return $response->withHeader('X-Middleware-1', 'Processed-1'); }, - function ($req, $handler) { - $response = $handler->handle($req); + ); - return $response->withHeader('X-Middleware-2', 'Processed-2'); - }, - ResponseFactoryMiddleware::class, - ]; + $app->add(function ($req, $handler) { + $response = $handler->handle($req); - $request = $request->withAttribute(MiddlewareRequestHandler::MIDDLEWARE, $middleware); + return $response->withHeader('X-Middleware-2', 'Processed-2'); + }); + + $app->add(ResponseFactoryMiddleware::class); $handler = $app->getContainer() - ->get(MiddlewareRequestHandler::class); + ->get(Router::class); $response = $handler->handle($request); @@ -54,18 +53,16 @@ function ($req, $handler) { public function testHandleWithoutMiddlewareStack() { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No middleware found. Add a response factory middleware.'); + $this->expectExceptionMessage('The middleware pipeline is empty.'); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $request = $request->withAttribute(MiddlewareRequestHandler::MIDDLEWARE, []); - $handler = $app->getContainer() - ->get(MiddlewareRequestHandler::class); + ->get(Router::class); $response = $handler->handle($request); @@ -74,127 +71,76 @@ public function testHandleWithoutMiddlewareStack() public function testHandleWithClassMiddlewareStack() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $middleware = []; - $middleware[] = new class implements MiddlewareInterface { - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler, - ): ResponseInterface { - $response = $handler->handle($request); - - return $response->withHeader('X-Middleware-1', 'Processed-1'); - } - }; - - $middleware[] = ResponseFactoryMiddleware::class; - - $request = $request->withAttribute(MiddlewareRequestHandler::MIDDLEWARE, $middleware); - - $handler = $app->getContainer() - ->get(MiddlewareRequestHandler::class); - - $response = $handler->handle($request); - - $this->assertSame('Processed-1', $response->getHeaderLine('X-Middleware-1')); - } - - public function testHandleWithNoMiddlewareAttribute() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No middleware found. Add a response factory middleware.'); + $app->add( + new class implements MiddlewareInterface { + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ): ResponseInterface { + $response = $handler->handle($request); - $app = (new AppBuilder())->build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); + return $response->withHeader('X-Middleware-1', 'Processed-1'); + } + }, + ); - $request = $request->withoutAttribute(MiddlewareRequestHandler::MIDDLEWARE); + $app->add(ResponseFactoryMiddleware::class); $handler = $app->getContainer() - ->get(MiddlewareRequestHandler::class); + ->get(Router::class); $response = $handler->handle($request); $this->assertSame('Processed-1', $response->getHeaderLine('X-Middleware-1')); } - public function testHandleWithInvalidMiddleware() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'A middleware must be an object or callable that implements "MiddlewareInterface".' - ); - - $app = (new AppBuilder())->build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $middleware = []; - - // invalid middleware - $middleware[] = []; - - $middleware[] = ResponseFactoryMiddleware::class; - - $request = $request->withAttribute(MiddlewareRequestHandler::MIDDLEWARE, $middleware); - - $handler = $app->getContainer() - ->get(MiddlewareRequestHandler::class); - - $handler->handle($request); - } - public function testHandleWithFifoMiddlewareStack() { - $builder = new AppBuilder(); + $app = AppFactory::create(); // $builder->setMiddlewareOrder(MiddlewareOrder::FIFO); - $app = $builder->build(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $middleware = []; - - $middleware[] = new class implements MiddlewareInterface { - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler, - ): ResponseInterface { - $response = $handler->handle($request); - $response->getBody()->write('2'); - - return $response; - } - }; - - $middleware[] = new class implements MiddlewareInterface { - public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler, - ): ResponseInterface { - $response = $handler->handle($request); - $response->getBody()->write('1'); - - return $response; - } - }; + $app->add( + new class implements MiddlewareInterface { + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ): ResponseInterface { + $response = $handler->handle($request); + $response->getBody()->write('2'); + + return $response; + } + }, + ); - $middleware[] = ResponseFactoryMiddleware::class; + $app->add( + new class implements MiddlewareInterface { + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ): ResponseInterface { + $response = $handler->handle($request); + $response->getBody()->write('1'); + + return $response; + } + }, + ); - $request = $request->withAttribute(MiddlewareRequestHandler::MIDDLEWARE, $middleware); + $app->add(ResponseFactoryMiddleware::class); $handler = $app->getContainer() - ->get(MiddlewareRequestHandler::class); + ->get(Router::class); $response = $handler->handle($request); diff --git a/tests/RequestHandler/RunnerTest.php b/tests/RequestHandler/RunnerTest.php index c51150070..be42c2fc7 100644 --- a/tests/RequestHandler/RunnerTest.php +++ b/tests/RequestHandler/RunnerTest.php @@ -4,6 +4,7 @@ namespace Slim\Tests\RequestHandler; +use DI\NotFoundException; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; @@ -12,29 +13,31 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; -use Slim\Builder\AppBuilder; -use Slim\RequestHandler\Runner; +use Slim\Factory\AppFactory; +use Slim\Routing\PipelineRunner; use stdClass; final class RunnerTest extends TestCase { - public function testHandleWithMiddlewareInterface() + public function testHandleWithMiddlewareInterface(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $request = $app->getContainer() + $request = $app + ->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/') ->withHeader('X-Test', 'Modified'); - $response = $app->getContainer() + $response = $app + ->getContainer() ->get(ResponseFactoryInterface::class) ->createResponse(); $middleware = new class implements MiddlewareInterface { public function process( ServerRequestInterface $request, - RequestHandlerInterface $handler + RequestHandlerInterface $handler, ): ResponseInterface { $response = $handler->handle($request); @@ -42,14 +45,15 @@ public function process( } }; - $runner = new Runner( - [ + $runner = $app + ->getContainer() + ->get(PipelineRunner::class) + ->withPipeline([ $middleware, function () use ($response) { return $response->withHeader('X-Result', 'Success'); }, - ] - ); + ]); $result = $runner->handle($request); @@ -57,15 +61,17 @@ function () use ($response) { $this->assertSame('Success', $result->getHeaderLine('X-Result')); } - public function testHandleWithRequestHandlerInterface() + public function testHandleWithRequestHandlerInterface(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $request = $app->getContainer() + $request = $app + ->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $response = $app->getContainer() + $response = $app + ->getContainer() ->get(ResponseFactoryInterface::class) ->createResponse(); @@ -83,78 +89,105 @@ public function handle(ServerRequestInterface $request): ResponseInterface } }; - $runner = new Runner([$handler]); + $runner = $app + ->getContainer() + ->get(PipelineRunner::class) + ->withPipeline([$handler]); $result = $runner->handle($request); $this->assertSame('Handled', $result->getHeaderLine('X-Handler')); } - public function testHandleWithCallableMiddleware() + public function testHandleWithCallableMiddleware(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $request = $app->getContainer() + $request = $app + ->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $response = $app->getContainer() + $response = $app + ->getContainer() ->get(ResponseFactoryInterface::class) ->createResponse(); - $runner = new Runner([ - function (ServerRequestInterface $req, RequestHandlerInterface $handler) use ($response) { - return $response->withHeader('X-Callable', 'Called'); - }, - ]); + $runner = $app + ->getContainer() + ->get(PipelineRunner::class) + ->withPipeline([ + function (ServerRequestInterface $req, RequestHandlerInterface $handler) use ($response) { + return $response->withHeader('X-Callable', 'Called'); + }, + ]); $result = $runner->handle($request); $this->assertSame('Called', $result->getHeaderLine('X-Callable')); } - public function testHandleWithEmptyQueueThrowsException() + public function testHandleWithEmptyQueueThrowsException(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No middleware found. Add a response factory middleware.'); + $this->expectExceptionMessage('The middleware pipeline is empty.'); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $request = $app->getContainer() + $request = $app + ->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $runner = new Runner([]); + $runner = $app + ->getContainer() + ->get(PipelineRunner::class) + ->withPipeline([ + + ]); + $runner->handle($request); } - public function testHandleWithInvalidObjectMiddlewareThrowsException() + public function testHandleWithInvalidObjectMiddlewareThrowsException(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Invalid middleware queue entry "object"'); + $this->expectExceptionMessage( + 'Invalid pipeline entry of type "stdClass". Expected one of: callable, Psr\Http\Server\MiddlewareInterface, or Psr\Http\Server\RequestHandlerInterface.', + ); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $request = $app->getContainer() + $request = $app + ->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $runner = new Runner([new stdClass()]); + $runner = $app + ->getContainer() + ->get(PipelineRunner::class) + ->withPipeline([new stdClass()]); + $runner->handle($request); } - public function testHandleWithInvalidMiddlewareStringThrowsException() + public function testHandleWithInvalidMiddlewareStringThrowsException(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Invalid middleware queue entry "foo"'); + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage("No entry or class found for 'foo'"); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $request = $app->getContainer() + $request = $app + ->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $runner = new Runner(['foo']); + $runner = $app + ->getContainer() + ->get(PipelineRunner::class) + ->withPipeline(['foo']); + $runner->handle($request); } } diff --git a/tests/Routing/RouteContextTest.php b/tests/Routing/RouteContextTest.php index 1d560da99..dec989701 100644 --- a/tests/Routing/RouteContextTest.php +++ b/tests/Routing/RouteContextTest.php @@ -13,11 +13,10 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestFactoryInterface; use RuntimeException; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Routing\Route; use Slim\Routing\RouteContext; use Slim\Routing\RoutingResults; -use Slim\Routing\UrlGenerator; class RouteContextTest extends TestCase { @@ -27,71 +26,39 @@ class RouteContextTest extends TestCase */ public function testFromRequestCreatesInstanceWithValidAttributes(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $routingResults = new RoutingResults(200, null, 'GET', '/test', []); $basePath = '/base-path'; $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults) ->withAttribute(RouteContext::BASE_PATH, $basePath); $routeContext = RouteContext::fromRequest($request); - $this->assertInstanceOf(RouteContext::class, $routeContext); - $this->assertSame($urlGenerator, $routeContext->getUrlGenerator()); $this->assertSame($routingResults, $routeContext->getRoutingResults()); $this->assertSame($basePath, $routeContext->getBasePath()); } - /** - * Tests that an exception is thrown when attempting to create a RouteContext - * without a URL generator attribute set in the request. - */ - public function testFromRequestThrowsExceptionIfUrlGeneratorIsMissing(): void - { - $app = (new AppBuilder())->build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $routingResults = new RoutingResults(200, null, 'GET', '/test', []); - - $request = $request - ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'Cannot create RouteContext before routing has been completed. Add UrlGeneratorMiddleware to fix this.' - ); - - RouteContext::fromRequest($request); - } - /** * Tests that an exception is thrown when attempting to create a RouteContext * without routing results attribute set in the request. */ public function testFromRequestThrowsExceptionIfRoutingResultsAreMissing(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator); + ->withAttribute(RouteContext::BASE_PATH, '/base-path'); $this->expectException(RuntimeException::class); $this->expectExceptionMessage( @@ -101,49 +68,21 @@ public function testFromRequestThrowsExceptionIfRoutingResultsAreMissing(): void RouteContext::fromRequest($request); } - /** - * Tests that the URL generator instance returned by getUrlGenerator matches - * the one originally provided in the request attributes. - */ - public function testGetUrlGeneratorReturnsCorrectInstance(): void - { - $app = (new AppBuilder())->build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - - $routingResults = new RoutingResults(200, null, 'GET', '/test', []); - - $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) - ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); - - $routeContext = RouteContext::fromRequest($request); - - $this->assertSame($urlGenerator, $routeContext->getUrlGenerator()); - } - /** * Tests that the RoutingResults instance returned by getRoutingResults matches * the one originally provided in the request attributes. */ public function testGetRoutingResultsReturnsCorrectInstance(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $routingResults = new RoutingResults(200, null, 'GET', '/test', []); $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); $routeContext = RouteContext::fromRequest($request); @@ -157,19 +96,16 @@ public function testGetRoutingResultsReturnsCorrectInstance(): void */ public function testGetBasePathReturnsCorrectValue(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $routingResults = new RoutingResults(200, null, 'GET', '/test', []); $basePath = '/base-path'; $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults) ->withAttribute(RouteContext::BASE_PATH, $basePath); @@ -184,18 +120,15 @@ public function testGetBasePathReturnsCorrectValue(): void */ public function testGetBasePathReturnsNullIfNotSet(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $routingResults = new RoutingResults(200, null, 'GET', '/test', []); $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); $routeContext = RouteContext::fromRequest($request); @@ -208,21 +141,17 @@ public function testGetBasePathReturnsNullIfNotSet(): void */ public function testGetRouteReturnsCorrectInstance(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - // Create a route for testing - $route = $app->get('/test', function () { - })->setName('test-route'); + $route = $app->get('/test', function () {})->setName('test-route'); $routingResults = new RoutingResults(200, $route, 'GET', '/test', []); $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); $routeContext = RouteContext::fromRequest($request); @@ -237,18 +166,15 @@ public function testGetRouteReturnsCorrectInstance(): void */ public function testGetRouteReturnsNullWhenNoRouteMatched(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $routingResults = new RoutingResults(404, null, 'GET', '/not-found', []); $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); $routeContext = RouteContext::fromRequest($request); @@ -261,19 +187,16 @@ public function testGetRouteReturnsNullWhenNoRouteMatched(): void */ public function testGetArgumentsReturnsCorrectValues(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $arguments = ['id' => '123', 'name' => 'test']; $routingResults = new RoutingResults(200, null, 'GET', '/test', $arguments); $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); $routeContext = RouteContext::fromRequest($request); @@ -286,19 +209,16 @@ public function testGetArgumentsReturnsCorrectValues(): void */ public function testGetArgumentReturnsCorrectValue(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $arguments = ['id' => '123', 'name' => 'test']; $routingResults = new RoutingResults(200, null, 'GET', '/test', $arguments); $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); $routeContext = RouteContext::fromRequest($request); @@ -312,19 +232,16 @@ public function testGetArgumentReturnsCorrectValue(): void */ public function testGetArgumentReturnsNullForNonExistentKey(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) ->createServerRequest('GET', '/'); - $urlGenerator = $app->getContainer()->get(UrlGenerator::class); - $arguments = ['id' => '123']; $routingResults = new RoutingResults(200, null, 'GET', '/test', $arguments); $request = $request - ->withAttribute(RouteContext::URL_GENERATOR, $urlGenerator) ->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); $routeContext = RouteContext::fromRequest($request); diff --git a/tests/Routing/RouteGroupTest.php b/tests/Routing/RouteGroupTest.php index 9861e476f..fafb89874 100644 --- a/tests/Routing/RouteGroupTest.php +++ b/tests/Routing/RouteGroupTest.php @@ -11,7 +11,7 @@ namespace Slim\Tests\Routing; use PHPUnit\Framework\TestCase; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Routing\Route; use Slim\Routing\RouteGroup; use Slim\Routing\Router; @@ -21,10 +21,9 @@ class RouteGroupTest extends TestCase public function testConstructorInitializesPropertiesCorrectly(): void { $router = $this->createRouter(); - $callback = function () { - }; + $callback = function () {}; $prefix = '/test'; - $routeGroup = new RouteGroup($prefix, $callback, $router); + $routeGroup = new RouteGroup($prefix, $callback, $router->getRouteCollector()); $this->assertSame('/test', $routeGroup->getPrefix()); $this->assertSame($callback, $routeGroup->getRouteGroup() === null ? $callback : null); @@ -35,12 +34,10 @@ public function testConstructorInitializesPropertiesCorrectly(): void public function testConstructorWithParentGroup(): void { $router = $this->createRouter(); - $parentGroupCallback = function () { - }; - $childGroupCallback = function () { - }; - $parentGroup = new RouteGroup('/parent', $parentGroupCallback, $router); - $childGroup = new RouteGroup('/child', $childGroupCallback, $router, $parentGroup); + $parentGroupCallback = function () {}; + $childGroupCallback = function () {}; + $parentGroup = new RouteGroup('/parent', $parentGroupCallback, $router->getRouteCollector()); + $childGroup = new RouteGroup('/child', $childGroupCallback, $router->getRouteCollector(), $parentGroup); $this->assertSame('/child', $childGroup->getPrefix()); $this->assertSame($parentGroup, $childGroup->getRouteGroup()); @@ -53,7 +50,7 @@ public function testInvokeExecutesCallback(): void $callback = function () use (&$called) { $called = true; }; - $routeGroup = new RouteGroup('/test', $callback, $router); + $routeGroup = new RouteGroup('/test', $callback, $router->getRouteCollector()); $routeGroup(); $this->assertTrue($called); @@ -62,9 +59,8 @@ public function testInvokeExecutesCallback(): void public function testMapCreatesAndRegistersRoute(): void { $router = $this->createRouter(); - $callback = function () { - }; - $routeGroup = new RouteGroup('/test', $callback, $router); + $callback = function () {}; + $routeGroup = new RouteGroup('/test', $callback, $router->getRouteCollector()); $route = $routeGroup->map(['GET'], '/foo', 'handler'); $this->assertInstanceOf(Route::class, $route); @@ -75,9 +71,8 @@ public function testMapCreatesAndRegistersRoute(): void public function testMapCreatesAndRegistersRouteWithEmptyRoute(): void { $router = $this->createRouter(); - $callback = function () { - }; - $routeGroup = new RouteGroup('/test', $callback, $router); + $callback = function () {}; + $routeGroup = new RouteGroup('/test', $callback, $router->getRouteCollector()); $route = $routeGroup->map(['GET'], '', 'handler'); $this->assertInstanceOf(Route::class, $route); @@ -88,11 +83,10 @@ public function testMapCreatesAndRegistersRouteWithEmptyRoute(): void public function testMapCreatesAndRegistersRouteWithSlashRoute(): void { $router = $this->createRouter(); - $callback = function () { - }; - $routeGroup = new RouteGroup('/test', $callback, $router); + $callback = function () {}; + $routeGroup = new RouteGroup('/test', $callback, $router->getRouteCollector()); - $route = $routeGroup->map(['GET'], '/', 'handler'); + $route = $routeGroup->map(['GET'], '', 'handler'); $this->assertInstanceOf(Route::class, $route); $this->assertSame(['GET'], $route->getMethods()); $this->assertSame('/test', $route->getPattern()); @@ -101,12 +95,10 @@ public function testMapCreatesAndRegistersRouteWithSlashRoute(): void public function testGroupCreatesAndRegistersNestedRouteGroup(): void { $router = $this->createRouter(); - $callback = function () { - }; - $routeGroup = new RouteGroup('/test', $callback, $router); + $callback = function () {}; + $routeGroup = new RouteGroup('/test', $callback, $router->getRouteCollector()); - $nestedGroup = $routeGroup->group('/nested', function () { - }); + $nestedGroup = $routeGroup->group('/nested', function () {}); $this->assertInstanceOf(RouteGroup::class, $nestedGroup); $this->assertSame('/test/nested', $nestedGroup->getPrefix()); @@ -115,7 +107,7 @@ public function testGroupCreatesAndRegistersNestedRouteGroup(): void private function createRouter(): Router { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); return $app->getContainer()->get(Router::class); } diff --git a/tests/Routing/RouteTest.php b/tests/Routing/RouteTest.php index 0c855a1e9..4889e19b3 100644 --- a/tests/Routing/RouteTest.php +++ b/tests/Routing/RouteTest.php @@ -15,7 +15,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Routing\Route; use Slim\Routing\RouteGroup; use Slim\Routing\Router; @@ -50,7 +50,7 @@ public function testGetMiddlewareStackWithoutGroup(): void $middleware2 = $this->createMiddleware(); $route->add($middleware1)->add($middleware2); - $middlewareStack = $route->getMiddlewareStack(); + $middlewareStack = $route->getMiddleware(); $this->assertCount(2, $middlewareStack); $this->assertSame([$middleware1, $middleware2], $middlewareStack); @@ -75,7 +75,7 @@ public function testGetMiddlewareStackWithGroup(): void // Create a RouteGroup with middleware $routeGroup = new RouteGroup('/group', function (RouteGroup $group) use ($groupMiddleware) { $group->add($groupMiddleware); - }, $router); + }, $router->getRouteCollector()); $route = new Route($methods, $pattern, $handler, $routeGroup); @@ -85,13 +85,13 @@ public function testGetMiddlewareStackWithGroup(): void // Simulate fastroute group collector $routeGroup(); - $middlewareStack = $route->getMiddlewareStack(); + $middlewareStack = $route->getMiddleware(); // The stack should contain route middlewares followed by group middleware $this->assertCount(2, $middlewareStack); $this->assertSame([$middleware1, $middleware2], $middlewareStack); - $groupMiddlewares = $routeGroup->getMiddlewareStack(); + $groupMiddlewares = $routeGroup->getMiddleware(); $this->assertCount(1, $groupMiddlewares); $this->assertSame($groupMiddleware, $groupMiddlewares[0]); } @@ -151,7 +151,7 @@ public function process( private function createRouter(): Router { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); return $app->getContainer()->get(Router::class); } diff --git a/tests/Routing/RouterTest.php b/tests/Routing/RouterTest.php index 855febb94..9c980308a 100644 --- a/tests/Routing/RouterTest.php +++ b/tests/Routing/RouterTest.php @@ -16,10 +16,8 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Middleware\ContentLengthMiddleware; -use Slim\Middleware\EndpointMiddleware; -use Slim\Middleware\RoutingMiddleware; use Slim\Routing\Route; use Slim\Routing\RouteGroup; use Slim\Routing\Router; @@ -29,7 +27,7 @@ class RouterTest extends TestCase #[DataProvider('httpMethodProvider')] public function testHttpMethods(string $methodName, string $path, callable $handler, array $expectedMethods): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); // Define a route using the HTTP method from the data provider @@ -45,7 +43,7 @@ public function testHttpMethods(string $methodName, string $path, callable $hand $this->assertContains( $expectedMethod, $route->getMethods(), - "Method $expectedMethod not found in route methods" + "Method $expectedMethod not found in route methods", ); } } @@ -114,7 +112,7 @@ function () { public function testMapCreatesRoute(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $methods = ['GET']; @@ -133,7 +131,7 @@ public function testMapCreatesRoute(): void public function testGroupCreatesRouteGroup(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $pattern = '/group'; @@ -149,7 +147,7 @@ public function testGroupCreatesRouteGroup(): void public function testGetRouteCollectorReturnsCollector(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $collector = $router->getRouteCollector(); @@ -158,7 +156,7 @@ public function testGetRouteCollectorReturnsCollector(): void public function testSetAndGetBasePath(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $basePath = '/base-path'; @@ -169,7 +167,7 @@ public function testSetAndGetBasePath(): void public function testMapWithBasePath(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $basePath = '/base-path'; @@ -191,12 +189,10 @@ public function testMapWithBasePath(): void public function testOptionsAnyCorsRoute(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(new ContentLengthMiddleware()); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->options('/{routes:.+}', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Body'); @@ -214,12 +210,10 @@ public function testOptionsAnyCorsRoute(): void public function testOptionsAnyRoute(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(new ContentLengthMiddleware()); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->options('/{any:.*}', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('Body'); @@ -244,12 +238,10 @@ public function testOptionsAnyRoute(): void public function testRouteWithParameters(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(new ContentLengthMiddleware()); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get('/books/{id}', function (ServerRequestInterface $request, ResponseInterface $response, array $args) { $response->getBody()->write(json_encode($args)); @@ -267,12 +259,10 @@ public function testRouteWithParameters(): void public function testCustomRoute(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(new ContentLengthMiddleware()); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->map(['GET', 'POST'], '/books', function (ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write('OK'); @@ -297,12 +287,10 @@ public function testCustomRoute(): void public function testRegexRoute(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(new ContentLengthMiddleware()); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get( '/users/{id:[0-9]+}', @@ -310,7 +298,7 @@ function (ServerRequestInterface $request, ResponseInterface $response, array $a $response->getBody()->write($args['id']); return $response; - } + }, ); $request = $app->getContainer() @@ -323,12 +311,10 @@ function (ServerRequestInterface $request, ResponseInterface $response, array $a public function testMultipleOptionalParameters(): void { - $builder = new AppBuilder(); - $app = $builder->build(); + $app = AppFactory::create(); $app->add(new ContentLengthMiddleware()); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); $app->get( '/news[/{year}[/{month}]]', @@ -336,7 +322,7 @@ function (ServerRequestInterface $request, ResponseInterface $response, array $a $response->getBody()->write(json_encode($args)); return $response; - } + }, ); $request = $app->getContainer() diff --git a/tests/Routing/RoutingResultsTest.php b/tests/Routing/RoutingResultsTest.php index ceafaed92..b0147d9c0 100644 --- a/tests/Routing/RoutingResultsTest.php +++ b/tests/Routing/RoutingResultsTest.php @@ -14,9 +14,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; -use Slim\Middleware\EndpointMiddleware; -use Slim\Middleware\RoutingMiddleware; +use Slim\Factory\AppFactory; use Slim\Routing\Route; use Slim\Routing\RouteContext; use Slim\Routing\RoutingResults; @@ -25,8 +23,7 @@ class RoutingResultsTest extends TestCase { public function testConstructAndGetters(): void { - $route = new Route(['GET'], '/test', function () { - }); + $route = new Route(['GET'], '/test', function () {}); // Define test parameters $status = RoutingResults::FOUND; @@ -42,7 +39,7 @@ public function testConstructAndGetters(): void $method, $uri, $routeArguments, - $allowedMethods + $allowedMethods, ); $this->assertSame($status, $routingResults->getRouteStatus()); @@ -71,7 +68,7 @@ public function testGettersWithNullRoute(): void $method, $uri, $routeArguments, - $allowedMethods + $allowedMethods, ); $this->assertSame($status, $routingResults->getRouteStatus()); @@ -84,10 +81,9 @@ public function testGettersWithNullRoute(): void public function testRoutingArgumentsFromRouteContext(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); - $app->add(RoutingMiddleware::class); - $app->add(EndpointMiddleware::class); + $app->addRoutingMiddleware(); // Define a route with arguments $app->get('/test/{id}', function (ServerRequestInterface $request, ResponseInterface $response) { diff --git a/tests/Routing/UrlGeneratorTest.php b/tests/Routing/UrlGeneratorTest.php index 5234376a6..44ae6ce06 100644 --- a/tests/Routing/UrlGeneratorTest.php +++ b/tests/Routing/UrlGeneratorTest.php @@ -13,7 +13,7 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Psr\Http\Message\UriInterface; -use Slim\Builder\AppBuilder; +use Slim\Factory\AppFactory; use Slim\Routing\Router; use Slim\Routing\UrlGenerator; use UnexpectedValueException; @@ -22,7 +22,7 @@ class UrlGeneratorTest extends TestCase { public function testRelativeUrlFor(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $urlGenerator = new UrlGenerator($router); @@ -37,7 +37,7 @@ public function testRelativeUrlFor(): void public function testUrlFor(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $urlGenerator = new UrlGenerator($router); @@ -51,7 +51,7 @@ public function testUrlFor(): void public function testFullUrlFor(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $urlGenerator = new UrlGenerator($router); @@ -73,7 +73,7 @@ public function testGetNamedRouteThrowsExceptionIfRouteNotFound(): void { $this->expectException(UnexpectedValueException::class); - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $urlGenerator = new UrlGenerator($router); @@ -83,7 +83,7 @@ public function testGetNamedRouteThrowsExceptionIfRouteNotFound(): void public function testGetSegmentsThrowsExceptionIfDataIsMissing(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $urlGenerator = new UrlGenerator($router); @@ -99,7 +99,7 @@ public function testGetSegmentsThrowsExceptionIfDataIsMissing(): void public function testRelativeUrlForWithBasePath(): void { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $router = $app->getContainer()->get(Router::class); $router->setBasePath('/api'); $urlGenerator = new UrlGenerator($router); diff --git a/tests/Strategies/RequestHandlerTest.php b/tests/Strategies/RequestHandlerTest.php index a65136e06..f4f66b22e 100644 --- a/tests/Strategies/RequestHandlerTest.php +++ b/tests/Strategies/RequestHandlerTest.php @@ -14,8 +14,8 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; -use Slim\Routing\Strategies\RequestHandler; +use Slim\Factory\AppFactory; +use Slim\Strategy\RequestHandler; use Slim\Tests\Traits\AppTestTrait; final class RequestHandlerTest extends TestCase @@ -24,7 +24,7 @@ final class RequestHandlerTest extends TestCase public function testInvokeReturnsResponse() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -47,7 +47,7 @@ public function testInvokeReturnsResponse() public function testInvokeWithModifiedRequest() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) diff --git a/tests/Strategies/RequestResponseArgsTest.php b/tests/Strategies/RequestResponseArgsTest.php index dd699df97..373c55b10 100644 --- a/tests/Strategies/RequestResponseArgsTest.php +++ b/tests/Strategies/RequestResponseArgsTest.php @@ -13,8 +13,8 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestFactoryInterface; -use Slim\Builder\AppBuilder; -use Slim\Routing\Strategies\RequestResponseArgs; +use Slim\Factory\AppFactory; +use Slim\Strategy\RequestResponseArgs; use Slim\Tests\Traits\AppTestTrait; final class RequestResponseArgsTest extends TestCase @@ -23,7 +23,7 @@ final class RequestResponseArgsTest extends TestCase public function testInvokeWithArguments() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -53,7 +53,7 @@ public function testInvokeWithArguments() public function testInvokeWithSingleArgument() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -80,7 +80,7 @@ public function testInvokeWithSingleArgument() public function testInvokeWithoutArguments() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) diff --git a/tests/Strategies/RequestResponseNamedArgsTest.php b/tests/Strategies/RequestResponseNamedArgsTest.php index 7507167e9..551eb4373 100644 --- a/tests/Strategies/RequestResponseNamedArgsTest.php +++ b/tests/Strategies/RequestResponseNamedArgsTest.php @@ -15,8 +15,8 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Slim\Builder\AppBuilder; -use Slim\Routing\Strategies\RequestResponseNamedArgs; +use Slim\Factory\AppFactory; +use Slim\Strategy\RequestResponseNamedArgs; use Slim\Tests\Traits\AppTestTrait; final class RequestResponseNamedArgsTest extends TestCase @@ -28,7 +28,7 @@ final class RequestResponseNamedArgsTest extends TestCase public function testCallingWithEmptyArguments() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -50,7 +50,7 @@ public function testCallingWithEmptyArguments() public function testCallingWithKnownArguments() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -79,7 +79,7 @@ public function testCallingWithKnownArguments() public function testCallingWithOptionalArguments() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -107,7 +107,7 @@ public function testCallingWithOptionalArguments() public function testCallingWithUnknownAndVariadic() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -135,7 +135,7 @@ public function testCallingWithUnknownAndVariadic() public function testCallingWithMixedKnownAndUnknownParametersAndVariadic() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) diff --git a/tests/Strategies/RequestResponseTest.php b/tests/Strategies/RequestResponseTest.php index a72b24618..1b4e89b48 100644 --- a/tests/Strategies/RequestResponseTest.php +++ b/tests/Strategies/RequestResponseTest.php @@ -13,8 +13,8 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestFactoryInterface; -use Slim\Builder\AppBuilder; -use Slim\Routing\Strategies\RequestResponse; +use Slim\Factory\AppFactory; +use Slim\Strategy\RequestResponse; use Slim\Tests\Traits\AppTestTrait; final class RequestResponseTest extends TestCase @@ -23,7 +23,7 @@ final class RequestResponseTest extends TestCase public function testInvokeWithArguments() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) @@ -54,7 +54,7 @@ public function testInvokeWithArguments() public function testInvokeWithoutArguments() { - $app = (new AppBuilder())->build(); + $app = AppFactory::create(); $request = $app->getContainer() ->get(ServerRequestFactoryInterface::class) diff --git a/tests/Strategies/RequestResponseTypedArgsTest.php b/tests/Strategies/RequestResponseTypedArgsTest.php deleted file mode 100644 index 365b0b06f..000000000 --- a/tests/Strategies/RequestResponseTypedArgsTest.php +++ /dev/null @@ -1,143 +0,0 @@ -build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $response = $app->getContainer() - ->get(ResponseFactoryInterface::class) - ->createResponse(); - - $invocationStrategy = $app->getContainer()->get(RequestResponseTypedArgs::class); - - $args = [ - 'name' => 'John', - ]; - - $callback = function ($request, $response, $name) { - return $response->withHeader('X-Foo', $name); - }; - - $response = $invocationStrategy($callback, $request, $response, $args); - - $this->assertSame('John', $response->getHeaderLine('X-Foo')); - } - - // https://github.com/slimphp/Slim/issues/3198 - public function testCallingWithKnownArguments() - { - $app = (new AppBuilder())->build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $response = $app->getContainer() - ->get(ResponseFactoryInterface::class) - ->createResponse(); - - $invocationStrategy = $app->getContainer()->get(RequestResponseTypedArgs::class); - - $args = [ - 'name' => 'John', - 'id' => '123', - ]; - - $callback = function ($request, $response, string $name, int $id) { - $this->assertSame('John', $name); - $this->assertSame(123, $id); - - return $response->withHeader('X-Foo', $name); - }; - - $response = $invocationStrategy($callback, $request, $response, $args); - - $this->assertSame('John', $response->getHeaderLine('X-Foo')); - } - - public function testCallingWithOptionalArguments() - { - $app = (new AppBuilder())->build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $response = $app->getContainer() - ->get(ResponseFactoryInterface::class) - ->createResponse(); - - $invocationStrategy = $app->getContainer()->get(RequestResponseTypedArgs::class); - - $args = [ - 'name' => 'world', - ]; - - $callback = function ($request, $response, string $greeting = 'Hello', string $name = 'Rob') { - $this->assertSame('Hello', $greeting); - $this->assertSame('world', $name); - - return $response->withHeader('X-Foo', $name); - }; - - $response = $invocationStrategy($callback, $request, $response, $args); - - $this->assertSame('world', $response->getHeaderLine('X-Foo')); - } - - public function testCallingWithNotEnoughParameters() - { - $app = (new AppBuilder())->build(); - - $request = $app->getContainer() - ->get(ServerRequestFactoryInterface::class) - ->createServerRequest('GET', '/'); - - $response = $app->getContainer() - ->get(ResponseFactoryInterface::class) - ->createResponse(); - - $invocationStrategy = $app->getContainer()->get(RequestResponseTypedArgs::class); - - $this->expectException(NotEnoughParametersException::class); - $args = [ - 'greeting' => 'hello', - ]; - - $callback = function ($request, $response, $arguments) use ($args) { - $this->assertSame($args, $arguments); - - return $response->withHeader('X-Foo', $args['greeting']); - }; - - $response = $invocationStrategy($callback, $request, $response, $args); - - $this->assertSame('hello', $response->getHeaderLine('X-Foo')); - } -} diff --git a/tests/Traits/AppTestTrait.php b/tests/Traits/AppTestTrait.php index 163c9b6ed..9fce8dd4f 100644 --- a/tests/Traits/AppTestTrait.php +++ b/tests/Traits/AppTestTrait.php @@ -12,19 +12,11 @@ use PHPUnit\Framework\Constraint\IsIdentical; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestFactoryInterface; use Slim\App; -use Slim\Builder\AppBuilder; trait AppTestTrait { - protected function createApp(array $definitions = []): App - { - $builder = new AppBuilder(); - $builder->addDefinitions($definitions); - - return $builder->build(); - } - protected function assertJsonResponse(mixed $expected, ResponseInterface $actual, string $message = ''): void { self::assertThat( @@ -33,4 +25,9 @@ protected function assertJsonResponse(mixed $expected, ResponseInterface $actual $message, ); } + + protected function getServerRequestFactory(App $app): ServerRequestFactoryInterface + { + return $app->getContainer()->get(ServerRequestFactoryInterface::class); + } }