diff --git a/.gitignore b/.gitignore index 7a07598..cefc3fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ -php73.zip -php74.zip -phpunit.xml -vendor +layer.zip diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..37c18ff --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM public.ecr.aws/lambda/provided:latest + +RUN dnf install -y php php-mysqlnd php-intl diff --git a/README.md b/README.md index 40e88c3..767a52d 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,9 @@ # PHP Runtime Layer for AWS Lambda -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. +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 application. This layer utilises the PHP CGI runtime to replicate the environment offered in other runtime setups such as PHP FPM. -## PHP 8 - -This repo has been updated to PHP 8.0 and with the intention of fully supporting the `arm64` Lambda environment. - -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. - ## Usage ### General Usage @@ -46,70 +40,38 @@ You can optionally format the output by declaring the `ACCESS_FORMAT` variable. The default value is `"%m %r%Q%q" %s %f %d`. ### Extensions -The following extensions are built into the layer and available in `/opt/lib/php/8.0/modules`: -``` -bz2.so -calendar.so -ctype.so -curl.so -dom.so -exif.so -fileinfo.so -ftp.so -gettext.so -iconv.so -mbstring.so -mysqli.so -mysqlnd.so -pdo.so -pdo_mysql.so -pdo_sqlite.so -phar.so -simplexml.so -sockets.so -sqlite3.so -tokenizer.so -xml.so -xmlreader.so -xmlwriter.so -xsl.so -zip.so -``` +When building, the default extensions for the most recent PHP version will be included in `/opt/lib/php/modules`. -These extensions are not loaded by default. You must add the extension to a php.ini file to use it: +These extensions are not loaded by default. You must add the extension to a `php.ini` file to use it, e.g. ```ini extension=dom.so ``` -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. - -### Amazon Linux 2 +It is recommended that additional extensions be provided by a separate Lambda Layer with the extension .so files placed in the `/lib/php/modules` directory so they can be loaded alongside the built-in extensions listed above. -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. +### Architecture -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. +`arm64` is the target architecture for this Lambda layer. ## Development ### Building -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. +The build process requires Docker to boot a container provided by Amazon which replicates the current Lambda environment. -Once your EC2 instance is booted and you have a copy of this repo available, run the `build.sh` script. +With a system that has Docker installed, run `./build.sh`. This script will build a new Docker image from the Amazon-supplied one, launch a container and then copy the required files for PHP to execute on Lambda from the container to the local file system, before creating a zip file. -This will build a layer zip file (`php80.zip`) for you in the current directory. +The resulting `layer.zip` file is your final Lambda layer. ### Testing -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. +In the `test` directory is as `run.sh` script which will launch the Amazon-supplied Lambda environment container on a local Docker install, supplying the most recent layer build on your local system to it. -``` -composer test -``` +This will result in an HTTP server on port 9000 which mimics the Lambda environment, ready to receive invocation requests. -This assumes you have PHP and composer setup in your local environment. +The second script in this directory, `request.sh`, provides an example of how to send a request to the HTTP server. ## Disclaimer diff --git a/build.sh b/build.sh index 431f5db..c6270e3 100755 --- a/build.sh +++ b/build.sh @@ -1,29 +1,48 @@ #!/usr/bin/env sh -REQUIRED_LIB_FILES="/usr/lib64/libedit.so.0 -/usr/lib64/libonig.so.2" - -echo "Building layer for PHP 8 - using Amazon Linux Extras" - -amazon-linux-extras enable php8.0 -yum install -y php-cli php-dom php-mbstring php-mysqlnd - -mkdir /tmp/layer -cd /tmp/layer -cp /opt/layer/php.ini . -cp /opt/layer/bootstrap . -chmod a+x bootstrap - -mkdir bin -cp /usr/bin/php bin/ -cp /usr/bin/php-cgi bin/ - -mkdir lib -for LIB in $REQUIRED_LIB_FILES; do - cp $LIB lib/ +# tidy up any previous attempts +rm layer.zip +rm -rf build + +# this line force Docker to build for arm64 regardless of host architecture +docker buildx build --platform linux/arm64 -t aiir/php . + +# create a container from the docker image, which should contain a PHP installation +docker run -d --name dummy --platform linux/arm64 --entrypoint sleep aiir/php infinity + +# extract the required files from the container to the host file system +mkdir -p build/bin build/lib/php + +docker cp dummy:/usr/bin/php build/bin/ +docker cp dummy:/usr/bin/php-cgi build/bin/ + +EXTENSION_DIR=$(docker exec dummy php -r "echo ini_get('extension_dir');") + +docker cp -a "dummy:$EXTENSION_DIR" build/lib/php/modules + +# get all unique shared library dependencies for both binaries, resolve symlinks, and copy both symlink and real file +docker exec dummy sh -c " + ldd /usr/bin/php /usr/bin/php-cgi $EXTENSION_DIR/*.so \ + | grep -oP '(?<=> )/[^ ]+' \ + | sort -u \ + | while read -r lib; do + echo \"\$lib\" + readlink -f \"\$lib\" + done \ + | sort -u +" | while read -r path; do + docker cp "dummy:$path" build/lib/ done -mkdir -p lib/php/8.0 -cp -a /usr/lib64/php/modules lib/php/8.0/ +# destroy the container +docker rm -f dummy + +# copy the bootstrap and config +cp src/bootstrap build/ +cp src/php.ini build/ -zip -r /opt/layer/php80.zip . +# create the layer zip file +cd build +zip -ry ../layer.zip . +cd .. +rm -rf build diff --git a/composer.json b/composer.json deleted file mode 100644 index 99b59e3..0000000 --- a/composer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "aiir/php-lambda-layer", - "type": "project", - "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", - "keywords": [ - "lambda", - "php", - "runtime", - "layer" - ], - "authors": [ - { - "name": "Andy Buckingham", - "email": "andy@aiir.com", - "homepage": "https://twitter.com/andybee" - } - ], - "require": {}, - "require-dev": { - "slim/slim": "^3.12", - "phpunit/phpunit": "^8.0", - "donatj/mock-webserver": "^2.0" - }, - "scripts": { - "test": [ - "phpunit" - ] - } -} diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 602e84e..0000000 --- a/composer.lock +++ /dev/null @@ -1,2183 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "f890551350407d9d12e95b2664a4cb1e", - "packages": [], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "donatj/mock-webserver", - "version": "v2.4.1", - "source": { - "type": "git", - "url": "https://github.com/donatj/mock-webserver.git", - "reference": "bcec733923068135bd4a2da359aa038a93697c5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/donatj/mock-webserver/zipball/bcec733923068135bd4a2da359aa038a93697c5b", - "reference": "bcec733923068135bd4a2da359aa038a93697c5b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-sockets": "*", - "php": ">=5.4", - "ralouphie/getallheaders": "~2.0 || ~3.0" - }, - "require-dev": { - "donatj/drop": "^1.0", - "phpunit/phpunit": "~4|~9" - }, - "type": "library", - "autoload": { - "psr-4": { - "donatj\\MockWebServer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jesse G. Donat", - "email": "donatj@gmail.com", - "homepage": "https://donatstudios.com", - "role": "Lead" - } - ], - "description": "Simple mock web server for unit testing", - "support": { - "issues": "https://github.com/donatj/mock-webserver/issues", - "source": "https://github.com/donatj/mock-webserver/tree/v2.4.1" - }, - "funding": [ - { - "url": "https://www.paypal.me/donatj/5", - "type": "custom" - }, - { - "url": "https://github.com/donatj", - "type": "github" - } - ], - "time": "2022-01-19T16:59:52+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2022-03-03T13:19:32+00:00" - }, - { - "name": "nikic/fast-route", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/FastRoute.git", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", - "reference": "181d480e08d9476e61381e04a71b34dc0432e812", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|~5.7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "FastRoute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov", - "email": "nikic@php.net" - } - ], - "description": "Fast request router for PHP", - "keywords": [ - "router", - "routing" - ], - "support": { - "issues": "https://github.com/nikic/FastRoute/issues", - "source": "https://github.com/nikic/FastRoute/tree/master" - }, - "time": "2018-02-13T20:26:39+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" - }, - "time": "2021-07-20T11:28:43+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" - }, - "time": "2022-03-15T21:29:03+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" - }, - "time": "2021-12-08T12:19:24+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "7.0.15", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "819f92bba8b001d4363065928088de22f25a3a48" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", - "reference": "819f92bba8b001d4363065928088de22f25a3a48", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": ">=7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.3 || ^4.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.2.2", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1.3" - }, - "require-dev": { - "phpunit/phpunit": "^8.2.2" - }, - "suggest": { - "ext-xdebug": "^2.7.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-07-26T12:20:09+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "2.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:42:26+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" - }, - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "2.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T08:20:02+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "abandoned": true, - "time": "2020-08-04T08:28:15+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "8.5.26", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ef117c59fc4c54a979021b26d08a3373e386606d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ef117c59fc4c54a979021b26d08a3373e386606d", - "reference": "ef117c59fc4c54a979021b26d08a3373e386606d", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.0", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.2", - "phpspec/prophecy": "^1.10.3", - "phpunit/php-code-coverage": "^7.0.12", - "phpunit/php-file-iterator": "^2.0.4", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.2", - "sebastian/comparator": "^3.0.2", - "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.3", - "sebastian/exporter": "^3.1.2", - "sebastian/global-state": "^3.0.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.3", - "sebastian/version": "^2.0.1" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0.0" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "8.5-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.26" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-04-01T12:34:39+00:00" - }, - { - "name": "pimple/pimple", - "version": "v3.5.0", - "source": { - "type": "git", - "url": "https://github.com/silexphp/Pimple.git", - "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", - "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1 || ^2.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "^5.4@dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4.x-dev" - } - }, - "autoload": { - "psr-0": { - "Pimple": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Pimple, a simple Dependency Injection Container", - "homepage": "https://pimple.symfony.com", - "keywords": [ - "container", - "dependency injection" - ], - "support": { - "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" - }, - "time": "2021-10-28T11:13:42+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T08:15:22+00:00" - }, - { - "name": "sebastian/comparator", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", - "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T08:04:30+00:00" - }, - { - "name": "sebastian/diff", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:59:04+00:00" - }, - { - "name": "sebastian/environment", - "version": "4.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:53:42+00:00" - }, - { - "name": "sebastian/exporter", - "version": "3.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-11-11T13:51:24+00:00" - }, - { - "name": "sebastian/global-state", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^8.0" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-02-10T06:55:38+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:40:27+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:37:18+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:34:24+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:30:19+00:00" - }, - { - "name": "sebastian/type", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:25:11+00:00" - }, - { - "name": "sebastian/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" - }, - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "slim/slim", - "version": "3.12.3", - "source": { - "type": "git", - "url": "https://github.com/slimphp/Slim.git", - "reference": "1c9318a84ffb890900901136d620b4f03a59da38" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/1c9318a84ffb890900901136d620b4f03a59da38", - "reference": "1c9318a84ffb890900901136d620b4f03a59da38", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-libxml": "*", - "ext-simplexml": "*", - "nikic/fast-route": "^1.0", - "php": ">=5.5.0", - "pimple/pimple": "^3.0", - "psr/container": "^1.0", - "psr/http-message": "^1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0", - "squizlabs/php_codesniffer": "^2.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Slim\\": "Slim" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Josh Lockhart", - "email": "hello@joshlockhart.com", - "homepage": "https://joshlockhart.com" - }, - { - "name": "Andrew Smith", - "email": "a.smith@silentworks.co.uk", - "homepage": "http://silentworks.co.uk" - }, - { - "name": "Rob Allen", - "email": "rob@akrabat.com", - "homepage": "http://akrabat.com" - }, - { - "name": "Gabriel Manricks", - "email": "gmanricks@me.com", - "homepage": "http://gabrielmanricks.com" - } - ], - "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", - "homepage": "https://slimframework.com", - "keywords": [ - "api", - "framework", - "micro", - "router" - ], - "support": { - "issues": "https://github.com/slimphp/Slim/issues", - "source": "https://github.com/slimphp/Slim/tree/3.x" - }, - "time": "2019-11-28T17:40:33+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-10-20T20:35:02+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" - }, - "time": "2021-03-09T10:59:23+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/php.ini b/php.ini deleted file mode 100644 index a02927a..0000000 --- a/php.ini +++ /dev/null @@ -1,3 +0,0 @@ -display_errors=On -extension=curl.so -extension_dir=/opt/lib/php/8.0/modules diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 88fb7cb..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,9 +0,0 @@ - - - - ./tests/ - - - diff --git a/bootstrap b/src/bootstrap similarity index 94% rename from bootstrap rename to src/bootstrap index 6cec557..7ba9244 100755 --- a/bootstrap +++ b/src/bootstrap @@ -11,7 +11,7 @@ define('LAMBDA_TASK_ROOT', getenv('LAMBDA_TASK_ROOT')); // path where the functi /** These environment variables are specific to this runtime bootstrap */ define('CONFIG_PATH', getenv('CONFIG_PATH') ?: LAMBDA_TASK_ROOT . '/php.ini'); -define('EXTENSION_DIR', getenv('EXTENSION_DIR') ?: '/opt/lib/php/8.0/modules'); +define('EXTENSION_DIR', getenv('EXTENSION_DIR') ?: '/opt/lib/php/modules'); define('LAMBDA_RUNTIME_API_VERSION', '2018-06-01'); // defines the version of the Lambda runtime API to target define( 'EVENT_TYPE_METHODS', @@ -37,13 +37,12 @@ foreach ([AWS_LAMBDA_RUNTIME_API, HANDLER, LAMBDA_TASK_ROOT] as $value) { /** * Sends events to the Lambda custom runtime API. - * @return array */ -function sendRuntimeEvent(string $type, array $body = null, array $invocation = null): array +function sendRuntimeEvent(string $type, ?array $body = null, ?array $invocation = null): array { $isValidEventType = array_key_exists($type, EVENT_TYPE_METHODS); if (!$isValidEventType) { - throw new Exception("Unrecognised runtime event type: ${type}"); + throw new Exception("Unrecognised runtime event type: {$type}"); } $method = EVENT_TYPE_METHODS[$type]; if ($method === 'GET' && $body !== null) { @@ -53,9 +52,9 @@ function sendRuntimeEvent(string $type, array $body = null, array $invocation = $version = LAMBDA_RUNTIME_API_VERSION; if ($invocation !== null && array_key_exists('id', $invocation)) { $invocationId = $invocation['id']; - $type = str_replace('/invocation/', "/invocation/${invocationId}/", $type); + $type = str_replace('/invocation/', "/invocation/{$invocationId}/", $type); } - $url = "http://${host}/${version}/runtime${type}"; + $url = "http://{$host}/{$version}/runtime{$type}"; $ch = curl_init($url); @@ -70,6 +69,7 @@ function sendRuntimeEvent(string $type, array $body = null, array $invocation = ]); } + /** @var array> $responseHeaders */ $responseHeaders = []; curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$responseHeaders) { $isValidHeader = preg_match('/:\s*/', $header); @@ -95,11 +95,9 @@ function sendRuntimeEvent(string $type, array $body = null, array $invocation = if (curl_error($ch)) { $error = curl_error($ch); - throw new Exception("Failed to reach Lambda runtime API: ${error}"); + throw new Exception("Failed to reach Lambda runtime API: {$error}"); } - curl_close($ch); - $response = ['headers' => $responseHeaders, 'body' => $responseBody]; return $response; @@ -109,7 +107,7 @@ function sendRuntimeEvent(string $type, array $body = null, array $invocation = * If the runtime encounters an error during initialization, it posts an error * message to the initialization error path. */ -function sendInitilizationError($message): array +function sendInitilizationError(string $message): array { $body = ['errorMessage' => $message, 'errorType' => 'InitError']; $response = sendRuntimeEvent('/init/error', $body); @@ -121,7 +119,7 @@ function sendInitilizationError($message): array * If the function returns an error, the runtime formats the error into a JSON * document, and posts it to the invocation error path. */ -function sendInvocationError($invocation, $message): array +function sendInvocationError(array $invocation, string $message): array { $body = ['errorMessage' => $message, 'errorType' => 'InvocationError']; $response = sendRuntimeEvent('/invocation/error', $body, $invocation); @@ -138,7 +136,7 @@ function getNextInvocation(): array $response = sendRuntimeEvent('/invocation/next'); } catch (Exception $error) { $message = $error->getMessage(); - throw new Exception("Failed to fetch next Lambda invocation: ${message}"); + throw new Exception("Failed to fetch next Lambda invocation: {$message}"); } ['headers' => $headers, 'body' => $body] = $response; if (!array_key_exists('lambda-runtime-aws-request-id', $headers) || @@ -159,9 +157,6 @@ function getNextInvocation(): array /** * Formats an incoming invocation event in to an HTTP request object for passing * to the CGI process. - * - * @param array $event The invocation's API Gateway or ALB event object. - * @return array HTTP request object. */ function createRequest(array $invocation): array { @@ -249,13 +244,13 @@ function performRequest(array $request): array $cgiPath = ''; } - $cmd = "${cgiPath}php-cgi -c \"${configPath}\" -d extension_dir=\"${extensionDir}\""; + $cmd = "{$cgiPath}php-cgi -c \"{$configPath}\" -d extension_dir=\"{$extensionDir}\""; $descriptorSpec = [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']]; $cwd = LAMBDA_TASK_ROOT; $env = array_merge( $_SERVER, [ - 'CONTENT_LENGTH' => strlen($requestBody), + 'CONTENT_LENGTH' => strlen($requestBody ?? ''), 'CONTENT_TYPE' => (@$requestHeaders['content-type'] ?: ''), 'QUERY_STRING' => $queryString, 'REDIRECT_STATUS' => 200, @@ -278,7 +273,7 @@ function performRequest(array $request): array throw $exception; } - fwrite($pipes[0], $requestBody); + fwrite($pipes[0], $requestBody ?? ''); fclose($pipes[0]); stream_set_blocking($pipes[1], false); @@ -286,6 +281,7 @@ function performRequest(array $request): array $responseRaw = ''; $err = ''; + $duration = 0; $start = microtime(true); $timeout = false; while (!feof($pipes[1])) { @@ -325,7 +321,8 @@ function performRequest(array $request): array $responseHeaders = array_reduce( explode(PHP_EOL, $responseHeadersRaw), function ($carry, $line) use (&$status) { - [$key, $value] = array_map('trim', explode(':', $line, 2)); + if (trim($line) === '') return $carry; + [$key, $value] = array_map('trim', explode(':', $line, 2)) + ['', '']; if ($key === 'Status') { $status = $value; return $carry; @@ -458,6 +455,6 @@ function handleNextRequest(): void } } -while (isset($loop) ? $loop : true) { +while (true) { handleNextRequest(); } diff --git a/src/php.ini b/src/php.ini new file mode 100644 index 0000000..3f03ed8 --- /dev/null +++ b/src/php.ini @@ -0,0 +1,6 @@ +display_errors=Off +display_startup_errors=Off +log_errors=On +error_log=/dev/stderr +extension=curl.so +extension_dir=/opt/lib/php/modules diff --git a/test/request.sh b/test/request.sh new file mode 100755 index 0000000..6ff1201 --- /dev/null +++ b/test/request.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +# creates an invocation for the running test harness to pass to our bootstrap and execute + +curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \ + -H "Content-Type: application/json" \ + -d '{"httpMethod":"GET","path":"/","headers":{},"queryStringParameters":null,"body":null}' diff --git a/test/run.sh b/test/run.sh new file mode 100755 index 0000000..5ed4f4e --- /dev/null +++ b/test/run.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# extract the most recent build to disc +unzip ../layer.zip -d ./layer + +# we have to fiddle with the bootstrap's shebang during local testing because of limitations in the +# Amazon provided test harness +BOOTSTRAP_TMP=$(mktemp) +{ echo '#!/bin/sh'; echo 'exec /opt/bin/php -c /opt/php.ini /opt/bootstrap "$@"'; } > "$BOOTSTRAP_TMP" +chmod +x "$BOOTSTRAP_TMP" + +# run the Amazon docker container and mount our layer and test code at the relevant paths +docker run -p 9000:8080 \ + -v $(pwd)/layer:/opt \ + -v $(pwd)/src:/var/task \ + -v "$BOOTSTRAP_TMP":/var/runtime/bootstrap \ + public.ecr.aws/lambda/provided:al2023 \ + index.php + +# once docker is terminated, clean up +rm "$BOOSTRAP_TMP" +rm -rf layer diff --git a/test/src/index.php b/test/src/index.php new file mode 100644 index 0000000..e974c40 --- /dev/null +++ b/test/src/index.php @@ -0,0 +1 @@ +start(); - - putenv('AWS_LAMBDA_RUNTIME_API=' . self::$server->getHost() . ':' . self::$server->getPort()); - putenv('LAMBDA_TASK_ROOT=' . __DIR__ . '/_files'); - putenv('MAX_EXECUTION_TIME=5'); - if (!file_exists('/var/task/php.ini')) { - // use default environment settings if not running inside test container - putenv('CONFIG_PATH=/dev/null'); - } - putenv('_HANDLER=public/index.php'); - $loop = false; - - ob_start(); // capture output to avoid echo'ing the shebang at top of bootstrap - if (file_exists('/opt/bootstrap')) { - require '/opt/bootstrap'; - } else { - require __DIR__ . '/../bootstrap'; - } - ob_end_clean(); - } - - /** - * Helper function for setting the next response from the Lambda runtime API - * next invocation endpoint. - */ - private function setNextInvocation(array $response): string - { - $id = sprintf( - '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - mt_rand(0, 0x0fff) | 0x4000, - mt_rand(0, 0x3fff) | 0x8000, - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - mt_rand(0, 0xffff) - ); - - $body = json_encode($response); - $headers = ['Lambda-Runtime-Aws-Request-Id' => $id]; - self::$server->setResponseOfPath( - '/' . LAMBDA_RUNTIME_API_VERSION . '/runtime/invocation/next', - new Response($body, $headers, 200) - ); - - return $id; - } - - /** - * Helper function for getting and decoding the last response sent to the - * Lambda runtime API invocation response endpoint. - */ - private function getInvocationResponse(): \stdclass - { - $request = self::$server->getLastRequest(); - if ($request === null) { - $error = new \Exception('No request received'); - throw $error; - } - - $body = $request->getInput(); - $response = json_decode($body); - - return $response; - } - - /** - * Calls the root path and ensures we get the home page. - */ - public function testGetRoot(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/', - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals(200, $response->statusCode); - $this->assertEquals('Home', $response->body); - } - - /** - * ??? - */ - public function testPostRoot(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'POST', - 'headers' => [ - 'content-type' => 'application/x-www-form-urlencoded; charset=UTF-8', - ], - 'path' => '/', - 'body' => 'foo=bar&bar=foo', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals('barfoo', $response->body); - } - - /** - * Calls a path handled by the dynamic routing and ensure the correct - * response is returned. - */ - public function testRouterPath(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/foo/bar/baz', - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals('foo/bar/baz', $response->body); - } - - /** - * Calls a path that refers to an explicit PHP script and ensure the - * correct response is returned. - */ - public function testExplicitPath(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/subdir/index.php', - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals(200, $response->statusCode); - $this->assertEquals('subdir', $response->body); - } - - /** - * Calls the boostrap as if the incoming event is coming from API Gateway - * (no `requestContext` property), ensuring `statusDescription` is not set. - */ - public function testNoStatusDescriptionHeaderForAPIGateway(): void - { - $this->setNextInvocation([ - 'httpMethod' => 'GET', - 'path' => '/', - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertFalse(property_exists($response, 'statusDescription')); - } - - /** - * Calls the boostrap as if the incoming event is coming from ALB (includes - * a `requestContext` property), ensuring `statusDescription` is set. - */ - public function testStatusDescriptionHeaderForALBRequest(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/', - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals('200 OK', $response->statusDescription); - } - - /** - * Calls the bootstrap with a path that matches a directory inside the - * handler path that contains an index.php script. - */ - public function testSubDirectoryWithIndex(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/subdir/', - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals('subdir', $response->body); - } - - /** - * Requests a path that matches a directory inside the handler path that - * contains an index.php script, but without a trailing slash. - */ - public function testSubDirectoryWithIndexWithoutTrailingSlash(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/subdir', - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals(301, $response->statusCode); - $this->assertEquals('/subdir/', $response->headers->Location); - } - - /** - * Requests a path that returns slower than the declared max execution time. - */ - public function testSlowRequest(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/slow', - 'body' => '', - ]); - - $this->expectOutputString('PHP took longer than 5 seconds to return response'); - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals(500, $response->statusCode); - } - - /** - * Performs request with a `multiValueHeaders` property. - */ - public function testMultiValueHeaderRequest(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/multi-value-header', - 'multiValueHeaders' => [ - 'Test' => [ - 'foo', - 'bar', - ], - ], - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals('foo', $response->body); - } - - /** - * Receive a response with multiple values for the same header key. - */ - public function testMultiValueHeaderResponse(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/multi-value-header', - 'multiValueHeaders' => [], - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals(['foo', 'bar'], $response->multiValueHeaders->Test); - } - - /** - * Performs a request with a standard query string. - */ - public function testQueryString(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/echo', - 'multiValueHeaders' => [], - 'queryStringParameters' => [ - 'a' => 'foo', - 'b' => 'bar', - ], - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals('{"a":"foo","b":"bar"}', $response->body); - } - - /** - * Performs a request with a query string with multiple values against the - * same key. - */ - public function testMultiValueQueryString(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/echo', - 'multiValueHeaders' => [], - 'multiValueQueryStringParameters' => [ - 'a' => [ - 'foo', - 'bar', - ], - ], - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals('{"a":"bar"}', $response->body); - } - - /** - * Performs a request with a PHP-specific array style query string. - */ - public function testArrayQueryString(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/echo', - 'multiValueHeaders' => [], - 'multiValueQueryStringParameters' => [ - 'a' => [ - 'foo', - ], - 'b[]' => [ - 'foo', - 'bar', - ], - 'c[a]' => [ - 'foo', - ], - 'c[b]' => [ - 'bar', - ], - ], - 'body' => '', - ]); - - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals( - '{"a":"foo","b":["foo","bar"],"c":{"a":"foo","b":"bar"}}', - $response->body - ); - } - - /** - * Requests an endpoint which returns an oversized response. - */ - public function testLargeResponse(): void - { - $this->setNextInvocation([ - 'requestContext' => [], - 'httpMethod' => 'GET', - 'path' => '/large-response', - 'body' => '', - ]); - - $this->expectOutputRegex('/^Response size is too large for ALB \([0-9]{7,} bytes\)\n$/'); - $invocation = handleNextRequest(); - - $response = $this->getInvocationResponse(); - $this->assertEquals(500, $response->statusCode); - } - - public function testCurl(): void - { - $url = 'https://www.google.com/'; - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - curl_exec($ch); - - $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - curl_close($ch); - - $this->assertEquals(200, $status); - } - - /** - * Stops the mock server to ensure the address can be re-used. - */ - public static function tearDownAfterClass(): void - { - self::$server->stop(); - } -} diff --git a/tests/_files/public/index.php b/tests/_files/public/index.php deleted file mode 100644 index 641e1e7..0000000 --- a/tests/_files/public/index.php +++ /dev/null @@ -1,50 +0,0 @@ -get('/', function ($request, $response) { - return $response->getBody()->write('Home'); -}); - -$app->post('/', function ($request, $response) { - $post = $request->getParsedBody(); - $string = $post['foo'] . $post['bar']; - return $response->getBody()->write($string); -}); - -$app->get('/foo/{bar}/baz', function ($request, $response, array $args) { - ['bar' => $bar] = $args; - return $response->getBody()->write("foo/${bar}/baz"); -}); - -$app->get('/slow', function ($request, $response) { - sleep(10); -}); - -$app->get('/multi-value-header', function ($request, $response) { - $body = $response->getBody(); - $body->write($request->getHeaderLine('Test')); - return $response - ->withAddedHeader('Test', 'foo') - ->withAddedHeader('Test', 'bar'); -}); - -$app->get('/echo', function ($request, $response) { - return $response->withJson($request->getQueryParams()); -}); - -$app->get('/large-response', function ($request, $response) { - $body = $response->getBody(); - while ($body->getSize() < 1000000) { - $body->write(' '); - } - return $response; -}); - -$app->run(); diff --git a/tests/_files/public/subdir/index.php b/tests/_files/public/subdir/index.php deleted file mode 100644 index 05336bf..0000000 --- a/tests/_files/public/subdir/index.php +++ /dev/null @@ -1 +0,0 @@ -