├── .gitignore ├── LICENSE ├── README.md ├── bootstrap ├── build.sh ├── composer.json ├── composer.lock ├── php.ini ├── phpunit.xml.dist └── tests ├── BootstrapTest.php ├── _files └── public │ ├── index.php │ └── subdir │ └── index.php └── php.ini /.gitignore: -------------------------------------------------------------------------------- 1 | php73.zip 2 | php74.zip 3 | phpunit.xml 4 | vendor 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------- 2 | The PHP License, version 3.01 3 | Copyright (c) 1999 - 2019 The PHP Group. All rights reserved. 4 | -------------------------------------------------------------------- 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, is permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. The name "PHP" must not be used to endorse or promote products 19 | derived from this software without prior written permission. For 20 | written permission, please contact group@php.net. 21 | 22 | 4. Products derived from this software may not be called "PHP", nor 23 | may "PHP" appear in their name, without prior written permission 24 | from group@php.net. You may indicate that your software works in 25 | conjunction with PHP by saying "Foo for PHP" instead of calling 26 | it "PHP Foo" or "phpfoo" 27 | 28 | 5. The PHP Group may publish revised and/or new versions of the 29 | license from time to time. Each version will be given a 30 | distinguishing version number. 31 | Once covered code has been published under a particular version 32 | of the license, you may always continue to use it under the terms 33 | of that version. You may also choose to use such covered code 34 | under the terms of any subsequent version of the license 35 | published by the PHP Group. No one other than the PHP Group has 36 | the right to modify the terms applicable to covered code created 37 | under this License. 38 | 39 | 6. Redistributions of any form whatsoever must retain the following 40 | acknowledgment: 41 | "This product includes PHP software, freely available from 42 | ". 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND 45 | ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 46 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 47 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP 48 | DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 49 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 50 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 51 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 53 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 54 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 55 | OF THE POSSIBILITY OF SUCH DAMAGE. 56 | 57 | -------------------------------------------------------------------- 58 | 59 | This software consists of voluntary contributions made by many 60 | individuals on behalf of the PHP Group. 61 | 62 | The PHP Group can be contacted via Email at group@php.net. 63 | 64 | For more information on the PHP Group and the PHP project, 65 | please see . 66 | 67 | PHP includes the Zend Engine, freely available at 68 | . 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Runtime Layer for AWS Lambda 2 | 3 | This runtime layer aims to replicate the typical web server environment for executing PHP scripts within AWS Lambda, so that an Lambda function can be used as an alternative hosting environment for a PHP-based site. 4 | 5 | This layer utilises the PHP CGI runtime to replicate the environment offered in other runtime setups such as PHP FPM. 6 | 7 | ## PHP 8 8 | 9 | This repo has been updated to PHP 8.0 and with the intention of fully supporting the `arm64` Lambda environment. 10 | 11 | Because of this this version now uses Amazon Linux Extras for a PHP 8.0 build. At the time of development, despite the stable release of 8.1, no repositories for x86 and arm64 builds of PHP 8.x were available. Should this change in the future it is hoped to update this project accordingly. 12 | 13 | ## Usage 14 | 15 | ### General Usage 16 | 17 | A Lambda function using this runtime layer is intended to sit behind either an Application Load Balancer or API Gateway, providing an HTTP interface in to the function. 18 | 19 | The layer runs a PHP CGI process for each incoming request, executing either a PHP script whose path matches the incoming HTTP request or alternatively using the script at the path configured as the handler for the Lambda function. 20 | 21 | The bootstrap is responsible for obtaining an incoming request, re-formatting it from the ALB/Gateway event object in to a format that the PHP process understands, executing the script, then re-formatting the response back to a format that AWS can return to the originating requester. 22 | 23 | ### Configuration 24 | 25 | The layer will attempt to load `php.ini` from inside your Lambda function distribution at the root level. 26 | 27 | You can enable access logging by declaring the environment variable `ACCESS_LOG` and setting the value to `true`. 28 | 29 | You can optionally format the output by declaring the `ACCESS_FORMAT` variable. It can take the following arguments: 30 | 31 | ``` 32 | %m: request method 33 | %r: the request URI (without the query string, see %q and %Q) 34 | %Q: the '?' character if query string exists 35 | %q: the query string 36 | %s: status (response code) 37 | %f: script filename 38 | %d: time taken to serve the request in seconds 39 | %e: an environment variable (same as $_ENV or $_SERVER) 40 | it must be associated with embraces to specify the name of the env 41 | variable. Some exemples: 42 | - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e 43 | - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e 44 | ``` 45 | 46 | The default value is `"%m %r%Q%q" %s %f %d`. 47 | 48 | ### Extensions 49 | The following extensions are built into the layer and available in `/opt/lib/php/8.0/modules`: 50 | 51 | ``` 52 | bz2.so 53 | calendar.so 54 | ctype.so 55 | curl.so 56 | dom.so 57 | exif.so 58 | fileinfo.so 59 | ftp.so 60 | gettext.so 61 | iconv.so 62 | mbstring.so 63 | mysqli.so 64 | mysqlnd.so 65 | pdo.so 66 | pdo_mysql.so 67 | pdo_sqlite.so 68 | phar.so 69 | simplexml.so 70 | sockets.so 71 | sqlite3.so 72 | tokenizer.so 73 | xml.so 74 | xmlreader.so 75 | xmlwriter.so 76 | xsl.so 77 | zip.so 78 | ``` 79 | 80 | These extensions are not loaded by default. You must add the extension to a php.ini file to use it: 81 | 82 | ```ini 83 | extension=dom.so 84 | ``` 85 | 86 | It is recommended that custom extensions be provided by a separate Lambda Layer with the extension .so files placed in `/lib/php/8.0/modules/` so they can be loaded alongside the built-in extensions listed above. 87 | 88 | ### Amazon Linux 2 89 | 90 | The PHP 8.0 layer is targeted to an environment running Amazon Linux 2, which at the time of development was the default environment for Lambda functions. 91 | 92 | When the build script is run under an arm64 environment, it will produce a layer suitable for running within an arm64 Lambda. Similarly running under x86 will provide an x86 layer. 93 | 94 | ## Development 95 | 96 | ### Building 97 | 98 | To build the layer zip package you will need to launch an EC2 instance running Amazon Linux v2. Choose the architecture based on which you wish to use for your final Lambda functions. 99 | 100 | Once your EC2 instance is booted and you have a copy of this repo available, run the `build.sh` script. 101 | 102 | This will build a layer zip file (`php80.zip`) for you in the current directory. 103 | 104 | ### Testing 105 | 106 | A basic PHPUnit functional test case is provided for the bootstrap. You can run this locally although this will target your local machines PHP environment. 107 | 108 | ``` 109 | composer test 110 | ``` 111 | 112 | This assumes you have PHP and composer setup in your local environment. 113 | 114 | ## Disclaimer 115 | 116 | > THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND 117 | > ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 118 | > THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 119 | > PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP 120 | > DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 121 | > INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 122 | > (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 123 | > SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 124 | > HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 125 | > STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 126 | > ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 127 | > OF THE POSSIBILITY OF SUCH DAMAGE. 128 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/opt/bin/php -c/opt/php.ini 2 | 'POST', 20 | '/invocation/next' => 'GET', 21 | '/invocation/response' => 'POST', 22 | '/invocation/error' => 'POST' 23 | ] 24 | ); // maps API routes to which HTTP method they use 25 | define('MAX_EXECUTION_TIME', intval(getenv('MAX_EXECUTION_TIME') ?: 10)); // how long PHP should be allow to run for 26 | define('ACCESS_LOG', getenv('ACCESS_LOG')); // should access log string be output 27 | define('ACCESS_FORMAT', getenv('ACCESS_FORMAT') ?: '"%m %r%Q%q" %s %f %d'); // format access log string takes 28 | 29 | /** Test required environment variables are set */ 30 | foreach ([AWS_LAMBDA_RUNTIME_API, HANDLER, LAMBDA_TASK_ROOT] as $value) { 31 | if ($value !== false) { 32 | continue; 33 | } 34 | echo 'Environment variables AWS_LAMBDA_RUNTIME_API, HANDLER and LAMBDA_TASK_ROOT must all be set'; 35 | exit(1); 36 | } 37 | 38 | /** 39 | * Sends events to the Lambda custom runtime API. 40 | * @return array 41 | */ 42 | function sendRuntimeEvent(string $type, array $body = null, array $invocation = null): array 43 | { 44 | $isValidEventType = array_key_exists($type, EVENT_TYPE_METHODS); 45 | if (!$isValidEventType) { 46 | throw new Exception("Unrecognised runtime event type: ${type}"); 47 | } 48 | $method = EVENT_TYPE_METHODS[$type]; 49 | if ($method === 'GET' && $body !== null) { 50 | throw new Exception('Cannot set body on a GET event request'); 51 | } 52 | $host = AWS_LAMBDA_RUNTIME_API; 53 | $version = LAMBDA_RUNTIME_API_VERSION; 54 | if ($invocation !== null && array_key_exists('id', $invocation)) { 55 | $invocationId = $invocation['id']; 56 | $type = str_replace('/invocation/', "/invocation/${invocationId}/", $type); 57 | } 58 | $url = "http://${host}/${version}/runtime${type}"; 59 | 60 | $ch = curl_init($url); 61 | 62 | curl_setopt($ch, CURLOPT_FAILONERROR, true); 63 | if ($method === 'POST') { 64 | $bodyString = json_encode($body); 65 | curl_setopt($ch, CURLOPT_POST, 1); 66 | curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyString); 67 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 68 | 'Content-Type: application/json', 69 | 'Content-Length: ' . strlen($bodyString), 70 | ]); 71 | } 72 | 73 | $responseHeaders = []; 74 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$responseHeaders) { 75 | $isValidHeader = preg_match('/:\s*/', $header); 76 | if (!$isValidHeader) { 77 | return strlen($header); 78 | } 79 | [$rawKey, $value] = preg_split('/:\s*/', $header, 2); 80 | $key = strtolower($rawKey); 81 | if (!array_key_exists($key, $responseHeaders)) { 82 | $responseHeaders[$key] = []; 83 | } 84 | $responseHeaders[$key][] = trim($value); 85 | return strlen($header); 86 | }); 87 | 88 | $responseBody = ''; 89 | curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$responseBody) { 90 | $responseBody .= $chunk; 91 | return strlen($chunk); 92 | }); 93 | 94 | curl_exec($ch); 95 | 96 | if (curl_error($ch)) { 97 | $error = curl_error($ch); 98 | throw new Exception("Failed to reach Lambda runtime API: ${error}"); 99 | } 100 | 101 | curl_close($ch); 102 | 103 | $response = ['headers' => $responseHeaders, 'body' => $responseBody]; 104 | 105 | return $response; 106 | } 107 | 108 | /** 109 | * If the runtime encounters an error during initialization, it posts an error 110 | * message to the initialization error path. 111 | */ 112 | function sendInitilizationError($message): array 113 | { 114 | $body = ['errorMessage' => $message, 'errorType' => 'InitError']; 115 | $response = sendRuntimeEvent('/init/error', $body); 116 | 117 | return $response; 118 | } 119 | 120 | /** 121 | * If the function returns an error, the runtime formats the error into a JSON 122 | * document, and posts it to the invocation error path. 123 | */ 124 | function sendInvocationError($invocation, $message): array 125 | { 126 | $body = ['errorMessage' => $message, 'errorType' => 'InvocationError']; 127 | $response = sendRuntimeEvent('/invocation/error', $body, $invocation); 128 | 129 | return $response; 130 | } 131 | 132 | /** 133 | * Calls the Lambda runtime API to obtain details of the next invocation. 134 | */ 135 | function getNextInvocation(): array 136 | { 137 | try { 138 | $response = sendRuntimeEvent('/invocation/next'); 139 | } catch (Exception $error) { 140 | $message = $error->getMessage(); 141 | throw new Exception("Failed to fetch next Lambda invocation: ${message}"); 142 | } 143 | ['headers' => $headers, 'body' => $body] = $response; 144 | if (!array_key_exists('lambda-runtime-aws-request-id', $headers) || 145 | count($headers['lambda-runtime-aws-request-id']) !== 1) { 146 | throw new Exception('Failed to determine Lambda invocation ID'); 147 | } 148 | $id = $headers['lambda-runtime-aws-request-id'][0]; 149 | if (empty($body)) { 150 | throw new Exception('Empty Lambda invocation response'); 151 | } 152 | 153 | $event = (array) json_decode($body, true); 154 | $invocation = ['id' => $id, 'event' => $event]; 155 | 156 | return $invocation; 157 | } 158 | 159 | /** 160 | * Formats an incoming invocation event in to an HTTP request object for passing 161 | * to the CGI process. 162 | * 163 | * @param array $event The invocation's API Gateway or ALB event object. 164 | * @return array HTTP request object. 165 | */ 166 | function createRequest(array $invocation): array 167 | { 168 | ['event' => $event] = $invocation; 169 | ['httpMethod' => $method, 'path' => $path, 'body' => $body] = $event; 170 | 171 | $eventHeader = @$event['multiValueHeaders'] ?: @$event['headers'] ?: []; 172 | $headers = array_map( 173 | function ($value) { 174 | return is_array($value) ? @$value[0] : $value; 175 | }, 176 | $eventHeader 177 | ); 178 | $eventQueryParameters = @$event['multiValueQueryStringParameters'] ?: @$event['queryStringParameters'] ?: []; 179 | $queryString = implode( 180 | '&', 181 | array_reduce( 182 | array_keys($eventQueryParameters), 183 | function ($carry, $key) use ($eventQueryParameters) { 184 | $values = is_array($eventQueryParameters[$key]) ? $eventQueryParameters[$key] : [$eventQueryParameters[$key]]; 185 | foreach ($values as $value) { 186 | $carry[] = sprintf('%s=%s', $key, $value); 187 | } 188 | return $carry; 189 | }, 190 | [] 191 | ) 192 | ); 193 | 194 | $isBase64Encoded = array_key_exists('isBase64Encoded', $event) && $event['isBase64Encoded']; 195 | if ($isBase64Encoded) { 196 | $body = base64_decode($body); 197 | } 198 | 199 | $request = [ 200 | 'method' => $method, 201 | 'path' => preg_replace('/\/{2,}/', '/', $path), // basic normalisation of the path 202 | 'query_string' => $queryString, 203 | 'headers' => $headers, 204 | 'body' => $body, 205 | ]; 206 | 207 | return $request; 208 | } 209 | 210 | /** 211 | * Takes an incoming request object, formats it suitable for the PHP CGI 212 | * process, opens a process and awaits the response. 213 | */ 214 | function performRequest(array $request): array 215 | { 216 | [ 217 | 'method' => $method, 218 | 'path' => $path, 219 | 'query_string' => $queryString, 220 | 'headers' => $requestHeaders, 221 | 'body' => $requestBody, 222 | ] = $request; 223 | 224 | $taskRoot = LAMBDA_TASK_ROOT; 225 | $configPath = CONFIG_PATH; 226 | $extensionDir = EXTENSION_DIR; 227 | $absolutePath = $taskRoot . '/' . dirname(HANDLER); 228 | $scriptFilename = $absolutePath . $path; 229 | if (!is_file($scriptFilename)) { 230 | $scriptFilename = rtrim($scriptFilename, '/') . '/index.php'; 231 | if (!is_file($scriptFilename)) { 232 | $scriptFilename = ltrim(HANDLER, '/'); 233 | } else if (substr($path, -1) !== '/') { 234 | return [ 235 | 'status' => '301 Moved Permanently', 236 | 'statusCode' => 301, 237 | 'headers' => [ 238 | 'Location' => [$path . '/'], 239 | ], 240 | 'body' => '', 241 | ]; 242 | } 243 | } 244 | $scriptName = str_replace($absolutePath, '', $scriptFilename); 245 | 246 | if (file_exists('/opt/bin/php-cgi')) { 247 | $cgiPath = '/opt/bin/'; 248 | } else { 249 | $cgiPath = ''; 250 | } 251 | 252 | $cmd = "${cgiPath}php-cgi -c \"${configPath}\" -d extension_dir=\"${extensionDir}\""; 253 | $descriptorSpec = [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']]; 254 | $cwd = LAMBDA_TASK_ROOT; 255 | $env = array_merge( 256 | $_SERVER, 257 | [ 258 | 'CONTENT_LENGTH' => strlen($requestBody), 259 | 'CONTENT_TYPE' => (@$requestHeaders['content-type'] ?: ''), 260 | 'QUERY_STRING' => $queryString, 261 | 'REDIRECT_STATUS' => 200, 262 | 'REQUEST_METHOD' => $method, 263 | 'REQUEST_URI' => $path, 264 | 'SCRIPT_FILENAME' => $scriptFilename, 265 | 'SCRIPT_NAME' => $scriptName, 266 | 'SERVER_PROTOCOL' => 'HTTP/1.1', 267 | ] 268 | ); 269 | unset($env['argv']); 270 | foreach ($requestHeaders as $rawKey => $value) { 271 | $key = 'HTTP_' . str_replace('-', '_', strtoupper($rawKey)); 272 | $env[$key] = $value; 273 | } 274 | 275 | $process = proc_open($cmd, $descriptorSpec, $pipes, $cwd, $env); 276 | if (!is_resource($process)) { 277 | $exception = new Exception('Failed to launch PHP process'); 278 | throw $exception; 279 | } 280 | 281 | fwrite($pipes[0], $requestBody); 282 | fclose($pipes[0]); 283 | 284 | stream_set_blocking($pipes[1], false); 285 | stream_set_blocking($pipes[2], false); 286 | $responseRaw = ''; 287 | $err = ''; 288 | 289 | $start = microtime(true); 290 | $timeout = false; 291 | while (!feof($pipes[1])) { 292 | $duration = (microtime(true) - $start); 293 | if ($duration > MAX_EXECUTION_TIME) { 294 | $timeout = true; 295 | break; 296 | } 297 | $responseRaw .= fread($pipes[1], 1024); 298 | $err .= fread($pipes[2], 1024); 299 | usleep(100); 300 | } 301 | 302 | fclose($pipes[1]); 303 | fclose($pipes[2]); 304 | 305 | proc_terminate($process, 2); 306 | 307 | if ($err !== '') { 308 | echo $err; 309 | } 310 | 311 | if ($timeout) { 312 | $maxExecutionTime = MAX_EXECUTION_TIME; 313 | echo "PHP took longer than $maxExecutionTime seconds to return response"; 314 | 315 | return [ 316 | 'status' => '500 Internal Server Error', 317 | 'statusCode' => 500, 318 | 'headers' => [], 319 | 'body' => 'Internal Server Error', 320 | ]; 321 | } 322 | 323 | $status = '200 OK'; 324 | [$responseHeadersRaw, $responseBody] = explode("\r\n\r\n", $responseRaw, 2); 325 | $responseHeaders = array_reduce( 326 | explode(PHP_EOL, $responseHeadersRaw), 327 | function ($carry, $line) use (&$status) { 328 | [$key, $value] = array_map('trim', explode(':', $line, 2)); 329 | if ($key === 'Status') { 330 | $status = $value; 331 | return $carry; 332 | } 333 | if (!array_key_exists($key, $carry)) { 334 | $carry[$key] = []; 335 | } 336 | $carry[$key][] = $value; 337 | return $carry; 338 | }, 339 | [] 340 | ); 341 | [$statusCode,] = explode(' ', $status, 2); 342 | 343 | if (ACCESS_LOG) { 344 | $patterns = [ 345 | '%m', 346 | '%r', 347 | '%Q', 348 | '%q', 349 | '%s', 350 | '%f', 351 | '%d', 352 | ]; 353 | $replacements = [ 354 | $method, 355 | $path, 356 | ($queryString ? '?' : ''), 357 | ($queryString ?: ''), 358 | $statusCode, 359 | $scriptName, 360 | sprintf('%.4f', $duration), 361 | ]; 362 | echo preg_replace_callback( 363 | '/%{(.+?)}e/', 364 | function ($matches) use ($env) { 365 | $key = $matches[1]; 366 | return @$env[$key] ?: '-'; 367 | }, 368 | str_replace($patterns, $replacements, ACCESS_FORMAT) 369 | ) . PHP_EOL; 370 | } 371 | 372 | return [ 373 | 'status' => $status, 374 | 'statusCode' => $statusCode, 375 | 'headers' => $responseHeaders, 376 | 'body' => $responseBody, 377 | ]; 378 | } 379 | 380 | /** 381 | * Formats an HTTP response in to an AWS (API Gateway Proxy or ALB) response 382 | * object. 383 | */ 384 | function createResponse(array $invocation, array $httpResponse): array 385 | { 386 | ['event' => $event] = $invocation; 387 | ['status' => $status, 'statusCode' => $statusCode, 'headers' => $headers, 'body' => $body] = $httpResponse; 388 | $response = ['statusCode' => (int) $statusCode, 'body' => $body]; 389 | 390 | $isApplicationLoadBalancerRequest = array_key_exists('requestContext', $event); 391 | if ($isApplicationLoadBalancerRequest) { 392 | $response['statusDescription'] = $status; 393 | } 394 | 395 | $hasMultiValueHeaders = array_key_exists('multiValueHeaders', $event); 396 | if ($hasMultiValueHeaders) { 397 | $response['multiValueHeaders'] = $headers; 398 | } else { 399 | $response['headers'] = array_map( 400 | function ($value) { 401 | return $value[0]; 402 | }, 403 | $headers 404 | ); 405 | } 406 | 407 | $hasBase64EncodingProperty = array_key_exists('isBase64Encoded', $event); 408 | if ($hasBase64EncodingProperty) { 409 | $response['isBase64Encoded'] = false; 410 | } 411 | 412 | if ($isApplicationLoadBalancerRequest) { 413 | $size = strlen(json_encode($response)); 414 | if ($size > 1000000) { 415 | echo "Response size is too large for ALB ($size bytes)" . PHP_EOL; 416 | 417 | $errorResponse = [ 418 | 'status' => '500 Internal Server Error', 419 | 'statusCode' => 500, 420 | 'headers' => [], 421 | 'body' => 'Internal Server Error', 422 | ]; 423 | 424 | return createResponse($invocation, $errorResponse); 425 | } 426 | } 427 | 428 | return $response; 429 | } 430 | 431 | /** 432 | * Forwards the Lambda-formatted response object to the Lambda runtime API. 433 | */ 434 | function sendLambdaResponse(array $invocation, array $lambdaResponse): array 435 | { 436 | return sendRuntimeEvent('/invocation/response', $lambdaResponse, $invocation); 437 | } 438 | 439 | /** 440 | * Performs the tasks required to handle an individual incoming request. 441 | */ 442 | function handleNextRequest(): void 443 | { 444 | try { 445 | $invocation = getNextInvocation(); 446 | } catch (Exception $e) { 447 | sendInitilizationError($e->getMessage()); 448 | return; 449 | } 450 | 451 | try { 452 | $request = createRequest($invocation); 453 | $httpResponse = performRequest($request); 454 | $lambdaResponse = createResponse($invocation, $httpResponse); 455 | sendLambdaResponse($invocation, $lambdaResponse); 456 | } catch (Exception $e) { 457 | sendInvocationError($invocation, $e->getMessage()); 458 | } 459 | } 460 | 461 | while (isset($loop) ? $loop : true) { 462 | handleNextRequest(); 463 | } 464 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | REQUIRED_LIB_FILES="/usr/lib64/libedit.so.0 4 | /usr/lib64/libonig.so.2" 5 | 6 | echo "Building layer for PHP 8 - using Amazon Linux Extras" 7 | 8 | amazon-linux-extras enable php8.0 9 | yum install -y php-cli php-dom php-mbstring php-mysqlnd 10 | 11 | mkdir /tmp/layer 12 | cd /tmp/layer 13 | cp /opt/layer/php.ini . 14 | cp /opt/layer/bootstrap . 15 | chmod a+x bootstrap 16 | 17 | mkdir bin 18 | cp /usr/bin/php bin/ 19 | cp /usr/bin/php-cgi bin/ 20 | 21 | mkdir lib 22 | for LIB in $REQUIRED_LIB_FILES; do 23 | cp $LIB lib/ 24 | done 25 | 26 | mkdir -p lib/php/8.0 27 | cp -a /usr/lib64/php/modules lib/php/8.0/ 28 | 29 | zip -r /opt/layer/php80.zip . 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aiir/php-lambda-layer", 3 | "type": "project", 4 | "description": "PHP runtime layer for AWS Lambda, allowing PHP scripts to be executed in an environment as close to a standard web server as possible", 5 | "keywords": [ 6 | "lambda", 7 | "php", 8 | "runtime", 9 | "layer" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Andy Buckingham", 14 | "email": "andy@aiir.com", 15 | "homepage": "https://twitter.com/andybee" 16 | } 17 | ], 18 | "require": {}, 19 | "require-dev": { 20 | "slim/slim": "^3.12", 21 | "phpunit/phpunit": "^8.0", 22 | "donatj/mock-webserver": "^2.0" 23 | }, 24 | "scripts": { 25 | "test": [ 26 | "phpunit" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "f890551350407d9d12e95b2664a4cb1e", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.4.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", 21 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1 || ^8.0" 26 | }, 27 | "require-dev": { 28 | "doctrine/coding-standard": "^9", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpbench/phpbench": "^0.16 || ^1", 32 | "phpstan/phpstan": "^1.4", 33 | "phpstan/phpstan-phpunit": "^1", 34 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 35 | "vimeo/psalm": "^4.22" 36 | }, 37 | "type": "library", 38 | "autoload": { 39 | "psr-4": { 40 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Marco Pivetta", 50 | "email": "ocramius@gmail.com", 51 | "homepage": "https://ocramius.github.io/" 52 | } 53 | ], 54 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 55 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 56 | "keywords": [ 57 | "constructor", 58 | "instantiate" 59 | ], 60 | "support": { 61 | "issues": "https://github.com/doctrine/instantiator/issues", 62 | "source": "https://github.com/doctrine/instantiator/tree/1.4.1" 63 | }, 64 | "funding": [ 65 | { 66 | "url": "https://www.doctrine-project.org/sponsorship.html", 67 | "type": "custom" 68 | }, 69 | { 70 | "url": "https://www.patreon.com/phpdoctrine", 71 | "type": "patreon" 72 | }, 73 | { 74 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 75 | "type": "tidelift" 76 | } 77 | ], 78 | "time": "2022-03-03T08:28:38+00:00" 79 | }, 80 | { 81 | "name": "donatj/mock-webserver", 82 | "version": "v2.4.1", 83 | "source": { 84 | "type": "git", 85 | "url": "https://github.com/donatj/mock-webserver.git", 86 | "reference": "bcec733923068135bd4a2da359aa038a93697c5b" 87 | }, 88 | "dist": { 89 | "type": "zip", 90 | "url": "https://api.github.com/repos/donatj/mock-webserver/zipball/bcec733923068135bd4a2da359aa038a93697c5b", 91 | "reference": "bcec733923068135bd4a2da359aa038a93697c5b", 92 | "shasum": "" 93 | }, 94 | "require": { 95 | "ext-json": "*", 96 | "ext-sockets": "*", 97 | "php": ">=5.4", 98 | "ralouphie/getallheaders": "~2.0 || ~3.0" 99 | }, 100 | "require-dev": { 101 | "donatj/drop": "^1.0", 102 | "phpunit/phpunit": "~4|~9" 103 | }, 104 | "type": "library", 105 | "autoload": { 106 | "psr-4": { 107 | "donatj\\MockWebServer\\": "src/" 108 | } 109 | }, 110 | "notification-url": "https://packagist.org/downloads/", 111 | "license": [ 112 | "MIT" 113 | ], 114 | "authors": [ 115 | { 116 | "name": "Jesse G. Donat", 117 | "email": "donatj@gmail.com", 118 | "homepage": "https://donatstudios.com", 119 | "role": "Lead" 120 | } 121 | ], 122 | "description": "Simple mock web server for unit testing", 123 | "support": { 124 | "issues": "https://github.com/donatj/mock-webserver/issues", 125 | "source": "https://github.com/donatj/mock-webserver/tree/v2.4.1" 126 | }, 127 | "funding": [ 128 | { 129 | "url": "https://www.paypal.me/donatj/5", 130 | "type": "custom" 131 | }, 132 | { 133 | "url": "https://github.com/donatj", 134 | "type": "github" 135 | } 136 | ], 137 | "time": "2022-01-19T16:59:52+00:00" 138 | }, 139 | { 140 | "name": "myclabs/deep-copy", 141 | "version": "1.11.0", 142 | "source": { 143 | "type": "git", 144 | "url": "https://github.com/myclabs/DeepCopy.git", 145 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" 146 | }, 147 | "dist": { 148 | "type": "zip", 149 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", 150 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", 151 | "shasum": "" 152 | }, 153 | "require": { 154 | "php": "^7.1 || ^8.0" 155 | }, 156 | "conflict": { 157 | "doctrine/collections": "<1.6.8", 158 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 159 | }, 160 | "require-dev": { 161 | "doctrine/collections": "^1.6.8", 162 | "doctrine/common": "^2.13.3 || ^3.2.2", 163 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 164 | }, 165 | "type": "library", 166 | "autoload": { 167 | "files": [ 168 | "src/DeepCopy/deep_copy.php" 169 | ], 170 | "psr-4": { 171 | "DeepCopy\\": "src/DeepCopy/" 172 | } 173 | }, 174 | "notification-url": "https://packagist.org/downloads/", 175 | "license": [ 176 | "MIT" 177 | ], 178 | "description": "Create deep copies (clones) of your objects", 179 | "keywords": [ 180 | "clone", 181 | "copy", 182 | "duplicate", 183 | "object", 184 | "object graph" 185 | ], 186 | "support": { 187 | "issues": "https://github.com/myclabs/DeepCopy/issues", 188 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" 189 | }, 190 | "funding": [ 191 | { 192 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 193 | "type": "tidelift" 194 | } 195 | ], 196 | "time": "2022-03-03T13:19:32+00:00" 197 | }, 198 | { 199 | "name": "nikic/fast-route", 200 | "version": "v1.3.0", 201 | "source": { 202 | "type": "git", 203 | "url": "https://github.com/nikic/FastRoute.git", 204 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812" 205 | }, 206 | "dist": { 207 | "type": "zip", 208 | "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", 209 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812", 210 | "shasum": "" 211 | }, 212 | "require": { 213 | "php": ">=5.4.0" 214 | }, 215 | "require-dev": { 216 | "phpunit/phpunit": "^4.8.35|~5.7" 217 | }, 218 | "type": "library", 219 | "autoload": { 220 | "files": [ 221 | "src/functions.php" 222 | ], 223 | "psr-4": { 224 | "FastRoute\\": "src/" 225 | } 226 | }, 227 | "notification-url": "https://packagist.org/downloads/", 228 | "license": [ 229 | "BSD-3-Clause" 230 | ], 231 | "authors": [ 232 | { 233 | "name": "Nikita Popov", 234 | "email": "nikic@php.net" 235 | } 236 | ], 237 | "description": "Fast request router for PHP", 238 | "keywords": [ 239 | "router", 240 | "routing" 241 | ], 242 | "support": { 243 | "issues": "https://github.com/nikic/FastRoute/issues", 244 | "source": "https://github.com/nikic/FastRoute/tree/master" 245 | }, 246 | "time": "2018-02-13T20:26:39+00:00" 247 | }, 248 | { 249 | "name": "phar-io/manifest", 250 | "version": "2.0.3", 251 | "source": { 252 | "type": "git", 253 | "url": "https://github.com/phar-io/manifest.git", 254 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 255 | }, 256 | "dist": { 257 | "type": "zip", 258 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 259 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 260 | "shasum": "" 261 | }, 262 | "require": { 263 | "ext-dom": "*", 264 | "ext-phar": "*", 265 | "ext-xmlwriter": "*", 266 | "phar-io/version": "^3.0.1", 267 | "php": "^7.2 || ^8.0" 268 | }, 269 | "type": "library", 270 | "extra": { 271 | "branch-alias": { 272 | "dev-master": "2.0.x-dev" 273 | } 274 | }, 275 | "autoload": { 276 | "classmap": [ 277 | "src/" 278 | ] 279 | }, 280 | "notification-url": "https://packagist.org/downloads/", 281 | "license": [ 282 | "BSD-3-Clause" 283 | ], 284 | "authors": [ 285 | { 286 | "name": "Arne Blankerts", 287 | "email": "arne@blankerts.de", 288 | "role": "Developer" 289 | }, 290 | { 291 | "name": "Sebastian Heuer", 292 | "email": "sebastian@phpeople.de", 293 | "role": "Developer" 294 | }, 295 | { 296 | "name": "Sebastian Bergmann", 297 | "email": "sebastian@phpunit.de", 298 | "role": "Developer" 299 | } 300 | ], 301 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 302 | "support": { 303 | "issues": "https://github.com/phar-io/manifest/issues", 304 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 305 | }, 306 | "time": "2021-07-20T11:28:43+00:00" 307 | }, 308 | { 309 | "name": "phar-io/version", 310 | "version": "3.2.1", 311 | "source": { 312 | "type": "git", 313 | "url": "https://github.com/phar-io/version.git", 314 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 315 | }, 316 | "dist": { 317 | "type": "zip", 318 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 319 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 320 | "shasum": "" 321 | }, 322 | "require": { 323 | "php": "^7.2 || ^8.0" 324 | }, 325 | "type": "library", 326 | "autoload": { 327 | "classmap": [ 328 | "src/" 329 | ] 330 | }, 331 | "notification-url": "https://packagist.org/downloads/", 332 | "license": [ 333 | "BSD-3-Clause" 334 | ], 335 | "authors": [ 336 | { 337 | "name": "Arne Blankerts", 338 | "email": "arne@blankerts.de", 339 | "role": "Developer" 340 | }, 341 | { 342 | "name": "Sebastian Heuer", 343 | "email": "sebastian@phpeople.de", 344 | "role": "Developer" 345 | }, 346 | { 347 | "name": "Sebastian Bergmann", 348 | "email": "sebastian@phpunit.de", 349 | "role": "Developer" 350 | } 351 | ], 352 | "description": "Library for handling version information and constraints", 353 | "support": { 354 | "issues": "https://github.com/phar-io/version/issues", 355 | "source": "https://github.com/phar-io/version/tree/3.2.1" 356 | }, 357 | "time": "2022-02-21T01:04:05+00:00" 358 | }, 359 | { 360 | "name": "phpdocumentor/reflection-common", 361 | "version": "2.2.0", 362 | "source": { 363 | "type": "git", 364 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 365 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 366 | }, 367 | "dist": { 368 | "type": "zip", 369 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 370 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 371 | "shasum": "" 372 | }, 373 | "require": { 374 | "php": "^7.2 || ^8.0" 375 | }, 376 | "type": "library", 377 | "extra": { 378 | "branch-alias": { 379 | "dev-2.x": "2.x-dev" 380 | } 381 | }, 382 | "autoload": { 383 | "psr-4": { 384 | "phpDocumentor\\Reflection\\": "src/" 385 | } 386 | }, 387 | "notification-url": "https://packagist.org/downloads/", 388 | "license": [ 389 | "MIT" 390 | ], 391 | "authors": [ 392 | { 393 | "name": "Jaap van Otterdijk", 394 | "email": "opensource@ijaap.nl" 395 | } 396 | ], 397 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 398 | "homepage": "http://www.phpdoc.org", 399 | "keywords": [ 400 | "FQSEN", 401 | "phpDocumentor", 402 | "phpdoc", 403 | "reflection", 404 | "static analysis" 405 | ], 406 | "support": { 407 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", 408 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" 409 | }, 410 | "time": "2020-06-27T09:03:43+00:00" 411 | }, 412 | { 413 | "name": "phpdocumentor/reflection-docblock", 414 | "version": "5.3.0", 415 | "source": { 416 | "type": "git", 417 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 418 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" 419 | }, 420 | "dist": { 421 | "type": "zip", 422 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", 423 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", 424 | "shasum": "" 425 | }, 426 | "require": { 427 | "ext-filter": "*", 428 | "php": "^7.2 || ^8.0", 429 | "phpdocumentor/reflection-common": "^2.2", 430 | "phpdocumentor/type-resolver": "^1.3", 431 | "webmozart/assert": "^1.9.1" 432 | }, 433 | "require-dev": { 434 | "mockery/mockery": "~1.3.2", 435 | "psalm/phar": "^4.8" 436 | }, 437 | "type": "library", 438 | "extra": { 439 | "branch-alias": { 440 | "dev-master": "5.x-dev" 441 | } 442 | }, 443 | "autoload": { 444 | "psr-4": { 445 | "phpDocumentor\\Reflection\\": "src" 446 | } 447 | }, 448 | "notification-url": "https://packagist.org/downloads/", 449 | "license": [ 450 | "MIT" 451 | ], 452 | "authors": [ 453 | { 454 | "name": "Mike van Riel", 455 | "email": "me@mikevanriel.com" 456 | }, 457 | { 458 | "name": "Jaap van Otterdijk", 459 | "email": "account@ijaap.nl" 460 | } 461 | ], 462 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 463 | "support": { 464 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", 465 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" 466 | }, 467 | "time": "2021-10-19T17:43:47+00:00" 468 | }, 469 | { 470 | "name": "phpdocumentor/type-resolver", 471 | "version": "1.6.1", 472 | "source": { 473 | "type": "git", 474 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 475 | "reference": "77a32518733312af16a44300404e945338981de3" 476 | }, 477 | "dist": { 478 | "type": "zip", 479 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", 480 | "reference": "77a32518733312af16a44300404e945338981de3", 481 | "shasum": "" 482 | }, 483 | "require": { 484 | "php": "^7.2 || ^8.0", 485 | "phpdocumentor/reflection-common": "^2.0" 486 | }, 487 | "require-dev": { 488 | "ext-tokenizer": "*", 489 | "psalm/phar": "^4.8" 490 | }, 491 | "type": "library", 492 | "extra": { 493 | "branch-alias": { 494 | "dev-1.x": "1.x-dev" 495 | } 496 | }, 497 | "autoload": { 498 | "psr-4": { 499 | "phpDocumentor\\Reflection\\": "src" 500 | } 501 | }, 502 | "notification-url": "https://packagist.org/downloads/", 503 | "license": [ 504 | "MIT" 505 | ], 506 | "authors": [ 507 | { 508 | "name": "Mike van Riel", 509 | "email": "me@mikevanriel.com" 510 | } 511 | ], 512 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 513 | "support": { 514 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", 515 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" 516 | }, 517 | "time": "2022-03-15T21:29:03+00:00" 518 | }, 519 | { 520 | "name": "phpspec/prophecy", 521 | "version": "v1.15.0", 522 | "source": { 523 | "type": "git", 524 | "url": "https://github.com/phpspec/prophecy.git", 525 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" 526 | }, 527 | "dist": { 528 | "type": "zip", 529 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 530 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 531 | "shasum": "" 532 | }, 533 | "require": { 534 | "doctrine/instantiator": "^1.2", 535 | "php": "^7.2 || ~8.0, <8.2", 536 | "phpdocumentor/reflection-docblock": "^5.2", 537 | "sebastian/comparator": "^3.0 || ^4.0", 538 | "sebastian/recursion-context": "^3.0 || ^4.0" 539 | }, 540 | "require-dev": { 541 | "phpspec/phpspec": "^6.0 || ^7.0", 542 | "phpunit/phpunit": "^8.0 || ^9.0" 543 | }, 544 | "type": "library", 545 | "extra": { 546 | "branch-alias": { 547 | "dev-master": "1.x-dev" 548 | } 549 | }, 550 | "autoload": { 551 | "psr-4": { 552 | "Prophecy\\": "src/Prophecy" 553 | } 554 | }, 555 | "notification-url": "https://packagist.org/downloads/", 556 | "license": [ 557 | "MIT" 558 | ], 559 | "authors": [ 560 | { 561 | "name": "Konstantin Kudryashov", 562 | "email": "ever.zet@gmail.com", 563 | "homepage": "http://everzet.com" 564 | }, 565 | { 566 | "name": "Marcello Duarte", 567 | "email": "marcello.duarte@gmail.com" 568 | } 569 | ], 570 | "description": "Highly opinionated mocking framework for PHP 5.3+", 571 | "homepage": "https://github.com/phpspec/prophecy", 572 | "keywords": [ 573 | "Double", 574 | "Dummy", 575 | "fake", 576 | "mock", 577 | "spy", 578 | "stub" 579 | ], 580 | "support": { 581 | "issues": "https://github.com/phpspec/prophecy/issues", 582 | "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" 583 | }, 584 | "time": "2021-12-08T12:19:24+00:00" 585 | }, 586 | { 587 | "name": "phpunit/php-code-coverage", 588 | "version": "7.0.15", 589 | "source": { 590 | "type": "git", 591 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 592 | "reference": "819f92bba8b001d4363065928088de22f25a3a48" 593 | }, 594 | "dist": { 595 | "type": "zip", 596 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", 597 | "reference": "819f92bba8b001d4363065928088de22f25a3a48", 598 | "shasum": "" 599 | }, 600 | "require": { 601 | "ext-dom": "*", 602 | "ext-xmlwriter": "*", 603 | "php": ">=7.2", 604 | "phpunit/php-file-iterator": "^2.0.2", 605 | "phpunit/php-text-template": "^1.2.1", 606 | "phpunit/php-token-stream": "^3.1.3 || ^4.0", 607 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 608 | "sebastian/environment": "^4.2.2", 609 | "sebastian/version": "^2.0.1", 610 | "theseer/tokenizer": "^1.1.3" 611 | }, 612 | "require-dev": { 613 | "phpunit/phpunit": "^8.2.2" 614 | }, 615 | "suggest": { 616 | "ext-xdebug": "^2.7.2" 617 | }, 618 | "type": "library", 619 | "extra": { 620 | "branch-alias": { 621 | "dev-master": "7.0-dev" 622 | } 623 | }, 624 | "autoload": { 625 | "classmap": [ 626 | "src/" 627 | ] 628 | }, 629 | "notification-url": "https://packagist.org/downloads/", 630 | "license": [ 631 | "BSD-3-Clause" 632 | ], 633 | "authors": [ 634 | { 635 | "name": "Sebastian Bergmann", 636 | "email": "sebastian@phpunit.de", 637 | "role": "lead" 638 | } 639 | ], 640 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 641 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 642 | "keywords": [ 643 | "coverage", 644 | "testing", 645 | "xunit" 646 | ], 647 | "support": { 648 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 649 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" 650 | }, 651 | "funding": [ 652 | { 653 | "url": "https://github.com/sebastianbergmann", 654 | "type": "github" 655 | } 656 | ], 657 | "time": "2021-07-26T12:20:09+00:00" 658 | }, 659 | { 660 | "name": "phpunit/php-file-iterator", 661 | "version": "2.0.5", 662 | "source": { 663 | "type": "git", 664 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 665 | "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" 666 | }, 667 | "dist": { 668 | "type": "zip", 669 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", 670 | "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", 671 | "shasum": "" 672 | }, 673 | "require": { 674 | "php": ">=7.1" 675 | }, 676 | "require-dev": { 677 | "phpunit/phpunit": "^8.5" 678 | }, 679 | "type": "library", 680 | "extra": { 681 | "branch-alias": { 682 | "dev-master": "2.0.x-dev" 683 | } 684 | }, 685 | "autoload": { 686 | "classmap": [ 687 | "src/" 688 | ] 689 | }, 690 | "notification-url": "https://packagist.org/downloads/", 691 | "license": [ 692 | "BSD-3-Clause" 693 | ], 694 | "authors": [ 695 | { 696 | "name": "Sebastian Bergmann", 697 | "email": "sebastian@phpunit.de", 698 | "role": "lead" 699 | } 700 | ], 701 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 702 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 703 | "keywords": [ 704 | "filesystem", 705 | "iterator" 706 | ], 707 | "support": { 708 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 709 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" 710 | }, 711 | "funding": [ 712 | { 713 | "url": "https://github.com/sebastianbergmann", 714 | "type": "github" 715 | } 716 | ], 717 | "time": "2021-12-02T12:42:26+00:00" 718 | }, 719 | { 720 | "name": "phpunit/php-text-template", 721 | "version": "1.2.1", 722 | "source": { 723 | "type": "git", 724 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 725 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 726 | }, 727 | "dist": { 728 | "type": "zip", 729 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 730 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 731 | "shasum": "" 732 | }, 733 | "require": { 734 | "php": ">=5.3.3" 735 | }, 736 | "type": "library", 737 | "autoload": { 738 | "classmap": [ 739 | "src/" 740 | ] 741 | }, 742 | "notification-url": "https://packagist.org/downloads/", 743 | "license": [ 744 | "BSD-3-Clause" 745 | ], 746 | "authors": [ 747 | { 748 | "name": "Sebastian Bergmann", 749 | "email": "sebastian@phpunit.de", 750 | "role": "lead" 751 | } 752 | ], 753 | "description": "Simple template engine.", 754 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 755 | "keywords": [ 756 | "template" 757 | ], 758 | "support": { 759 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 760 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" 761 | }, 762 | "time": "2015-06-21T13:50:34+00:00" 763 | }, 764 | { 765 | "name": "phpunit/php-timer", 766 | "version": "2.1.3", 767 | "source": { 768 | "type": "git", 769 | "url": "https://github.com/sebastianbergmann/php-timer.git", 770 | "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" 771 | }, 772 | "dist": { 773 | "type": "zip", 774 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", 775 | "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", 776 | "shasum": "" 777 | }, 778 | "require": { 779 | "php": ">=7.1" 780 | }, 781 | "require-dev": { 782 | "phpunit/phpunit": "^8.5" 783 | }, 784 | "type": "library", 785 | "extra": { 786 | "branch-alias": { 787 | "dev-master": "2.1-dev" 788 | } 789 | }, 790 | "autoload": { 791 | "classmap": [ 792 | "src/" 793 | ] 794 | }, 795 | "notification-url": "https://packagist.org/downloads/", 796 | "license": [ 797 | "BSD-3-Clause" 798 | ], 799 | "authors": [ 800 | { 801 | "name": "Sebastian Bergmann", 802 | "email": "sebastian@phpunit.de", 803 | "role": "lead" 804 | } 805 | ], 806 | "description": "Utility class for timing", 807 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 808 | "keywords": [ 809 | "timer" 810 | ], 811 | "support": { 812 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 813 | "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" 814 | }, 815 | "funding": [ 816 | { 817 | "url": "https://github.com/sebastianbergmann", 818 | "type": "github" 819 | } 820 | ], 821 | "time": "2020-11-30T08:20:02+00:00" 822 | }, 823 | { 824 | "name": "phpunit/php-token-stream", 825 | "version": "4.0.4", 826 | "source": { 827 | "type": "git", 828 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 829 | "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" 830 | }, 831 | "dist": { 832 | "type": "zip", 833 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", 834 | "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", 835 | "shasum": "" 836 | }, 837 | "require": { 838 | "ext-tokenizer": "*", 839 | "php": "^7.3 || ^8.0" 840 | }, 841 | "require-dev": { 842 | "phpunit/phpunit": "^9.0" 843 | }, 844 | "type": "library", 845 | "extra": { 846 | "branch-alias": { 847 | "dev-master": "4.0-dev" 848 | } 849 | }, 850 | "autoload": { 851 | "classmap": [ 852 | "src/" 853 | ] 854 | }, 855 | "notification-url": "https://packagist.org/downloads/", 856 | "license": [ 857 | "BSD-3-Clause" 858 | ], 859 | "authors": [ 860 | { 861 | "name": "Sebastian Bergmann", 862 | "email": "sebastian@phpunit.de" 863 | } 864 | ], 865 | "description": "Wrapper around PHP's tokenizer extension.", 866 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 867 | "keywords": [ 868 | "tokenizer" 869 | ], 870 | "support": { 871 | "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", 872 | "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" 873 | }, 874 | "funding": [ 875 | { 876 | "url": "https://github.com/sebastianbergmann", 877 | "type": "github" 878 | } 879 | ], 880 | "abandoned": true, 881 | "time": "2020-08-04T08:28:15+00:00" 882 | }, 883 | { 884 | "name": "phpunit/phpunit", 885 | "version": "8.5.26", 886 | "source": { 887 | "type": "git", 888 | "url": "https://github.com/sebastianbergmann/phpunit.git", 889 | "reference": "ef117c59fc4c54a979021b26d08a3373e386606d" 890 | }, 891 | "dist": { 892 | "type": "zip", 893 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ef117c59fc4c54a979021b26d08a3373e386606d", 894 | "reference": "ef117c59fc4c54a979021b26d08a3373e386606d", 895 | "shasum": "" 896 | }, 897 | "require": { 898 | "doctrine/instantiator": "^1.3.1", 899 | "ext-dom": "*", 900 | "ext-json": "*", 901 | "ext-libxml": "*", 902 | "ext-mbstring": "*", 903 | "ext-xml": "*", 904 | "ext-xmlwriter": "*", 905 | "myclabs/deep-copy": "^1.10.0", 906 | "phar-io/manifest": "^2.0.3", 907 | "phar-io/version": "^3.0.2", 908 | "php": ">=7.2", 909 | "phpspec/prophecy": "^1.10.3", 910 | "phpunit/php-code-coverage": "^7.0.12", 911 | "phpunit/php-file-iterator": "^2.0.4", 912 | "phpunit/php-text-template": "^1.2.1", 913 | "phpunit/php-timer": "^2.1.2", 914 | "sebastian/comparator": "^3.0.2", 915 | "sebastian/diff": "^3.0.2", 916 | "sebastian/environment": "^4.2.3", 917 | "sebastian/exporter": "^3.1.2", 918 | "sebastian/global-state": "^3.0.0", 919 | "sebastian/object-enumerator": "^3.0.3", 920 | "sebastian/resource-operations": "^2.0.1", 921 | "sebastian/type": "^1.1.3", 922 | "sebastian/version": "^2.0.1" 923 | }, 924 | "require-dev": { 925 | "ext-pdo": "*" 926 | }, 927 | "suggest": { 928 | "ext-soap": "*", 929 | "ext-xdebug": "*", 930 | "phpunit/php-invoker": "^2.0.0" 931 | }, 932 | "bin": [ 933 | "phpunit" 934 | ], 935 | "type": "library", 936 | "extra": { 937 | "branch-alias": { 938 | "dev-master": "8.5-dev" 939 | } 940 | }, 941 | "autoload": { 942 | "classmap": [ 943 | "src/" 944 | ] 945 | }, 946 | "notification-url": "https://packagist.org/downloads/", 947 | "license": [ 948 | "BSD-3-Clause" 949 | ], 950 | "authors": [ 951 | { 952 | "name": "Sebastian Bergmann", 953 | "email": "sebastian@phpunit.de", 954 | "role": "lead" 955 | } 956 | ], 957 | "description": "The PHP Unit Testing framework.", 958 | "homepage": "https://phpunit.de/", 959 | "keywords": [ 960 | "phpunit", 961 | "testing", 962 | "xunit" 963 | ], 964 | "support": { 965 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 966 | "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.26" 967 | }, 968 | "funding": [ 969 | { 970 | "url": "https://phpunit.de/sponsors.html", 971 | "type": "custom" 972 | }, 973 | { 974 | "url": "https://github.com/sebastianbergmann", 975 | "type": "github" 976 | } 977 | ], 978 | "time": "2022-04-01T12:34:39+00:00" 979 | }, 980 | { 981 | "name": "pimple/pimple", 982 | "version": "v3.5.0", 983 | "source": { 984 | "type": "git", 985 | "url": "https://github.com/silexphp/Pimple.git", 986 | "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" 987 | }, 988 | "dist": { 989 | "type": "zip", 990 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", 991 | "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", 992 | "shasum": "" 993 | }, 994 | "require": { 995 | "php": ">=7.2.5", 996 | "psr/container": "^1.1 || ^2.0" 997 | }, 998 | "require-dev": { 999 | "symfony/phpunit-bridge": "^5.4@dev" 1000 | }, 1001 | "type": "library", 1002 | "extra": { 1003 | "branch-alias": { 1004 | "dev-master": "3.4.x-dev" 1005 | } 1006 | }, 1007 | "autoload": { 1008 | "psr-0": { 1009 | "Pimple": "src/" 1010 | } 1011 | }, 1012 | "notification-url": "https://packagist.org/downloads/", 1013 | "license": [ 1014 | "MIT" 1015 | ], 1016 | "authors": [ 1017 | { 1018 | "name": "Fabien Potencier", 1019 | "email": "fabien@symfony.com" 1020 | } 1021 | ], 1022 | "description": "Pimple, a simple Dependency Injection Container", 1023 | "homepage": "https://pimple.symfony.com", 1024 | "keywords": [ 1025 | "container", 1026 | "dependency injection" 1027 | ], 1028 | "support": { 1029 | "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" 1030 | }, 1031 | "time": "2021-10-28T11:13:42+00:00" 1032 | }, 1033 | { 1034 | "name": "psr/container", 1035 | "version": "1.1.2", 1036 | "source": { 1037 | "type": "git", 1038 | "url": "https://github.com/php-fig/container.git", 1039 | "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" 1040 | }, 1041 | "dist": { 1042 | "type": "zip", 1043 | "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", 1044 | "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", 1045 | "shasum": "" 1046 | }, 1047 | "require": { 1048 | "php": ">=7.4.0" 1049 | }, 1050 | "type": "library", 1051 | "autoload": { 1052 | "psr-4": { 1053 | "Psr\\Container\\": "src/" 1054 | } 1055 | }, 1056 | "notification-url": "https://packagist.org/downloads/", 1057 | "license": [ 1058 | "MIT" 1059 | ], 1060 | "authors": [ 1061 | { 1062 | "name": "PHP-FIG", 1063 | "homepage": "https://www.php-fig.org/" 1064 | } 1065 | ], 1066 | "description": "Common Container Interface (PHP FIG PSR-11)", 1067 | "homepage": "https://github.com/php-fig/container", 1068 | "keywords": [ 1069 | "PSR-11", 1070 | "container", 1071 | "container-interface", 1072 | "container-interop", 1073 | "psr" 1074 | ], 1075 | "support": { 1076 | "issues": "https://github.com/php-fig/container/issues", 1077 | "source": "https://github.com/php-fig/container/tree/1.1.2" 1078 | }, 1079 | "time": "2021-11-05T16:50:12+00:00" 1080 | }, 1081 | { 1082 | "name": "psr/http-message", 1083 | "version": "1.0.1", 1084 | "source": { 1085 | "type": "git", 1086 | "url": "https://github.com/php-fig/http-message.git", 1087 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 1088 | }, 1089 | "dist": { 1090 | "type": "zip", 1091 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 1092 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 1093 | "shasum": "" 1094 | }, 1095 | "require": { 1096 | "php": ">=5.3.0" 1097 | }, 1098 | "type": "library", 1099 | "extra": { 1100 | "branch-alias": { 1101 | "dev-master": "1.0.x-dev" 1102 | } 1103 | }, 1104 | "autoload": { 1105 | "psr-4": { 1106 | "Psr\\Http\\Message\\": "src/" 1107 | } 1108 | }, 1109 | "notification-url": "https://packagist.org/downloads/", 1110 | "license": [ 1111 | "MIT" 1112 | ], 1113 | "authors": [ 1114 | { 1115 | "name": "PHP-FIG", 1116 | "homepage": "http://www.php-fig.org/" 1117 | } 1118 | ], 1119 | "description": "Common interface for HTTP messages", 1120 | "homepage": "https://github.com/php-fig/http-message", 1121 | "keywords": [ 1122 | "http", 1123 | "http-message", 1124 | "psr", 1125 | "psr-7", 1126 | "request", 1127 | "response" 1128 | ], 1129 | "support": { 1130 | "source": "https://github.com/php-fig/http-message/tree/master" 1131 | }, 1132 | "time": "2016-08-06T14:39:51+00:00" 1133 | }, 1134 | { 1135 | "name": "ralouphie/getallheaders", 1136 | "version": "3.0.3", 1137 | "source": { 1138 | "type": "git", 1139 | "url": "https://github.com/ralouphie/getallheaders.git", 1140 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 1141 | }, 1142 | "dist": { 1143 | "type": "zip", 1144 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 1145 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 1146 | "shasum": "" 1147 | }, 1148 | "require": { 1149 | "php": ">=5.6" 1150 | }, 1151 | "require-dev": { 1152 | "php-coveralls/php-coveralls": "^2.1", 1153 | "phpunit/phpunit": "^5 || ^6.5" 1154 | }, 1155 | "type": "library", 1156 | "autoload": { 1157 | "files": [ 1158 | "src/getallheaders.php" 1159 | ] 1160 | }, 1161 | "notification-url": "https://packagist.org/downloads/", 1162 | "license": [ 1163 | "MIT" 1164 | ], 1165 | "authors": [ 1166 | { 1167 | "name": "Ralph Khattar", 1168 | "email": "ralph.khattar@gmail.com" 1169 | } 1170 | ], 1171 | "description": "A polyfill for getallheaders.", 1172 | "support": { 1173 | "issues": "https://github.com/ralouphie/getallheaders/issues", 1174 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 1175 | }, 1176 | "time": "2019-03-08T08:55:37+00:00" 1177 | }, 1178 | { 1179 | "name": "sebastian/code-unit-reverse-lookup", 1180 | "version": "1.0.2", 1181 | "source": { 1182 | "type": "git", 1183 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1184 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" 1185 | }, 1186 | "dist": { 1187 | "type": "zip", 1188 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", 1189 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", 1190 | "shasum": "" 1191 | }, 1192 | "require": { 1193 | "php": ">=5.6" 1194 | }, 1195 | "require-dev": { 1196 | "phpunit/phpunit": "^8.5" 1197 | }, 1198 | "type": "library", 1199 | "extra": { 1200 | "branch-alias": { 1201 | "dev-master": "1.0.x-dev" 1202 | } 1203 | }, 1204 | "autoload": { 1205 | "classmap": [ 1206 | "src/" 1207 | ] 1208 | }, 1209 | "notification-url": "https://packagist.org/downloads/", 1210 | "license": [ 1211 | "BSD-3-Clause" 1212 | ], 1213 | "authors": [ 1214 | { 1215 | "name": "Sebastian Bergmann", 1216 | "email": "sebastian@phpunit.de" 1217 | } 1218 | ], 1219 | "description": "Looks up which function or method a line of code belongs to", 1220 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1221 | "support": { 1222 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1223 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" 1224 | }, 1225 | "funding": [ 1226 | { 1227 | "url": "https://github.com/sebastianbergmann", 1228 | "type": "github" 1229 | } 1230 | ], 1231 | "time": "2020-11-30T08:15:22+00:00" 1232 | }, 1233 | { 1234 | "name": "sebastian/comparator", 1235 | "version": "3.0.3", 1236 | "source": { 1237 | "type": "git", 1238 | "url": "https://github.com/sebastianbergmann/comparator.git", 1239 | "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" 1240 | }, 1241 | "dist": { 1242 | "type": "zip", 1243 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", 1244 | "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", 1245 | "shasum": "" 1246 | }, 1247 | "require": { 1248 | "php": ">=7.1", 1249 | "sebastian/diff": "^3.0", 1250 | "sebastian/exporter": "^3.1" 1251 | }, 1252 | "require-dev": { 1253 | "phpunit/phpunit": "^8.5" 1254 | }, 1255 | "type": "library", 1256 | "extra": { 1257 | "branch-alias": { 1258 | "dev-master": "3.0-dev" 1259 | } 1260 | }, 1261 | "autoload": { 1262 | "classmap": [ 1263 | "src/" 1264 | ] 1265 | }, 1266 | "notification-url": "https://packagist.org/downloads/", 1267 | "license": [ 1268 | "BSD-3-Clause" 1269 | ], 1270 | "authors": [ 1271 | { 1272 | "name": "Sebastian Bergmann", 1273 | "email": "sebastian@phpunit.de" 1274 | }, 1275 | { 1276 | "name": "Jeff Welch", 1277 | "email": "whatthejeff@gmail.com" 1278 | }, 1279 | { 1280 | "name": "Volker Dusch", 1281 | "email": "github@wallbash.com" 1282 | }, 1283 | { 1284 | "name": "Bernhard Schussek", 1285 | "email": "bschussek@2bepublished.at" 1286 | } 1287 | ], 1288 | "description": "Provides the functionality to compare PHP values for equality", 1289 | "homepage": "https://github.com/sebastianbergmann/comparator", 1290 | "keywords": [ 1291 | "comparator", 1292 | "compare", 1293 | "equality" 1294 | ], 1295 | "support": { 1296 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1297 | "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" 1298 | }, 1299 | "funding": [ 1300 | { 1301 | "url": "https://github.com/sebastianbergmann", 1302 | "type": "github" 1303 | } 1304 | ], 1305 | "time": "2020-11-30T08:04:30+00:00" 1306 | }, 1307 | { 1308 | "name": "sebastian/diff", 1309 | "version": "3.0.3", 1310 | "source": { 1311 | "type": "git", 1312 | "url": "https://github.com/sebastianbergmann/diff.git", 1313 | "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" 1314 | }, 1315 | "dist": { 1316 | "type": "zip", 1317 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", 1318 | "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", 1319 | "shasum": "" 1320 | }, 1321 | "require": { 1322 | "php": ">=7.1" 1323 | }, 1324 | "require-dev": { 1325 | "phpunit/phpunit": "^7.5 || ^8.0", 1326 | "symfony/process": "^2 || ^3.3 || ^4" 1327 | }, 1328 | "type": "library", 1329 | "extra": { 1330 | "branch-alias": { 1331 | "dev-master": "3.0-dev" 1332 | } 1333 | }, 1334 | "autoload": { 1335 | "classmap": [ 1336 | "src/" 1337 | ] 1338 | }, 1339 | "notification-url": "https://packagist.org/downloads/", 1340 | "license": [ 1341 | "BSD-3-Clause" 1342 | ], 1343 | "authors": [ 1344 | { 1345 | "name": "Sebastian Bergmann", 1346 | "email": "sebastian@phpunit.de" 1347 | }, 1348 | { 1349 | "name": "Kore Nordmann", 1350 | "email": "mail@kore-nordmann.de" 1351 | } 1352 | ], 1353 | "description": "Diff implementation", 1354 | "homepage": "https://github.com/sebastianbergmann/diff", 1355 | "keywords": [ 1356 | "diff", 1357 | "udiff", 1358 | "unidiff", 1359 | "unified diff" 1360 | ], 1361 | "support": { 1362 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1363 | "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" 1364 | }, 1365 | "funding": [ 1366 | { 1367 | "url": "https://github.com/sebastianbergmann", 1368 | "type": "github" 1369 | } 1370 | ], 1371 | "time": "2020-11-30T07:59:04+00:00" 1372 | }, 1373 | { 1374 | "name": "sebastian/environment", 1375 | "version": "4.2.4", 1376 | "source": { 1377 | "type": "git", 1378 | "url": "https://github.com/sebastianbergmann/environment.git", 1379 | "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" 1380 | }, 1381 | "dist": { 1382 | "type": "zip", 1383 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", 1384 | "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", 1385 | "shasum": "" 1386 | }, 1387 | "require": { 1388 | "php": ">=7.1" 1389 | }, 1390 | "require-dev": { 1391 | "phpunit/phpunit": "^7.5" 1392 | }, 1393 | "suggest": { 1394 | "ext-posix": "*" 1395 | }, 1396 | "type": "library", 1397 | "extra": { 1398 | "branch-alias": { 1399 | "dev-master": "4.2-dev" 1400 | } 1401 | }, 1402 | "autoload": { 1403 | "classmap": [ 1404 | "src/" 1405 | ] 1406 | }, 1407 | "notification-url": "https://packagist.org/downloads/", 1408 | "license": [ 1409 | "BSD-3-Clause" 1410 | ], 1411 | "authors": [ 1412 | { 1413 | "name": "Sebastian Bergmann", 1414 | "email": "sebastian@phpunit.de" 1415 | } 1416 | ], 1417 | "description": "Provides functionality to handle HHVM/PHP environments", 1418 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1419 | "keywords": [ 1420 | "Xdebug", 1421 | "environment", 1422 | "hhvm" 1423 | ], 1424 | "support": { 1425 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1426 | "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" 1427 | }, 1428 | "funding": [ 1429 | { 1430 | "url": "https://github.com/sebastianbergmann", 1431 | "type": "github" 1432 | } 1433 | ], 1434 | "time": "2020-11-30T07:53:42+00:00" 1435 | }, 1436 | { 1437 | "name": "sebastian/exporter", 1438 | "version": "3.1.4", 1439 | "source": { 1440 | "type": "git", 1441 | "url": "https://github.com/sebastianbergmann/exporter.git", 1442 | "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" 1443 | }, 1444 | "dist": { 1445 | "type": "zip", 1446 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", 1447 | "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", 1448 | "shasum": "" 1449 | }, 1450 | "require": { 1451 | "php": ">=7.0", 1452 | "sebastian/recursion-context": "^3.0" 1453 | }, 1454 | "require-dev": { 1455 | "ext-mbstring": "*", 1456 | "phpunit/phpunit": "^8.5" 1457 | }, 1458 | "type": "library", 1459 | "extra": { 1460 | "branch-alias": { 1461 | "dev-master": "3.1.x-dev" 1462 | } 1463 | }, 1464 | "autoload": { 1465 | "classmap": [ 1466 | "src/" 1467 | ] 1468 | }, 1469 | "notification-url": "https://packagist.org/downloads/", 1470 | "license": [ 1471 | "BSD-3-Clause" 1472 | ], 1473 | "authors": [ 1474 | { 1475 | "name": "Sebastian Bergmann", 1476 | "email": "sebastian@phpunit.de" 1477 | }, 1478 | { 1479 | "name": "Jeff Welch", 1480 | "email": "whatthejeff@gmail.com" 1481 | }, 1482 | { 1483 | "name": "Volker Dusch", 1484 | "email": "github@wallbash.com" 1485 | }, 1486 | { 1487 | "name": "Adam Harvey", 1488 | "email": "aharvey@php.net" 1489 | }, 1490 | { 1491 | "name": "Bernhard Schussek", 1492 | "email": "bschussek@gmail.com" 1493 | } 1494 | ], 1495 | "description": "Provides the functionality to export PHP variables for visualization", 1496 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1497 | "keywords": [ 1498 | "export", 1499 | "exporter" 1500 | ], 1501 | "support": { 1502 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1503 | "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" 1504 | }, 1505 | "funding": [ 1506 | { 1507 | "url": "https://github.com/sebastianbergmann", 1508 | "type": "github" 1509 | } 1510 | ], 1511 | "time": "2021-11-11T13:51:24+00:00" 1512 | }, 1513 | { 1514 | "name": "sebastian/global-state", 1515 | "version": "3.0.2", 1516 | "source": { 1517 | "type": "git", 1518 | "url": "https://github.com/sebastianbergmann/global-state.git", 1519 | "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" 1520 | }, 1521 | "dist": { 1522 | "type": "zip", 1523 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", 1524 | "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", 1525 | "shasum": "" 1526 | }, 1527 | "require": { 1528 | "php": ">=7.2", 1529 | "sebastian/object-reflector": "^1.1.1", 1530 | "sebastian/recursion-context": "^3.0" 1531 | }, 1532 | "require-dev": { 1533 | "ext-dom": "*", 1534 | "phpunit/phpunit": "^8.0" 1535 | }, 1536 | "suggest": { 1537 | "ext-uopz": "*" 1538 | }, 1539 | "type": "library", 1540 | "extra": { 1541 | "branch-alias": { 1542 | "dev-master": "3.0-dev" 1543 | } 1544 | }, 1545 | "autoload": { 1546 | "classmap": [ 1547 | "src/" 1548 | ] 1549 | }, 1550 | "notification-url": "https://packagist.org/downloads/", 1551 | "license": [ 1552 | "BSD-3-Clause" 1553 | ], 1554 | "authors": [ 1555 | { 1556 | "name": "Sebastian Bergmann", 1557 | "email": "sebastian@phpunit.de" 1558 | } 1559 | ], 1560 | "description": "Snapshotting of global state", 1561 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1562 | "keywords": [ 1563 | "global state" 1564 | ], 1565 | "support": { 1566 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1567 | "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" 1568 | }, 1569 | "funding": [ 1570 | { 1571 | "url": "https://github.com/sebastianbergmann", 1572 | "type": "github" 1573 | } 1574 | ], 1575 | "time": "2022-02-10T06:55:38+00:00" 1576 | }, 1577 | { 1578 | "name": "sebastian/object-enumerator", 1579 | "version": "3.0.4", 1580 | "source": { 1581 | "type": "git", 1582 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1583 | "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" 1584 | }, 1585 | "dist": { 1586 | "type": "zip", 1587 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", 1588 | "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", 1589 | "shasum": "" 1590 | }, 1591 | "require": { 1592 | "php": ">=7.0", 1593 | "sebastian/object-reflector": "^1.1.1", 1594 | "sebastian/recursion-context": "^3.0" 1595 | }, 1596 | "require-dev": { 1597 | "phpunit/phpunit": "^6.0" 1598 | }, 1599 | "type": "library", 1600 | "extra": { 1601 | "branch-alias": { 1602 | "dev-master": "3.0.x-dev" 1603 | } 1604 | }, 1605 | "autoload": { 1606 | "classmap": [ 1607 | "src/" 1608 | ] 1609 | }, 1610 | "notification-url": "https://packagist.org/downloads/", 1611 | "license": [ 1612 | "BSD-3-Clause" 1613 | ], 1614 | "authors": [ 1615 | { 1616 | "name": "Sebastian Bergmann", 1617 | "email": "sebastian@phpunit.de" 1618 | } 1619 | ], 1620 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1621 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1622 | "support": { 1623 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1624 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" 1625 | }, 1626 | "funding": [ 1627 | { 1628 | "url": "https://github.com/sebastianbergmann", 1629 | "type": "github" 1630 | } 1631 | ], 1632 | "time": "2020-11-30T07:40:27+00:00" 1633 | }, 1634 | { 1635 | "name": "sebastian/object-reflector", 1636 | "version": "1.1.2", 1637 | "source": { 1638 | "type": "git", 1639 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1640 | "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" 1641 | }, 1642 | "dist": { 1643 | "type": "zip", 1644 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", 1645 | "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", 1646 | "shasum": "" 1647 | }, 1648 | "require": { 1649 | "php": ">=7.0" 1650 | }, 1651 | "require-dev": { 1652 | "phpunit/phpunit": "^6.0" 1653 | }, 1654 | "type": "library", 1655 | "extra": { 1656 | "branch-alias": { 1657 | "dev-master": "1.1-dev" 1658 | } 1659 | }, 1660 | "autoload": { 1661 | "classmap": [ 1662 | "src/" 1663 | ] 1664 | }, 1665 | "notification-url": "https://packagist.org/downloads/", 1666 | "license": [ 1667 | "BSD-3-Clause" 1668 | ], 1669 | "authors": [ 1670 | { 1671 | "name": "Sebastian Bergmann", 1672 | "email": "sebastian@phpunit.de" 1673 | } 1674 | ], 1675 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1676 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1677 | "support": { 1678 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1679 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" 1680 | }, 1681 | "funding": [ 1682 | { 1683 | "url": "https://github.com/sebastianbergmann", 1684 | "type": "github" 1685 | } 1686 | ], 1687 | "time": "2020-11-30T07:37:18+00:00" 1688 | }, 1689 | { 1690 | "name": "sebastian/recursion-context", 1691 | "version": "3.0.1", 1692 | "source": { 1693 | "type": "git", 1694 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1695 | "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" 1696 | }, 1697 | "dist": { 1698 | "type": "zip", 1699 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", 1700 | "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", 1701 | "shasum": "" 1702 | }, 1703 | "require": { 1704 | "php": ">=7.0" 1705 | }, 1706 | "require-dev": { 1707 | "phpunit/phpunit": "^6.0" 1708 | }, 1709 | "type": "library", 1710 | "extra": { 1711 | "branch-alias": { 1712 | "dev-master": "3.0.x-dev" 1713 | } 1714 | }, 1715 | "autoload": { 1716 | "classmap": [ 1717 | "src/" 1718 | ] 1719 | }, 1720 | "notification-url": "https://packagist.org/downloads/", 1721 | "license": [ 1722 | "BSD-3-Clause" 1723 | ], 1724 | "authors": [ 1725 | { 1726 | "name": "Sebastian Bergmann", 1727 | "email": "sebastian@phpunit.de" 1728 | }, 1729 | { 1730 | "name": "Jeff Welch", 1731 | "email": "whatthejeff@gmail.com" 1732 | }, 1733 | { 1734 | "name": "Adam Harvey", 1735 | "email": "aharvey@php.net" 1736 | } 1737 | ], 1738 | "description": "Provides functionality to recursively process PHP variables", 1739 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1740 | "support": { 1741 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1742 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" 1743 | }, 1744 | "funding": [ 1745 | { 1746 | "url": "https://github.com/sebastianbergmann", 1747 | "type": "github" 1748 | } 1749 | ], 1750 | "time": "2020-11-30T07:34:24+00:00" 1751 | }, 1752 | { 1753 | "name": "sebastian/resource-operations", 1754 | "version": "2.0.2", 1755 | "source": { 1756 | "type": "git", 1757 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1758 | "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" 1759 | }, 1760 | "dist": { 1761 | "type": "zip", 1762 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", 1763 | "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", 1764 | "shasum": "" 1765 | }, 1766 | "require": { 1767 | "php": ">=7.1" 1768 | }, 1769 | "type": "library", 1770 | "extra": { 1771 | "branch-alias": { 1772 | "dev-master": "2.0-dev" 1773 | } 1774 | }, 1775 | "autoload": { 1776 | "classmap": [ 1777 | "src/" 1778 | ] 1779 | }, 1780 | "notification-url": "https://packagist.org/downloads/", 1781 | "license": [ 1782 | "BSD-3-Clause" 1783 | ], 1784 | "authors": [ 1785 | { 1786 | "name": "Sebastian Bergmann", 1787 | "email": "sebastian@phpunit.de" 1788 | } 1789 | ], 1790 | "description": "Provides a list of PHP built-in functions that operate on resources", 1791 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1792 | "support": { 1793 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1794 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" 1795 | }, 1796 | "funding": [ 1797 | { 1798 | "url": "https://github.com/sebastianbergmann", 1799 | "type": "github" 1800 | } 1801 | ], 1802 | "time": "2020-11-30T07:30:19+00:00" 1803 | }, 1804 | { 1805 | "name": "sebastian/type", 1806 | "version": "1.1.4", 1807 | "source": { 1808 | "type": "git", 1809 | "url": "https://github.com/sebastianbergmann/type.git", 1810 | "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" 1811 | }, 1812 | "dist": { 1813 | "type": "zip", 1814 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", 1815 | "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", 1816 | "shasum": "" 1817 | }, 1818 | "require": { 1819 | "php": ">=7.2" 1820 | }, 1821 | "require-dev": { 1822 | "phpunit/phpunit": "^8.2" 1823 | }, 1824 | "type": "library", 1825 | "extra": { 1826 | "branch-alias": { 1827 | "dev-master": "1.1-dev" 1828 | } 1829 | }, 1830 | "autoload": { 1831 | "classmap": [ 1832 | "src/" 1833 | ] 1834 | }, 1835 | "notification-url": "https://packagist.org/downloads/", 1836 | "license": [ 1837 | "BSD-3-Clause" 1838 | ], 1839 | "authors": [ 1840 | { 1841 | "name": "Sebastian Bergmann", 1842 | "email": "sebastian@phpunit.de", 1843 | "role": "lead" 1844 | } 1845 | ], 1846 | "description": "Collection of value objects that represent the types of the PHP type system", 1847 | "homepage": "https://github.com/sebastianbergmann/type", 1848 | "support": { 1849 | "issues": "https://github.com/sebastianbergmann/type/issues", 1850 | "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" 1851 | }, 1852 | "funding": [ 1853 | { 1854 | "url": "https://github.com/sebastianbergmann", 1855 | "type": "github" 1856 | } 1857 | ], 1858 | "time": "2020-11-30T07:25:11+00:00" 1859 | }, 1860 | { 1861 | "name": "sebastian/version", 1862 | "version": "2.0.1", 1863 | "source": { 1864 | "type": "git", 1865 | "url": "https://github.com/sebastianbergmann/version.git", 1866 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1867 | }, 1868 | "dist": { 1869 | "type": "zip", 1870 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1871 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1872 | "shasum": "" 1873 | }, 1874 | "require": { 1875 | "php": ">=5.6" 1876 | }, 1877 | "type": "library", 1878 | "extra": { 1879 | "branch-alias": { 1880 | "dev-master": "2.0.x-dev" 1881 | } 1882 | }, 1883 | "autoload": { 1884 | "classmap": [ 1885 | "src/" 1886 | ] 1887 | }, 1888 | "notification-url": "https://packagist.org/downloads/", 1889 | "license": [ 1890 | "BSD-3-Clause" 1891 | ], 1892 | "authors": [ 1893 | { 1894 | "name": "Sebastian Bergmann", 1895 | "email": "sebastian@phpunit.de", 1896 | "role": "lead" 1897 | } 1898 | ], 1899 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1900 | "homepage": "https://github.com/sebastianbergmann/version", 1901 | "support": { 1902 | "issues": "https://github.com/sebastianbergmann/version/issues", 1903 | "source": "https://github.com/sebastianbergmann/version/tree/master" 1904 | }, 1905 | "time": "2016-10-03T07:35:21+00:00" 1906 | }, 1907 | { 1908 | "name": "slim/slim", 1909 | "version": "3.12.3", 1910 | "source": { 1911 | "type": "git", 1912 | "url": "https://github.com/slimphp/Slim.git", 1913 | "reference": "1c9318a84ffb890900901136d620b4f03a59da38" 1914 | }, 1915 | "dist": { 1916 | "type": "zip", 1917 | "url": "https://api.github.com/repos/slimphp/Slim/zipball/1c9318a84ffb890900901136d620b4f03a59da38", 1918 | "reference": "1c9318a84ffb890900901136d620b4f03a59da38", 1919 | "shasum": "" 1920 | }, 1921 | "require": { 1922 | "ext-json": "*", 1923 | "ext-libxml": "*", 1924 | "ext-simplexml": "*", 1925 | "nikic/fast-route": "^1.0", 1926 | "php": ">=5.5.0", 1927 | "pimple/pimple": "^3.0", 1928 | "psr/container": "^1.0", 1929 | "psr/http-message": "^1.0" 1930 | }, 1931 | "provide": { 1932 | "psr/http-message-implementation": "1.0" 1933 | }, 1934 | "require-dev": { 1935 | "phpunit/phpunit": "^4.0", 1936 | "squizlabs/php_codesniffer": "^2.5" 1937 | }, 1938 | "type": "library", 1939 | "autoload": { 1940 | "psr-4": { 1941 | "Slim\\": "Slim" 1942 | } 1943 | }, 1944 | "notification-url": "https://packagist.org/downloads/", 1945 | "license": [ 1946 | "MIT" 1947 | ], 1948 | "authors": [ 1949 | { 1950 | "name": "Josh Lockhart", 1951 | "email": "hello@joshlockhart.com", 1952 | "homepage": "https://joshlockhart.com" 1953 | }, 1954 | { 1955 | "name": "Andrew Smith", 1956 | "email": "a.smith@silentworks.co.uk", 1957 | "homepage": "http://silentworks.co.uk" 1958 | }, 1959 | { 1960 | "name": "Rob Allen", 1961 | "email": "rob@akrabat.com", 1962 | "homepage": "http://akrabat.com" 1963 | }, 1964 | { 1965 | "name": "Gabriel Manricks", 1966 | "email": "gmanricks@me.com", 1967 | "homepage": "http://gabrielmanricks.com" 1968 | } 1969 | ], 1970 | "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", 1971 | "homepage": "https://slimframework.com", 1972 | "keywords": [ 1973 | "api", 1974 | "framework", 1975 | "micro", 1976 | "router" 1977 | ], 1978 | "support": { 1979 | "issues": "https://github.com/slimphp/Slim/issues", 1980 | "source": "https://github.com/slimphp/Slim/tree/3.x" 1981 | }, 1982 | "time": "2019-11-28T17:40:33+00:00" 1983 | }, 1984 | { 1985 | "name": "symfony/polyfill-ctype", 1986 | "version": "v1.25.0", 1987 | "source": { 1988 | "type": "git", 1989 | "url": "https://github.com/symfony/polyfill-ctype.git", 1990 | "reference": "30885182c981ab175d4d034db0f6f469898070ab" 1991 | }, 1992 | "dist": { 1993 | "type": "zip", 1994 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", 1995 | "reference": "30885182c981ab175d4d034db0f6f469898070ab", 1996 | "shasum": "" 1997 | }, 1998 | "require": { 1999 | "php": ">=7.1" 2000 | }, 2001 | "provide": { 2002 | "ext-ctype": "*" 2003 | }, 2004 | "suggest": { 2005 | "ext-ctype": "For best performance" 2006 | }, 2007 | "type": "library", 2008 | "extra": { 2009 | "branch-alias": { 2010 | "dev-main": "1.23-dev" 2011 | }, 2012 | "thanks": { 2013 | "name": "symfony/polyfill", 2014 | "url": "https://github.com/symfony/polyfill" 2015 | } 2016 | }, 2017 | "autoload": { 2018 | "files": [ 2019 | "bootstrap.php" 2020 | ], 2021 | "psr-4": { 2022 | "Symfony\\Polyfill\\Ctype\\": "" 2023 | } 2024 | }, 2025 | "notification-url": "https://packagist.org/downloads/", 2026 | "license": [ 2027 | "MIT" 2028 | ], 2029 | "authors": [ 2030 | { 2031 | "name": "Gert de Pagter", 2032 | "email": "BackEndTea@gmail.com" 2033 | }, 2034 | { 2035 | "name": "Symfony Community", 2036 | "homepage": "https://symfony.com/contributors" 2037 | } 2038 | ], 2039 | "description": "Symfony polyfill for ctype functions", 2040 | "homepage": "https://symfony.com", 2041 | "keywords": [ 2042 | "compatibility", 2043 | "ctype", 2044 | "polyfill", 2045 | "portable" 2046 | ], 2047 | "support": { 2048 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" 2049 | }, 2050 | "funding": [ 2051 | { 2052 | "url": "https://symfony.com/sponsor", 2053 | "type": "custom" 2054 | }, 2055 | { 2056 | "url": "https://github.com/fabpot", 2057 | "type": "github" 2058 | }, 2059 | { 2060 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2061 | "type": "tidelift" 2062 | } 2063 | ], 2064 | "time": "2021-10-20T20:35:02+00:00" 2065 | }, 2066 | { 2067 | "name": "theseer/tokenizer", 2068 | "version": "1.2.1", 2069 | "source": { 2070 | "type": "git", 2071 | "url": "https://github.com/theseer/tokenizer.git", 2072 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 2073 | }, 2074 | "dist": { 2075 | "type": "zip", 2076 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 2077 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 2078 | "shasum": "" 2079 | }, 2080 | "require": { 2081 | "ext-dom": "*", 2082 | "ext-tokenizer": "*", 2083 | "ext-xmlwriter": "*", 2084 | "php": "^7.2 || ^8.0" 2085 | }, 2086 | "type": "library", 2087 | "autoload": { 2088 | "classmap": [ 2089 | "src/" 2090 | ] 2091 | }, 2092 | "notification-url": "https://packagist.org/downloads/", 2093 | "license": [ 2094 | "BSD-3-Clause" 2095 | ], 2096 | "authors": [ 2097 | { 2098 | "name": "Arne Blankerts", 2099 | "email": "arne@blankerts.de", 2100 | "role": "Developer" 2101 | } 2102 | ], 2103 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2104 | "support": { 2105 | "issues": "https://github.com/theseer/tokenizer/issues", 2106 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 2107 | }, 2108 | "funding": [ 2109 | { 2110 | "url": "https://github.com/theseer", 2111 | "type": "github" 2112 | } 2113 | ], 2114 | "time": "2021-07-28T10:34:58+00:00" 2115 | }, 2116 | { 2117 | "name": "webmozart/assert", 2118 | "version": "1.10.0", 2119 | "source": { 2120 | "type": "git", 2121 | "url": "https://github.com/webmozarts/assert.git", 2122 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" 2123 | }, 2124 | "dist": { 2125 | "type": "zip", 2126 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", 2127 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", 2128 | "shasum": "" 2129 | }, 2130 | "require": { 2131 | "php": "^7.2 || ^8.0", 2132 | "symfony/polyfill-ctype": "^1.8" 2133 | }, 2134 | "conflict": { 2135 | "phpstan/phpstan": "<0.12.20", 2136 | "vimeo/psalm": "<4.6.1 || 4.6.2" 2137 | }, 2138 | "require-dev": { 2139 | "phpunit/phpunit": "^8.5.13" 2140 | }, 2141 | "type": "library", 2142 | "extra": { 2143 | "branch-alias": { 2144 | "dev-master": "1.10-dev" 2145 | } 2146 | }, 2147 | "autoload": { 2148 | "psr-4": { 2149 | "Webmozart\\Assert\\": "src/" 2150 | } 2151 | }, 2152 | "notification-url": "https://packagist.org/downloads/", 2153 | "license": [ 2154 | "MIT" 2155 | ], 2156 | "authors": [ 2157 | { 2158 | "name": "Bernhard Schussek", 2159 | "email": "bschussek@gmail.com" 2160 | } 2161 | ], 2162 | "description": "Assertions to validate method input/output with nice error messages.", 2163 | "keywords": [ 2164 | "assert", 2165 | "check", 2166 | "validate" 2167 | ], 2168 | "support": { 2169 | "issues": "https://github.com/webmozarts/assert/issues", 2170 | "source": "https://github.com/webmozarts/assert/tree/1.10.0" 2171 | }, 2172 | "time": "2021-03-09T10:59:23+00:00" 2173 | } 2174 | ], 2175 | "aliases": [], 2176 | "minimum-stability": "stable", 2177 | "stability-flags": [], 2178 | "prefer-stable": false, 2179 | "prefer-lowest": false, 2180 | "platform": [], 2181 | "platform-dev": [], 2182 | "plugin-api-version": "2.3.0" 2183 | } 2184 | -------------------------------------------------------------------------------- /php.ini: -------------------------------------------------------------------------------- 1 | display_errors=On 2 | extension=curl.so 3 | extension_dir=/opt/lib/php/8.0/modules 4 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | ./tests/ 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/BootstrapTest.php: -------------------------------------------------------------------------------- 1 | start(); 24 | 25 | putenv('AWS_LAMBDA_RUNTIME_API=' . self::$server->getHost() . ':' . self::$server->getPort()); 26 | putenv('LAMBDA_TASK_ROOT=' . __DIR__ . '/_files'); 27 | putenv('MAX_EXECUTION_TIME=5'); 28 | if (!file_exists('/var/task/php.ini')) { 29 | // use default environment settings if not running inside test container 30 | putenv('CONFIG_PATH=/dev/null'); 31 | } 32 | putenv('_HANDLER=public/index.php'); 33 | $loop = false; 34 | 35 | ob_start(); // capture output to avoid echo'ing the shebang at top of bootstrap 36 | if (file_exists('/opt/bootstrap')) { 37 | require '/opt/bootstrap'; 38 | } else { 39 | require __DIR__ . '/../bootstrap'; 40 | } 41 | ob_end_clean(); 42 | } 43 | 44 | /** 45 | * Helper function for setting the next response from the Lambda runtime API 46 | * next invocation endpoint. 47 | */ 48 | private function setNextInvocation(array $response): string 49 | { 50 | $id = sprintf( 51 | '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', 52 | mt_rand(0, 0xffff), 53 | mt_rand(0, 0xffff), 54 | mt_rand(0, 0xffff), 55 | mt_rand(0, 0x0fff) | 0x4000, 56 | mt_rand(0, 0x3fff) | 0x8000, 57 | mt_rand(0, 0xffff), 58 | mt_rand(0, 0xffff), 59 | mt_rand(0, 0xffff) 60 | ); 61 | 62 | $body = json_encode($response); 63 | $headers = ['Lambda-Runtime-Aws-Request-Id' => $id]; 64 | self::$server->setResponseOfPath( 65 | '/' . LAMBDA_RUNTIME_API_VERSION . '/runtime/invocation/next', 66 | new Response($body, $headers, 200) 67 | ); 68 | 69 | return $id; 70 | } 71 | 72 | /** 73 | * Helper function for getting and decoding the last response sent to the 74 | * Lambda runtime API invocation response endpoint. 75 | */ 76 | private function getInvocationResponse(): \stdclass 77 | { 78 | $request = self::$server->getLastRequest(); 79 | if ($request === null) { 80 | $error = new \Exception('No request received'); 81 | throw $error; 82 | } 83 | 84 | $body = $request->getInput(); 85 | $response = json_decode($body); 86 | 87 | return $response; 88 | } 89 | 90 | /** 91 | * Calls the root path and ensures we get the home page. 92 | */ 93 | public function testGetRoot(): void 94 | { 95 | $this->setNextInvocation([ 96 | 'requestContext' => [], 97 | 'httpMethod' => 'GET', 98 | 'path' => '/', 99 | 'body' => '', 100 | ]); 101 | 102 | $invocation = handleNextRequest(); 103 | 104 | $response = $this->getInvocationResponse(); 105 | $this->assertEquals(200, $response->statusCode); 106 | $this->assertEquals('Home', $response->body); 107 | } 108 | 109 | /** 110 | * ??? 111 | */ 112 | public function testPostRoot(): void 113 | { 114 | $this->setNextInvocation([ 115 | 'requestContext' => [], 116 | 'httpMethod' => 'POST', 117 | 'headers' => [ 118 | 'content-type' => 'application/x-www-form-urlencoded; charset=UTF-8', 119 | ], 120 | 'path' => '/', 121 | 'body' => 'foo=bar&bar=foo', 122 | ]); 123 | 124 | $invocation = handleNextRequest(); 125 | 126 | $response = $this->getInvocationResponse(); 127 | $this->assertEquals('barfoo', $response->body); 128 | } 129 | 130 | /** 131 | * Calls a path handled by the dynamic routing and ensure the correct 132 | * response is returned. 133 | */ 134 | public function testRouterPath(): void 135 | { 136 | $this->setNextInvocation([ 137 | 'requestContext' => [], 138 | 'httpMethod' => 'GET', 139 | 'path' => '/foo/bar/baz', 140 | 'body' => '', 141 | ]); 142 | 143 | $invocation = handleNextRequest(); 144 | 145 | $response = $this->getInvocationResponse(); 146 | $this->assertEquals('foo/bar/baz', $response->body); 147 | } 148 | 149 | /** 150 | * Calls a path that refers to an explicit PHP script and ensure the 151 | * correct response is returned. 152 | */ 153 | public function testExplicitPath(): void 154 | { 155 | $this->setNextInvocation([ 156 | 'requestContext' => [], 157 | 'httpMethod' => 'GET', 158 | 'path' => '/subdir/index.php', 159 | 'body' => '', 160 | ]); 161 | 162 | $invocation = handleNextRequest(); 163 | 164 | $response = $this->getInvocationResponse(); 165 | $this->assertEquals(200, $response->statusCode); 166 | $this->assertEquals('subdir', $response->body); 167 | } 168 | 169 | /** 170 | * Calls the boostrap as if the incoming event is coming from API Gateway 171 | * (no `requestContext` property), ensuring `statusDescription` is not set. 172 | */ 173 | public function testNoStatusDescriptionHeaderForAPIGateway(): void 174 | { 175 | $this->setNextInvocation([ 176 | 'httpMethod' => 'GET', 177 | 'path' => '/', 178 | 'body' => '', 179 | ]); 180 | 181 | $invocation = handleNextRequest(); 182 | 183 | $response = $this->getInvocationResponse(); 184 | $this->assertFalse(property_exists($response, 'statusDescription')); 185 | } 186 | 187 | /** 188 | * Calls the boostrap as if the incoming event is coming from ALB (includes 189 | * a `requestContext` property), ensuring `statusDescription` is set. 190 | */ 191 | public function testStatusDescriptionHeaderForALBRequest(): void 192 | { 193 | $this->setNextInvocation([ 194 | 'requestContext' => [], 195 | 'httpMethod' => 'GET', 196 | 'path' => '/', 197 | 'body' => '', 198 | ]); 199 | 200 | $invocation = handleNextRequest(); 201 | 202 | $response = $this->getInvocationResponse(); 203 | $this->assertEquals('200 OK', $response->statusDescription); 204 | } 205 | 206 | /** 207 | * Calls the bootstrap with a path that matches a directory inside the 208 | * handler path that contains an index.php script. 209 | */ 210 | public function testSubDirectoryWithIndex(): void 211 | { 212 | $this->setNextInvocation([ 213 | 'requestContext' => [], 214 | 'httpMethod' => 'GET', 215 | 'path' => '/subdir/', 216 | 'body' => '', 217 | ]); 218 | 219 | $invocation = handleNextRequest(); 220 | 221 | $response = $this->getInvocationResponse(); 222 | $this->assertEquals('subdir', $response->body); 223 | } 224 | 225 | /** 226 | * Requests a path that matches a directory inside the handler path that 227 | * contains an index.php script, but without a trailing slash. 228 | */ 229 | public function testSubDirectoryWithIndexWithoutTrailingSlash(): void 230 | { 231 | $this->setNextInvocation([ 232 | 'requestContext' => [], 233 | 'httpMethod' => 'GET', 234 | 'path' => '/subdir', 235 | 'body' => '', 236 | ]); 237 | 238 | $invocation = handleNextRequest(); 239 | 240 | $response = $this->getInvocationResponse(); 241 | $this->assertEquals(301, $response->statusCode); 242 | $this->assertEquals('/subdir/', $response->headers->Location); 243 | } 244 | 245 | /** 246 | * Requests a path that returns slower than the declared max execution time. 247 | */ 248 | public function testSlowRequest(): void 249 | { 250 | $this->setNextInvocation([ 251 | 'requestContext' => [], 252 | 'httpMethod' => 'GET', 253 | 'path' => '/slow', 254 | 'body' => '', 255 | ]); 256 | 257 | $this->expectOutputString('PHP took longer than 5 seconds to return response'); 258 | $invocation = handleNextRequest(); 259 | 260 | $response = $this->getInvocationResponse(); 261 | $this->assertEquals(500, $response->statusCode); 262 | } 263 | 264 | /** 265 | * Performs request with a `multiValueHeaders` property. 266 | */ 267 | public function testMultiValueHeaderRequest(): void 268 | { 269 | $this->setNextInvocation([ 270 | 'requestContext' => [], 271 | 'httpMethod' => 'GET', 272 | 'path' => '/multi-value-header', 273 | 'multiValueHeaders' => [ 274 | 'Test' => [ 275 | 'foo', 276 | 'bar', 277 | ], 278 | ], 279 | 'body' => '', 280 | ]); 281 | 282 | $invocation = handleNextRequest(); 283 | 284 | $response = $this->getInvocationResponse(); 285 | $this->assertEquals('foo', $response->body); 286 | } 287 | 288 | /** 289 | * Receive a response with multiple values for the same header key. 290 | */ 291 | public function testMultiValueHeaderResponse(): void 292 | { 293 | $this->setNextInvocation([ 294 | 'requestContext' => [], 295 | 'httpMethod' => 'GET', 296 | 'path' => '/multi-value-header', 297 | 'multiValueHeaders' => [], 298 | 'body' => '', 299 | ]); 300 | 301 | $invocation = handleNextRequest(); 302 | 303 | $response = $this->getInvocationResponse(); 304 | $this->assertEquals(['foo', 'bar'], $response->multiValueHeaders->Test); 305 | } 306 | 307 | /** 308 | * Performs a request with a standard query string. 309 | */ 310 | public function testQueryString(): void 311 | { 312 | $this->setNextInvocation([ 313 | 'requestContext' => [], 314 | 'httpMethod' => 'GET', 315 | 'path' => '/echo', 316 | 'multiValueHeaders' => [], 317 | 'queryStringParameters' => [ 318 | 'a' => 'foo', 319 | 'b' => 'bar', 320 | ], 321 | 'body' => '', 322 | ]); 323 | 324 | $invocation = handleNextRequest(); 325 | 326 | $response = $this->getInvocationResponse(); 327 | $this->assertEquals('{"a":"foo","b":"bar"}', $response->body); 328 | } 329 | 330 | /** 331 | * Performs a request with a query string with multiple values against the 332 | * same key. 333 | */ 334 | public function testMultiValueQueryString(): void 335 | { 336 | $this->setNextInvocation([ 337 | 'requestContext' => [], 338 | 'httpMethod' => 'GET', 339 | 'path' => '/echo', 340 | 'multiValueHeaders' => [], 341 | 'multiValueQueryStringParameters' => [ 342 | 'a' => [ 343 | 'foo', 344 | 'bar', 345 | ], 346 | ], 347 | 'body' => '', 348 | ]); 349 | 350 | $invocation = handleNextRequest(); 351 | 352 | $response = $this->getInvocationResponse(); 353 | $this->assertEquals('{"a":"bar"}', $response->body); 354 | } 355 | 356 | /** 357 | * Performs a request with a PHP-specific array style query string. 358 | */ 359 | public function testArrayQueryString(): void 360 | { 361 | $this->setNextInvocation([ 362 | 'requestContext' => [], 363 | 'httpMethod' => 'GET', 364 | 'path' => '/echo', 365 | 'multiValueHeaders' => [], 366 | 'multiValueQueryStringParameters' => [ 367 | 'a' => [ 368 | 'foo', 369 | ], 370 | 'b[]' => [ 371 | 'foo', 372 | 'bar', 373 | ], 374 | 'c[a]' => [ 375 | 'foo', 376 | ], 377 | 'c[b]' => [ 378 | 'bar', 379 | ], 380 | ], 381 | 'body' => '', 382 | ]); 383 | 384 | $invocation = handleNextRequest(); 385 | 386 | $response = $this->getInvocationResponse(); 387 | $this->assertEquals( 388 | '{"a":"foo","b":["foo","bar"],"c":{"a":"foo","b":"bar"}}', 389 | $response->body 390 | ); 391 | } 392 | 393 | /** 394 | * Requests an endpoint which returns an oversized response. 395 | */ 396 | public function testLargeResponse(): void 397 | { 398 | $this->setNextInvocation([ 399 | 'requestContext' => [], 400 | 'httpMethod' => 'GET', 401 | 'path' => '/large-response', 402 | 'body' => '', 403 | ]); 404 | 405 | $this->expectOutputRegex('/^Response size is too large for ALB \([0-9]{7,} bytes\)\n$/'); 406 | $invocation = handleNextRequest(); 407 | 408 | $response = $this->getInvocationResponse(); 409 | $this->assertEquals(500, $response->statusCode); 410 | } 411 | 412 | public function testCurl(): void 413 | { 414 | $url = 'https://www.google.com/'; 415 | 416 | $ch = curl_init(); 417 | curl_setopt($ch, CURLOPT_URL, $url); 418 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 419 | 420 | curl_exec($ch); 421 | 422 | $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 423 | 424 | curl_close($ch); 425 | 426 | $this->assertEquals(200, $status); 427 | } 428 | 429 | /** 430 | * Stops the mock server to ensure the address can be re-used. 431 | */ 432 | public static function tearDownAfterClass(): void 433 | { 434 | self::$server->stop(); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /tests/_files/public/index.php: -------------------------------------------------------------------------------- 1 | get('/', function ($request, $response) { 12 | return $response->getBody()->write('Home'); 13 | }); 14 | 15 | $app->post('/', function ($request, $response) { 16 | $post = $request->getParsedBody(); 17 | $string = $post['foo'] . $post['bar']; 18 | return $response->getBody()->write($string); 19 | }); 20 | 21 | $app->get('/foo/{bar}/baz', function ($request, $response, array $args) { 22 | ['bar' => $bar] = $args; 23 | return $response->getBody()->write("foo/${bar}/baz"); 24 | }); 25 | 26 | $app->get('/slow', function ($request, $response) { 27 | sleep(10); 28 | }); 29 | 30 | $app->get('/multi-value-header', function ($request, $response) { 31 | $body = $response->getBody(); 32 | $body->write($request->getHeaderLine('Test')); 33 | return $response 34 | ->withAddedHeader('Test', 'foo') 35 | ->withAddedHeader('Test', 'bar'); 36 | }); 37 | 38 | $app->get('/echo', function ($request, $response) { 39 | return $response->withJson($request->getQueryParams()); 40 | }); 41 | 42 | $app->get('/large-response', function ($request, $response) { 43 | $body = $response->getBody(); 44 | while ($body->getSize() < 1000000) { 45 | $body->write(' '); 46 | } 47 | return $response; 48 | }); 49 | 50 | $app->run(); 51 | -------------------------------------------------------------------------------- /tests/_files/public/subdir/index.php: -------------------------------------------------------------------------------- 1 |