diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0cf3adf --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +php filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ce721a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# PHP +/vendor/ +.php_cs.cache + +# Serverless directories +.serverless + +# NPM +node_modules/ + +# Editors / OS +*.swp +.DS_Store diff --git a/LICENSE b/LICENSE index 5a7abc2..a3ea892 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2017 Andy Raines +Copyright (c) 2016 Robert Anderson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 585b656..dc3edf1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ # serverless-php -PHP for AWS Lambda via Serverless Framework +PHP for AWS Lambda via Serverless Framework using Symfony components for +dependency injection. + +This repository is set up with [Git LFS](https://git-lfs.github.com/) for the +php executable, so make sure you have it installed and supported. + +## Prerequisites +* [Serverless](https://serverless.com/) +* [Node](https://nodejs.org) +* [Composer](https://getcomposer.org/) + +## Deploying to AWS +``` +composer install -o --no-dev +serverless deploy +``` + +## Running locally +``` +serverless invoke local -f hello +``` + +## Running on AWS +``` +serverless invoke -f hello +``` + +# Rebuilding PHP Binary +The PHP binary can be built with any flags you require and at any version. + +## Prerequisites +* [Docker](https://www.docker.com/) + +## Compiling +``` +sh buildphp.sh +``` + +## Altering compile flags etc +Edit `buildphp.sh` and `dockerfile.buildphp` to alter it. + +# Thanks +* [Robert Anderson](https://github.com/ZeroSharp/serverless-php) for the +inspiration and base for this project diff --git a/buildphp.sh b/buildphp.sh new file mode 100644 index 0000000..a20304a --- /dev/null +++ b/buildphp.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# This script builds a docker container, compiles PHP for use with AWS Lambda, +# and copies the final binary to the host and then removes the container. +# +# You can specify the PHP Version by setting the branch corresponding to the +# source from https://github.com/php/php-src + +PHP_VERSION_GIT_BRANCH=php-7.1.2 + +echo "Build PHP Binary from current branch '$PHP_VERSION_GIT_BRANCH' on https://github.com/php/php-src" + +docker build --build-arg PHP_VERSION=$PHP_VERSION_GIT_BRANCH -t php-build -f dockerfile.buildphp . + +container=$(docker create php-build) + +docker -D cp $container:/php-src-$PHP_VERSION_GIT_BRANCH/sapi/cli/php . + +docker rm $container \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b49757f --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "araines/serverless-php", + "description": "Boilerplate project for Serverless Framework and PHP", + "type": "project", + "autoload": { + "psr-4": { + "Raines\\Serverless\\": "src" + } + }, + "require": { + "monolog/monolog": "^1.22", + "php": "^7.0", + "psr/log": "^1.0", + "symfony/config": "^3.2", + "symfony/dependency-injection": "^3.2", + "symfony/yaml": "^3.2" + }, + "require-dev": { + } +} diff --git a/config/services.yml b/config/services.yml new file mode 100644 index 0000000..16a032d --- /dev/null +++ b/config/services.yml @@ -0,0 +1,34 @@ +parameters: + logging_level: !php/const:Monolog\Logger::INFO + +services: + logger: + class: Monolog\Logger + arguments: + - 'handler' + calls: + - [pushHandler, ['@logging.handler']] + + logging.formatter: + class: Monolog\Formatter\LineFormatter + arguments: + - '%%message%% %%context%% %%extra%%' + + logging.handler: + class: Monolog\Handler\StreamHandler + arguments: + - 'php://stderr' + - '%logging_level%' + calls: + - [setFormatter, ['@logging.formatter']] + +# Define your own handlers and other services here + handler.hello: + class: Raines\Serverless\HelloHandler + arguments: + - '@logger' + +# handler.example: +# class: Acme\ExampleHandler +# arguments: +# - '@logger' diff --git a/dockerfile.buildphp b/dockerfile.buildphp new file mode 100644 index 0000000..1250304 --- /dev/null +++ b/dockerfile.buildphp @@ -0,0 +1,43 @@ +# Compile PHP with static linked dependencies +# to create a single running binary + +FROM amazonlinux + +ARG PHP_VERSION + +RUN yum install \ + autoconf \ + automake \ + libtool \ + bison \ + re2c \ + libxml2-devel \ + openssl-devel \ + libpng-devel \ + libjpeg-devel \ + curl-devel -y + +RUN curl -sL https://github.com/php/php-src/archive/$PHP_VERSION.tar.gz | tar -zxv + +WORKDIR /php-src-$PHP_VERSION + +RUN ./buildconf --force + +RUN ./configure \ + --enable-static=yes \ + --enable-shared=no \ + --disable-all \ + --enable-json \ + --enable-libxml \ + --enable-mbstring \ + --enable-phar \ + --enable-soap \ + --enable-xml \ + --with-curl \ + --with-gd \ + --with-zlib \ + --with-openssl \ + --without-pear \ + --enable-ctype + +RUN make -j 5 diff --git a/handler.js b/handler.js new file mode 100644 index 0000000..35cdaf9 --- /dev/null +++ b/handler.js @@ -0,0 +1,32 @@ +'use strict'; + +var child_process = require('child_process'); + +module.exports.handle = (event, context, callback) => { + + var response = ''; + var php = './php'; + + // When using 'serverless invoke local' use the system PHP binary instead + if (typeof process.env.PWD !== "undefined") { + php = 'php'; + } + + var proc = child_process.spawn(php, ["handler.php", JSON.stringify(event)]); + + proc.stdout.on('data', function (data) { + response += data.toString() + }); + + proc.stderr.on('data', function (data) { + console.log(`${data}`); + }); + + proc.on('close', function(code) { + if (code !== 0) { + return callback(new Error(`Process error code ${code}: ${response}`)); + } + + callback(null, JSON.parse(response)); + }); +}; diff --git a/handler.php b/handler.php new file mode 100644 index 0000000..908288a --- /dev/null +++ b/handler.php @@ -0,0 +1,22 @@ +load('services.yml'); + +// Get event object +$event = json_decode($argv[1], true) ?: []; + +// Get the handler service and execute +$handler = $container->get(getenv('HANDLER')); +$response = $handler->handle($event); + +// Send data back to shim +printf(json_encode($response)); diff --git a/php b/php new file mode 100755 index 0000000..45f74c4 --- /dev/null +++ b/php @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:044ab10f6b5732ac5ca8bc53a275ec42708f77ab7e6d4e17ad588f48c19580f7 +size 25161497 diff --git a/serverless.yml b/serverless.yml new file mode 100644 index 0000000..e4c2a95 --- /dev/null +++ b/serverless.yml @@ -0,0 +1,116 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: php-hello-world # NOTE: update this with your service name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +# frameworkVersion: "=X.X.X" + +provider: + name: aws + runtime: nodejs6.10 + +# you can overwrite defaults here +# stage: dev +# region: us-east-1 + +# you can add statements to the Lambda function's IAM Role here +# iamRoleStatements: +# - Effect: "Allow" +# Action: +# - "s3:ListBucket" +# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } +# - Effect: "Allow" +# Action: +# - "s3:PutObject" +# Resource: +# Fn::Join: +# - "" +# - - "arn:aws:s3:::" +# - "Ref" : "ServerlessDeploymentBucket" +# - "/*" + +# you can define service wide environment variables here +# environment: +# variable1: value1 + +# you can add packaging information here +#package: +# include: +# - include-me.js +# - include-me-dir/** +# exclude: +# - exclude-me.js +# - exclude-me-dir/** +package: + exclude: + - '*' + - '**' + include: + - config/** + - handler.js + - handler.php + - php + - src/** + - vendor/** + +functions: + hello: + handler: handler.handle + environment: + HANDLER: handler.hello # This is the service name which will be used (from services.yml) + events: + - http: + path: hello + method: get + +# The following are a few example events you can configure +# NOTE: Please make sure to change your handler code to work with those events +# Check the event documentation for details +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env:BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic +# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 +# - alexaSkill +# - iot: +# sql: "SELECT * FROM 'some_topic'" +# - cloudwatchEvent: +# event: +# source: +# - "aws.ec2" +# detail-type: +# - "EC2 Instance State-change Notification" +# detail: +# state: +# - pending + +# Define function environment variables here +# environment: +# variable2: value2 + +# you can add CloudFormation resource templates here +#resources: +# Resources: +# NewResource: +# Type: AWS::S3::Bucket +# Properties: +# BucketName: my-new-bucket +# Outputs: +# NewOutput: +# Description: "Description for the output" +# Value: "Some output value" diff --git a/src/Handler.php b/src/Handler.php new file mode 100644 index 0000000..a815752 --- /dev/null +++ b/src/Handler.php @@ -0,0 +1,15 @@ +logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function handle(array $event) + { + $this->logger->notice('Got event', $event); + + return [ + 'statusCode' => 200, + 'body' => 'Go Serverless v1.0! Your function executed successfully!', + ]; + } +}