├── .github └── pull_request_template.md ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bootstrap ├── build-php-remi.sh ├── build.sh ├── create-buckets.sh ├── php.ini ├── publish.sh ├── regions.sh ├── unpublish.sh └── upload.sh /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Please make sure pull requests are opened against the development branch. Pull requests opened against master will be closed automatically. Please delete this line once you have confirmed the correct branch. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | php71.zip 2 | php73.zip 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------- 2 | The PHP License, version 3.01 3 | Copyright (c) 1999 - 2018 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 | . -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | all: php71.zip php73.zip 4 | 5 | php71.zip: 6 | docker run --rm -e http_proxy=${http_proxy} -v $(ROOT_DIR):/opt/layer lambci/lambda:build-provided /opt/layer/build.sh 7 | 8 | php73.zip: 9 | docker run --rm -e http_proxy=${http_proxy} -v $(ROOT_DIR):/opt/layer lambci/lambda:build-provided /opt/layer/build-php-remi.sh 3 10 | 11 | upload71: php71.zip 12 | ./upload.sh 7.1 13 | 14 | upload73: php73.zip 15 | ./upload.sh 7.3 16 | 17 | publish71: php71.zip 18 | ./publish.sh 7.1 19 | 20 | publish73: php73.zip 21 | ./publish.sh 7.3 22 | 23 | clean: 24 | rm -f php71.zip php73.zip 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Layer For AWS Lambda 2 | 3 | Ever wanted to run PHP websites in AWS Lambda? It's your lucky day! This Lambda Runtime Layer runs the [PHP 7.3/7.1 webserver](http://php.net/manual/en/features.commandline.webserver.php) in response to [AWS API Gateway](https://aws.amazon.com/api-gateway/) or [AWS Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/features/#Details_for_Elastic_Load_Balancing_Products) requests. 4 | 5 | And, if you're looking for a great way to build serverless apps of all kinds, be sure to check out [Stackery](https://stackery.io)! 6 | 7 | This is an early iteration of the PHP runtime Layer which is not yet ready for production. Please feel free to use this Layer to learn about the Lambda Layers feature and begin experimenting with PHP functions. We welcome feedback and stay tuned for the production-ready version coming soon. 8 | 9 | ## Current Layer Version ARN 10 | When creating/updating a Lambda function you must specify a specific version of the layer. This readme will be kept up to date with the latest version available. The latest available Lambda Layer Version ARNs for PHP 7.3 and 7.1 are: 11 | 12 | **arn:aws:lambda:\:887080169480:layer:php73:3** 13 | 14 | **arn:aws:lambda:\:887080169480:layer:php71:10** 15 | 16 | See [Releases](https://github.com/stackery/php-lambda-layer/releases) for release notes. 17 | 18 | ### Usage 19 | #### General Usage 20 | The layer runs the PHP 7.* [PHP webserver](http://php.net/manual/en/features.commandline.webserver.php) in /var/task, the root directory of function code packages: 21 | 22 | ```sh 23 | $ php -S localhost:8000 '' 24 | ``` 25 | 26 | The Lambda Function Handler property specifies the location of the the script executed in response to an incoming API Gateway or Application Load Balancer request. 27 | 28 | #### Configuration Files 29 | There are three locations where PHP configuration may be located: 30 | 31 | * Files in layer code packages located under /etc/php-${PHP_VERSION}.d/ 32 | * Files in function code package located under /php-${PHP_VERSION}.d/ 33 | * php.ini located at the root of the function code package 34 | 35 | Replace ${PHP_VERSION} with '7.3', or '7.1' according to your preferred runtime. 36 | 37 | ##### Extensions 38 | The following extensions are built into the layer and available in /opt/lib/php/${PHP_VERSION}/modules: 39 | 40 | PHP 7.3 Layer: 41 | ``` 42 | bz2.so 43 | calendar.so 44 | ctype.so 45 | curl.so 46 | dom.so 47 | exif.so 48 | fileinfo.so 49 | ftp.so 50 | gettext.so 51 | iconv.so 52 | json.so 53 | mbstring.so 54 | mysqli.so 55 | mysqlnd.so 56 | pdo_mysql.so 57 | pdo_pgsql.so 58 | pdo.so 59 | pdo_sqlite.so 60 | pgsql.so 61 | phar.so 62 | posix.so 63 | shmop.so 64 | simplexml.so 65 | sockets.so 66 | sqlite3.so 67 | sysvmsg.so 68 | sysvsem.so 69 | sysvshm.so 70 | tokenizer.so 71 | wddx.so 72 | xmlreader.so 73 | xml.so 74 | xmlwriter.so 75 | xsl.so 76 | ``` 77 | 78 | PHP 7.1 Layer: 79 | ``` 80 | bz2.so 81 | calendar.so 82 | ctype.so 83 | curl.so 84 | dom.so 85 | exif.so 86 | fileinfo.so 87 | ftp.so 88 | gettext.so 89 | iconv.so 90 | json.so 91 | phar.so 92 | posix.so 93 | shmop.so 94 | simplexml.so 95 | sockets.so 96 | sysvmsg.so 97 | sysvsem.so 98 | sysvshm.so 99 | tokenizer.so 100 | wddx.so 101 | xml.so 102 | xmlreader.so 103 | xmlwriter.so 104 | xsl.so 105 | zip.so 106 | ``` 107 | 108 | These extensions are not loaded by default. You must add the extension to a php.ini file to use it: 109 | 110 | ```ini 111 | extension=json.so 112 | ``` 113 | 114 | Extensions can be built using the lambci/lambda:build-nodejs8.10 Docker image. It is recommended that custom extensions be provided by a separate Lambda Layer with the extension .so files placed in /lib/php/${PHP_VERSION}/modules/ so they can be loaded alongside the built-in extensions listed above. 115 | 116 | #### SAM Example 117 | Let's create an AWS SAM PHP application. We suggest using [Stackery](https://stackery.io) to make this super simple. It automates all the scaffolding shown below. But you may also choose to roll your own application from scratch. 118 | 119 | First, install [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli). Make sure to create a SAM deployment bucket as shown in [Packaging your application](https://github.com/awslabs/aws-sam-cli/blob/develop/docs/deploying_serverless_applications.rst#packaging-your-application) 120 | 121 | Next, create a basic SAM application: 122 | 123 | ```sh 124 | $ mkdir my-php-app 125 | $ cd my-php-app 126 | ``` 127 | 128 | Create a template.yaml file with the following SAM infrastructure: 129 | 130 | ```yaml 131 | AWSTemplateFormatVersion: 2010-09-09 132 | Description: My PHP Application 133 | Transform: AWS::Serverless-2016-10-31 134 | Resources: 135 | phpserver: 136 | Type: AWS::Serverless::Function 137 | Properties: 138 | FunctionName: !Sub ${AWS::StackName}-phpserver 139 | Description: PHP Webserver 140 | CodeUri: src/php 141 | Runtime: provided 142 | Handler: index.php 143 | MemorySize: 3008 144 | Timeout: 30 145 | Tracing: Active 146 | Layers: 147 | - !Sub arn:aws:lambda:${AWS::Region}:887080169480:layer:php73:3 148 | Events: 149 | api: 150 | Type: Api 151 | Properties: 152 | Path: /{proxy+} 153 | Method: ANY 154 | ``` 155 | 156 | Lastly, let's write our script. Put this in `index.php`: 157 | 158 | ```php 159 | Hello World! You've reached 160 | 161 | ``` 162 | 163 | You should now have a directory structure like: 164 | 165 | ``` 166 | . 167 | ├── template.yaml 168 | └── src 169 | └── php 170 | └── index.php 171 | ``` 172 | 173 | We're ready to deploy! Run the following commands: 174 | 175 | ```sh 176 | $ sam package \ 177 | --template-file template.yaml \ 178 | --output-template-file serverless-output.yaml \ 179 | --s3-bucket 180 | 181 | $ sam deploy \ 182 | --template-file serverless-output.yaml \ 183 | --stack-name my-first-serverless-php-service \ 184 | --capabilities CAPABILITY_IAM 185 | ``` 186 | 187 | ### Development 188 | Build the layers by: 189 | 190 | 1. Installing a Docker environment 191 | 1. Running `make` 192 | 193 | This will launch Docker containers that will build php73.zip and php71.zip. 194 | 195 | If you are behind a proxy server, just set the environment variable `http_proxy` before 196 | invoking `make`, eg.: 197 | 198 | ```sh 199 | $ export http_proxy=http://myproxy.acme.com:8080 200 | $ make php73.zip 201 | ``` 202 | 203 | #### Debugging Layer Builds 204 | 205 | Run: 206 | 207 | ```sh 208 | $ docker run --rm -it -v `pwd`:/opt/layer lambci/lambda:build-nodejs8.10 /bin/bash 209 | ``` 210 | 211 | If you are on Windows, run this instead: 212 | 213 | ```sh 214 | > docker run --rm -it -v %cd%:/opt/layer lambci/lambda:build-nodejs8.10 /bin/bash 215 | ``` 216 | 217 | then manually execute the commands in the build.sh file. 218 | 219 | ### Disclaimer 220 | 221 | > THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND 222 | > ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 223 | > THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 224 | > PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP 225 | > DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 226 | > INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 227 | > (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 228 | > SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 229 | > HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 230 | > STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 231 | > ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 232 | > OF THE POSSIBILITY OF SUCH DAMAGE. 233 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/opt/bin/php -c/opt/php.ini 2 | 'Continue',101=>'Switching Protocols',102=>'Processing',200=>'OK',201=>'Created',202=>'Accepted',203=>'Non-Authoritative Information',204=>'No Content',205=>'Reset Content',206=>'Partial Content',207=>'Multi-Status',208=>'Already Reported',226=>'IM Used',300=>'Multiple Choices',301=>'Moved Permanently',302=>'Found',303=>'See Other',304=>'Not Modified',305=>'Use Proxy',306=>'Switch Proxy',307=>'Temporary Redirect',308=>'Permanent Redirect',400=>'Bad Request',401=>'Unauthorized',402=>'Payment Required',403=>'Forbidden',404=>'Not Found',405=>'Method Not Allowed',406=>'Not Acceptable',407=>'Proxy Authentication Required',408=>'Request Timeout',409=>'Conflict',410=>'Gone',411=>'Length Required',412=>'Precondition Failed',413=>'Request Entity Too Large',414=>'Request-URI Too Long',415=>'Unsupported Media Type',416=>'Requested Range Not Satisfiable',417=>'Expectation Failed',418=>'I\'m a teapot',419=>'Authentication Timeout',420=>'Enhance Your Calm',420=>'Method Failure',422=>'Unprocessable Entity',423=>'Locked',424=>'Failed Dependency',424=>'Method Failure',425=>'Unordered Collection',426=>'Upgrade Required',428=>'Precondition Required',429=>'Too Many Requests',431=>'Request Header Fields Too Large',444=>'No Response',449=>'Retry With',450=>'Blocked by Windows Parental Controls',451=>'Redirect',451=>'Unavailable For Legal Reasons',494=>'Request Header Too Large',495=>'Cert Error',496=>'No Cert',497=>'HTTP to HTTPS',499=>'Client Closed Request',500=>'Internal Server Error',501=>'Not Implemented',502=>'Bad Gateway',503=>'Service Unavailable',504=>'Gateway Timeout',505=>'HTTP Version Not Supported',506=>'Variant Also Negotiates',507=>'Insufficient Storage',508=>'Loop Detected',509=>'Bandwidth Limit Exceeded',510=>'Not Extended',511=>'Network Authentication Required',598=>'Network read timeout error',599=>'Network connect timeout error']; 10 | 11 | function start_webserver() { 12 | $SERVER_STARTUP_TIMEOUT = 1000000; // 1 second 13 | 14 | $pid = pcntl_fork(); 15 | switch($pid) { 16 | case -1: 17 | die("Failed to fork webserver process\n"); 18 | 19 | case 0: 20 | // exec the command 21 | $HANDLER = getenv('_HANDLER'); 22 | $phpMinorVersion = PHP_MINOR_VERSION; 23 | $handler_components = explode('/', $HANDLER); 24 | $handler_filename = array_pop($handler_components); 25 | $handler_path = implode('/', array_merge(['/var/task'], $handler_components)); 26 | chdir($handler_path); 27 | exec("PHP_INI_SCAN_DIR=/opt/etc/php-7.${phpMinorVersion}.d/:/var/task/php-7.${phpMinorVersion}.d/ php -S localhost:8000 -c /var/task/php.ini -d extension_dir=/opt/lib/php/7.${phpMinorVersion}/modules '$handler_filename'"); 28 | exit; 29 | 30 | default: 31 | // Wait for child server to start 32 | $start = microtime(true); 33 | 34 | do { 35 | if (microtime(true) - $start > $SERVER_STARTUP_TIMEOUT) { 36 | die("Webserver failed to start within one second\n"); 37 | } 38 | 39 | usleep(1000); 40 | $fp = @fsockopen('localhost', 8000, $errno, $errstr, 1); 41 | } while ($fp == false); 42 | 43 | fclose($fp); 44 | } 45 | } 46 | 47 | function fail($AWS_LAMBDA_RUNTIME_API, $invocation_id, $message) { 48 | $ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/$invocation_id/response"); 49 | 50 | $response = array(); 51 | 52 | $response['statusCode'] = 500; 53 | $response['body'] = $message; 54 | 55 | $response_json = json_encode($response); 56 | 57 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); 58 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 59 | curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json); 60 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 61 | 'Content-Type: application/json', 62 | 'Content-Length: ' . strlen($response_json) 63 | )); 64 | 65 | curl_exec($ch); 66 | curl_close($ch); 67 | } 68 | 69 | start_webserver(); 70 | 71 | while (true) { 72 | $ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/next"); 73 | 74 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE); 75 | curl_setopt($ch, CURLOPT_FAILONERROR, TRUE); 76 | 77 | $invocation_id = ''; 78 | 79 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$invocation_id) { 80 | if (!preg_match('/:\s*/', $header)) { 81 | return strlen($header); 82 | } 83 | 84 | [$name, $value] = preg_split('/:\s*/', $header, 2); 85 | 86 | if (strtolower($name) == 'lambda-runtime-aws-request-id') { 87 | $invocation_id = trim($value); 88 | } 89 | 90 | return strlen($header); 91 | }); 92 | 93 | $body = ''; 94 | 95 | curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$body) { 96 | $body .= $chunk; 97 | 98 | return strlen($chunk); 99 | }); 100 | 101 | curl_exec($ch); 102 | 103 | if (curl_error($ch)) { 104 | die('Failed to fetch next Lambda invocation: ' . curl_error($ch) . "\n"); 105 | } 106 | 107 | if ($invocation_id == '') { 108 | die("Failed to determine Lambda invocation ID\n"); 109 | } 110 | 111 | curl_close($ch); 112 | 113 | if (!$body) { 114 | die("Empty Lambda invocation response\n"); 115 | } 116 | 117 | $event = json_decode($body, TRUE); 118 | 119 | if (!array_key_exists('requestContext', $event)) { 120 | fail($AWS_LAMBDA_RUNTIME_API, $invocation_id, 'Event is not an API Gateway request'); 121 | continue; 122 | } 123 | 124 | $uri = $event['path']; 125 | 126 | if (array_key_exists('multiValueQueryStringParameters', $event) && $event['multiValueQueryStringParameters']) { 127 | $first = TRUE; 128 | foreach ($event['multiValueQueryStringParameters'] as $name => $values) { 129 | foreach ($values as $value) { 130 | if ($first) { 131 | $uri .= "?"; 132 | $first = FALSE; 133 | } else { 134 | $uri .= "&"; 135 | } 136 | 137 | $uri .= $name; 138 | 139 | if ($value != '') { 140 | $uri .= '=' . $value; 141 | } 142 | } 143 | } 144 | } 145 | 146 | $ch = curl_init("http://localhost:8000$uri"); 147 | 148 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE); 149 | 150 | if (array_key_exists('multiValueHeaders', $event)) { 151 | $headers = array(); 152 | 153 | foreach ($event['multiValueHeaders'] as $name => $values) { 154 | foreach ($values as $value) { 155 | array_push($headers, "${name}: ${value}"); 156 | } 157 | } 158 | 159 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 160 | } 161 | 162 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $event['httpMethod']); 163 | 164 | if (array_key_exists('body', $event)) { 165 | $body = $event['body']; 166 | if (array_key_exists('isBase64Encoded', $event) && $event['isBase64Encoded']) { 167 | $body = base64_decode($body); 168 | } 169 | } else { 170 | $body = ''; 171 | } 172 | 173 | if (strlen($body) > 0) { 174 | curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 175 | } 176 | 177 | $response = array(); 178 | $response['multiValueHeaders'] = array(); 179 | $response['body'] = ''; 180 | 181 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$response) { 182 | if (preg_match('/HTTP\/1.1 (\d+) .*/', $header, $matches)) { 183 | $response['statusCode'] = intval($matches[1]); 184 | return strlen($header); 185 | } 186 | 187 | if (!preg_match('/:\s*/', $header)) { 188 | return strlen($header); 189 | } 190 | 191 | [$name, $value] = preg_split('/:\s*/', $header, 2); 192 | 193 | $name = trim($name); 194 | $value = trim($value); 195 | 196 | if ($name == '') { 197 | return strlen($header); 198 | } 199 | 200 | if (!array_key_exists($name, $response['multiValueHeaders'])) { 201 | $response['multiValueHeaders'][$name] = array(); 202 | } 203 | 204 | array_push($response['multiValueHeaders'][$name], $value); 205 | 206 | return strlen($header); 207 | }); 208 | 209 | curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$response) { 210 | $response['body'] .= $chunk; 211 | 212 | return strlen($chunk); 213 | }); 214 | 215 | curl_exec($ch); 216 | curl_close($ch); 217 | 218 | $ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/$invocation_id/response"); 219 | 220 | $isALB = array_key_exists("elb", $event['requestContext']); 221 | if ($isALB) { // Add Headers For ALB 222 | $status = $response["statusCode"]; 223 | if (array_key_exists($status, $http_codes)) { 224 | $response["statusDescription"] = "$status ". $http_codes[$status]; 225 | } else { 226 | $response["statusDescription"] = "$status Unknown"; 227 | } 228 | $response["isBase64Encoded"] = false; 229 | } 230 | $response_json = json_encode($response); 231 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); 232 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 233 | curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json); 234 | if (!$isALB){ 235 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 236 | 'Content-Type: application/json', 237 | 'Content-Length: ' . strlen($response_json) 238 | )); 239 | } 240 | curl_exec($ch); 241 | curl_close($ch); 242 | } 243 | 244 | ?> 245 | -------------------------------------------------------------------------------- /build-php-remi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | PHP_MINOR_VERSION=$1 4 | 5 | echo "Building layer for PHP 7.$PHP_MINOR_VERSION - using Remi repository" 6 | 7 | yum install -y wget 8 | yum install -y yum-utils 9 | wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm 10 | wget https://rpms.remirepo.net/enterprise/remi-release-6.rpm 11 | rpm -Uvh epel-release-latest-6.noarch.rpm 12 | rpm -Uvh remi-release-6.rpm 13 | 14 | yum-config-manager --enable remi-php7${PHP_MINOR_VERSION} 15 | 16 | yum install -y httpd 17 | yum install -y postgresql-devel 18 | yum install -y libargon2-devel 19 | 20 | yum install -y --disablerepo="*" --enablerepo="remi,remi-php7${PHP_MINOR_VERSION}" php php-mbstring php-pdo php-mysql php-pgsql php-xml php-process 21 | 22 | 23 | mkdir /tmp/layer 24 | cd /tmp/layer 25 | cp /opt/layer/bootstrap bootstrap 26 | sed "s/PHP_MINOR_VERSION/${PHP_MINOR_VERSION}/g" /opt/layer/php.ini >php.ini 27 | 28 | mkdir bin 29 | cp /usr/bin/php bin/ 30 | 31 | mkdir lib 32 | for lib in libncurses.so.5 libtinfo.so.5 libpcre.so.0; do 33 | cp "/lib64/${lib}" lib/ 34 | done 35 | 36 | cp /usr/lib64/libedit.so.0 lib/ 37 | cp /usr/lib64/libargon2.so.0 lib/ 38 | cp /usr/lib64/libpq.so.5 lib/ 39 | cp /usr/lib64/libonig.so.5 lib/ 40 | 41 | mkdir -p lib/php/7.${PHP_MINOR_VERSION} 42 | cp -a /usr/lib64/php/modules lib/php/7.${PHP_MINOR_VERSION}/ 43 | 44 | zip -r /opt/layer/php7${PHP_MINOR_VERSION}.zip . 45 | 46 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yum install -y php71-mbstring.x86_64 zip php71-pgsql php71-mysqli 4 | 5 | mkdir /tmp/layer 6 | cd /tmp/layer 7 | cp /opt/layer/bootstrap . 8 | sed "s/PHP_MINOR_VERSION/1/g" /opt/layer/php.ini >php.ini 9 | 10 | mkdir bin 11 | cp /usr/bin/php bin/ 12 | 13 | mkdir lib 14 | for lib in libncurses.so.5 libtinfo.so.5 libpcre.so.0; do 15 | cp "/lib64/${lib}" lib/ 16 | done 17 | 18 | cp /usr/lib64/libedit.so.0 lib/ 19 | cp /usr/lib64/libpq.so.5 lib/ 20 | 21 | cp -a /usr/lib64/php lib/ 22 | 23 | zip -r /opt/layer/php71.zip . -------------------------------------------------------------------------------- /create-buckets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | source regions.sh 4 | 5 | for region in "${PHP71_REGIONS[@]}"; do 6 | bucket_name="stackery-layers-${region}" 7 | 8 | echo "Creating bucket ${bucket_name}..." 9 | 10 | aws s3 mb s3://$bucket_name --region $region 11 | done -------------------------------------------------------------------------------- /php.ini: -------------------------------------------------------------------------------- 1 | extension_dir=/opt/lib/php/7.PHP_MINOR_VERSION/modules 2 | display_errors=On 3 | 4 | extension=curl.so 5 | extension=json.so 6 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | source regions.sh 4 | 5 | PHP_VERSION="${1}" 6 | 7 | LAYER="php${PHP_VERSION//.}" 8 | MD5SUM=$(md5 -q "${LAYER}.zip") 9 | S3KEY="${LAYER}/${MD5SUM}" 10 | 11 | for region in "${PHP_REGIONS[@]}"; do 12 | bucket_name="stackery-layers-${region}" 13 | 14 | echo "Publishing Lambda Layer ${LAYER} in region ${region}..." 15 | # Must use --cli-input-json so AWS CLI doesn't attempt to fetch license URL 16 | version=$(aws --region $region lambda publish-layer-version --cli-input-json "{\"LayerName\": \"${LAYER}\",\"Description\": \"PHP ${PHP_VERSION} Web Server Lambda Runtime\",\"Content\": {\"S3Bucket\": \"${bucket_name}\",\"S3Key\": \"${S3KEY}\"},\"CompatibleRuntimes\": [\"provided\"],\"LicenseInfo\": \"http://www.php.net/license/3_01.txt\"}" --output text --query Version) 17 | echo "Published Lambda Layer ${LAYER} in region ${region} version ${version}" 18 | 19 | echo "Setting public permissions on Lambda Layer ${LAYER} version ${version} in region ${region}..." 20 | aws --region $region lambda add-layer-version-permission --layer-name "${LAYER}" --version-number $version --statement-id=public --action lambda:GetLayerVersion --principal '*' > /dev/null 21 | echo "Public permissions set on Lambda Layer ${LAYER} version ${version} in region ${region}" 22 | done 23 | -------------------------------------------------------------------------------- /regions.sh: -------------------------------------------------------------------------------- 1 | PHP_REGIONS=( 2 | ap-northeast-1 3 | ap-northeast-2 4 | ap-south-1 5 | ap-southeast-1 6 | ap-southeast-2 7 | ca-central-1 8 | eu-central-1 9 | eu-west-1 10 | eu-west-2 11 | eu-west-3 12 | sa-east-1 13 | us-east-1 14 | us-east-2 15 | us-west-1 16 | us-west-2 17 | ) 18 | -------------------------------------------------------------------------------- /unpublish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | PHP_VERSION="${1}" 4 | LAYER_VERSION="${2}" 5 | 6 | source regions.sh 7 | 8 | LAYER="php${PHP_VERSION//.}" 9 | MD5SUM=$(md5 -q "${LAYER}.zip") 10 | S3KEY="${LAYER}/${MD5SUM}" 11 | 12 | for region in "${PHP_REGIONS[@]}"; do 13 | bucket_name="stackery-layers-${region}" 14 | 15 | echo "Deleting Lambda Layer ${LAYER} version ${VERSION} in region ${region}..." 16 | aws --region $region lambda delete-layer-version --layer-name ${LAYER} --version-number $VERSION > /dev/null 17 | echo "Deleted Lambda Layer ${LAYER} version ${VERSION} in region ${region}" 18 | done 19 | -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | source regions.sh 4 | 5 | PHP_VERSION="${1}" 6 | 7 | LAYER="php${PHP_VERSION//.}" 8 | MD5SUM=$(md5 -q "${LAYER}.zip") 9 | S3KEY="${LAYER}/${MD5SUM}" 10 | 11 | for region in "${PHP_REGIONS[@]}"; do 12 | bucket_name="stackery-layers-${region}" 13 | 14 | echo "Uploading ${LAYER}.zip to s3://${bucket_name}/${S3KEY}" 15 | 16 | aws --region $region s3 cp ${LAYER}.zip "s3://${bucket_name}/${S3KEY}" 17 | done 18 | --------------------------------------------------------------------------------