├── .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 | [![Actions Status](https://github.com/ymirapp/php-runtime/workflows/Continuous%20Integration/badge.svg)](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 | [![Built with Depot](https://depot.dev/badges/built-with-depot.svg)](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 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | image/svg+xml 78 | 79 | 80 | 81 | 82 | 83 | image/svg+xml 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 |
104 |
105 | 106 |
107 | 108 |
109 | 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 | } --------------------------------------------------------------------------------