diff --git a/CHANGELOG.md b/CHANGELOG.md index 79497c2..e571d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Removed + +- The ability to use `string`s to reference services + ### Fixed - PHP `8.4` deprecations diff --git a/composer.json b/composer.json index f35e4e8..0e3e6ec 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,6 @@ "innmind/cli": "^3.1", "innmind/immutable": "~5.2", "innmind/di": "~2.1", - "ramsey/uuid": "^4.7", "innmind/url": "^4.1", "innmind/filesystem": "~7.0", "innmind/http-server": "~4.0", diff --git a/docs/cli.md b/docs/cli.md index 40f61bf..c5924d9 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -36,7 +36,10 @@ use Innmind\CLI\{ Console, Command, }; -use Innmind\DI\Container; +use Innmind\DI\{ + Container, + Service, +}; use Innmind\AMQP\{ Client, Command\Publish, @@ -45,13 +48,19 @@ use Innmind\AMQP\{ }; use Innmind\Immutable\Str; +enum Services implements Service +{ + case producerClient; + case consumerClient; +} + new class extends Cli { protected function configure(Application $app): Application { return $app - ->service('producer-client', /* see services topic */) - ->service('consumer-client', /* see services topic */) - ->command(static fn(Container $container) => new class($container('producer-client')) implements Command { + ->service(Services::producerClient, /* see services topic */) + ->service(Services::consumerClient, /* see services topic */) + ->command(static fn(Container $container) => new class($container(Services::producerClient)) implements Command { public function __construct( private Client $amqp, ) { @@ -82,7 +91,7 @@ new class extends Cli { return 'publish url'; } }) - ->command(static fn(Container $container) => new class($container('consumer-client')) implements Command { + ->command(static fn(Container $container) => new class($container(Services::consumerClient)) implements Command { public function __construct( private Client $amqp, ) { diff --git a/docs/http-and-cli.md b/docs/http-and-cli.md index eb2c17b..d5f69a3 100644 --- a/docs/http-and-cli.md +++ b/docs/http-and-cli.md @@ -1,6 +1,6 @@ # Build an app that runs through HTTP and CLI -If you looked at how to build an [HTTP](http.md) and [CLI](cli.md) app you may have noticed that we always configure the same `Application` class. This is intentional to allow you to configure services once (in a [middleware](middlewares)) and use them in both contexts. +If you looked at how to build an [HTTP](http.md) and [CLI](cli.md) app you may have noticed that we always configure the same `Application` class. This is intentional to allow you to configure services once (in a [middleware](middlewares.md)) and use them in both contexts. Let's take an imaginary app where you can upload images via HTTP (persists them to the filesystem) and a CLI command that pulls a message from an AMQP queue to build the thumbnail. We would build a middleware that roughly looks like this: @@ -12,34 +12,44 @@ use Innmind\Framework\{ Http\Service, }; use Innmind\OperatingSystem\OperatingSystem; -use Innmind\DI\Container; +use Innmind\DI\{ + Container, + Service, +}; use Innmind\Router\Route; use Innmind\Url\Path; +enum Services implements Service +{ + case images; + case amqp; + case upload; +} + final class Kernel implements Middleware { public function __invoke(Application $app): Application { return $app ->service( - 'images', + Services::images, static fn($_, OperatingSystem $os) => $os ->filesystem() ->mount(Path::of('somewhere/on/the/filesystem/')), ) - ->service('amqp', /* see services topic */) - ->service('upload', static fn(Container $container) => new UploadHandler( //(1) - $container('images'), - $container('amqp'), + ->service(Services::amqp, /* see services topic */) + ->service(Services::upload, static fn(Container $container) => new UploadHandler( //(1) + $container(Services::images), + $container(Services::amqp), )) ->appendRoutes( static fn(Routes $routes, Container $container) => $routes->add( - Route::literal('POST /upload')->handle(Service::of($container, 'upload')), + Route::literal('POST /upload')->handle(Service::of($container, Services::upload)), ), ) ->command(static fn(Container $container) => new ThumbnailWorker( //(2) - $container('images'), - $container('amqp'), + $container(Services::images), + $container(Services::amqp), )); } } diff --git a/docs/http.md b/docs/http.md index cfa86ed..37ba428 100644 --- a/docs/http.md +++ b/docs/http.md @@ -95,7 +95,7 @@ new class extends Http { }; ``` -For simple apps having the whole behaviour next to the route can be ok. But like in this case it can be repetitive, for such case we can specify our behaviours elsewhere: [services](#Services). +For simple apps having the whole behaviour next to the route can be ok. But like in this case it can be repetitive, for such case we can specify our behaviours elsewhere: [services](#services). ## Multiple methods for the same path @@ -203,7 +203,10 @@ use Innmind\Framework\{ Http\Service, Http\To, }; -use Innmind\DI\Container; +use Innmind\DI\{ + Container, + Service, +}; use Innmind\Router\{ Route, Route\Variables, @@ -215,12 +218,18 @@ use Innmind\Http\Message\{ }; use Innmind\Filesystem\File\Content; +enum Services implements Service +{ + case helloWorld; + case helloName; +} + new class extends Http { protected function configure(Application $app): Application { return $app ->service( - 'hello-word', + Services::helloWorld, static fn() => new class { public function __invoke(ServerRequest $request): Response { @@ -234,7 +243,7 @@ new class extends Http { } ) ->service( - 'hello-name', + Services::helloName, static fn() => new class { public function __invoke( ServerRequest $request, @@ -252,11 +261,11 @@ new class extends Http { ->appendRoutes( static fn(Routes $routes, Container $container) => $routes->add( Route::literal('GET /')->handle( - Service::of($container, 'hello-word'), + Service::of($container, Services::helloWorld), ), ), ) - ->route('GET /{name}', To::service('hello-name')); + ->route('GET /{name}', To::service(Services::helloName)); } }; ``` diff --git a/docs/middlewares.md b/docs/middlewares.md index b88417e..15f3331 100644 --- a/docs/middlewares.md +++ b/docs/middlewares.md @@ -1,6 +1,6 @@ # Middlewares -Middlewares are a way to regroup all the configuration you've seen in other topics under a name. This means that you can either group part of your own application undeer a middleware or expose a package for other to use via Packagist. +Middlewares are a way to regroup all the configuration you've seen in other topics under a name. This means that you can either group part of your own application under a middleware or expose a package for other to use via Packagist. !!! note "" You can search for [`innmind/framework-middlewares` on Packagist](https://packagist.org/providers/innmind/framework-middlewares) for middlewares published by others. @@ -12,17 +12,25 @@ use Innmind\Framework\{ Middleware, Environment, }; -use Innmind\DI\Container; +use Innmind\DI\{ + Container, + Service, +}; use Innmind\CLI\{ Console, Command, }; use Innmind\Url\Url; +enum Services implements Service +{ + case emailServer; +} + final class Emails implements Middleware { public function __construct( - private string $service, + private Service $service, ){ } @@ -30,7 +38,7 @@ final class Emails implements Middleware { return $app ->service( - 'email-server' + Services::emailServer static fn($_, $__, Environment $env) => Url::of( $env->get('EMAIL_SERVER'), ), @@ -38,7 +46,7 @@ final class Emails implements Middleware ->service( $this->service, static fn(Container $container) => new EmailClient( //(1) - $container('email-server'), + $container(Services::emailServer), ), ) ->command( @@ -75,10 +83,15 @@ use Innmind\Framework\{ Application, }; +enum MyServices implements Service +{ + case emailClient; +} + new class extends Cli { protected function configure(Application $app): Application { - return $app->map(new Emails('email-client-service-name')); + return $app->map(new Emails(MyServices::emailClient)); } }; ``` diff --git a/docs/testing.md b/docs/testing.md index c6d27d1..a42591c 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -117,7 +117,7 @@ final class AppTest extends TestCase ])) ->map(new Kernel) ->mapRequestHandler( - fn($handler, $container) => new class($handler, $container('pdo'), $this) implements RequestHandler { + fn($handler, $container) => new class($handler, $container(Services::pdo), $this) implements RequestHandler { public function __construct( private RequestHandler $handler, private \PDO $pdo, diff --git a/src/Application.php b/src/Application.php index db233d1..d1804bd 100644 --- a/src/Application.php +++ b/src/Application.php @@ -110,12 +110,11 @@ public function map(Middleware $map): self /** * @psalm-mutation-free * - * @param non-empty-string|Service $name * @param callable(Container, OperatingSystem, Environment): object $definition * * @return self */ - public function service(string|Service $name, callable $definition): self + public function service(Service $name, callable $definition): self { return new self($this->app->service($name, $definition)); } diff --git a/src/Application/Async/Http.php b/src/Application/Async/Http.php index 4f9617d..cc2e27f 100644 --- a/src/Application/Async/Http.php +++ b/src/Application/Async/Http.php @@ -145,7 +145,7 @@ static function(OperatingSystem $os, Environment $env) use ($previous, $map): ar * @psalm-mutation-free */ #[\Override] - public function service(string|Service $name, callable $definition): self + public function service(Service $name, callable $definition): self { $container = $this->container; diff --git a/src/Application/Cli.php b/src/Application/Cli.php index 8734eb3..1d87ec6 100644 --- a/src/Application/Cli.php +++ b/src/Application/Cli.php @@ -22,7 +22,6 @@ Sequence, Str, }; -use Ramsey\Uuid\Uuid; /** * @internal @@ -34,7 +33,7 @@ final class Cli implements Implementation private Environment $env; /** @var callable(OperatingSystem, Environment): Builder */ private $container; - /** @var Sequence */ + /** @var Sequence */ private Sequence $commands; /** @var callable(Command, Container, OperatingSystem, Environment): Command */ private $mapCommand; @@ -43,7 +42,7 @@ final class Cli implements Implementation * @psalm-mutation-free * * @param callable(OperatingSystem, Environment): Builder $container - * @param Sequence $commands + * @param Sequence $commands * @param callable(Command, Container, OperatingSystem, Environment): Command $mapCommand */ private function __construct( @@ -69,7 +68,7 @@ public static function of(OperatingSystem $os, Environment $env): self $os, $env, static fn() => Builder::new(), - Sequence::strings(), + Sequence::of(), static fn(Command $command) => $command, ); } @@ -110,7 +109,7 @@ public function mapOperatingSystem(callable $map): self * @psalm-mutation-free */ #[\Override] - public function service(string|Service $name, callable $definition): self + public function service(Service $name, callable $definition): self { $container = $this->container; @@ -132,16 +131,12 @@ public function service(string|Service $name, callable $definition): self #[\Override] public function command(callable $command): self { - /** @psalm-suppress ImpureMethodCall Mutation free to force the user to use the returned object */ - $reference = Uuid::uuid4()->toString(); - $self = $this->service($reference, $command); - return new self( - $self->os, - $self->env, - $self->container, - ($self->commands)($reference), - $self->mapCommand, + $this->os, + $this->env, + $this->container, + ($this->commands)($command), + $this->mapCommand, ); } @@ -221,9 +216,11 @@ public function run($input) $os, $env, ); - $commands = $this->commands->map(static fn($service) => new Defer( - $service, + $commands = $this->commands->map(static fn($command) => new Defer( + $command, $container, + $os, + $env, $mapCommand, )); diff --git a/src/Application/Http.php b/src/Application/Http.php index cf58942..b7f3a02 100644 --- a/src/Application/Http.php +++ b/src/Application/Http.php @@ -122,7 +122,7 @@ public function mapOperatingSystem(callable $map): self * @psalm-mutation-free */ #[\Override] - public function service(string|Service $name, callable $definition): self + public function service(Service $name, callable $definition): self { $container = $this->container; diff --git a/src/Application/Implementation.php b/src/Application/Implementation.php index fc5a313..dec7e6f 100644 --- a/src/Application/Implementation.php +++ b/src/Application/Implementation.php @@ -51,12 +51,11 @@ public function mapOperatingSystem(callable $map): self; /** * @psalm-mutation-free * - * @param non-empty-string|Service $name * @param callable(Container, OperatingSystem, Environment): object $definition * * @return self */ - public function service(string|Service $name, callable $definition): self; + public function service(Service $name, callable $definition): self; /** * @psalm-mutation-free diff --git a/src/Cli/Command/Defer.php b/src/Cli/Command/Defer.php index de3e4b8..03d2f76 100644 --- a/src/Cli/Command/Defer.php +++ b/src/Cli/Command/Defer.php @@ -3,36 +3,43 @@ namespace Innmind\Framework\Cli\Command; +use Innmind\Framework\Environment; use Innmind\CLI\{ Command, Console, }; -use Innmind\DI\{ - Container, - Service, -}; +use Innmind\OperatingSystem\OperatingSystem; +use Innmind\DI\Container; /** * @internal */ final class Defer implements Command { - private string|Service $service; + /** @var callable(Container, OperatingSystem, Environment): Command */ + private $build; private Container $locate; + private OperatingSystem $os; + private Environment $env; /** @var callable(Command): Command */ private $map; private ?Command $command = null; /** + * @param callable(Container, OperatingSystem, Environment): Command $build * @param callable(Command): Command $map */ public function __construct( - string|Service $service, + callable $build, Container $locate, + OperatingSystem $os, + Environment $env, callable $map, ) { - $this->service = $service; + $this->build = $build; $this->locate = $locate; + $this->os = $os; + $this->env = $env; $this->map = $map; } @@ -64,6 +71,6 @@ private function command(): Command * @psalm-suppress PropertyTypeCoercion * @var Command */ - return $this->command ??= ($this->locate)($this->service); + return $this->command ??= ($this->build)($this->locate, $this->os, $this->env); } } diff --git a/src/Http/Service.php b/src/Http/Service.php index f974f19..ab4de0a 100644 --- a/src/Http/Service.php +++ b/src/Http/Service.php @@ -16,9 +16,9 @@ final class Service { private Container $container; - private string|Ref $service; + private Ref $service; - private function __construct(Container $container, string|Ref $service) + private function __construct(Container $container, Ref $service) { $this->container = $container; $this->service = $service; @@ -33,7 +33,7 @@ public function __invoke(ServerRequest $request, Variables $variables): Response return ($this->container)($this->service)($request, $variables); } - public static function of(Container $container, string|Ref $service): self + public static function of(Container $container, Ref $service): self { return new self($container, $service); } diff --git a/src/Http/To.php b/src/Http/To.php index 51bbfd8..a281eb4 100644 --- a/src/Http/To.php +++ b/src/Http/To.php @@ -17,9 +17,9 @@ final class To { - private string|Service $service; + private Service $service; - private function __construct(string|Service $service) + private function __construct(Service $service) { $this->service = $service; } @@ -38,7 +38,7 @@ public function __invoke( return $container($this->service)($request, $variables); } - public static function service(string|Service $service): self + public static function service(Service $service): self { return new self($service); } diff --git a/tests/ApplicationTest.php b/tests/ApplicationTest.php index ad3c7cc..7d5ee13 100644 --- a/tests/ApplicationTest.php +++ b/tests/ApplicationTest.php @@ -18,6 +18,7 @@ Command, Console, }; +use Innmind\DI\Service; use Innmind\Router\{ Route, Under, @@ -43,6 +44,14 @@ }; use Fixtures\Innmind\Url\Url as FUrl; +enum Services implements Service +{ + case responseHandler; + case service; + case serviceA; + case serviceB; +} + class ApplicationTest extends TestCase { use BlackBox; @@ -275,11 +284,10 @@ public function testServicesAreNotLoadedIfNotUsed(): BlackBox\Proof Set::strings(), ), )->between(0, 10), - Set::strings()->atLeast(1), ) - ->prove(function($inputs, $interactive, $arguments, $variables, $service) { + ->prove(function($inputs, $interactive, $arguments, $variables) { $app = Application::cli(Factory::build(), Environment::test($variables)) - ->service($service, static fn() => throw new \Exception); + ->service(Services::service, static fn() => throw new \Exception); $env = $app->run(InMemory::of( $inputs, @@ -310,11 +318,10 @@ public function testServicesAreAccessibleToCommands(): BlackBox\Proof Set::strings(), ), )->between(0, 10), - Set::strings()->atLeast(1), ) - ->prove(function($inputs, $interactive, $variables, $service) { + ->prove(function($inputs, $interactive, $variables) { $app = Application::cli(Factory::build(), Environment::test($variables)) - ->command(static fn($get) => new class($get($service)) implements Command { + ->command(static fn($get) => new class($get(Services::service)) implements Command { public function __construct( private Str $output, ) { @@ -330,7 +337,7 @@ public function usage(): string return 'my-command'; } }) - ->service($service, static fn() => Str::of('my command output')); + ->service(Services::service, static fn() => Str::of('my command output')); $env = $app->run(InMemory::of( $inputs, @@ -361,12 +368,10 @@ public function testServiceDependencies(): BlackBox\Proof Set::strings(), ), )->between(0, 10), - Set::strings()->atLeast(1), - Set::strings()->atLeast(1), ) - ->prove(function($inputs, $interactive, $variables, $serviceA, $serviceB) { + ->prove(function($inputs, $interactive, $variables) { $app = Application::cli(Factory::build(), Environment::test($variables)) - ->command(static fn($get) => new class($get($serviceA)) implements Command { + ->command(static fn($get) => new class($get(Services::serviceA)) implements Command { public function __construct( private Str $output, ) { @@ -382,8 +387,8 @@ public function usage(): string return 'my-command'; } }) - ->service($serviceA, static fn($get) => Str::of('my command output')->append($get($serviceB)->toString())) - ->service($serviceB, static fn() => Str::of(' twice')); + ->service(Services::serviceA, static fn($get) => Str::of('my command output')->append($get(Services::serviceB)->toString())) + ->service(Services::serviceB, static fn() => Str::of(' twice')); $env = $app->run(InMemory::of( $inputs, @@ -933,8 +938,8 @@ public function testRouteToService(): BlackBox\Proof $expected = Response::of(StatusCode::ok, $protocol); $app = Application::http(Factory::build(), Environment::test($variables)) - ->route('GET /foo', To::service('response-handler')) - ->service('response-handler', static fn() => new class($expected) { + ->route('GET /foo', To::service(Services::responseHandler)) + ->service(Services::responseHandler, static fn() => new class($expected) { public function __construct(private $response) { }