From 51b6a5675e1666afb88451560a339cc603a15e6d Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Mon, 27 Sep 2021 14:48:19 +0200 Subject: [PATCH 01/17] Support class handlers in the function runtime --- .github/workflows/ci.yml | 6 +-- .gitignore | 1 + README.md | 47 +++++++++++++++++++ composer.json | 13 ++++-- src/HandlerResolver.php | 73 ++++++++++++++++++++++++++++++ src/Http/KernelAdapter.php | 41 +++++++++++++++++ src/bootstrap.php | 12 +++++ tests/Fixtures/MyService.php | 7 +++ tests/Fixtures/TestKernel.php | 35 ++++++++++++++ tests/Fixtures/fake-handler.php | 5 ++ tests/TestKernel.php | 23 ---------- tests/Unit/BrefKernelTest.php | 6 +-- tests/Unit/HandlerResolverTest.php | 54 ++++++++++++++++++++++ 13 files changed, 290 insertions(+), 33 deletions(-) create mode 100644 src/HandlerResolver.php create mode 100644 src/Http/KernelAdapter.php create mode 100644 src/bootstrap.php create mode 100644 tests/Fixtures/MyService.php create mode 100644 tests/Fixtures/TestKernel.php create mode 100644 tests/Fixtures/fake-handler.php delete mode 100644 tests/TestKernel.php create mode 100644 tests/Unit/HandlerResolverTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1854fcc..e5d7b79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: max-parallel: 10 fail-fast: false matrix: - php: ['7.3', '7.4', '8.0'] + php: ['7.4', '8.0'] sf_version: ['4.4.*', '5.0.*', '5.2.*'] steps: @@ -42,7 +42,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@2.7.0 with: - php-version: 7.3 + php-version: 7.4 coverage: pcov - name: Checkout code @@ -53,5 +53,5 @@ jobs: - name: Run tests env: - PHP_VERSION: 7.3 + PHP_VERSION: 7.4 run: ./vendor/bin/phpunit -v --coverage-text diff --git a/.gitignore b/.gitignore index 1928cc9..1a2bb4b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /composer.phar /composer.lock .phpunit.result.cache +/var/ diff --git a/README.md b/README.md index 4f0351f..5abd74b 100644 --- a/README.md +++ b/README.md @@ -95,3 +95,50 @@ class Kernel extends BrefKernel + } } ``` + +## Handling requests in a kept-alive process without FPM + +> Note: this is an advanced topic. Don't bother with this unless you know what you are doing. + +To handle HTTP requests via the Symfony Kernel, without using PHP-FPM, by keeping the process alive: + +```diff +# serverless.yml + +functions: + app: +- handler: public/index.php ++ handler: App\Kernel + layers: + # Switch from PHP-FPM to the "function" runtime: +- - ${bref:layer.php-80-fpm} ++ - ${bref:layer.php-80} + environment: + # The Symfony process will restart every 100 requests + BREF_LOOP_MAX: 100 +``` + +## Class handlers + +To handle other events (e.g. [SQS messages with Symfony Messenger](https://github.com/brefphp/symfony-messenger)) via a class name: + +```diff +# serverless.yml + +functions: + sqsHandler: +- handler: bin/consumer.php ++ handler: Bref\Symfony\Messenger\Service\Sqs\SqsConsumer + layers: + - ${bref:layer.php-80} +``` + +If your Symfony kernel is not named `App\Kernel`, you can configure that in a `SYMFONY_KERNEL_CLASS` environment variable: + +```diff +# serverless.yml +provider: + ... + environment: + SYMFONY_KERNEL_CLASS: MyNamespace\App\Kernel +``` diff --git a/composer.json b/composer.json index c68eb42..11f6447 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,10 @@ "autoload": { "psr-4": { "Bref\\SymfonyBridge\\": "src/" - } + }, + "files": [ + "src/bootstrap.php" + ] }, "autoload-dev": { "psr-4": { @@ -19,16 +22,18 @@ } }, "require": { - "php": ">=7.3", - "bref/bref": "^1.0", + "php": ">=7.4", + "bref/bref": "^1.2", "symfony/filesystem": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0" + "symfony/http-kernel": "^4.4|^5.0", + "symfony/psr-http-message-bridge": "^2.1" }, "require-dev": { "mnapoli/hard-mode": "^0.3.0", "phpunit/phpunit": "^8.0", "symfony/config": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", + "symfony/framework-bundle": "^4.4|^5.0", "symfony/process": "^4.4|^5.0" }, "config": { diff --git a/src/HandlerResolver.php b/src/HandlerResolver.php new file mode 100644 index 0000000..519a7ef --- /dev/null +++ b/src/HandlerResolver.php @@ -0,0 +1,73 @@ +fileLocator = new FileHandlerLocator; + $this->symfonyContainer = null; + } + + /** + * {@inheritDoc} + */ + public function get($id) + { + // By default we check if the handler is a file name (classic Bref behavior) + if ($this->fileLocator->has($id)) { + return $this->fileLocator->get($id); + } + + // If not, we try to get the handler from the Symfony container + $handler = $this->symfonyContainer()->get($id); + + if ($handler instanceof HttpKernelInterface) { + $handler = new KernelAdapter($handler); + } + + return $handler; + } + + /** + * {@inheritDoc} + */ + public function has($id): bool + { + return $this->fileLocator->has($id) || $this->symfonyContainer()->has($id); + } + + private function symfonyContainer(): ContainerInterface + { + if (! $this->symfonyContainer) { + $kernelClass = $_SERVER['SYMFONY_KERNEL_CLASS'] ?? 'App\Kernel'; + if (! class_exists($kernelClass)) { + throw new Exception( + <<boot(); + $this->symfonyContainer = $kernel->getContainer(); + } + + return $this->symfonyContainer; + } +} diff --git a/src/Http/KernelAdapter.php b/src/Http/KernelAdapter.php new file mode 100644 index 0000000..2ae35ed --- /dev/null +++ b/src/Http/KernelAdapter.php @@ -0,0 +1,41 @@ +kernel = $kernel; + $this->symfonyFactory = new HttpFoundationFactory; + $psr17Factory = new Psr17Factory; + $this->psrFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $symfonyRequest = $this->symfonyFactory->createRequest($request); + + $symfonyResponse = $this->kernel->handle($symfonyRequest); + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($symfonyRequest, $symfonyResponse); + } + + return $this->psrFactory->createResponse($symfonyResponse); + } +} diff --git a/src/bootstrap.php b/src/bootstrap.php new file mode 100644 index 0000000..19de4ab --- /dev/null +++ b/src/bootstrap.php @@ -0,0 +1,12 @@ + new HandlerResolver); diff --git a/tests/Fixtures/MyService.php b/tests/Fixtures/MyService.php new file mode 100644 index 0000000..7d58195 --- /dev/null +++ b/tests/Fixtures/MyService.php @@ -0,0 +1,7 @@ +services()->set(MyService::class)->public(); + } + + protected function configureRoutes(RoutingConfigurator $routes): void + { + } +} diff --git a/tests/Fixtures/fake-handler.php b/tests/Fixtures/fake-handler.php new file mode 100644 index 0000000..1bed93a --- /dev/null +++ b/tests/Fixtures/fake-handler.php @@ -0,0 +1,5 @@ +assertFalse($kernel->isLambda()); + self::assertFalse($kernel->isLambda()); putenv('LAMBDA_TASK_ROOT=/var/task'); - $this->assertTrue($kernel->isLambda()); + self::assertTrue($kernel->isLambda()); } } diff --git a/tests/Unit/HandlerResolverTest.php b/tests/Unit/HandlerResolverTest.php new file mode 100644 index 0000000..8047e1b --- /dev/null +++ b/tests/Unit/HandlerResolverTest.php @@ -0,0 +1,54 @@ +has('tests/Fixtures/fake-handler.php')); + $fileHandler = $resolver->get('tests/Fixtures/fake-handler.php'); + self::assertInstanceOf(Closure::class, $fileHandler); + self::assertEquals('hello world', $fileHandler()); + } + + public function test Symfony services can be used as Lambda handlers() + { + $resolver = new HandlerResolver; + self::assertInstanceOf(MyService::class, $resolver->get(MyService::class)); + self::assertTrue($resolver->has(MyService::class)); + } + + public function test the Symfony kernel can be used as a HTTP handler() + { + $resolver = new HandlerResolver; + $handler = $resolver->get(TestKernel::class); + self::assertInstanceOf(RequestHandlerInterface::class, $handler); + self::assertInstanceOf(KernelAdapter::class, $handler); + } +} From 7e5e4a49cf8884241474b732b7a599b90261db43 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 3 Oct 2021 20:54:09 +0200 Subject: [PATCH 02/17] Document the code --- src/HandlerResolver.php | 15 +++++++++++++++ src/Http/KernelAdapter.php | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/src/HandlerResolver.php b/src/HandlerResolver.php index 519a7ef..d877b20 100644 --- a/src/HandlerResolver.php +++ b/src/HandlerResolver.php @@ -8,6 +8,12 @@ use Psr\Container\ContainerInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; +/** + * This class resolves handlers. + * + * For example, if we configure `handler: xyz` in serverless.yml, then Bref + * will call this class to resolve `xyz` into the real Lambda handler. + */ class HandlerResolver implements ContainerInterface { private ?ContainerInterface $symfonyContainer; @@ -15,6 +21,7 @@ class HandlerResolver implements ContainerInterface public function __construct() { + // Bref's default handler resolver $this->fileLocator = new FileHandlerLocator; $this->symfonyContainer = null; } @@ -32,6 +39,7 @@ public function get($id) // If not, we try to get the handler from the Symfony container $handler = $this->symfonyContainer()->get($id); + // If the kernel was configured as a handler, then we wrap it to make it a valid HTTP handler for Lambda if ($handler instanceof HttpKernelInterface) { $handler = new KernelAdapter($handler); } @@ -47,8 +55,12 @@ public function has($id): bool return $this->fileLocator->has($id) || $this->symfonyContainer()->has($id); } + /** + * Create and return the Symfony container. + */ private function symfonyContainer(): ContainerInterface { + // Only create it once if (! $this->symfonyContainer) { $kernelClass = $_SERVER['SYMFONY_KERNEL_CLASS'] ?? 'App\Kernel'; if (! class_exists($kernelClass)) { @@ -60,9 +72,12 @@ private function symfonyContainer(): ContainerInterface ); } + // Sane defaults for running on AWS Lambda: prod and no debug + // Can be overridden via the environment variables of course $env = $_SERVER['APP_ENV'] ?? 'prod'; $debug = (bool) ($_SERVER['APP_DEBUG'] ?? false); + // This is where the Symfony Kernel is created and booted $kernel = new $kernelClass($env, $debug); $kernel->boot(); $this->symfonyContainer = $kernel->getContainer(); diff --git a/src/Http/KernelAdapter.php b/src/Http/KernelAdapter.php index 2ae35ed..941c4f2 100644 --- a/src/Http/KernelAdapter.php +++ b/src/Http/KernelAdapter.php @@ -11,6 +11,12 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\TerminableInterface; +/** + * This turns a Symfony Kernel into a PSR-15 handler. + * + * That means the Symfony Kernel can now be used by Bref (which supports PSR-15) + * to handle HTTP requests from API Gateway. + */ class KernelAdapter implements RequestHandlerInterface { private HttpKernelInterface $kernel; @@ -28,6 +34,7 @@ public function __construct(HttpKernelInterface $kernel) public function handle(ServerRequestInterface $request): ResponseInterface { + // From PSR-7 to Symfony $symfonyRequest = $this->symfonyFactory->createRequest($request); $symfonyResponse = $this->kernel->handle($symfonyRequest); @@ -36,6 +43,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $this->kernel->terminate($symfonyRequest, $symfonyResponse); } + // From Symfony to PSR-7 return $this->psrFactory->createResponse($symfonyResponse); } } From 271a931beafa149ac72391f6044d1035891a5011 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 11:08:35 +0100 Subject: [PATCH 03/17] Retrieve the Symfony Kernel from a file Look in `public/index.php` by default. --- README.md | 28 ++++++++++++++++++----- src/HandlerResolver.php | 49 +++++++++++++++++++++++++++-------------- src/bootstrap.php | 2 +- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 5abd74b..418587b 100644 --- a/README.md +++ b/README.md @@ -128,17 +128,33 @@ To handle other events (e.g. [SQS messages with Symfony Messenger](https://githu functions: sqsHandler: - handler: bin/consumer.php -+ handler: Bref\Symfony\Messenger\Service\Sqs\SqsConsumer ++ handler: App\Service\MyService layers: - ${bref:layer.php-80} ``` -If your Symfony kernel is not named `App\Kernel`, you can configure that in a `SYMFONY_KERNEL_CLASS` environment variable: +The service will be retrieved from the Symfony Kernel returned by `public/index.php`. + +### Custom bootstrap file + +If you do not have a `public/index.php` file, you can create a file that returns the kernel (or any PSR-11 container): + +```php +fileLocator->has($id)) { + if (!$isComposed && $this->fileLocator->has($id)) { return $this->fileLocator->get($id); } + $service = $id; + + $bootstrapFile = null; + if ($isComposed) { + [$bootstrapFile, $service] = explode(':', $id, 2); + } + // If not, we try to get the handler from the Symfony container - $handler = $this->symfonyContainer()->get($id); + $handler = $this->symfonyContainer($bootstrapFile)->get($service); // If the kernel was configured as a handler, then we wrap it to make it a valid HTTP handler for Lambda if ($handler instanceof HttpKernelInterface) { @@ -58,29 +68,34 @@ public function has($id): bool /** * Create and return the Symfony container. */ - private function symfonyContainer(): ContainerInterface + private function symfonyContainer(string $bootstrapFile = null): ContainerInterface { // Only create it once if (! $this->symfonyContainer) { - $kernelClass = $_SERVER['SYMFONY_KERNEL_CLASS'] ?? 'App\Kernel'; - if (! class_exists($kernelClass)) { + $bootstrapFile = $bootstrapFile ?: 'public/index.php'; + + if (! file_exists($bootstrapFile)) { throw new Exception( - <<boot(); + $container = $container->getContainer(); + } + + if (! $container instanceof ContainerInterface) { + throw new Exception(sprintf( + "The closure returned by '%s' must return either a Symfony Kernel or a PSR-11 container. Instead it returned '%s'", + $bootstrapFile, + is_object($container) ? get_class($container) : gettype($container), + )); + } - // This is where the Symfony Kernel is created and booted - $kernel = new $kernelClass($env, $debug); - $kernel->boot(); - $this->symfonyContainer = $kernel->getContainer(); + $this->symfonyContainer = $container; } return $this->symfonyContainer; diff --git a/src/bootstrap.php b/src/bootstrap.php index 19de4ab..860207e 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -9,4 +9,4 @@ * This is what Bref will use to turn handler names (strings defined in serverless.yml/AWS Lambda) * into classes that can handle the Lambda events. */ -Bref::setContainer(fn () => new HandlerResolver); +Bref::setContainer(static fn () => new HandlerResolver); From 0770efc3ceb4b383561234858af2991bccd27392 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 12:09:59 +0100 Subject: [PATCH 04/17] Avoid hard errors in case Bref 1.2 isn't installed --- src/bootstrap.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bootstrap.php b/src/bootstrap.php index 860207e..b1eb3f2 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -9,4 +9,6 @@ * This is what Bref will use to turn handler names (strings defined in serverless.yml/AWS Lambda) * into classes that can handle the Lambda events. */ -Bref::setContainer(static fn () => new HandlerResolver); +if (class_exists(Bref::class)) { + Bref::setContainer(static fn() => new HandlerResolver); +} From 22978f589ad406442a992dc74f622b8b8b8a8906 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 12:22:27 +0100 Subject: [PATCH 05/17] Fix tests --- src/HandlerResolver.php | 36 +++++++++++++++++++---- src/bootstrap.php | 2 +- tests/Unit/HandlerResolverTest.php | 11 ++++--- tests/{Fixtures => Unit}/fake-handler.php | 0 tests/Unit/public/index.php | 5 ++++ 5 files changed, 44 insertions(+), 10 deletions(-) rename tests/{Fixtures => Unit}/fake-handler.php (100%) create mode 100644 tests/Unit/public/index.php diff --git a/src/HandlerResolver.php b/src/HandlerResolver.php index 8f15f83..31a38fc 100644 --- a/src/HandlerResolver.php +++ b/src/HandlerResolver.php @@ -4,6 +4,7 @@ use Bref\Runtime\FileHandlerLocator; use Bref\SymfonyBridge\Http\KernelAdapter; +use Closure; use Exception; use Psr\Container\ContainerInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -35,7 +36,7 @@ public function get($id) $isComposed = strpos($id, ':') !== false; // By default we check if the handler is a file name (classic Bref behavior) - if (!$isComposed && $this->fileLocator->has($id)) { + if (! $isComposed && $this->fileLocator->has($id)) { return $this->fileLocator->get($id); } @@ -62,13 +63,28 @@ public function get($id) */ public function has($id): bool { - return $this->fileLocator->has($id) || $this->symfonyContainer()->has($id); + $isComposed = strpos($id, ':') !== false; + + // By default we check if the handler is a file name (classic Bref behavior) + if (! $isComposed && $this->fileLocator->has($id)) { + return true; + } + + $service = $id; + + $bootstrapFile = null; + if ($isComposed) { + [$bootstrapFile, $service] = explode(':', $id, 2); + } + + // If not, we try to get the handler from the Symfony container + return $this->symfonyContainer($bootstrapFile)->has($service); } /** * Create and return the Symfony container. */ - private function symfonyContainer(string $bootstrapFile = null): ContainerInterface + private function symfonyContainer(?string $bootstrapFile = null): ContainerInterface { // Only create it once if (! $this->symfonyContainer) { @@ -76,11 +92,21 @@ private function symfonyContainer(string $bootstrapFile = null): ContainerInterf if (! file_exists($bootstrapFile)) { throw new Exception( - "Cannot find file '$bootstrapFile': the Bref-Symfony bridge tried to require that file to get the Symfony kernel." + "Cannot find file '$bootstrapFile': the Bref-Symfony bridge tried to require that file to get the Symfony kernel. If your application does not have that file, follow the Bref-Symfony documentation to create and configure a file that returns the Symfony Kernel." ); } - $container = require $bootstrapFile; + $closure = require $bootstrapFile; + + if (! $closure instanceof Closure) { + throw new Exception(sprintf( + "The '%s' file must return an anonymous function (that returns the Symfony Kernel). Instead it returned '%s'. Either edit the file to return an anynomous function, or create a separate file (follow the online documentation to do so).", + $bootstrapFile, + is_object($closure) ? get_class($closure) : gettype($closure), + )); + } + + $container = $closure(); if ($container instanceof KernelInterface) { $container->boot(); diff --git a/src/bootstrap.php b/src/bootstrap.php index b1eb3f2..0e451c1 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -10,5 +10,5 @@ * into classes that can handle the Lambda events. */ if (class_exists(Bref::class)) { - Bref::setContainer(static fn() => new HandlerResolver); + Bref::setContainer(static fn () => new HandlerResolver); } diff --git a/tests/Unit/HandlerResolverTest.php b/tests/Unit/HandlerResolverTest.php index 8047e1b..79520d4 100644 --- a/tests/Unit/HandlerResolverTest.php +++ b/tests/Unit/HandlerResolverTest.php @@ -13,14 +13,17 @@ class HandlerResolverTest extends TestCase { + private string $cwd; + public function setUp(): void { - $_SERVER['SYMFONY_KERNEL_CLASS'] = TestKernel::class; + $this->cwd = getcwd(); + chdir(__DIR__); } public function tearDown(): void { - unset($_SERVER['SYMFONY_KERNEL_CLASS']); + chdir($this->cwd); } public function test Bref uses our handler resolver() @@ -31,8 +34,8 @@ public function test Bref uses our handler resolver() public function test files are resolved() { $resolver = new HandlerResolver; - self::assertTrue($resolver->has('tests/Fixtures/fake-handler.php')); - $fileHandler = $resolver->get('tests/Fixtures/fake-handler.php'); + self::assertTrue($resolver->has('fake-handler.php')); + $fileHandler = $resolver->get('fake-handler.php'); self::assertInstanceOf(Closure::class, $fileHandler); self::assertEquals('hello world', $fileHandler()); } diff --git a/tests/Fixtures/fake-handler.php b/tests/Unit/fake-handler.php similarity index 100% rename from tests/Fixtures/fake-handler.php rename to tests/Unit/fake-handler.php diff --git a/tests/Unit/public/index.php b/tests/Unit/public/index.php new file mode 100644 index 0000000..91adfa9 --- /dev/null +++ b/tests/Unit/public/index.php @@ -0,0 +1,5 @@ + Date: Tue, 28 Dec 2021 12:23:29 +0100 Subject: [PATCH 06/17] Fix code style --- tests/Unit/public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/public/index.php b/tests/Unit/public/index.php index 91adfa9..363d54e 100644 --- a/tests/Unit/public/index.php +++ b/tests/Unit/public/index.php @@ -1,4 +1,4 @@ - Date: Tue, 28 Dec 2021 12:28:39 +0100 Subject: [PATCH 07/17] Fix types on multiple Symfony versions --- tests/Fixtures/TestKernel.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/Fixtures/TestKernel.php b/tests/Fixtures/TestKernel.php index 3006e28..155fdcf 100644 --- a/tests/Fixtures/TestKernel.php +++ b/tests/Fixtures/TestKernel.php @@ -5,8 +5,8 @@ use Bref\SymfonyBridge\BrefKernel; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; class TestKernel extends BrefKernel { @@ -24,12 +24,11 @@ public function registerBundles(): array ]; } - protected function configureContainer(ContainerConfigurator $c): void + /** + * @param ContainerConfigurator|ContainerBuilder $c + */ + protected function configureContainer($c): void { $c->services()->set(MyService::class)->public(); } - - protected function configureRoutes(RoutingConfigurator $routes): void - { - } } From 9adf9c3d42cddbafc7c1853b95aef6bfdaa06b46 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 12:34:53 +0100 Subject: [PATCH 08/17] Fix tests on older Symfony versions --- tests/Fixtures/TestKernel.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Fixtures/TestKernel.php b/tests/Fixtures/TestKernel.php index 155fdcf..4208af6 100644 --- a/tests/Fixtures/TestKernel.php +++ b/tests/Fixtures/TestKernel.php @@ -31,4 +31,8 @@ protected function configureContainer($c): void { $c->services()->set(MyService::class)->public(); } + + protected function configureRoutes(): void + { + } } From bb6a80973358164bcf89c0258f5de265d06160e3 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 12:42:27 +0100 Subject: [PATCH 09/17] Fix tests on older Symfony versions --- README.md | 2 ++ tests/Fixtures/TestKernel.php | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 418587b..53a60b4 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,8 @@ functions: The service will be retrieved from the Symfony Kernel returned by `public/index.php`. +> Note: the service must be configured as `public: true` in the Symfony configuration. + ### Custom bootstrap file If you do not have a `public/index.php` file, you can create a file that returns the kernel (or any PSR-11 container): diff --git a/tests/Fixtures/TestKernel.php b/tests/Fixtures/TestKernel.php index 4208af6..2e1fdf8 100644 --- a/tests/Fixtures/TestKernel.php +++ b/tests/Fixtures/TestKernel.php @@ -6,7 +6,7 @@ use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\DependencyInjection\Definition; class TestKernel extends BrefKernel { @@ -25,11 +25,13 @@ public function registerBundles(): array } /** - * @param ContainerConfigurator|ContainerBuilder $c + * @param ContainerBuilder $c */ protected function configureContainer($c): void { - $c->services()->set(MyService::class)->public(); + $definition = new Definition(MyService::class); + $definition->setPublic(true); + $c->setDefinition(MyService::class, $definition); } protected function configureRoutes(): void From e5c57a6e69da010969fedbcb4c42d8f8fba10786 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 13:26:28 +0100 Subject: [PATCH 10/17] Fix tests on multiple Symfony versions --- tests/Fixtures/TestKernel.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/Fixtures/TestKernel.php b/tests/Fixtures/TestKernel.php index 2e1fdf8..28c65ec 100644 --- a/tests/Fixtures/TestKernel.php +++ b/tests/Fixtures/TestKernel.php @@ -7,6 +7,7 @@ use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; class TestKernel extends BrefKernel { @@ -25,13 +26,17 @@ public function registerBundles(): array } /** - * @param ContainerBuilder $c + * @param ContainerBuilder|ContainerConfigurator $c */ protected function configureContainer($c): void { - $definition = new Definition(MyService::class); - $definition->setPublic(true); - $c->setDefinition(MyService::class, $definition); + if ($c instanceof ContainerBuilder) { + $definition = new Definition(MyService::class); + $definition->setPublic(true); + $c->setDefinition(MyService::class, $definition); + } else { + $c->services()->set(MyService::class)->public(); + } } protected function configureRoutes(): void From c1c80f083001d9dc901556d706aea97f09a1a739 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 13:30:37 +0100 Subject: [PATCH 11/17] Fix test on lower Symfony versions --- tests/Fixtures/TestKernel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Fixtures/TestKernel.php b/tests/Fixtures/TestKernel.php index 28c65ec..f5ca0a6 100644 --- a/tests/Fixtures/TestKernel.php +++ b/tests/Fixtures/TestKernel.php @@ -39,7 +39,7 @@ protected function configureContainer($c): void } } - protected function configureRoutes(): void + protected function configureRoutes($routes): void { } } From d67cec54c9b925d5d57d2043a77028d81ec4db91 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 13:49:13 +0100 Subject: [PATCH 12/17] Fix test on lower Symfony versions --- tests/Fixtures/TestKernel.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Fixtures/TestKernel.php b/tests/Fixtures/TestKernel.php index f5ca0a6..a15b8e8 100644 --- a/tests/Fixtures/TestKernel.php +++ b/tests/Fixtures/TestKernel.php @@ -27,8 +27,9 @@ public function registerBundles(): array /** * @param ContainerBuilder|ContainerConfigurator $c + * @param mixed $loader */ - protected function configureContainer($c): void + protected function configureContainer($c, $loader = null): void { if ($c instanceof ContainerBuilder) { $definition = new Definition(MyService::class); @@ -39,6 +40,11 @@ protected function configureContainer($c): void } } + /** + * This method is needed only for supporting lower Symfony versions + * + * @param mixed $routes + */ protected function configureRoutes($routes): void { } From a4f2acd2da46147980c437770b762fc78965ac63 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 13:53:56 +0100 Subject: [PATCH 13/17] Require Symfony 5.2+ --- .github/workflows/ci.yml | 2 +- composer.json | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa8925e..ed32dbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: php: ['7.4', '8.0', '8.1'] - sf_version: ['4.4.*', '5.0.*', '5.2.*', '5.4.*', '6.0.*'] + sf_version: ['5.2.*', '5.4.*', '6.0.*'] exclude: - php: 7.4 sf_version: 6.0.* diff --git a/composer.json b/composer.json index 3cf3887..7f257f4 100644 --- a/composer.json +++ b/composer.json @@ -24,17 +24,17 @@ "require": { "php": ">=7.4", "bref/bref": "^1.2", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^5.2|^6.0", + "symfony/http-kernel": "^5.2|^6.0", "symfony/psr-http-message-bridge": "^2.1" }, "require-dev": { "mnapoli/hard-mode": "^0.3.0", - "phpunit/phpunit": "^8.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "phpunit/phpunit": "^8.5.22", + "symfony/config": "^5.2|^6.0", + "symfony/dependency-injection": "^5.2|^6.0", + "symfony/framework-bundle": "^5.2|^6.0", + "symfony/process": "^5.2|^6.0" }, "config": { "sort-packages": true, From d578da84776182998235c9ebc576ffcede6fb636 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 28 Dec 2021 14:16:57 +0100 Subject: [PATCH 14/17] Pass the context to closures --- src/HandlerResolver.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/HandlerResolver.php b/src/HandlerResolver.php index 31a38fc..0cf515d 100644 --- a/src/HandlerResolver.php +++ b/src/HandlerResolver.php @@ -106,7 +106,9 @@ private function symfonyContainer(?string $bootstrapFile = null): ContainerInter )); } - $container = $closure(); + $context = $_SERVER; + + $container = $closure($context); if ($container instanceof KernelInterface) { $container->boot(); From e3a56a51cf43f21ad918a42d7290c906509773ec Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Thu, 30 Dec 2021 13:14:59 +0100 Subject: [PATCH 15/17] Implement a Symfony Runtime for Bref to resolve the Kernel & container --- README.md | 11 ++++++----- composer.json | 3 ++- src/HandlerResolver.php | 25 ++++++++++++++++++------- src/Runtime/BrefRuntime.php | 9 +++++++++ 4 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 src/Runtime/BrefRuntime.php diff --git a/README.md b/README.md index 53a60b4..6ef38d5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -This package configures Symfony to run on AWS Lambda using [Bref](https://bref.sh/). +Run Symfony on AWS Lambda using the [Bref](https://bref.sh/) runtime. [![Build Status](https://github.com/brefphp/symfony-bridge/workflows/Tests/badge.svg)](https://github.com/brefphp/symfony-bridge/actions) [![Latest Version](https://img.shields.io/packagist/v/bref/symfony-bridge?style=flat-square)](https://packagist.org/packages/bref/symfony-bridge) @@ -118,6 +118,8 @@ functions: BREF_LOOP_MAX: 100 ``` +The `App\Kernel` will be retrieved via Symfony Runtime from `public/index.php`. If you don't have a `public/index.php`, read the next sections. + ## Class handlers To handle other events (e.g. [SQS messages with Symfony Messenger](https://github.com/brefphp/symfony-messenger)) via a class name: @@ -133,9 +135,9 @@ functions: - ${bref:layer.php-80} ``` -The service will be retrieved from the Symfony Kernel returned by `public/index.php`. +The service will be retrieved via Symfony Runtime from the Symfony Kernel returned by `public/index.php`. -> Note: the service must be configured as `public: true` in the Symfony configuration. +> Note: the service must be configured as **public** (`public: true`) in the Symfony configuration. ### Custom bootstrap file @@ -157,6 +159,5 @@ And configure it in `serverless.yml`: # serverless.yml functions: sqsHandler: -- handler: bin/consumer.php -+ handler: kernel.php:App\Service\MyService + handler: kernel.php:App\Service\MyService ``` diff --git a/composer.json b/composer.json index 7f257f4..1fd12d7 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "bref/bref": "^1.2", "symfony/filesystem": "^5.2|^6.0", "symfony/http-kernel": "^5.2|^6.0", - "symfony/psr-http-message-bridge": "^2.1" + "symfony/psr-http-message-bridge": "^2.1", + "symfony/runtime": "^5.2|^6.0" }, "require-dev": { "mnapoli/hard-mode": "^0.3.0", diff --git a/src/HandlerResolver.php b/src/HandlerResolver.php index 0cf515d..ff75d3f 100644 --- a/src/HandlerResolver.php +++ b/src/HandlerResolver.php @@ -4,7 +4,7 @@ use Bref\Runtime\FileHandlerLocator; use Bref\SymfonyBridge\Http\KernelAdapter; -use Closure; +use Bref\SymfonyBridge\Runtime\BrefRuntime; use Exception; use Psr\Container\ContainerInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -96,19 +96,30 @@ private function symfonyContainer(?string $bootstrapFile = null): ContainerInter ); } - $closure = require $bootstrapFile; + $app = require $bootstrapFile; - if (! $closure instanceof Closure) { + if (!is_object($app)) { throw new Exception(sprintf( - "The '%s' file must return an anonymous function (that returns the Symfony Kernel). Instead it returned '%s'. Either edit the file to return an anynomous function, or create a separate file (follow the online documentation to do so).", + "The '%s' file must return an anonymous function (that returns the Symfony Kernel). Instead it returned '%s'. Either edit the file to return an anonymous function, or create a separate file (follow the online documentation to do so).", $bootstrapFile, - is_object($closure) ? get_class($closure) : gettype($closure), + is_object($app) ? get_class($app) : gettype($app), )); } - $context = $_SERVER; + $projectDir = getenv('LAMBDA_TASK_ROOT') ?: null; - $container = $closure($context); + // Use the Symfony Runtime component to resolve the closure and get the PSR-11 container + $options = $_SERVER['APP_RUNTIME_OPTIONS'] ?? []; + if ($projectDir) { + $options['project_dir'] = $projectDir; + } + $runtime = new BrefRuntime($options); + + [$app, $args] = $runtime + ->getResolver($app) + ->resolve(); + + $container = $app(...$args); if ($container instanceof KernelInterface) { $container->boot(); diff --git a/src/Runtime/BrefRuntime.php b/src/Runtime/BrefRuntime.php new file mode 100644 index 0000000..4ffddc8 --- /dev/null +++ b/src/Runtime/BrefRuntime.php @@ -0,0 +1,9 @@ + Date: Thu, 30 Dec 2021 14:22:03 +0100 Subject: [PATCH 16/17] Require phpstan --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 1fd12d7..0221a92 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ }, "require-dev": { "mnapoli/hard-mode": "^0.3.0", + "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^8.5.22", "symfony/config": "^5.2|^6.0", "symfony/dependency-injection": "^5.2|^6.0", From 3160d41f19938ef8869af3cbfc8eb876bf089124 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Thu, 30 Dec 2021 14:22:12 +0100 Subject: [PATCH 17/17] Fix static analysis --- src/HandlerResolver.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/HandlerResolver.php b/src/HandlerResolver.php index ff75d3f..f9363e8 100644 --- a/src/HandlerResolver.php +++ b/src/HandlerResolver.php @@ -98,10 +98,11 @@ private function symfonyContainer(?string $bootstrapFile = null): ContainerInter $app = require $bootstrapFile; - if (!is_object($app)) { + if (! is_object($app)) { throw new Exception(sprintf( "The '%s' file must return an anonymous function (that returns the Symfony Kernel). Instead it returned '%s'. Either edit the file to return an anonymous function, or create a separate file (follow the online documentation to do so).", $bootstrapFile, + // @phpstan-ignore-next-line is_object($app) ? get_class($app) : gettype($app), )); }