├── .release-please-manifest.json
├── src
├── Lambda
│ ├── InvocationEvent
│ │ ├── PingEvent.php
│ │ ├── InvocationEventInterface.php
│ │ ├── PhpConsoleCommandEvent.php
│ │ ├── AbstractEvent.php
│ │ ├── ConsoleCommandEvent.php
│ │ ├── WarmUpEvent.php
│ │ ├── InvocationEventFactory.php
│ │ └── HttpRequestEvent.php
│ ├── Response
│ │ ├── ResponseInterface.php
│ │ ├── ForbiddenHttpResponse.php
│ │ ├── NotFoundHttpResponse.php
│ │ ├── ProcessResponse.php
│ │ ├── AbstractErrorHttpResponse.php
│ │ ├── StaticFileResponse.php
│ │ └── HttpResponse.php
│ ├── Handler
│ │ ├── LambdaEventHandlerInterface.php
│ │ ├── PingLambdaEventHandler.php
│ │ ├── PhpScriptLambdaEventHandler.php
│ │ ├── ConsoleCommandLambdaEventHandler.php
│ │ ├── AbstractPhpFpmRequestEventHandler.php
│ │ ├── WordPressLambdaEventHandler.php
│ │ ├── WarmUpEventHandler.php
│ │ ├── LambdaEventHandlerCollection.php
│ │ ├── AbstractHttpRequestEventHandler.php
│ │ ├── BedrockLambdaEventHandler.php
│ │ └── RadicleLambdaEventHandler.php
│ └── RuntimeApiClient.php
├── FastCgi
│ ├── FastCgiHttpResponse.php
│ ├── FastCgiServerClient.php
│ ├── PhpFpmProcess.php
│ └── FastCgiRequest.php
├── Logger.php
└── Runtime.php
├── release-please-config.json
├── LICENSE
├── README.md
├── composer.json
├── .php-cs-fixer.dist.php
├── templates
└── error.html.php
├── CHANGELOG.md
├── layers.php
└── layers.json
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | ".": "1.15.4"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/PingEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | /**
17 | * Lambda invocation event for a ping.
18 | */
19 | class PingEvent extends AbstractEvent
20 | {
21 | }
22 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/InvocationEventInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | /**
17 | * A Lambda invocation event from the runtime API.
18 | */
19 | interface InvocationEventInterface
20 | {
21 | /**
22 | * Get the ID of the Lambda invocation.
23 | */
24 | public function getId(): string;
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lambda/Response/ResponseInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Response;
15 |
16 | /**
17 | * A response sent back to the Lambda runtime API.
18 | */
19 | interface ResponseInterface
20 | {
21 | /**
22 | * Get the response data to send back to the Lambda runtime API.
23 | */
24 | public function getResponseData(): array;
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/PhpConsoleCommandEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | /**
17 | * Lambda invocation event for a console command.
18 | */
19 | class PhpConsoleCommandEvent extends ConsoleCommandEvent
20 | {
21 | /**
22 | * Constructor.
23 | */
24 | public function __construct(string $id, string $command)
25 | {
26 | parent::__construct($id, sprintf('/opt/bin/php %s', $command));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Lambda/Response/ForbiddenHttpResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Response;
15 |
16 | /**
17 | * A Lambda response for a 403 HTTP response.
18 | */
19 | class ForbiddenHttpResponse extends AbstractErrorHttpResponse
20 | {
21 | /**
22 | * Constructor.
23 | */
24 | public function __construct(string $message = 'Forbidden', string $templatesDirectory = '')
25 | {
26 | parent::__construct($message, 403, $templatesDirectory);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Lambda/Response/NotFoundHttpResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Response;
15 |
16 | /**
17 | * A Lambda response for a 404 HTTP response.
18 | */
19 | class NotFoundHttpResponse extends AbstractErrorHttpResponse
20 | {
21 | /**
22 | * Constructor.
23 | */
24 | public function __construct(string $message = 'Not Found', string $templatesDirectory = '')
25 | {
26 | parent::__construct($message, 404, $templatesDirectory);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3 | "bootstrap-sha": "53c85b142f6383f0e3a8168022b39eec30a95306",
4 | "plugins": [ "sentence-case" ],
5 | "skip-github-release": true,
6 | "packages": {
7 | ".": {
8 | "release-type": "php",
9 | "pull-request-title-pattern": "chore: release ${version}",
10 | "changelog-sections": [
11 | { "type" :"feat", "section" :"Features", "hidden": false },
12 | { "type":"fix", "section": "Bug Fixes", "hidden": false },
13 | { "type": "deps", "section": "Dependency Changes", "hidden": false },
14 | { "type":"chore", "section": "Miscellaneous" ,"hidden":true }
15 | ]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/AbstractEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | /**
17 | * Base Lambda invocation event from the runtime API.
18 | */
19 | abstract class AbstractEvent implements InvocationEventInterface
20 | {
21 | /**
22 | * The ID of the Lambda invocation.
23 | *
24 | * @var string
25 | */
26 | private $id;
27 |
28 | /**
29 | * Constructor.
30 | */
31 | public function __construct(string $id)
32 | {
33 | $this->id = $id;
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function getId(): string
40 | {
41 | return $this->id;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/ConsoleCommandEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | /**
17 | * Lambda invocation event for a console command.
18 | */
19 | class ConsoleCommandEvent extends AbstractEvent
20 | {
21 | /**
22 | * The console command to perform.
23 | *
24 | * @var string
25 | */
26 | private $command;
27 |
28 | /**
29 | * Constructor.
30 | */
31 | public function __construct(string $id, string $command)
32 | {
33 | parent::__construct($id);
34 |
35 | $this->command = $command;
36 | }
37 |
38 | /**
39 | * Get the command that the event wants to run.
40 | */
41 | public function getCommand(): string
42 | {
43 | return $this->command;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Carl Alexander
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/WarmUpEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | /**
17 | * Lambda invocation event for a warm up request.
18 | */
19 | class WarmUpEvent extends AbstractEvent
20 | {
21 | /**
22 | * The number of concurrent Lambda functions to keep warm.
23 | *
24 | * @var int
25 | */
26 | private $concurrency;
27 |
28 | /**
29 | * Constructor.
30 | */
31 | public function __construct(string $id, int $concurrency)
32 | {
33 | parent::__construct($id);
34 |
35 | $this->concurrency = $concurrency;
36 | }
37 |
38 | /**
39 | * Get the number of concurrent Lambda functions to keep warm.
40 | */
41 | public function getConcurrency(): int
42 | {
43 | return $this->concurrency;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/LambdaEventHandlerInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
17 | use Ymir\Runtime\Lambda\Response\ResponseInterface;
18 |
19 | /**
20 | * Event handlers take a Lambda invocation event and convert it to a response
21 | * to send back to the Lambda runtime API.
22 | */
23 | interface LambdaEventHandlerInterface
24 | {
25 | /**
26 | * Can the handler handle the given Lambda invocation event.
27 | */
28 | public function canHandle(InvocationEventInterface $event): bool;
29 |
30 | /**
31 | * Handles the given Lambda invocation event and returns the response
32 | * to send back to the Lambda runtime API.
33 | */
34 | public function handle(InvocationEventInterface $event): ResponseInterface;
35 | }
36 |
--------------------------------------------------------------------------------
/src/Lambda/Response/ProcessResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Response;
15 |
16 | use Symfony\Component\Process\Process;
17 |
18 | /**
19 | * A lambda response for a Symfony process.
20 | */
21 | class ProcessResponse implements ResponseInterface
22 | {
23 | /**
24 | * The process that we're generating a response for.
25 | *
26 | * @var Process
27 | */
28 | private $process;
29 |
30 | /**
31 | * Constructor.
32 | */
33 | public function __construct(Process $process)
34 | {
35 | $this->process = $process;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function getResponseData(): array
42 | {
43 | return [
44 | 'exitCode' => $this->process->getExitCode(),
45 | 'output' => $this->process->getOutput(),
46 | ];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Lambda/Response/AbstractErrorHttpResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Response;
15 |
16 | /**
17 | * A Lambda response for a 4XX/5XX HTTP responses.
18 | */
19 | abstract class AbstractErrorHttpResponse extends HttpResponse
20 | {
21 | /**
22 | * Constructor.
23 | */
24 | public function __construct(string $message, int $statusCode, string $templatesDirectory = '')
25 | {
26 | if (empty($templatesDirectory) && getenv('LAMBDA_TASK_ROOT')) {
27 | $templatesDirectory = '/opt/templates';
28 | }
29 |
30 | $body = '';
31 | $template = $templatesDirectory.'/error.html.php';
32 |
33 | if (file_exists($template)) {
34 | ob_start();
35 |
36 | include $template;
37 |
38 | $body = (string) ob_get_clean();
39 | }
40 |
41 | parent::__construct($body, [], $statusCode, '2.0');
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/FastCgi/FastCgiHttpResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\FastCgi;
15 |
16 | use hollodotme\FastCGI\Interfaces\ProvidesResponseData;
17 | use Ymir\Runtime\Lambda\Response\HttpResponse;
18 |
19 | /**
20 | * A Lambda response from a FastCGI server.
21 | */
22 | class FastCgiHttpResponse extends HttpResponse
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function __construct(ProvidesResponseData $response, string $formatVersion = '1.0', bool $compressible = true)
28 | {
29 | $headers = array_change_key_case($response->getHeaders(), CASE_LOWER);
30 | $statusCode = 200;
31 |
32 | if (isset($headers['status'][0])) {
33 | $statusCode = (int) explode(' ', $headers['status'][0])[0];
34 | }
35 |
36 | unset($headers['status']);
37 |
38 | parent::__construct($response->getBody(), $headers, $statusCode, $formatVersion, $compressible);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/PingLambdaEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
17 | use Ymir\Runtime\Lambda\InvocationEvent\PingEvent;
18 | use Ymir\Runtime\Lambda\Response\HttpResponse;
19 | use Ymir\Runtime\Lambda\Response\ResponseInterface;
20 |
21 | /**
22 | * Lambda invocation event handler for pings.
23 | */
24 | class PingLambdaEventHandler implements LambdaEventHandlerInterface
25 | {
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function canHandle(InvocationEventInterface $event): bool
30 | {
31 | return $event instanceof PingEvent;
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function handle(InvocationEventInterface $event): ResponseInterface
38 | {
39 | if (!$event instanceof PingEvent) {
40 | throw new \InvalidArgumentException('PingLambdaEventHandler can only handle PingEvent objects');
41 | }
42 |
43 | usleep(50 * 1000);
44 |
45 | return new HttpResponse('Pong');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # Ymir PHP Runtime
8 |
9 | [](https://github.com/ymirapp/php-runtime/actions)
10 |
11 | [Ymir][1] PHP runtime for [AWS Lambda][2].
12 |
13 | ## Acknowledgements
14 |
15 | This PHP runtime wouldn't exist without the tireless work of [Matthieu Napoli][3] and the rest of the [bref][4] contributors.
16 | Most of the code to compile PHP started from their implementation. The initial inspiration for the PHP code comes from an
17 | [interview][5] between [Adam Wathan][6] and [Taylor Otwell][7] about [Laravel Valet][8].
18 |
19 | [](https://depot.dev/?utm_source=Ymir)
20 |
21 | ## How to use the runtime
22 |
23 | The runtime layers are publicly available. You can just add one to your Lambda function. You'll find all the current layer ARN in the `layers.json` or `layers.php` files.
24 |
25 | ## Contributing
26 |
27 | Install dependencies using composer:
28 |
29 | ```console
30 | $ composer install
31 | ```
32 |
33 | Run tests using phpunit:
34 |
35 | ```console
36 | $ vendor/bin/phpunit
37 | ```
38 |
39 | ## Links
40 |
41 | * [Documentation][9]
42 |
43 | [1]: https://ymirapp.com
44 | [2]: https://aws.amazon.com/lambda/
45 | [3]: https://github.com/mnapoli
46 | [4]: https://github.com/brefphp/bref
47 | [5]: https://fullstackradio.com/120
48 | [6]: https://github.com/adamwathan
49 | [7]: https://github.com/taylorotwell
50 | [8]: https://github.com/laravel/valet
51 | [9]: https://docs.ymirapp.com
52 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/PhpScriptLambdaEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\FastCgi\PhpFpmProcess;
17 | use Ymir\Runtime\Lambda\InvocationEvent\HttpRequestEvent;
18 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
19 | use Ymir\Runtime\Logger;
20 |
21 | /**
22 | * Lambda invocation event handler for a specific PHP script.
23 | */
24 | class PhpScriptLambdaEventHandler extends AbstractPhpFpmRequestEventHandler
25 | {
26 | /**
27 | * The path to the PHP script that this event handler uses.
28 | *
29 | * @var string
30 | */
31 | private $scriptFilePath;
32 |
33 | /**
34 | * Constructor.
35 | */
36 | public function __construct(Logger $logger, PhpFpmProcess $process, string $rootDirectory, string $scriptFilename)
37 | {
38 | parent::__construct($logger, $process, $rootDirectory);
39 |
40 | $this->scriptFilePath = $this->rootDirectory.'/'.ltrim($scriptFilename, '/');
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function canHandle(InvocationEventInterface $event): bool
47 | {
48 | return parent::canHandle($event)
49 | && false !== stripos($this->scriptFilePath, '.php')
50 | && file_exists($this->scriptFilePath);
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | protected function getScriptFilePath(HttpRequestEvent $event): string
57 | {
58 | return $this->scriptFilePath;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Lambda/Response/StaticFileResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Response;
15 |
16 | use Mimey\MimeTypes;
17 | use Mimey\MimeTypesInterface;
18 |
19 | /**
20 | * A Lambda response for a static file.
21 | */
22 | class StaticFileResponse implements ResponseInterface
23 | {
24 | /**
25 | * The path to the file that we're sending back to the Lambda runtime API.
26 | *
27 | * @var string
28 | */
29 | private $filePath;
30 |
31 | /**
32 | * MIME type handling library.
33 | *
34 | * @var MimeTypesInterface
35 | */
36 | private $mimeTypes;
37 |
38 | /**
39 | * Constructor.
40 | */
41 | public function __construct(string $filePath, MimeTypesInterface $mimeTypes = null)
42 | {
43 | $this->filePath = $filePath;
44 | $this->mimeTypes = $mimeTypes ?? new MimeTypes();
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function getResponseData(): array
51 | {
52 | $file = file_get_contents($this->filePath);
53 |
54 | if (!is_string($file)) {
55 | throw new \RuntimeException(sprintf('Unable to get the contents of "%s"', $this->filePath));
56 | }
57 |
58 | $contentType = null;
59 | $extension = pathinfo($this->filePath, PATHINFO_EXTENSION);
60 |
61 | if (is_string($extension)) {
62 | $contentType = $this->mimeTypes->getMimeType($extension);
63 | }
64 |
65 | return [
66 | 'isBase64Encoded' => true,
67 | 'statusCode' => 200,
68 | 'headers' => ['Content-Type' => $contentType ?? 'text/plain'],
69 | 'body' => base64_encode($file),
70 | ];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/FastCgi/FastCgiServerClient.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\FastCgi;
15 |
16 | use hollodotme\FastCGI\Client;
17 | use hollodotme\FastCGI\Interfaces\ConfiguresSocketConnection;
18 | use hollodotme\FastCGI\Interfaces\ProvidesRequestData;
19 | use hollodotme\FastCGI\Interfaces\ProvidesResponseData;
20 | use hollodotme\FastCGI\SocketConnections\UnixDomainSocket;
21 |
22 | /**
23 | * A client that connects to a FastCGI server.
24 | */
25 | class FastCgiServerClient
26 | {
27 | /**
28 | * The FastCGI client used to interact with the PHP-FPM process.
29 | *
30 | * @var Client
31 | */
32 | private $client;
33 |
34 | /**
35 | * The FastCGI socket connection.
36 | *
37 | * @var ConfiguresSocketConnection
38 | */
39 | private $socketConnection;
40 |
41 | /**
42 | * Constructor.
43 | */
44 | public function __construct(Client $client, ConfiguresSocketConnection $socketConnection)
45 | {
46 | $this->client = $client;
47 | $this->socketConnection = $socketConnection;
48 | }
49 |
50 | /**
51 | * Create a FastCGI client for the given unix socket path.
52 | */
53 | public static function createFromSocketPath(string $socketPath, int $connectTimeout = 1000, int $readWriteTimeout = 900000): self
54 | {
55 | return new self(new Client(), new UnixDomainSocket($socketPath, $connectTimeout, $readWriteTimeout));
56 | }
57 |
58 | /**
59 | * Handles the given request and return the response from the FastCGI socket.
60 | */
61 | public function handle(ProvidesRequestData $request): ProvidesResponseData
62 | {
63 | return $this->client->sendRequest($this->socketConnection, $request);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ymirapp/php-runtime",
3 | "description": "Ymir PHP runtime",
4 | "keywords": [
5 | "aws",
6 | "lambda",
7 | "php"
8 | ],
9 | "type": "project",
10 | "license": "MIT",
11 | "authors": [
12 | {
13 | "name": "Carl Alexander",
14 | "email": "support@ymirapp.com",
15 | "homepage": "https://ymirapp.com"
16 | }
17 | ],
18 | "require": {
19 | "php": ">=7.2.5",
20 | "ext-curl": "*",
21 | "ext-fileinfo": "*",
22 | "ext-json": "*",
23 | "ext-pcntl": "*",
24 | "ext-posix": "*",
25 | "ext-zlib": "*",
26 | "async-aws/lambda": "^1.0",
27 | "async-aws/ssm": "^1.0",
28 | "hollodotme/fast-cgi-client": "^3.0",
29 | "monolog/monolog": "^1.0",
30 | "ralouphie/mimey": "^2.1",
31 | "symfony/http-client": "^5.4",
32 | "symfony/polyfill-php80": "^1.29",
33 | "symfony/process": "^5.4",
34 | "tightenco/collect": "^7.19"
35 | },
36 | "require-dev": {
37 | "aws/aws-sdk-php": "^3.171",
38 | "friendsofphp/php-cs-fixer": "^3.0",
39 | "php-mock/php-mock-phpunit": "^2.6",
40 | "phpstan/phpstan": "^1.0",
41 | "phpunit/phpunit": "^8.0"
42 | },
43 | "config": {
44 | "optimize-autoloader": true,
45 | "platform": {
46 | "php": "7.4.33"
47 | },
48 | "platform-check": false,
49 | "preferred-install": "dist",
50 | "sort-packages": true
51 | },
52 | "autoload": {
53 | "psr-4": {
54 | "Ymir\\Runtime\\": "src"
55 | }
56 | },
57 | "autoload-dev": {
58 | "psr-4": {
59 | "Ymir\\Runtime\\Tests\\": "tests"
60 | }
61 | },
62 | "prefer-stable": true,
63 | "scripts": {
64 | "php-cs-fixer": "php-cs-fixer fix --dry-run --allow-risky=yes",
65 | "phpstan": "phpstan analyze --ansi",
66 | "tests-unit": "phpunit --testsuite unit"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/ConsoleCommandLambdaEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Symfony\Component\Process\Process;
17 | use Ymir\Runtime\Lambda\InvocationEvent\ConsoleCommandEvent;
18 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
19 | use Ymir\Runtime\Lambda\Response\ProcessResponse;
20 | use Ymir\Runtime\Lambda\Response\ResponseInterface;
21 | use Ymir\Runtime\Logger;
22 |
23 | /**
24 | * Lambda invocation event handler for console commands.
25 | */
26 | class ConsoleCommandLambdaEventHandler implements LambdaEventHandlerInterface
27 | {
28 | /**
29 | * The logger that sends logs to CloudWatch.
30 | *
31 | * @var Logger
32 | */
33 | private $logger;
34 |
35 | /**
36 | * Constructor.
37 | */
38 | public function __construct(Logger $logger)
39 | {
40 | $this->logger = $logger;
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function canHandle(InvocationEventInterface $event): bool
47 | {
48 | return $event instanceof ConsoleCommandEvent;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function handle(InvocationEventInterface $event): ResponseInterface
55 | {
56 | if (!$event instanceof ConsoleCommandEvent) {
57 | throw new \InvalidArgumentException('ConsoleCommandLambdaEventHandler can only handle ConsoleCommandEvent objects');
58 | }
59 |
60 | $process = Process::fromShellCommandline("{$event->getCommand()} 2>&1");
61 | $process->setTimeout(null);
62 | $process->run(function ($type, $output) {
63 | $this->logger->info($output);
64 | });
65 |
66 | return new ProcessResponse($process);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/AbstractPhpFpmRequestEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\FastCgi\FastCgiHttpResponse;
17 | use Ymir\Runtime\FastCgi\FastCgiRequest;
18 | use Ymir\Runtime\FastCgi\PhpFpmProcess;
19 | use Ymir\Runtime\Lambda\InvocationEvent\HttpRequestEvent;
20 | use Ymir\Runtime\Lambda\Response\HttpResponse;
21 | use Ymir\Runtime\Logger;
22 |
23 | /**
24 | * Base Lambda invocation event handler for handlers that use PHP-FPM.
25 | */
26 | abstract class AbstractPhpFpmRequestEventHandler extends AbstractHttpRequestEventHandler
27 | {
28 | /**
29 | * The logger that sends logs to CloudWatch.
30 | *
31 | * @var Logger
32 | */
33 | private $logger;
34 |
35 | /**
36 | * The PHP-FPM process.
37 | *
38 | * @var PhpFpmProcess
39 | */
40 | private $process;
41 |
42 | /**
43 | * Constructor.
44 | */
45 | public function __construct(Logger $logger, PhpFpmProcess $process, string $rootDirectory)
46 | {
47 | parent::__construct($rootDirectory);
48 |
49 | $this->logger = $logger;
50 | $this->process = $process;
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | protected function createLambdaEventResponse(HttpRequestEvent $event): HttpResponse
57 | {
58 | $request = FastCgiRequest::createFromInvocationEvent($event, $this->getScriptFilePath($event));
59 |
60 | $this->logger->debug('FastCgi request sent:', [
61 | 'content' => $request->getContent(),
62 | 'parameters' => $request->getParams(),
63 | ]);
64 |
65 | return new FastCgiHttpResponse($this->process->handle($request), $event->getPayloadVersion(), in_array('gzip', $request->getAcceptableEncodings()));
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | protected function isStaticFile(string $path): bool
72 | {
73 | return parent::isStaticFile($path) && false === stripos($path, '.php');
74 | }
75 |
76 | /**
77 | * Get the path to script file to pass to PHP-FPM based on the Lambda invocation event.
78 | */
79 | abstract protected function getScriptFilePath(HttpRequestEvent $event): string;
80 | }
81 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/WordPressLambdaEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\HttpRequestEvent;
17 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
18 |
19 | /**
20 | * Lambda invocation event handler for a regular WordPress installation.
21 | */
22 | class WordPressLambdaEventHandler extends AbstractPhpFpmRequestEventHandler
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function canHandle(InvocationEventInterface $event): bool
28 | {
29 | return parent::canHandle($event)
30 | && file_exists($this->rootDirectory.'/index.php')
31 | && file_exists($this->rootDirectory.'/wp-config.php');
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | protected function getEventFilePath(HttpRequestEvent $event): string
38 | {
39 | $path = $event->getPath();
40 |
41 | if (1 === preg_match('%^(.+\.php)%i', $path, $matches)) {
42 | $path = $matches[1];
43 | }
44 |
45 | if ($this->isMultisite() && (1 === preg_match('/^(.*)?(\/wp-(content|admin|includes).*)/', $path, $matches) || 1 === preg_match('/^(.*)?(\/.*\.php)/', $path, $matches))) {
46 | $path = $matches[2];
47 | }
48 |
49 | return $this->rootDirectory.'/'.ltrim($path, '/');
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | */
55 | protected function getScriptFilePath(HttpRequestEvent $event): string
56 | {
57 | $filePath = $this->getEventFilePath($event);
58 |
59 | if (is_dir($filePath)) {
60 | $filePath = rtrim($filePath, '/').'/index.php';
61 | }
62 |
63 | return file_exists($filePath) ? $filePath : $this->rootDirectory.'/index.php';
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | protected function isPubliclyAccessible(string $filePath): bool
70 | {
71 | return 1 !== preg_match('/(wp-config\.php|readme\.html|license\.txt|wp-cli\.local\.yml|wp-cli\.yml)$/', $filePath);
72 | }
73 |
74 | /**
75 | * Checks if we're dealing with a multisite installation or not.
76 | */
77 | private function isMultisite(): bool
78 | {
79 | $wpConfig = file_get_contents($this->rootDirectory.'/wp-config.php');
80 |
81 | return is_string($wpConfig) && 1 === preg_match('/define\(\s*(\'|\")MULTISITE\1\s*,\s*true\s*\)/', $wpConfig);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/WarmUpEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use AsyncAws\Lambda\LambdaClient;
17 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
18 | use Ymir\Runtime\Lambda\InvocationEvent\WarmUpEvent;
19 | use Ymir\Runtime\Lambda\Response\HttpResponse;
20 | use Ymir\Runtime\Lambda\Response\ResponseInterface;
21 |
22 | /**
23 | * Lambda invocation event handler for warming up Lambda functions.
24 | */
25 | class WarmUpEventHandler implements LambdaEventHandlerInterface
26 | {
27 | /**
28 | * Lambda API client.
29 | *
30 | * @var LambdaClient
31 | */
32 | private $lambdaClient;
33 |
34 | /**
35 | * Constructor.
36 | */
37 | public function __construct(LambdaClient $lambdaClient)
38 | {
39 | $this->lambdaClient = $lambdaClient;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function canHandle(InvocationEventInterface $event): bool
46 | {
47 | return $event instanceof WarmUpEvent;
48 | }
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | public function handle(InvocationEventInterface $event): ResponseInterface
54 | {
55 | if (!$event instanceof WarmUpEvent) {
56 | throw new \InvalidArgumentException('WarmUpEventHandler can only handle WarmUpEvent objects');
57 | }
58 |
59 | $concurrency = $event->getConcurrency();
60 |
61 | if (1 >= $concurrency) {
62 | return new HttpResponse('No additional function warmed up');
63 | }
64 |
65 | $functionName = getenv('AWS_LAMBDA_FUNCTION_NAME');
66 |
67 | if (!is_string($functionName)) {
68 | throw new \RuntimeException('"AWS_LAMBDA_FUNCTION_NAME" environment variable is\'t set');
69 | }
70 |
71 | // The first Lambda function invoked will be the one running this code. So, if we want the number of concurrent
72 | // Lambda functions to match, we need to keep the concurrency number the same and not subtract one from it.
73 | for ($i = 0; $i < $concurrency; ++$i) {
74 | $this->lambdaClient->invoke([
75 | 'FunctionName' => $functionName,
76 | 'Qualifier' => 'deployed',
77 | 'InvocationType' => 'Event',
78 | 'LogType' => 'None',
79 | 'Payload' => '{"ping": true}',
80 | ]);
81 | }
82 |
83 | return new HttpResponse('Warmed up additional functions');
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Logger.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime;
15 |
16 | use Monolog\Formatter\LineFormatter;
17 | use Monolog\Handler\StreamHandler;
18 | use Monolog\Logger as MonologLogger;
19 |
20 | /**
21 | * Logger that outputs logs to CloudWatch.
22 | */
23 | class Logger extends MonologLogger
24 | {
25 | /**
26 | * The logging level of the logger.
27 | *
28 | * @var int
29 | */
30 | private $level;
31 |
32 | /**
33 | * The stream to use with the logger.
34 | *
35 | * @var resource|string
36 | */
37 | private $stream;
38 |
39 | /**
40 | * Constructor.
41 | */
42 | public function __construct($level, $stream = STDERR)
43 | {
44 | $this->level = self::toMonologLevel($level);
45 | $this->stream = $stream;
46 |
47 | parent::__construct('ymir', [$this->getStreamHandler()]);
48 | }
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | public function addRecord($level, $message, array $context = []): bool
54 | {
55 | // When killing the Lambda container, it appears that PHP closes the stream with the `__destruct` method before
56 | // we're done sending logs. We use a try/catch block here in order to reset the stream handler that way we can
57 | // send the final logs during the shutdown.
58 | try {
59 | return parent::addRecord($level, $message, $context);
60 | } catch (\LogicException $exception) {
61 | $this->setHandlers([$this->getStreamHandler()]);
62 |
63 | return parent::addRecord($level, $message, $context);
64 | }
65 | }
66 |
67 | /**
68 | * Logs an exception.
69 | */
70 | public function exception(\Throwable $exception): void
71 | {
72 | $errorMessage = $exception->getMessage();
73 |
74 | if ($exception instanceof \Exception) {
75 | $errorMessage = sprintf('Uncaught %s: %s', get_class($exception), $errorMessage);
76 | }
77 |
78 | $this->alert(sprintf(
79 | "Fatal error: %s in %s:%d\nStack trace:\n%s",
80 | $errorMessage,
81 | $exception->getFile(),
82 | $exception->getLine(),
83 | $exception->getTraceAsString()
84 | ));
85 | }
86 |
87 | /**
88 | * Get the stream handler used by the logger.
89 | */
90 | private function getStreamHandler(): StreamHandler
91 | {
92 | $handler = new StreamHandler($this->stream, $this->level);
93 |
94 | $handler->setFormatter(new LineFormatter("%message% %context% %extra%\n", null, true, true));
95 |
96 | return $handler;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/LambdaEventHandlerCollection.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
17 | use Ymir\Runtime\Lambda\Response\ResponseInterface;
18 | use Ymir\Runtime\Logger;
19 |
20 | /**
21 | * A collection of Lambda invocation event handlers.
22 | */
23 | class LambdaEventHandlerCollection implements LambdaEventHandlerInterface
24 | {
25 | /**
26 | * The Lambda invocation event handlers handled by the collection.
27 | *
28 | * @var LambdaEventHandlerInterface[]
29 | */
30 | private $handlers;
31 |
32 | /**
33 | * The logger that sends logs to CloudWatch.
34 | *
35 | * @var Logger
36 | */
37 | private $logger;
38 |
39 | /**
40 | * Constructor.
41 | */
42 | public function __construct(Logger $logger, array $handlers = [])
43 | {
44 | $this->logger = $logger;
45 |
46 | foreach ($handlers as $handler) {
47 | $this->addHandler($handler);
48 | }
49 | }
50 |
51 | /**
52 | * Add a new Lambda invocation event handler to the collection.
53 | */
54 | public function addHandler(LambdaEventHandlerInterface $handler): void
55 | {
56 | $this->handlers[] = $handler;
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function canHandle(InvocationEventInterface $event): bool
63 | {
64 | return $this->getHandlerForEvent($event) instanceof LambdaEventHandlerInterface;
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | public function handle(InvocationEventInterface $event): ResponseInterface
71 | {
72 | $handler = $this->getHandlerForEvent($event);
73 |
74 | if (!$handler instanceof LambdaEventHandlerInterface) {
75 | throw new \RuntimeException('No handler found to handle the event');
76 | }
77 |
78 | $this->logger->debug(sprintf('"%s" handler selected for the event', get_class($handler)));
79 |
80 | $response = $handler->handle($event);
81 |
82 | $this->logger->debug(sprintf('"%s" handler response:', get_class($handler)), $response->getResponseData());
83 |
84 | return $response;
85 | }
86 |
87 | /**
88 | * Get the Lambda invocation event handler than can handle the given Lambda invocation event.
89 | */
90 | private function getHandlerForEvent(InvocationEventInterface $event): ?LambdaEventHandlerInterface
91 | {
92 | $handler = collect($this->handlers)->first(function (LambdaEventHandlerInterface $handler) use ($event) {
93 | return $handler->canHandle($event);
94 | });
95 |
96 | return $handler instanceof LambdaEventHandlerInterface ? $handler : null;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/AbstractHttpRequestEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\HttpRequestEvent;
17 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
18 | use Ymir\Runtime\Lambda\Response\HttpResponse;
19 | use Ymir\Runtime\Lambda\Response\NotFoundHttpResponse;
20 | use Ymir\Runtime\Lambda\Response\ResponseInterface;
21 | use Ymir\Runtime\Lambda\Response\StaticFileResponse;
22 |
23 | /**
24 | * Base handler for HTTP request events.
25 | */
26 | abstract class AbstractHttpRequestEventHandler implements LambdaEventHandlerInterface
27 | {
28 | /**
29 | * The Lambda root directory.
30 | *
31 | * @var string
32 | */
33 | protected $rootDirectory;
34 |
35 | /**
36 | * Constructor.
37 | */
38 | public function __construct(string $rootDirectory)
39 | {
40 | $this->rootDirectory = rtrim($rootDirectory, '/');
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function canHandle(InvocationEventInterface $event): bool
47 | {
48 | return $event instanceof HttpRequestEvent;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function handle(InvocationEventInterface $event): ResponseInterface
55 | {
56 | if (!$event instanceof HttpRequestEvent || !$this->canHandle($event)) {
57 | throw new \InvalidArgumentException(sprintf('%s cannot handle the given invocation event object', (new \ReflectionClass(static::class))->getShortName()));
58 | }
59 |
60 | $filePath = $this->getEventFilePath($event);
61 |
62 | if (!$this->isPubliclyAccessible($filePath)) {
63 | return new NotFoundHttpResponse();
64 | }
65 |
66 | return $this->isStaticFile($filePath) ? new StaticFileResponse($filePath) : $this->createLambdaEventResponse($event);
67 | }
68 |
69 | /**
70 | * Get the file path requested by the given Lambda invocation event.
71 | */
72 | protected function getEventFilePath(HttpRequestEvent $event): string
73 | {
74 | return $this->rootDirectory.'/'.ltrim($event->getPath(), '/');
75 | }
76 |
77 | /**
78 | * Checks if the given file path is publicly accessible.
79 | */
80 | protected function isPubliclyAccessible(string $filePath): bool
81 | {
82 | return true;
83 | }
84 |
85 | /**
86 | * Checks if the given path is for a static file.
87 | */
88 | protected function isStaticFile(string $path): bool
89 | {
90 | return !is_dir($path) && file_exists($path);
91 | }
92 |
93 | /**
94 | * Create the Lambda response for the given Lambda invocation event.
95 | */
96 | abstract protected function createLambdaEventResponse(HttpRequestEvent $event): HttpResponse;
97 | }
98 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/BedrockLambdaEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\HttpRequestEvent;
17 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
18 |
19 | /**
20 | * Lambda invocation event handler for a Bedrock WordPress installation.
21 | */
22 | class BedrockLambdaEventHandler extends AbstractPhpFpmRequestEventHandler
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function canHandle(InvocationEventInterface $event): bool
28 | {
29 | return parent::canHandle($event)
30 | && (file_exists($this->rootDirectory.'/web/app/mu-plugins/bedrock-autoloader.php')
31 | || (is_dir($this->rootDirectory.'/web/app/') && file_exists($this->rootDirectory.'/web/wp-config.php') && file_exists($this->rootDirectory.'/config/application.php')));
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | protected function getEventFilePath(HttpRequestEvent $event): string
38 | {
39 | $path = $event->getPath();
40 |
41 | if (1 === preg_match('%^(.+\.php)%i', $path, $matches)) {
42 | $path = $matches[1];
43 | }
44 |
45 | if ($this->isMultisite() && (1 === preg_match('/^(.*)?(\/wp-(content|admin|includes).*)/', $path, $matches) || 1 === preg_match('/^(.*)?(\/.*\.php)$/', $path, $matches))) {
46 | $path = 'wp/'.ltrim($matches[2], '/');
47 | } elseif ('/wp-login.php' !== $path && 1 === preg_match('/^\/(wp-.*.php)$/', $path, $matches) || 1 === preg_match('/\/(wp-(content|admin|includes).*)/', $path, $matches)) {
48 | $path = 'wp/'.ltrim($matches[1], '/');
49 | }
50 |
51 | $path = ltrim($path, '/');
52 |
53 | if (!str_starts_with($path, 'web/')) {
54 | $path = 'web/'.$path;
55 | }
56 |
57 | return $this->rootDirectory.'/'.$path;
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | protected function getScriptFilePath(HttpRequestEvent $event): string
64 | {
65 | $filePath = $this->getEventFilePath($event);
66 |
67 | if (is_dir($filePath)) {
68 | $filePath = rtrim($filePath, '/').'/index.php';
69 | }
70 |
71 | return file_exists($filePath) ? $filePath : $this->rootDirectory.'/web/index.php';
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | protected function isPubliclyAccessible(string $filePath): bool
78 | {
79 | return 1 !== preg_match('/(composer\.(json|lock)|composer\/installed\.json|wp-cli\.local\.yml|wp-cli\.yml)$/', $filePath);
80 | }
81 |
82 | /**
83 | * Checks if we're dealing with a multisite installation or not.
84 | */
85 | private function isMultisite(): bool
86 | {
87 | $application = file_get_contents($this->rootDirectory.'/config/application.php');
88 |
89 | return is_string($application) && 1 === preg_match('/Config::define\(\s*(\'|\")MULTISITE\1\s*,\s*true\s*\)/', $application);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Lambda/Handler/RadicleLambdaEventHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Handler;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\HttpRequestEvent;
17 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
18 |
19 | /**
20 | * Lambda invocation event handler for a Radicle WordPress installation.
21 | */
22 | class RadicleLambdaEventHandler extends AbstractPhpFpmRequestEventHandler
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function canHandle(InvocationEventInterface $event): bool
28 | {
29 | return parent::canHandle($event)
30 | && (file_exists($this->rootDirectory.'/public/content/mu-plugins/bedrock-autoloader.php')
31 | || (is_dir($this->rootDirectory.'/public/') && file_exists($this->rootDirectory.'/public/wp-config.php') && file_exists($this->rootDirectory.'/bedrock/application.php')));
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | protected function getEventFilePath(HttpRequestEvent $event): string
38 | {
39 | $path = $event->getPath();
40 |
41 | if (1 === preg_match('%^(.+\.php)%i', $path, $matches)) {
42 | $path = $matches[1];
43 | }
44 |
45 | if ($this->isMultisite() && (1 === preg_match('/^(.*)?(\/wp-(content|admin|includes).*)/', $path, $matches) || 1 === preg_match('/^(.*)?(\/.*\.php)$/', $path, $matches))) {
46 | $path = 'wp/'.ltrim($matches[2], '/');
47 | } elseif ('/wp-login.php' !== $path && 1 === preg_match('/^\/(wp-.*.php)$/', $path, $matches) || 1 === preg_match('/\/(wp-(content|admin|includes).*)/', $path, $matches)) {
48 | $path = 'wp/'.ltrim($matches[1], '/');
49 | }
50 |
51 | $path = ltrim($path, '/');
52 |
53 | if (!str_starts_with($path, 'public/')) {
54 | $path = 'public/'.$path;
55 | }
56 |
57 | return $this->rootDirectory.'/'.$path;
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | protected function getScriptFilePath(HttpRequestEvent $event): string
64 | {
65 | $filePath = $this->getEventFilePath($event);
66 |
67 | if (is_dir($filePath)) {
68 | $filePath = rtrim($filePath, '/').'/index.php';
69 | }
70 |
71 | return file_exists($filePath) ? $filePath : $this->rootDirectory.'/public/index.php';
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | protected function isPubliclyAccessible(string $filePath): bool
78 | {
79 | return 1 !== preg_match('/(composer\.(json|lock)|composer\/installed\.json|wp-cli\.local\.yml|wp-cli\.yml)$/', $filePath);
80 | }
81 |
82 | /**
83 | * Checks if we're dealing with a multisite installation or not.
84 | */
85 | private function isMultisite(): bool
86 | {
87 | $application = file_get_contents($this->rootDirectory.'/bedrock/application.php');
88 |
89 | return is_string($application) && 1 === preg_match('/Config::define\(\s*(\'|\")MULTISITE\1\s*,\s*true\s*\)/', $application);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 |
7 |
8 | For the full copyright and license information, please view the LICENSE
9 | file that was distributed with this source code.
10 | EOF;
11 |
12 | $finder = PhpCsFixer\Finder::create()
13 | ->ignoreDotFiles(false)
14 | ->ignoreVCSIgnored(true)
15 | ->in([
16 | __DIR__ . '/src',
17 | __DIR__ . '/tests',
18 | ])
19 | ;
20 |
21 | $config = new PhpCsFixer\Config();
22 | $config
23 | ->setRiskyAllowed(true)
24 | ->setRules([
25 | '@Symfony' => true,
26 | '@Symfony:risky' => true,
27 | 'align_multiline_comment' => true,
28 | 'array_syntax' => ['syntax' => 'short'],
29 | 'blank_line_before_statement' => true,
30 | 'combine_consecutive_issets' => true,
31 | 'combine_consecutive_unsets' => true,
32 | 'declare_strict_types' => true,
33 | // one should use PHPUnit methods to set up expected exception instead of annotations
34 | 'general_phpdoc_annotation_remove' => ['annotations' => ['expectedException', 'expectedExceptionMessage', 'expectedExceptionMessageRegExp']],
35 | 'get_class_to_class_keyword' => false,
36 | 'header_comment' => ['header' => $header],
37 | 'heredoc_to_nowdoc' => true,
38 | 'method_chaining_indentation' => false,
39 | 'native_constant_invocation' => false,
40 | 'native_function_invocation' => false,
41 | 'no_extra_blank_lines' => ['tokens' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block']],
42 | 'no_null_property_initialization' => true,
43 | 'echo_tag_syntax' => ['format' => 'short'],
44 | 'no_superfluous_phpdoc_tags' => ['allow_mixed' => false],
45 | 'no_unneeded_curly_braces' => true,
46 | 'no_unneeded_final_method' => true,
47 | 'no_unreachable_default_argument_value' => true,
48 | 'no_useless_else' => true,
49 | 'no_useless_return' => true,
50 | 'ordered_class_elements' => [
51 | 'order' => [
52 | 'use_trait',
53 | 'constant_public',
54 | 'constant_protected',
55 | 'constant_private',
56 | 'property_public',
57 | 'property_protected',
58 | 'property_private',
59 | 'construct',
60 | 'destruct',
61 | 'magic',
62 | 'phpunit',
63 | 'method_public_static',
64 | 'method_protected_static',
65 | 'method_private_static',
66 | 'method_public',
67 | 'method_public_abstract',
68 | 'method_protected',
69 | 'method_protected_abstract',
70 | 'method_private',
71 | ],
72 | 'sort_algorithm' => 'alpha',
73 | ],
74 | 'ordered_imports' => true,
75 | 'php_unit_construct' => true,
76 | 'php_unit_dedicate_assert' => true,
77 | 'phpdoc_order' => true,
78 | 'phpdoc_types_order' => ['null_adjustment' => 'always_last'],
79 | 'semicolon_after_instruction' => true,
80 | 'single_line_comment_style' => true,
81 | 'yoda_style' => true,
82 | ])
83 | ->setFinder($finder)
84 | ;
85 |
86 | return $config;
87 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/InvocationEventFactory.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | use Ymir\Runtime\Logger;
17 |
18 | /**
19 | * Factory that creates Lambda invocation events from the runtime API.
20 | */
21 | class InvocationEventFactory
22 | {
23 | /**
24 | * Create a new Lambda event from the Lambda next invocation API.
25 | *
26 | * This call is blocking because the Lambda runtime API is blocking.
27 | */
28 | public static function createFromApi($handle, Logger $logger): InvocationEventInterface
29 | {
30 | if (!self::isHandle($handle)) {
31 | throw new \RuntimeException('The given "handle" must be a resource or a CurlHandle object');
32 | }
33 |
34 | $requestId = '';
35 | curl_setopt($handle, CURLOPT_HEADERFUNCTION, function ($handle, $header) use (&$requestId) {
36 | if (!preg_match('/:\s*/', $header)) {
37 | return strlen($header);
38 | }
39 |
40 | [$name, $value] = (array) preg_split('/:\s*/', $header, 2);
41 |
42 | if ('lambda-runtime-aws-request-id' == strtolower((string) $name)) {
43 | $requestId = trim((string) $value);
44 | }
45 |
46 | return strlen($header);
47 | });
48 |
49 | $body = '';
50 | curl_setopt($handle, CURLOPT_WRITEFUNCTION, function ($handle, $chunk) use (&$body) {
51 | $body .= $chunk;
52 |
53 | return strlen($chunk);
54 | });
55 |
56 | curl_exec($handle);
57 |
58 | if (curl_error($handle)) {
59 | throw new \Exception('Failed to get the next Lambda invocation: '.curl_error($handle));
60 | } elseif ('' === $requestId) {
61 | throw new \Exception('Unable to parse the Lambda invocation ID');
62 | } elseif ('' === $body) {
63 | throw new \Exception('Unable to parse the Lambda runtime API response');
64 | }
65 |
66 | $event = json_decode($body, true);
67 |
68 | if (!is_array($event)) {
69 | throw new \Exception('Unable to decode the Lambda runtime API response');
70 | }
71 |
72 | $logger->debug('Lambda event received:', $event);
73 |
74 | return self::createFromInvocationEvent($requestId, $event);
75 | }
76 |
77 | /**
78 | * Creates a new invocation event object based on the given event information from the Lambda runtime API.
79 | */
80 | public static function createFromInvocationEvent(string $requestId, array $event): InvocationEventInterface
81 | {
82 | $invocationEvent = null;
83 |
84 | if (isset($event['command'])) {
85 | $invocationEvent = new ConsoleCommandEvent($requestId, (string) $event['command']);
86 | } elseif (isset($event['httpMethod']) || isset($event['requestContext']['http']['method'])) {
87 | $invocationEvent = new HttpRequestEvent($requestId, $event);
88 | } elseif (isset($event['ping']) && true === $event['ping']) {
89 | $invocationEvent = new PingEvent($requestId);
90 | } elseif (isset($event['php'])) {
91 | $invocationEvent = new PhpConsoleCommandEvent($requestId, (string) $event['php']);
92 | } elseif (isset($event['warmup'])) {
93 | $invocationEvent = new WarmUpEvent($requestId, (int) $event['warmup']);
94 | }
95 |
96 | if (!$invocationEvent instanceof InvocationEventInterface) {
97 | throw new \InvalidArgumentException('Unknown Lambda event type');
98 | }
99 |
100 | return $invocationEvent;
101 | }
102 |
103 | /**
104 | * Checks if we have a valid cURL handle.
105 | */
106 | private static function isHandle($handle): bool
107 | {
108 | return (\PHP_VERSION_ID < 80000 && is_resource($handle))
109 | || (\PHP_VERSION_ID >= 80000 && $handle instanceof \CurlHandle);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Lambda/InvocationEvent/HttpRequestEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\InvocationEvent;
15 |
16 | /**
17 | * Lambda invocation event for an HTTP request.
18 | */
19 | class HttpRequestEvent extends AbstractEvent
20 | {
21 | /**
22 | * The Lambda event details.
23 | *
24 | * @var array
25 | */
26 | private $event;
27 |
28 | /**
29 | * Constructor.
30 | */
31 | public function __construct(string $id, array $event = [])
32 | {
33 | parent::__construct($id);
34 |
35 | $this->event = $event;
36 | }
37 |
38 | /**
39 | * Get the body of the invocation request.
40 | */
41 | public function getBody(): string
42 | {
43 | $body = (string) ($this->event['body'] ?? '');
44 |
45 | if (!empty($this->event['isBase64Encoded'])) {
46 | $body = base64_decode($body);
47 | }
48 |
49 | return $body;
50 | }
51 |
52 | /**
53 | * Get the headers of the invocation request.
54 | */
55 | public function getHeaders(): array
56 | {
57 | $headers = [];
58 |
59 | if (isset($this->event['multiValueHeaders'])) {
60 | $headers = $this->event['multiValueHeaders'];
61 | } elseif (isset($this->event['headers'])) {
62 | $headers = array_map(function ($value) {
63 | return [$value];
64 | }, $this->event['headers']);
65 | }
66 |
67 | if ('2.0' === $this->getPayloadVersion() && !empty($this->event['cookies'])) {
68 | $headers['cookie'] = [implode('; ', $this->event['cookies'])];
69 | }
70 |
71 | return array_change_key_case($headers, CASE_LOWER);
72 | }
73 |
74 | /**
75 | * Get the HTTP method of the invocation request.
76 | */
77 | public function getMethod(): string
78 | {
79 | return strtoupper((string) ($this->event['httpMethod'] ?? $this->event['requestContext']['http']['method'] ?? 'GET'));
80 | }
81 |
82 | /**
83 | * Get the path of the invocation request.
84 | */
85 | public function getPath(): string
86 | {
87 | return (string) ($this->event['path'] ?? $this->event['rawPath'] ?? '/');
88 | }
89 |
90 | /**
91 | * Get the payload version of the event.
92 | *
93 | * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format
94 | */
95 | public function getPayloadVersion(): string
96 | {
97 | return (string) ($this->event['version'] ?? '1.0');
98 | }
99 |
100 | /**
101 | * Get the protocol used by the invocation request.
102 | */
103 | public function getProtocol(): string
104 | {
105 | return (string) ($this->event['requestContext']['protocol'] ?? $this->event['requestContext']['http']['protocol'] ?? 'HTTP/1.1');
106 | }
107 |
108 | /**
109 | * Get the query string of the invocation request.
110 | */
111 | public function getQueryString(): string
112 | {
113 | $payloadVersion = $this->getPayloadVersion();
114 | $queryString = '';
115 |
116 | if ('1.0' === $payloadVersion) {
117 | collect($this->event['multiValueQueryStringParameters'] ?? $this->event['queryStringParameters'] ?? [])->each(function ($values, $key) use (&$queryString) {
118 | $queryString .= array_reduce((array) $values, function ($carry, $value) use ($key) {
119 | return $carry.$key.'='.urlencode($value).'&';
120 | });
121 | });
122 | } elseif ('2.0' === $payloadVersion) {
123 | $queryString = $this->event['rawQueryString'] ?? '';
124 | }
125 |
126 | parse_str($queryString, $decodedQueryParameters);
127 |
128 | return http_build_query($decodedQueryParameters);
129 | }
130 |
131 | /**
132 | * Get the source IP address of the invocation request.
133 | */
134 | public function getSourceIp(): string
135 | {
136 | return (string) ($this->event['requestContext']['http']['sourceIp'] ?? $this->event['requestContext']['identity']['sourceIp'] ?? '127.0.0.1');
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/Lambda/Response/HttpResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda\Response;
15 |
16 | use Tightenco\Collect\Support\Arr;
17 | use Tightenco\Collect\Support\Collection;
18 |
19 | /**
20 | * An HTTP lambda response.
21 | */
22 | class HttpResponse implements ResponseInterface
23 | {
24 | /**
25 | * The body of the Lambda response.
26 | *
27 | * @var string
28 | */
29 | private $body;
30 |
31 | /**
32 | * Flag whether the response can be compressed or not.
33 | *
34 | * @var bool
35 | */
36 | private $compressible;
37 |
38 | /**
39 | * The response format version.
40 | *
41 | * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.response
42 | *
43 | * @var string
44 | */
45 | private $formatVersion;
46 |
47 | /**
48 | * The headers to send with the Lambda response.
49 | *
50 | * @var array
51 | */
52 | private $headers;
53 |
54 | /**
55 | * The HTTP status code of the response.
56 | *
57 | * @var int
58 | */
59 | private $statusCode;
60 |
61 | /**
62 | * Constructor.
63 | */
64 | public function __construct(string $body, array $headers = [], int $statusCode = 200, string $formatVersion = '1.0', bool $compressible = true)
65 | {
66 | if (!in_array($formatVersion, ['1.0', '2.0'])) {
67 | throw new \InvalidArgumentException('"formatVersion" must be either "1.0" or "2.0"');
68 | }
69 |
70 | $this->body = $body;
71 | $this->compressible = $compressible;
72 | $this->formatVersion = $formatVersion;
73 | $this->headers = $headers;
74 | $this->statusCode = $statusCode;
75 | }
76 |
77 | /**
78 | * {@inheritdoc}
79 | */
80 | public function getResponseData(): array
81 | {
82 | $data = [
83 | 'isBase64Encoded' => true,
84 | 'statusCode' => $this->statusCode,
85 | ];
86 |
87 | // API Gateway generates an error when sending 304 responses with a body and headers.
88 | if (304 === $this->statusCode) {
89 | return $data;
90 | }
91 |
92 | $body = $this->body;
93 | $headers = $this->getFormattedHeaders();
94 | $headersKey = '1.0' === $this->formatVersion ? 'multiValueHeaders' : 'headers';
95 |
96 | // Compress the response body if we hit the 6MB Lambda payload limit and the response supports it.
97 | if ($this->shouldCompressResponse($body, $headers)) {
98 | $body = (string) gzencode($body, 9);
99 | $headers['Content-Encoding'] = ['gzip'];
100 | $headers['Content-Length'] = [strlen($body)];
101 | }
102 |
103 | if ('2.0' === $this->formatVersion && isset($headers['Set-Cookie'])) {
104 | $data['cookies'] = $headers['Set-Cookie'];
105 | unset($headers['Set-Cookie']);
106 | }
107 |
108 | if ('headers' === $headersKey) {
109 | $headers = $headers->map(function (array $values) {
110 | return end($values);
111 | });
112 | }
113 |
114 | $data['body'] = base64_encode($body);
115 |
116 | // PHP will serialize an empty array to `[]`. However, we need it to be an empty JSON object
117 | // which is `{}` so we convert an empty array to an empty object.
118 | $data[$headersKey] = $headers->isEmpty() ? new \stdClass() : $headers->all();
119 |
120 | return $data;
121 | }
122 |
123 | /**
124 | * Check if the HTTP response can be compressed or not.
125 | */
126 | public function isCompressible(): bool
127 | {
128 | return $this->compressible;
129 | }
130 |
131 | /**
132 | * Get the HTTP response headers properly formatted for a Lambda response.
133 | */
134 | private function getFormattedHeaders(): Collection
135 | {
136 | $headers = collect($this->headers)->mapWithKeys(function ($values, $name) {
137 | $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
138 | $values = array_values((array) $values);
139 |
140 | return [$name => $values];
141 | });
142 |
143 | if (!isset($headers['Content-Type'])) {
144 | $headers['Content-Type'] = ['text/html'];
145 | }
146 |
147 | return $headers;
148 | }
149 |
150 | /**
151 | * Determine if we should compress the HTTP response or not.
152 | */
153 | private function shouldCompressResponse(string $body, Collection $headers): bool
154 | {
155 | if (!$this->isCompressible() || mb_strlen(base64_encode($body)) < 6000000 || isset($headers['Content-Encoding']) || !isset($headers['Content-Type']) || !is_array($headers['Content-Type'])) {
156 | return false;
157 | }
158 |
159 | $contentType = Arr::last($headers['Content-Type']);
160 |
161 | if (!is_string($contentType)) {
162 | return false;
163 | }
164 |
165 | return 0 === stripos($contentType, 'text/html') || 0 === stripos($contentType, 'application/json');
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/Lambda/RuntimeApiClient.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\Lambda;
15 |
16 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventFactory;
17 | use Ymir\Runtime\Lambda\InvocationEvent\InvocationEventInterface;
18 | use Ymir\Runtime\Lambda\Response\ForbiddenHttpResponse;
19 | use Ymir\Runtime\Lambda\Response\ResponseInterface;
20 | use Ymir\Runtime\Logger;
21 |
22 | /**
23 | * Client for interacting with the AWS Lambda runtime API.
24 | */
25 | class RuntimeApiClient
26 | {
27 | /**
28 | * The URL of the Lambda runtime API.
29 | *
30 | * @var string
31 | */
32 | private $apiUrl;
33 |
34 | /**
35 | * The logger that sends logs to CloudWatch.
36 | *
37 | * @var Logger
38 | */
39 | private $logger;
40 |
41 | /**
42 | * The cURL handle for the Lambda next invocation API.
43 | *
44 | * @var \CurlHandle|resource
45 | */
46 | private $nextInvocationHandle;
47 |
48 | /**
49 | * Constructor.
50 | */
51 | public function __construct(string $apiUrl, Logger $logger)
52 | {
53 | $handle = curl_init("http://$apiUrl/2018-06-01/runtime/invocation/next");
54 |
55 | if (false === $handle) {
56 | throw new \RuntimeException('Failed to connect to the AWS Lambda next invocation API');
57 | }
58 |
59 | curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
60 | curl_setopt($handle, CURLOPT_FAILONERROR, true);
61 |
62 | $this->apiUrl = $apiUrl;
63 | $this->logger = $logger;
64 | $this->nextInvocationHandle = $handle;
65 | }
66 |
67 | /**
68 | * Destructor.
69 | */
70 | public function __destruct()
71 | {
72 | curl_close($this->nextInvocationHandle);
73 | }
74 |
75 | /**
76 | * Get the next Lambda invocation event.
77 | *
78 | * This call is blocking because the Lambda runtime API is blocking.
79 | */
80 | public function getNextEvent(): InvocationEventInterface
81 | {
82 | return InvocationEventFactory::createFromApi($this->nextInvocationHandle, $this->logger);
83 | }
84 |
85 | /**
86 | * Send an error back to the Lambda runtime API for the given event.
87 | */
88 | public function sendEventError(InvocationEventInterface $event, \Throwable $error): void
89 | {
90 | $this->sendData($this->getErrorData($error), "invocation/{$event->getId()}/error");
91 | }
92 |
93 | /**
94 | * Send an initialization error to the Lambda runtime API.
95 | */
96 | public function sendInitializationError(\Throwable $error): void
97 | {
98 | $this->sendData($this->getErrorData($error), 'init/error');
99 | }
100 |
101 | /**
102 | * Send a response to the Lambda runtime API for the given event.
103 | */
104 | public function sendResponse(InvocationEventInterface $event, ResponseInterface $response): void
105 | {
106 | $data = $response->getResponseData();
107 |
108 | // Lambda has a 6MB response payload limit. Send an error if we hit this limit instead of getting an
109 | // error from the API gateway.
110 | if (!empty($data['body']) && mb_strlen((string) $data['body']) >= 6000000) {
111 | $data = (new ForbiddenHttpResponse('Response Too Large'))->getResponseData();
112 | }
113 |
114 | $this->sendData($data, "invocation/{$event->getId()}/response");
115 | }
116 |
117 | /**
118 | * Get the error data to send to the Lambda runtime API for the given exception.
119 | */
120 | private function getErrorData(\Throwable $error): array
121 | {
122 | return [
123 | 'errorMessage' => $error->getMessage(),
124 | 'errorType' => get_class($error),
125 | 'stackTrace' => explode(PHP_EOL, $error->getTraceAsString()),
126 | ];
127 | }
128 |
129 | /**
130 | * Send data back to the Lambda runtime API.
131 | */
132 | private function sendData($data, string $uri): void
133 | {
134 | $json = json_encode($data);
135 |
136 | if (false === $json) {
137 | throw new \Exception('Error encoding JSON data: '.json_last_error_msg());
138 | }
139 |
140 | $url = "http://{$this->apiUrl}/2018-06-01/runtime/".ltrim($uri, '/');
141 | $handle = curl_init($url);
142 |
143 | if (false === $handle) {
144 | throw new \Exception('Unable to initialize curl session for: '.$url);
145 | }
146 |
147 | curl_setopt($handle, CURLOPT_CUSTOMREQUEST, 'POST');
148 | curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
149 | curl_setopt($handle, CURLOPT_POSTFIELDS, $json);
150 | curl_setopt($handle, CURLOPT_HTTPHEADER, [
151 | 'Content-Type: application/json',
152 | 'Content-Length: '.strlen($json),
153 | ]);
154 |
155 | curl_exec($handle);
156 |
157 | if (curl_error($handle)) {
158 | $errorMessage = curl_error($handle);
159 |
160 | throw new \Exception('Error sending data to the Lambda runtime API: '.$errorMessage);
161 | }
162 |
163 | curl_setopt($handle, CURLOPT_HEADERFUNCTION, null);
164 | curl_setopt($handle, CURLOPT_READFUNCTION, null);
165 | curl_setopt($handle, CURLOPT_WRITEFUNCTION, null);
166 | curl_setopt($handle, CURLOPT_PROGRESSFUNCTION, null);
167 |
168 | curl_reset($handle);
169 |
170 | curl_close($handle);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/FastCgi/PhpFpmProcess.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\FastCgi;
15 |
16 | use hollodotme\FastCGI\Interfaces\ProvidesRequestData;
17 | use hollodotme\FastCGI\Interfaces\ProvidesResponseData;
18 | use Symfony\Component\Process\Process;
19 | use Ymir\Runtime\Logger;
20 |
21 | /**
22 | * The PHP-FPM process that handles Lambda requests using FastCGI.
23 | */
24 | class PhpFpmProcess
25 | {
26 | /**
27 | * Default path to the PHP-FPM configuration file.
28 | *
29 | * @var string
30 | */
31 | private const DEFAULT_CONFIG_PATH = '/opt/ymir/etc/php-fpm.d/php-fpm.conf';
32 |
33 | /**
34 | * Path to the PHP-FPM PID file.
35 | *
36 | * @var string
37 | */
38 | private const PID_PATH = '/tmp/.ymir/php-fpm.pid';
39 |
40 | /**
41 | * Path to the PHP-FPM socket file.
42 | *
43 | * @var string
44 | */
45 | private const SOCKET_PATH = '/tmp/.ymir/php-fpm.sock';
46 |
47 | /**
48 | * The FastCGI server client used to connect to the PHP-FPM process.
49 | *
50 | * @var FastCgiServerClient
51 | */
52 | private $client;
53 |
54 | /**
55 | * The CloudWatch logger.
56 | *
57 | * @var Logger
58 | */
59 | private $logger;
60 |
61 | /**
62 | * The PHP-FPM process.
63 | *
64 | * @var Process
65 | */
66 | private $process;
67 |
68 | /**
69 | * Constructor.
70 | */
71 | public function __construct(FastCgiServerClient $client, Logger $logger, Process $process)
72 | {
73 | $this->client = $client;
74 | $this->logger = $logger;
75 | $this->process = $process;
76 | }
77 |
78 | /**
79 | * Destructor.
80 | */
81 | public function __destruct()
82 | {
83 | $this->stop();
84 | }
85 |
86 | /**
87 | * Create a new PHP-FPM process for the given configuration file.
88 | */
89 | public static function createForConfig(Logger $logger, string $configPath = self::DEFAULT_CONFIG_PATH): self
90 | {
91 | return new self(
92 | FastCgiServerClient::createFromSocketPath(self::SOCKET_PATH),
93 | $logger,
94 | new Process(['php-fpm', '--nodaemonize', '--force-stderr', '--fpm-config', $configPath])
95 | );
96 | }
97 |
98 | /**
99 | * Handles the given request and returns the response from the PHP-FPM process.
100 | */
101 | public function handle(ProvidesRequestData $request): ProvidesResponseData
102 | {
103 | $response = $this->client->handle($request);
104 |
105 | // This also triggers "updateStatus" inside the Symfony process which will make it output the logs from PHP-FPM.
106 | if (!$this->process->isRunning()) {
107 | throw new \Exception('PHP-FPM has stopped unexpectedly');
108 | }
109 |
110 | return $response;
111 | }
112 |
113 | /**
114 | * Start the PHP-FPM process.
115 | */
116 | public function start(): void
117 | {
118 | if ($this->isStarted()) {
119 | $this->killExistingProcess();
120 | }
121 |
122 | $socketDirectory = dirname(self::SOCKET_PATH);
123 |
124 | if (!is_dir($socketDirectory)) {
125 | mkdir($socketDirectory);
126 | }
127 |
128 | $this->logger->info('Starting PHP-FPM process');
129 |
130 | $this->process->setTimeout(null);
131 | $this->process->start(function ($type, $output) {
132 | $this->logger->info($output);
133 | });
134 |
135 | $this->wait(function () {
136 | if (!$this->process->isRunning()) {
137 | throw new \Exception('PHP-FPM process failed to start');
138 | }
139 |
140 | return !$this->isStarted();
141 | }, 'Timeout while waiting for PHP-FPM process to start', 5000000);
142 | }
143 |
144 | /**
145 | * Checks if the PHP-FPM process is started.
146 | */
147 | private function isStarted(): bool
148 | {
149 | clearstatcache(false, self::SOCKET_PATH);
150 |
151 | return file_exists(self::SOCKET_PATH);
152 | }
153 |
154 | /**
155 | * Kill an existing PHP-FPM process.
156 | */
157 | private function killExistingProcess(): void
158 | {
159 | $this->logger->info('Killing existing PHP-FPM process');
160 |
161 | if (!file_exists(self::PID_PATH)) {
162 | unlink(self::SOCKET_PATH);
163 |
164 | return;
165 | }
166 |
167 | $pid = (int) file_get_contents(self::PID_PATH);
168 |
169 | if (0 <= $pid || false === posix_getpgid($pid)) {
170 | $this->removeProcessFiles();
171 |
172 | return;
173 | }
174 |
175 | $result = posix_kill($pid, SIGTERM);
176 |
177 | if (false === $result) {
178 | $this->removeProcessFiles();
179 |
180 | return;
181 | }
182 |
183 | $this->wait(function () use ($pid) {
184 | return false !== posix_getpgid($pid);
185 | }, 'Timeout while waiting for PHP-FPM process to stop', 1000000);
186 |
187 | $this->removeProcessFiles();
188 | }
189 |
190 | /**
191 | * Removes all the files associated with the PHP-FPM process.
192 | */
193 | private function removeProcessFiles(): void
194 | {
195 | unlink(self::SOCKET_PATH);
196 | unlink(self::PID_PATH);
197 | }
198 |
199 | /**
200 | * Stop the PHP-FPM process.
201 | */
202 | private function stop(): void
203 | {
204 | if ($this->process->isRunning()) {
205 | $this->process->stop();
206 | }
207 | }
208 |
209 | /**
210 | * Wait for the given callback to finish.
211 | */
212 | private function wait(callable $callback, string $message, int $timeout): void
213 | {
214 | $elapsed = 0;
215 | $wait = 5000; // 5ms
216 |
217 | while ($callback()) {
218 | usleep($wait);
219 |
220 | $elapsed += $wait;
221 |
222 | if ($elapsed > $timeout) {
223 | throw new \Exception($message);
224 | }
225 | }
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/templates/error.html.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | = $statusCode; ?> | = $message; ?>
18 |
19 |
20 |
21 |
22 |
23 |
24 |
67 |
68 |
69 |
70 |
102 |
103 |
104 |
105 | = $statusCode; ?>
106 |
107 |
108 |
109 | = $message; ?>
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/src/Runtime.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime;
15 |
16 | use AsyncAws\Lambda\LambdaClient;
17 | use AsyncAws\Ssm\Input\GetParametersByPathRequest;
18 | use AsyncAws\Ssm\SsmClient;
19 | use AsyncAws\Ssm\ValueObject\Parameter;
20 | use Tightenco\Collect\Support\Arr;
21 | use Ymir\Runtime\FastCgi\PhpFpmProcess;
22 | use Ymir\Runtime\Lambda\Handler\BedrockLambdaEventHandler;
23 | use Ymir\Runtime\Lambda\Handler\ConsoleCommandLambdaEventHandler;
24 | use Ymir\Runtime\Lambda\Handler\LambdaEventHandlerCollection;
25 | use Ymir\Runtime\Lambda\Handler\LambdaEventHandlerInterface;
26 | use Ymir\Runtime\Lambda\Handler\PhpScriptLambdaEventHandler;
27 | use Ymir\Runtime\Lambda\Handler\PingLambdaEventHandler;
28 | use Ymir\Runtime\Lambda\Handler\RadicleLambdaEventHandler;
29 | use Ymir\Runtime\Lambda\Handler\WarmUpEventHandler;
30 | use Ymir\Runtime\Lambda\Handler\WordPressLambdaEventHandler;
31 | use Ymir\Runtime\Lambda\RuntimeApiClient;
32 |
33 | /**
34 | * The Ymir PHP runtime.
35 | */
36 | class Runtime
37 | {
38 | /**
39 | * The Lambda runtime API client.
40 | *
41 | * @var RuntimeApiClient
42 | */
43 | private $client;
44 |
45 | /**
46 | * The Lambda invocation event handler used by the runtime.
47 | *
48 | * @var LambdaEventHandlerInterface
49 | */
50 | private $handler;
51 |
52 | /**
53 | * The current number of invocations.
54 | *
55 | * @var int
56 | */
57 | private $invocations;
58 |
59 | /**
60 | * The logger that sends logs to CloudWatch.
61 | *
62 | * @var Logger
63 | */
64 | private $logger;
65 |
66 | /**
67 | * The maximum number of invocations.
68 | *
69 | * @var int
70 | */
71 | private $maxInvocations;
72 |
73 | /**
74 | * The PHP-FPM process used by the runtime.
75 | *
76 | * @var PhpFpmProcess
77 | */
78 | private $phpFpmProcess;
79 |
80 | /**
81 | * Constructor.
82 | */
83 | public function __construct(RuntimeApiClient $client, LambdaEventHandlerInterface $handler, Logger $logger, PhpFpmProcess $phpFpmProcess, int $maxInvocations = 100)
84 | {
85 | if (0 >= $maxInvocations) {
86 | throw new \InvalidArgumentException('"maxInvocations" must be greater than 0');
87 | }
88 |
89 | $this->client = $client;
90 | $this->handler = $handler;
91 | $this->invocations = 0;
92 | $this->logger = $logger;
93 | $this->maxInvocations = $maxInvocations;
94 | $this->phpFpmProcess = $phpFpmProcess;
95 | }
96 |
97 | /**
98 | * Create new runtime from the Lambda environment variable.
99 | */
100 | public static function createFromEnvironmentVariable(): self
101 | {
102 | $apiUrl = getenv('AWS_LAMBDA_RUNTIME_API');
103 | $logger = new Logger(getenv('YMIR_RUNTIME_LOG_LEVEL') ?: Logger::INFO);
104 | $maxInvocations = getenv('YMIR_RUNTIME_MAX_INVOCATIONS') ?: 100;
105 | $phpFpmProcess = PhpFpmProcess::createForConfig($logger);
106 | $region = getenv('AWS_REGION');
107 | $rootDirectory = getenv('LAMBDA_TASK_ROOT');
108 |
109 | if (!is_string($apiUrl)) {
110 | throw new \Exception('The "AWS_LAMBDA_RUNTIME_API" environment variable is missing');
111 | } elseif (!is_string($rootDirectory)) {
112 | throw new \Exception('The "LAMBDA_TASK_ROOT" environment variable is missing');
113 | } elseif (!is_string($region)) {
114 | throw new \Exception('The "AWS_REGION" environment variable is missing');
115 | }
116 |
117 | self::injectSecretEnvironmentVariables($logger, $region);
118 |
119 | return new self(
120 | new RuntimeApiClient($apiUrl, $logger),
121 | new LambdaEventHandlerCollection($logger, [
122 | new PingLambdaEventHandler(),
123 | new WarmUpEventHandler(new LambdaClient(['region' => $region], null, null, $logger)),
124 | new ConsoleCommandLambdaEventHandler($logger),
125 | new WordPressLambdaEventHandler($logger, $phpFpmProcess, $rootDirectory),
126 | new BedrockLambdaEventHandler($logger, $phpFpmProcess, $rootDirectory),
127 | new RadicleLambdaEventHandler($logger, $phpFpmProcess, $rootDirectory),
128 | new PhpScriptLambdaEventHandler($logger, $phpFpmProcess, $rootDirectory, getenv('_HANDLER') ?: 'index.php'),
129 | ]),
130 | $logger,
131 | $phpFpmProcess,
132 | (int) $maxInvocations
133 | );
134 | }
135 |
136 | /**
137 | * Inject the secret environment variables into the runtime.
138 | */
139 | private static function injectSecretEnvironmentVariables(Logger $logger, string $region): void
140 | {
141 | $secretsPath = getenv('YMIR_SECRETS_PATH');
142 |
143 | if (!is_string($secretsPath)) {
144 | return;
145 | }
146 |
147 | // Need to pass results through iterator_to_array manually because the collection object
148 | // preserves keys. This causes the next page of results to overwrite the previous page of
149 | // results because they use a numbered index.
150 | //
151 | // @see https://stackoverflow.com/questions/70536304/why-does-iterator-to-array-give-different-results-than-foreach
152 | collect(iterator_to_array((new SsmClient(['region' => $region], null, null, $logger))->getParametersByPath(new GetParametersByPathRequest([
153 | 'Path' => $secretsPath,
154 | 'WithDecryption' => true,
155 | ])), false))->mapWithKeys(function (Parameter $parameter) {
156 | return [Arr::last(explode('/', (string) $parameter->getName())) => (string) $parameter->getValue()];
157 | })->filter()->each(function ($value, $name) use ($logger) {
158 | $logger->debug(sprintf('Injecting [%s] secret environment variable into runtime', $name));
159 | $_ENV[$name] = $value;
160 | });
161 | }
162 |
163 | /**
164 | * Process the next Lambda runtime API event.
165 | */
166 | public function processNextEvent(): void
167 | {
168 | $event = $this->client->getNextEvent();
169 |
170 | try {
171 | if (!$this->handler->canHandle($event)) {
172 | throw new \Exception('Unable to handle the given event');
173 | }
174 |
175 | $this->client->sendResponse($event, $this->handler->handle($event));
176 |
177 | ++$this->invocations;
178 | } catch (\Throwable $exception) {
179 | $this->logger->exception($exception);
180 | $this->client->sendEventError($event, $exception);
181 | }
182 |
183 | if ($this->invocations >= $this->maxInvocations) {
184 | $this->logger->info(sprintf('Killing Lambda container. Container has processed %s invocation events. (%s)', $this->maxInvocations, $event->getId()));
185 | exit(0);
186 | }
187 | }
188 |
189 | /**
190 | * Start the Lambda runtime.
191 | */
192 | public function start(): void
193 | {
194 | try {
195 | $this->phpFpmProcess->start();
196 | } catch (\Throwable $exception) {
197 | $this->logger->exception($exception);
198 | $this->client->sendInitializationError($exception);
199 |
200 | exit(1);
201 | }
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.15.4](https://github.com/ymirapp/php-runtime/compare/v1.15.3...v1.15.4) (2025-11-16)
4 |
5 |
6 | ### Dependency Changes
7 |
8 | * Updated composer dependencies ([2ceca69](https://github.com/ymirapp/php-runtime/commit/2ceca698ff5d3bc47596f21c6ae60f8f5d849c33))
9 | * Upgrade curl to 8.17.0 ([8a7781a](https://github.com/ymirapp/php-runtime/commit/8a7781ab1451cbeb31f6e3b10b89be935ac18ea5))
10 | * Upgrade imagick to 7.1.2-8 ([efe1a49](https://github.com/ymirapp/php-runtime/commit/efe1a498c1a6ae9a05f4d4629297d0641ef207e8))
11 | * Upgrade nghttp2 to 1.66.0 ([fe8b590](https://github.com/ymirapp/php-runtime/commit/fe8b590dca8bc39107e382dd19ed9a52e22d428b))
12 | * Upgrade php versions ([e94cbaa](https://github.com/ymirapp/php-runtime/commit/e94cbaa7e4b17c68c57d15aeb7a0aa976431d065))
13 | * Upgrade relay to 0.12.1 ([8e61f4d](https://github.com/ymirapp/php-runtime/commit/8e61f4d3d73c0673b29bc235c3efdc67e113737d))
14 | * Upgrade sqlite to 3.51.0 ([99c93e0](https://github.com/ymirapp/php-runtime/commit/99c93e050324c7ae4089baac42c9c4933f93fc32))
15 |
16 | ## [1.15.3](https://github.com/ymirapp/php-runtime/compare/v1.15.2...v1.15.3) (2025-08-23)
17 |
18 |
19 | ### Dependency Changes
20 |
21 | * Updated composer dependencies ([ab7968c](https://github.com/ymirapp/php-runtime/commit/ab7968c78179ca2c18945c53ab59f32fc903a201))
22 | * Upgrade curl to 8.15.0 ([21a750c](https://github.com/ymirapp/php-runtime/commit/21a750c691909a85b782cead845b0a7748b0be41))
23 | * Upgrade imagick to 7.1.2-1 ([f88bd21](https://github.com/ymirapp/php-runtime/commit/f88bd21fcf5ab7e57781afb202575a53381726f2))
24 | * Upgrade libde265 to 1.0.16 ([8f76dca](https://github.com/ymirapp/php-runtime/commit/8f76dca04b93b973aa0d89ea199f6fb66247ebce))
25 | * Upgrade libwebp to 1.6.0 ([0acc651](https://github.com/ymirapp/php-runtime/commit/0acc6510cfce23a0a0c9a13895e887036a04a571))
26 | * Upgrade nghttp2 to 1.66.0 ([d4328b6](https://github.com/ymirapp/php-runtime/commit/d4328b69760b0b1fff88df20d503c1596407d2f6))
27 | * Upgrade php versions ([688b610](https://github.com/ymirapp/php-runtime/commit/688b610b311d754e0624310a7c50e9983222a894))
28 | * Upgrade relay to 0.11.1 ([ffca105](https://github.com/ymirapp/php-runtime/commit/ffca105f00c45e4babd228d47081ca9651da20f0))
29 | * Upgrade sqlite to 3.50.4 ([1aa0a61](https://github.com/ymirapp/php-runtime/commit/1aa0a618ed4537a3ea5a98a94f67f3c501457f04))
30 |
31 | ## [1.15.2](https://github.com/ymirapp/php-runtime/compare/v1.15.1...v1.15.2) (2025-06-27)
32 |
33 |
34 | ### Dependency Changes
35 |
36 | * Upgrade curl to 8.14.1 ([17a6a2c](https://github.com/ymirapp/php-runtime/commit/17a6a2cf3cfc1d1c3c98122b752e6bb5920e5758))
37 | * Upgrade imagick extension to 3.8.0 ([5091e1f](https://github.com/ymirapp/php-runtime/commit/5091e1f41b0cd3b262b0f958f8ceccd41e6c5e48))
38 | * Upgrade imagick to 7.1.1-47 ([6e92b87](https://github.com/ymirapp/php-runtime/commit/6e92b8728c2c5b3c0e8efc067dcf34687ca19722))
39 | * Upgrade libzip to 1.11.4 ([0c0ae5d](https://github.com/ymirapp/php-runtime/commit/0c0ae5d763e3eb6266843cc4176f1fd490fc13a7))
40 | * Upgrade relay to 0.11.0 ([239fad2](https://github.com/ymirapp/php-runtime/commit/239fad2a72e7c986a4fa0ef48f9cca585e530b58))
41 |
42 | ## [1.15.1](https://github.com/ymirapp/php-runtime/compare/v1.15.0...v1.15.1) (2025-06-24)
43 |
44 |
45 | ### Bug Fixes
46 |
47 | * `jit_buffer_size` cannot go over 128M on aarch64 ([6ae62bb](https://github.com/ymirapp/php-runtime/commit/6ae62bbcd47d3ca6ce593e501483dda9bb276463))
48 |
49 | ## [1.15.0](https://github.com/ymirapp/php-runtime/compare/v1.14.0...v1.15.0) (2025-04-15)
50 |
51 |
52 | ### Features
53 |
54 | * Don't display deprecation notices when the runtime is starting ([abd781b](https://github.com/ymirapp/php-runtime/commit/abd781beacb6e720312fe4c9103d85c3dcce93db))
55 |
56 |
57 | ### Bug Fixes
58 |
59 | * Revert `jit` option to default `tracing` mode ([39d0fff](https://github.com/ymirapp/php-runtime/commit/39d0fffe21389165eb01387ed07036f10c228639))
60 |
61 | ## [1.14.0](https://github.com/ymirapp/php-runtime/compare/v1.13.0...v1.14.0) (2025-03-22)
62 |
63 |
64 | ### Features
65 |
66 | * Add event handler for radicle ([c0bde25](https://github.com/ymirapp/php-runtime/commit/c0bde25e613593d9e692af4fd74a2bd54fb922d3))
67 | * Turn on jit opcache for all php 8 releases ([12ac157](https://github.com/ymirapp/php-runtime/commit/12ac1575ed93e16052a3c57f742de36851a5efe1))
68 |
69 | ## [1.13.0](https://github.com/ymirapp/php-runtime/compare/v1.12.4...v1.13.0) (2025-01-21)
70 |
71 |
72 | ### Features
73 |
74 | * Add php 8.4 ([ff14738](https://github.com/ymirapp/php-runtime/commit/ff1473811243a5241763832c7c0cdb072f3690a1))
75 | * Switch to compiling libwebp ([4014aa9](https://github.com/ymirapp/php-runtime/commit/4014aa921c9fe4f4ce39e89e2c8c2d22dd988f52))
76 | * Switch to compiling sqlite ([ea275ae](https://github.com/ymirapp/php-runtime/commit/ea275ae37fa8917f2a3fc70e401a032e5d178795))
77 | * Switch to compiling zlib ([e2ebd02](https://github.com/ymirapp/php-runtime/commit/e2ebd02757c1a43fbd3291b1429f64cfcc6786e7))
78 |
79 |
80 | ### Bug Fixes
81 |
82 | * Downgrade libheif to fix build issue ([8448abc](https://github.com/ymirapp/php-runtime/commit/8448abc9a0594ef42d1c06adc1281608677fc774))
83 | * Downgrade libxml2 version to fix pear install on older php versions ([796b13c](https://github.com/ymirapp/php-runtime/commit/796b13c95e4e25caf0c8fa9e0b009efdd71ec699))
84 | * Fix broken libsodium build ([1cd905f](https://github.com/ymirapp/php-runtime/commit/1cd905f60bbef1bced5de87feff3505e25262ec5))
85 |
86 | ## [1.12.4](https://github.com/ymirapp/php-runtime/compare/v1.12.3...v1.12.4) (2024-12-20)
87 |
88 |
89 | ### Bug Fixes
90 |
91 | * Send output from running console commands to logger ([cfd2460](https://github.com/ymirapp/php-runtime/commit/cfd246014b82be4245436db7f33851e48764eb55))
92 |
93 | ## [1.12.3](https://github.com/ymirapp/php-runtime/compare/v1.12.2...v1.12.3) (2024-11-23)
94 |
95 |
96 | ### Bug Fixes
97 |
98 | * Don't rewrite `/wp-login.php` requests with bedrock ([fb29448](https://github.com/ymirapp/php-runtime/commit/fb29448fb275e5ca6422c340b36cbce4fc6f23c3))
99 |
100 | ## [1.12.2](https://github.com/ymirapp/php-runtime/compare/v1.12.1...v1.12.2) (2024-06-04)
101 |
102 |
103 | ### Bug Fixes
104 |
105 | * event file should always be in the `/web` directory with bedrock ([1620020](https://github.com/ymirapp/php-runtime/commit/16200204b704df165088c24f33042e6a51ae4e9d))
106 |
107 | ## [1.12.1](https://github.com/ymirapp/php-runtime/compare/v1.12.0...v1.12.1) (2024-03-01)
108 |
109 |
110 | ### Bug Fixes
111 |
112 | * fix broken curl build by adding libpsl ([70729cf](https://github.com/ymirapp/php-runtime/commit/70729cf630fe180382628870bc57e15c8820fe80))
113 | * fix libheif build process ([85cb429](https://github.com/ymirapp/php-runtime/commit/85cb429b64dac11ace02fd44d9720ebb8bdfc262))
114 |
115 | ## [1.12.0](https://github.com/ymirapp/php-runtime/compare/v1.11.2...v1.12.0) (2024-02-13)
116 |
117 |
118 | ### Features
119 |
120 | * add php 8.3 ([db3ed1c](https://github.com/ymirapp/php-runtime/commit/db3ed1c21cf5395f8e5a5a9fe9b6f9d4c9d2b514))
121 |
122 | ## [1.11.2](https://github.com/ymirapp/php-runtime/compare/v1.11.1...v1.11.2) (2023-11-28)
123 |
124 |
125 | ### Bug Fixes
126 |
127 | * use php 8.2 version of the relay extension ([857b858](https://github.com/ymirapp/php-runtime/commit/857b8587aa40bc73d0b24a4a7e15af3d999f63bd))
128 |
129 | ## [1.11.1](https://github.com/ymirapp/php-runtime/compare/v1.11.0...v1.11.1) (2023-11-16)
130 |
131 |
132 | ### Bug Fixes
133 |
134 | * base64 encoded `body` can be larger than original `body` ([eaf85f2](https://github.com/ymirapp/php-runtime/commit/eaf85f2a5f4778c90c2216cb81773a970d37913b))
135 |
136 | ## [1.11.0](https://github.com/ymirapp/php-runtime/compare/v1.10.2...v1.11.0) (2023-11-10)
137 |
138 |
139 | ### Features
140 |
141 | * add php 8.2 ([5a9fa6a](https://github.com/ymirapp/php-runtime/commit/5a9fa6a1e949b6bccac110257951679d7d23b3af))
142 |
143 |
144 | ### Bug Fixes
145 |
146 | * remove support for `x-forwarded-for` header ([b23993e](https://github.com/ymirapp/php-runtime/commit/b23993eaa6642a4a4071fb57f73bdf69658f4a6e))
147 |
--------------------------------------------------------------------------------
/src/FastCgi/FastCgiRequest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Ymir\Runtime\FastCgi;
15 |
16 | use hollodotme\FastCGI\Interfaces\ProvidesRequestData;
17 | use Ymir\Runtime\Lambda\InvocationEvent\HttpRequestEvent;
18 |
19 | /**
20 | * A request sent to a FastCGI server.
21 | */
22 | class FastCgiRequest implements ProvidesRequestData
23 | {
24 | /**
25 | * The content of the request.
26 | *
27 | * @var string
28 | */
29 | private $content;
30 |
31 | /**
32 | * Parameters to send to the FastCGI server.
33 | *
34 | * @var array
35 | */
36 | private $parameters;
37 |
38 | /**
39 | * Constructor.
40 | */
41 | public function __construct(string $content = '', array $parameters = [])
42 | {
43 | $this->content = $content;
44 | $this->parameters = array_change_key_case($parameters, CASE_UPPER);
45 | }
46 |
47 | /**
48 | * Create new FastCGI request from a Lambda invocation event.
49 | */
50 | public static function createFromInvocationEvent(HttpRequestEvent $event, string $scriptFilename): self
51 | {
52 | $content = $event->getBody();
53 | $documentRoot = getcwd() ?: '';
54 | $headers = $event->getHeaders();
55 | $method = strtoupper($event->getMethod());
56 | $path = $event->getPath();
57 | $pathInfo = '';
58 | $port = $headers['x-forwarded-port'][0] ?? 80;
59 | $queryString = $event->getQueryString();
60 | $scriptName = str_replace($documentRoot, '', $scriptFilename);
61 | $self = $scriptName.$path;
62 |
63 | // Parse path information using same regex for nginx with "fastcgi_split_path_info"
64 | if (1 === preg_match('%^(.+\.php)(/.+)$%i', $path, $matches)) {
65 | $pathInfo = $matches[2];
66 | $self = $matches[0];
67 | }
68 |
69 | $parameters = [
70 | 'DOCUMENT_ROOT' => $documentRoot,
71 | 'GATEWAY_INTERFACE' => 'FastCGI/1.0',
72 | 'PATH_INFO' => $pathInfo,
73 | 'PHP_SELF' => '/'.trim($self, '/'),
74 | 'QUERY_STRING' => $queryString,
75 | 'REMOTE_ADDR' => $event->getSourceIp(),
76 | 'REMOTE_PORT' => $port,
77 | 'REQUEST_METHOD' => $method,
78 | 'REQUEST_TIME' => time(),
79 | 'REQUEST_TIME_FLOAT' => microtime(true),
80 | 'SCRIPT_FILENAME' => $scriptFilename,
81 | 'SCRIPT_NAME' => $scriptName,
82 | 'SERVER_ADDR' => '127.0.0.1',
83 | 'SERVER_NAME' => $headers['x-forwarded-host'][0] ?? $headers['host'][0] ?? 'localhost',
84 | 'SERVER_PORT' => $port,
85 | 'SERVER_PROTOCOL' => $event->getProtocol(),
86 | 'SERVER_SOFTWARE' => 'ymir',
87 | ];
88 |
89 | $parameters['REQUEST_URI'] = empty($queryString) ? $path : $path.'?'.$queryString;
90 |
91 | if (isset($headers['x-forwarded-proto'][0]) && 'https' == strtolower($headers['x-forwarded-proto'][0])) {
92 | $parameters['HTTPS'] = 'on';
93 | }
94 |
95 | if (isset($headers['content-length'][0])) {
96 | $parameters['CONTENT_LENGTH'] = $headers['content-length'][0];
97 | } elseif ('TRACE' !== $method) {
98 | $parameters['CONTENT_LENGTH'] = strlen($content);
99 | }
100 |
101 | if (isset($headers['content-type'][0])) {
102 | $parameters['CONTENT_TYPE'] = $headers['content-type'][0];
103 | } elseif ('POST' === $method) {
104 | $parameters['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
105 | }
106 |
107 | foreach ($headers as $header => $value) {
108 | $parameters['HTTP_'.strtoupper(str_replace('-', '_', $header))] = $value[0];
109 | }
110 |
111 | // Force "HTTP_HOST" and "SERVER_NAME" to match because of the "X_FORWARDED_HOST" header.
112 | $parameters['HTTP_HOST'] = $parameters['SERVER_NAME'];
113 |
114 | ksort($parameters);
115 |
116 | return new self($content, $parameters);
117 | }
118 |
119 | /**
120 | * Get the list of content encodings that the client understands.
121 | */
122 | public function getAcceptableEncodings(): array
123 | {
124 | return collect(explode(',', $this->parameters['HTTP_ACCEPT_ENCODING'] ?? ''))->map(function (string $encoding) {
125 | return strtolower(trim($encoding));
126 | })->filter()->all();
127 | }
128 |
129 | /**
130 | * {@inheritdoc}
131 | */
132 | public function getContent(): string
133 | {
134 | return $this->content;
135 | }
136 |
137 | /**
138 | * {@inheritdoc}
139 | */
140 | public function getContentLength(): int
141 | {
142 | return (int) ($this->parameters['CONTENT_LENGTH'] ?? 0);
143 | }
144 |
145 | /**
146 | * {@inheritdoc}
147 | */
148 | public function getContentType(): string
149 | {
150 | return (string) ($this->parameters['CONTENT_TYPE'] ?? 'application/x-www-form-urlencoded');
151 | }
152 |
153 | /**
154 | * {@inheritdoc}
155 | */
156 | public function getCustomVars(): array
157 | {
158 | return $this->parameters;
159 | }
160 |
161 | /**
162 | * {@inheritdoc}
163 | */
164 | public function getFailureCallbacks(): array
165 | {
166 | return [];
167 | }
168 |
169 | /**
170 | * {@inheritdoc}
171 | */
172 | public function getGatewayInterface(): string
173 | {
174 | return (string) ($this->parameters['GATEWAY_INTERFACE'] ?? 'FastCGI/1.0');
175 | }
176 |
177 | /**
178 | * {@inheritdoc}
179 | */
180 | public function getParams(): array
181 | {
182 | return $this->parameters;
183 | }
184 |
185 | /**
186 | * {@inheritdoc}
187 | */
188 | public function getPassThroughCallbacks(): array
189 | {
190 | return [];
191 | }
192 |
193 | /**
194 | * {@inheritdoc}
195 | */
196 | public function getRemoteAddress(): string
197 | {
198 | return (string) ($this->parameters['REMOTE_ADDR'] ?? '192.168.0.1');
199 | }
200 |
201 | /**
202 | * {@inheritdoc}
203 | */
204 | public function getRemotePort(): int
205 | {
206 | return (int) ($this->parameters['REMOTE_PORT'] ?? 9985);
207 | }
208 |
209 | /**
210 | * {@inheritdoc}
211 | */
212 | public function getRequestMethod(): string
213 | {
214 | return strtoupper((string) ($this->parameters['REQUEST_METHOD'] ?? 'GET'));
215 | }
216 |
217 | /**
218 | * {@inheritdoc}
219 | */
220 | public function getRequestUri(): string
221 | {
222 | return (string) ($this->parameters['REQUEST_URI'] ?? '');
223 | }
224 |
225 | /**
226 | * {@inheritdoc}
227 | */
228 | public function getResponseCallbacks(): array
229 | {
230 | return [];
231 | }
232 |
233 | /**
234 | * {@inheritdoc}
235 | */
236 | public function getScriptFilename(): string
237 | {
238 | return (string) ($this->parameters['SCRIPT_FILENAME'] ?? '');
239 | }
240 |
241 | /**
242 | * {@inheritdoc}
243 | */
244 | public function getServerAddress(): string
245 | {
246 | return (string) ($this->parameters['SERVER_ADDR'] ?? '127.0.0.1');
247 | }
248 |
249 | /**
250 | * {@inheritdoc}
251 | */
252 | public function getServerName(): string
253 | {
254 | return (string) ($this->parameters['SERVER_NAME'] ?? 'localhost');
255 | }
256 |
257 | /**
258 | * {@inheritdoc}
259 | */
260 | public function getServerPort(): int
261 | {
262 | return (int) ($this->parameters['SERVER_PORT'] ?? 80);
263 | }
264 |
265 | /**
266 | * {@inheritdoc}
267 | */
268 | public function getServerProtocol(): string
269 | {
270 | return (string) ($this->parameters['SERVER_PROTOCOL'] ?? 'HTTP/1.1');
271 | }
272 |
273 | /**
274 | * {@inheritdoc}
275 | */
276 | public function getServerSoftware(): string
277 | {
278 | return (string) ($this->parameters['SERVER_SOFTWARE'] ?? 'ymir');
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/layers.php:
--------------------------------------------------------------------------------
1 | [
2 | 'ap-northeast-1' => [
3 | 'arm-php-72' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-72:15',
4 | 'arm-php-73' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-73:15',
5 | 'arm-php-74' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-74:15',
6 | 'arm-php-80' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-80:15',
7 | 'arm-php-81' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-81:15',
8 | 'arm-php-82' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-82:12',
9 | 'arm-php-83' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-83:9',
10 | 'arm-php-84' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-84:6',
11 | 'php-72' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-72:32',
12 | 'php-73' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-73:32',
13 | 'php-74' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-74:32',
14 | 'php-80' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-80:29',
15 | 'php-81' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-81:19',
16 | 'php-82' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-82:12',
17 | 'php-83' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-83:9',
18 | 'php-84' => 'arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-84:6',
19 | ],
20 | 'ap-northeast-2' => [
21 | 'arm-php-72' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-72:15',
22 | 'arm-php-73' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-73:15',
23 | 'arm-php-74' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-74:15',
24 | 'arm-php-80' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-80:15',
25 | 'arm-php-81' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-81:15',
26 | 'arm-php-82' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-82:12',
27 | 'arm-php-83' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-83:9',
28 | 'arm-php-84' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-84:6',
29 | 'php-72' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-72:32',
30 | 'php-73' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-73:32',
31 | 'php-74' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-74:32',
32 | 'php-80' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-80:29',
33 | 'php-81' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-81:19',
34 | 'php-82' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-82:12',
35 | 'php-83' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-83:9',
36 | 'php-84' => 'arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-84:6',
37 | ],
38 | 'ap-south-1' => [
39 | 'arm-php-72' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-72:15',
40 | 'arm-php-73' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-73:15',
41 | 'arm-php-74' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-74:15',
42 | 'arm-php-80' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-80:15',
43 | 'arm-php-81' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-81:15',
44 | 'arm-php-82' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-82:12',
45 | 'arm-php-83' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-83:9',
46 | 'arm-php-84' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-84:6',
47 | 'php-72' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-72:32',
48 | 'php-73' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-73:32',
49 | 'php-74' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-74:32',
50 | 'php-80' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-80:29',
51 | 'php-81' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-81:19',
52 | 'php-82' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-82:12',
53 | 'php-83' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-83:9',
54 | 'php-84' => 'arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-84:6',
55 | ],
56 | 'ap-southeast-1' => [
57 | 'arm-php-72' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-72:15',
58 | 'arm-php-73' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-73:15',
59 | 'arm-php-74' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-74:15',
60 | 'arm-php-80' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-80:15',
61 | 'arm-php-81' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-81:15',
62 | 'arm-php-82' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-82:12',
63 | 'arm-php-83' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-83:9',
64 | 'arm-php-84' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-84:6',
65 | 'php-72' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-72:32',
66 | 'php-73' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-73:32',
67 | 'php-74' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-74:32',
68 | 'php-80' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-80:29',
69 | 'php-81' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-81:19',
70 | 'php-82' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-82:12',
71 | 'php-83' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-83:9',
72 | 'php-84' => 'arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-84:6',
73 | ],
74 | 'ap-southeast-2' => [
75 | 'arm-php-72' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-72:15',
76 | 'arm-php-73' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-73:15',
77 | 'arm-php-74' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-74:15',
78 | 'arm-php-80' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-80:15',
79 | 'arm-php-81' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-81:15',
80 | 'arm-php-82' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-82:12',
81 | 'arm-php-83' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-83:9',
82 | 'arm-php-84' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-84:6',
83 | 'php-72' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-72:32',
84 | 'php-73' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-73:32',
85 | 'php-74' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-74:32',
86 | 'php-80' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-80:29',
87 | 'php-81' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-81:19',
88 | 'php-82' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-82:12',
89 | 'php-83' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-83:9',
90 | 'php-84' => 'arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-84:6',
91 | ],
92 | 'ca-central-1' => [
93 | 'arm-php-72' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-72:15',
94 | 'arm-php-73' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-73:15',
95 | 'arm-php-74' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-74:15',
96 | 'arm-php-80' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-80:15',
97 | 'arm-php-81' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-81:15',
98 | 'arm-php-82' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-82:12',
99 | 'arm-php-83' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-83:9',
100 | 'arm-php-84' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-84:6',
101 | 'php-72' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-72:32',
102 | 'php-73' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-73:32',
103 | 'php-74' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-74:32',
104 | 'php-80' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-80:29',
105 | 'php-81' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-81:19',
106 | 'php-82' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-82:12',
107 | 'php-83' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-83:9',
108 | 'php-84' => 'arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-84:6',
109 | ],
110 | 'eu-central-1' => [
111 | 'arm-php-72' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-72:15',
112 | 'arm-php-73' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-73:15',
113 | 'arm-php-74' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-74:15',
114 | 'arm-php-80' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-80:15',
115 | 'arm-php-81' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-81:15',
116 | 'arm-php-82' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-82:12',
117 | 'arm-php-83' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-83:9',
118 | 'arm-php-84' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-84:6',
119 | 'php-72' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-72:32',
120 | 'php-73' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-73:32',
121 | 'php-74' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-74:32',
122 | 'php-80' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-80:29',
123 | 'php-81' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-81:19',
124 | 'php-82' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-82:12',
125 | 'php-83' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-83:9',
126 | 'php-84' => 'arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-84:6',
127 | ],
128 | 'eu-north-1' => [
129 | 'arm-php-72' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-72:15',
130 | 'arm-php-73' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-73:15',
131 | 'arm-php-74' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-74:15',
132 | 'arm-php-80' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-80:15',
133 | 'arm-php-81' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-81:15',
134 | 'arm-php-82' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-82:12',
135 | 'arm-php-83' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-83:9',
136 | 'arm-php-84' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-84:6',
137 | 'php-72' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-72:32',
138 | 'php-73' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-73:32',
139 | 'php-74' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-74:32',
140 | 'php-80' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-80:29',
141 | 'php-81' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-81:19',
142 | 'php-82' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-82:12',
143 | 'php-83' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-83:9',
144 | 'php-84' => 'arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-84:6',
145 | ],
146 | 'eu-west-1' => [
147 | 'arm-php-72' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-72:15',
148 | 'arm-php-73' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-73:15',
149 | 'arm-php-74' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-74:15',
150 | 'arm-php-80' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-80:15',
151 | 'arm-php-81' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-81:15',
152 | 'arm-php-82' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-82:12',
153 | 'arm-php-83' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-83:9',
154 | 'arm-php-84' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-84:6',
155 | 'php-72' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-72:32',
156 | 'php-73' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-73:32',
157 | 'php-74' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-74:32',
158 | 'php-80' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-80:29',
159 | 'php-81' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-81:19',
160 | 'php-82' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-82:12',
161 | 'php-83' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-83:9',
162 | 'php-84' => 'arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-84:6',
163 | ],
164 | 'eu-west-2' => [
165 | 'arm-php-72' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-72:15',
166 | 'arm-php-73' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-73:15',
167 | 'arm-php-74' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-74:15',
168 | 'arm-php-80' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-80:15',
169 | 'arm-php-81' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-81:15',
170 | 'arm-php-82' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-82:12',
171 | 'arm-php-83' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-83:9',
172 | 'arm-php-84' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-84:6',
173 | 'php-72' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-72:32',
174 | 'php-73' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-73:32',
175 | 'php-74' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-74:32',
176 | 'php-80' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-80:29',
177 | 'php-81' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-81:19',
178 | 'php-82' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-82:12',
179 | 'php-83' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-83:9',
180 | 'php-84' => 'arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-84:6',
181 | ],
182 | 'eu-west-3' => [
183 | 'arm-php-72' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-72:15',
184 | 'arm-php-73' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-73:15',
185 | 'arm-php-74' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-74:15',
186 | 'arm-php-80' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-80:15',
187 | 'arm-php-81' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-81:15',
188 | 'arm-php-82' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-82:12',
189 | 'arm-php-83' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-83:9',
190 | 'arm-php-84' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-84:6',
191 | 'php-72' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-72:32',
192 | 'php-73' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-73:32',
193 | 'php-74' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-74:32',
194 | 'php-80' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-80:29',
195 | 'php-81' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-81:19',
196 | 'php-82' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-82:12',
197 | 'php-83' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-83:9',
198 | 'php-84' => 'arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-84:6',
199 | ],
200 | 'sa-east-1' => [
201 | 'arm-php-72' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-72:15',
202 | 'arm-php-73' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-73:15',
203 | 'arm-php-74' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-74:15',
204 | 'arm-php-80' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-80:15',
205 | 'arm-php-81' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-81:15',
206 | 'arm-php-82' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-82:12',
207 | 'arm-php-83' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-83:9',
208 | 'arm-php-84' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-84:6',
209 | 'php-72' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-72:32',
210 | 'php-73' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-73:32',
211 | 'php-74' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-74:32',
212 | 'php-80' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-80:29',
213 | 'php-81' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-81:19',
214 | 'php-82' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-82:12',
215 | 'php-83' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-83:9',
216 | 'php-84' => 'arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-84:6',
217 | ],
218 | 'us-east-1' => [
219 | 'arm-php-72' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-72:15',
220 | 'arm-php-73' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-73:15',
221 | 'arm-php-74' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-74:15',
222 | 'arm-php-80' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-80:15',
223 | 'arm-php-81' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-81:15',
224 | 'arm-php-82' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-82:12',
225 | 'arm-php-83' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-83:9',
226 | 'arm-php-84' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-84:6',
227 | 'php-72' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-72:32',
228 | 'php-73' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-73:32',
229 | 'php-74' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-74:32',
230 | 'php-80' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-80:29',
231 | 'php-81' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-81:19',
232 | 'php-82' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-82:12',
233 | 'php-83' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-83:9',
234 | 'php-84' => 'arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-84:6',
235 | ],
236 | 'us-east-2' => [
237 | 'arm-php-72' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-72:15',
238 | 'arm-php-73' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-73:15',
239 | 'arm-php-74' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-74:15',
240 | 'arm-php-80' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-80:15',
241 | 'arm-php-81' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-81:15',
242 | 'arm-php-82' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-82:12',
243 | 'arm-php-83' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-83:9',
244 | 'arm-php-84' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-84:6',
245 | 'php-72' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-72:32',
246 | 'php-73' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-73:32',
247 | 'php-74' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-74:32',
248 | 'php-80' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-80:29',
249 | 'php-81' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-81:19',
250 | 'php-82' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-82:12',
251 | 'php-83' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-83:9',
252 | 'php-84' => 'arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-84:6',
253 | ],
254 | 'us-west-1' => [
255 | 'arm-php-72' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-72:15',
256 | 'arm-php-73' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-73:15',
257 | 'arm-php-74' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-74:15',
258 | 'arm-php-80' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-80:15',
259 | 'arm-php-81' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-81:15',
260 | 'arm-php-82' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-82:12',
261 | 'arm-php-83' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-83:9',
262 | 'arm-php-84' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-84:6',
263 | 'php-72' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-72:32',
264 | 'php-73' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-73:32',
265 | 'php-74' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-74:32',
266 | 'php-80' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-80:29',
267 | 'php-81' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-81:19',
268 | 'php-82' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-82:12',
269 | 'php-83' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-83:9',
270 | 'php-84' => 'arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-84:6',
271 | ],
272 | 'us-west-2' => [
273 | 'arm-php-72' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-72:15',
274 | 'arm-php-73' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-73:15',
275 | 'arm-php-74' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-74:15',
276 | 'arm-php-80' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-80:15',
277 | 'arm-php-81' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-81:15',
278 | 'arm-php-82' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-82:12',
279 | 'arm-php-83' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-83:9',
280 | 'arm-php-84' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-84:6',
281 | 'php-72' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-72:32',
282 | 'php-73' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-73:32',
283 | 'php-74' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-74:32',
284 | 'php-80' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-80:29',
285 | 'php-81' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-81:19',
286 | 'php-82' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-82:12',
287 | 'php-83' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-83:9',
288 | 'php-84' => 'arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-84:6',
289 | ],
290 | ]
--------------------------------------------------------------------------------
/layers.json:
--------------------------------------------------------------------------------
1 | {
2 | "ap-northeast-1": {
3 | "arm-php-72": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-72:15",
4 | "arm-php-73": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-73:15",
5 | "arm-php-74": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-74:15",
6 | "arm-php-80": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-80:15",
7 | "arm-php-81": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-81:15",
8 | "arm-php-82": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-82:12",
9 | "arm-php-83": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-83:9",
10 | "arm-php-84": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-arm-php-84:6",
11 | "php-72": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-72:32",
12 | "php-73": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-73:32",
13 | "php-74": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-74:32",
14 | "php-80": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-80:29",
15 | "php-81": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-81:19",
16 | "php-82": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-82:12",
17 | "php-83": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-83:9",
18 | "php-84": "arn:aws:lambda:ap-northeast-1:070635646305:layer:ymir-php-84:6"
19 | },
20 | "ap-northeast-2": {
21 | "arm-php-72": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-72:15",
22 | "arm-php-73": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-73:15",
23 | "arm-php-74": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-74:15",
24 | "arm-php-80": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-80:15",
25 | "arm-php-81": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-81:15",
26 | "arm-php-82": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-82:12",
27 | "arm-php-83": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-83:9",
28 | "arm-php-84": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-arm-php-84:6",
29 | "php-72": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-72:32",
30 | "php-73": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-73:32",
31 | "php-74": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-74:32",
32 | "php-80": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-80:29",
33 | "php-81": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-81:19",
34 | "php-82": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-82:12",
35 | "php-83": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-83:9",
36 | "php-84": "arn:aws:lambda:ap-northeast-2:070635646305:layer:ymir-php-84:6"
37 | },
38 | "ap-south-1": {
39 | "arm-php-72": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-72:15",
40 | "arm-php-73": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-73:15",
41 | "arm-php-74": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-74:15",
42 | "arm-php-80": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-80:15",
43 | "arm-php-81": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-81:15",
44 | "arm-php-82": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-82:12",
45 | "arm-php-83": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-83:9",
46 | "arm-php-84": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-arm-php-84:6",
47 | "php-72": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-72:32",
48 | "php-73": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-73:32",
49 | "php-74": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-74:32",
50 | "php-80": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-80:29",
51 | "php-81": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-81:19",
52 | "php-82": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-82:12",
53 | "php-83": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-83:9",
54 | "php-84": "arn:aws:lambda:ap-south-1:070635646305:layer:ymir-php-84:6"
55 | },
56 | "ap-southeast-1": {
57 | "arm-php-72": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-72:15",
58 | "arm-php-73": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-73:15",
59 | "arm-php-74": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-74:15",
60 | "arm-php-80": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-80:15",
61 | "arm-php-81": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-81:15",
62 | "arm-php-82": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-82:12",
63 | "arm-php-83": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-83:9",
64 | "arm-php-84": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-arm-php-84:6",
65 | "php-72": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-72:32",
66 | "php-73": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-73:32",
67 | "php-74": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-74:32",
68 | "php-80": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-80:29",
69 | "php-81": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-81:19",
70 | "php-82": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-82:12",
71 | "php-83": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-83:9",
72 | "php-84": "arn:aws:lambda:ap-southeast-1:070635646305:layer:ymir-php-84:6"
73 | },
74 | "ap-southeast-2": {
75 | "arm-php-72": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-72:15",
76 | "arm-php-73": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-73:15",
77 | "arm-php-74": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-74:15",
78 | "arm-php-80": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-80:15",
79 | "arm-php-81": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-81:15",
80 | "arm-php-82": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-82:12",
81 | "arm-php-83": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-83:9",
82 | "arm-php-84": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-arm-php-84:6",
83 | "php-72": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-72:32",
84 | "php-73": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-73:32",
85 | "php-74": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-74:32",
86 | "php-80": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-80:29",
87 | "php-81": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-81:19",
88 | "php-82": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-82:12",
89 | "php-83": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-83:9",
90 | "php-84": "arn:aws:lambda:ap-southeast-2:070635646305:layer:ymir-php-84:6"
91 | },
92 | "ca-central-1": {
93 | "arm-php-72": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-72:15",
94 | "arm-php-73": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-73:15",
95 | "arm-php-74": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-74:15",
96 | "arm-php-80": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-80:15",
97 | "arm-php-81": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-81:15",
98 | "arm-php-82": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-82:12",
99 | "arm-php-83": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-83:9",
100 | "arm-php-84": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-arm-php-84:6",
101 | "php-72": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-72:32",
102 | "php-73": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-73:32",
103 | "php-74": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-74:32",
104 | "php-80": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-80:29",
105 | "php-81": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-81:19",
106 | "php-82": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-82:12",
107 | "php-83": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-83:9",
108 | "php-84": "arn:aws:lambda:ca-central-1:070635646305:layer:ymir-php-84:6"
109 | },
110 | "eu-central-1": {
111 | "arm-php-72": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-72:15",
112 | "arm-php-73": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-73:15",
113 | "arm-php-74": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-74:15",
114 | "arm-php-80": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-80:15",
115 | "arm-php-81": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-81:15",
116 | "arm-php-82": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-82:12",
117 | "arm-php-83": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-83:9",
118 | "arm-php-84": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-arm-php-84:6",
119 | "php-72": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-72:32",
120 | "php-73": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-73:32",
121 | "php-74": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-74:32",
122 | "php-80": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-80:29",
123 | "php-81": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-81:19",
124 | "php-82": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-82:12",
125 | "php-83": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-83:9",
126 | "php-84": "arn:aws:lambda:eu-central-1:070635646305:layer:ymir-php-84:6"
127 | },
128 | "eu-north-1": {
129 | "arm-php-72": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-72:15",
130 | "arm-php-73": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-73:15",
131 | "arm-php-74": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-74:15",
132 | "arm-php-80": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-80:15",
133 | "arm-php-81": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-81:15",
134 | "arm-php-82": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-82:12",
135 | "arm-php-83": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-83:9",
136 | "arm-php-84": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-arm-php-84:6",
137 | "php-72": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-72:32",
138 | "php-73": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-73:32",
139 | "php-74": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-74:32",
140 | "php-80": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-80:29",
141 | "php-81": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-81:19",
142 | "php-82": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-82:12",
143 | "php-83": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-83:9",
144 | "php-84": "arn:aws:lambda:eu-north-1:070635646305:layer:ymir-php-84:6"
145 | },
146 | "eu-west-1": {
147 | "arm-php-72": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-72:15",
148 | "arm-php-73": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-73:15",
149 | "arm-php-74": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-74:15",
150 | "arm-php-80": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-80:15",
151 | "arm-php-81": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-81:15",
152 | "arm-php-82": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-82:12",
153 | "arm-php-83": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-83:9",
154 | "arm-php-84": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-arm-php-84:6",
155 | "php-72": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-72:32",
156 | "php-73": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-73:32",
157 | "php-74": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-74:32",
158 | "php-80": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-80:29",
159 | "php-81": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-81:19",
160 | "php-82": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-82:12",
161 | "php-83": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-83:9",
162 | "php-84": "arn:aws:lambda:eu-west-1:070635646305:layer:ymir-php-84:6"
163 | },
164 | "eu-west-2": {
165 | "arm-php-72": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-72:15",
166 | "arm-php-73": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-73:15",
167 | "arm-php-74": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-74:15",
168 | "arm-php-80": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-80:15",
169 | "arm-php-81": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-81:15",
170 | "arm-php-82": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-82:12",
171 | "arm-php-83": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-83:9",
172 | "arm-php-84": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-arm-php-84:6",
173 | "php-72": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-72:32",
174 | "php-73": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-73:32",
175 | "php-74": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-74:32",
176 | "php-80": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-80:29",
177 | "php-81": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-81:19",
178 | "php-82": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-82:12",
179 | "php-83": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-83:9",
180 | "php-84": "arn:aws:lambda:eu-west-2:070635646305:layer:ymir-php-84:6"
181 | },
182 | "eu-west-3": {
183 | "arm-php-72": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-72:15",
184 | "arm-php-73": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-73:15",
185 | "arm-php-74": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-74:15",
186 | "arm-php-80": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-80:15",
187 | "arm-php-81": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-81:15",
188 | "arm-php-82": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-82:12",
189 | "arm-php-83": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-83:9",
190 | "arm-php-84": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-arm-php-84:6",
191 | "php-72": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-72:32",
192 | "php-73": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-73:32",
193 | "php-74": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-74:32",
194 | "php-80": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-80:29",
195 | "php-81": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-81:19",
196 | "php-82": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-82:12",
197 | "php-83": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-83:9",
198 | "php-84": "arn:aws:lambda:eu-west-3:070635646305:layer:ymir-php-84:6"
199 | },
200 | "sa-east-1": {
201 | "arm-php-72": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-72:15",
202 | "arm-php-73": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-73:15",
203 | "arm-php-74": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-74:15",
204 | "arm-php-80": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-80:15",
205 | "arm-php-81": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-81:15",
206 | "arm-php-82": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-82:12",
207 | "arm-php-83": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-83:9",
208 | "arm-php-84": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-arm-php-84:6",
209 | "php-72": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-72:32",
210 | "php-73": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-73:32",
211 | "php-74": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-74:32",
212 | "php-80": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-80:29",
213 | "php-81": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-81:19",
214 | "php-82": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-82:12",
215 | "php-83": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-83:9",
216 | "php-84": "arn:aws:lambda:sa-east-1:070635646305:layer:ymir-php-84:6"
217 | },
218 | "us-east-1": {
219 | "arm-php-72": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-72:15",
220 | "arm-php-73": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-73:15",
221 | "arm-php-74": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-74:15",
222 | "arm-php-80": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-80:15",
223 | "arm-php-81": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-81:15",
224 | "arm-php-82": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-82:12",
225 | "arm-php-83": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-83:9",
226 | "arm-php-84": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-arm-php-84:6",
227 | "php-72": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-72:32",
228 | "php-73": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-73:32",
229 | "php-74": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-74:32",
230 | "php-80": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-80:29",
231 | "php-81": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-81:19",
232 | "php-82": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-82:12",
233 | "php-83": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-83:9",
234 | "php-84": "arn:aws:lambda:us-east-1:070635646305:layer:ymir-php-84:6"
235 | },
236 | "us-east-2": {
237 | "arm-php-72": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-72:15",
238 | "arm-php-73": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-73:15",
239 | "arm-php-74": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-74:15",
240 | "arm-php-80": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-80:15",
241 | "arm-php-81": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-81:15",
242 | "arm-php-82": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-82:12",
243 | "arm-php-83": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-83:9",
244 | "arm-php-84": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-arm-php-84:6",
245 | "php-72": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-72:32",
246 | "php-73": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-73:32",
247 | "php-74": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-74:32",
248 | "php-80": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-80:29",
249 | "php-81": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-81:19",
250 | "php-82": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-82:12",
251 | "php-83": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-83:9",
252 | "php-84": "arn:aws:lambda:us-east-2:070635646305:layer:ymir-php-84:6"
253 | },
254 | "us-west-1": {
255 | "arm-php-72": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-72:15",
256 | "arm-php-73": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-73:15",
257 | "arm-php-74": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-74:15",
258 | "arm-php-80": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-80:15",
259 | "arm-php-81": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-81:15",
260 | "arm-php-82": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-82:12",
261 | "arm-php-83": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-83:9",
262 | "arm-php-84": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-arm-php-84:6",
263 | "php-72": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-72:32",
264 | "php-73": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-73:32",
265 | "php-74": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-74:32",
266 | "php-80": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-80:29",
267 | "php-81": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-81:19",
268 | "php-82": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-82:12",
269 | "php-83": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-83:9",
270 | "php-84": "arn:aws:lambda:us-west-1:070635646305:layer:ymir-php-84:6"
271 | },
272 | "us-west-2": {
273 | "arm-php-72": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-72:15",
274 | "arm-php-73": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-73:15",
275 | "arm-php-74": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-74:15",
276 | "arm-php-80": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-80:15",
277 | "arm-php-81": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-81:15",
278 | "arm-php-82": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-82:12",
279 | "arm-php-83": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-83:9",
280 | "arm-php-84": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-arm-php-84:6",
281 | "php-72": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-72:32",
282 | "php-73": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-73:32",
283 | "php-74": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-74:32",
284 | "php-80": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-80:29",
285 | "php-81": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-81:19",
286 | "php-82": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-82:12",
287 | "php-83": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-83:9",
288 | "php-84": "arn:aws:lambda:us-west-2:070635646305:layer:ymir-php-84:6"
289 | }
290 | }
--------------------------------------------------------------------------------