Serverless PHP using AWS Lambda

Jacques Fu
StartUpward
Published in
3 min readAug 17, 2019

PHP is not often thought of as being a native player in the serverless stack. In fact, AWS Lambda, which is one of the most popular serverless providers, only provides native support for Node.js, Python, Java, Ruby, C#, Go and PowerShell. Similarly, Google Functions only supports Node.js, Python, and Go.

Lucky for us, using Bref, we can quickly and easily standup our serverless environment by taking advantage of custom runtimes using Lambda’s new layer feature.

Serverless PHP Quickstart with Laravel Sample App

To get started, ensure that you have installed the serverless framework and configured with your AWS keys. If not, I will be later adding instructions to do this from scratch.

First, you must clone this sample project from GitHub: github.com/mnapoli/bref-laravel-demo.

$ cd bref-laravel-demo$ cp .env.production .env$ serverless deploy

And that’s it! You should see something like the below screenshot.

Serverless PHP From Scratch

Let’s assume you have nothing and are starting from scratch.

First, you will need to ensure PHP is updated on your machine to at least 7.2 or above. You will also need to install composer and serverless framework.

Second, you’ll want to register for AWS and get credentials.

While it is possible to write simple php functions in serverless, I believe they will have limited use without a framework that helps you secure validate inputs, manage routes, etc… you could include those libraries via composer, but that’s what Laravel essentially is, a collection of libraries into a framework with some built-in patterns.

Starting in an empty directory, install Bref using Composer:

composer require bref/bref

Then let’s start by initializing the project by running:

vendor/bin/bref init

You are free to edit the code in index.php, you must, however, keep the call to the lambda() function:

require __DIR__.’/vendor/autoload.php’;lambda(function (array $event) {
// Do anything you want here
// For example:
return ‘Hello ‘ . ($event[‘name’] ?? ‘world’);
});

From here you can deploy the initialized sample.

serverless deploy

A .serverless/ directory will be created. You can add it to .gitignore.

Serverless Laravel

Starting with an existing Laravel project, let’s install Bref via Composer (this is mostly a copy of instructions from here):

First, add Bref.

composer require bref/bref

Then let’s create a serverless.yml configuration file (at the root of the project) optimized for Laravel:

service: bref-demo-laravelprovider:
name: aws
region: us-east-1
runtime: provided
environment:
# Laravel environment variables
APP_STORAGE: '/tmp'
plugins:
- ./vendor/bref/bref
functions:
website:
handler: public/index.php
timeout: 30 # in seconds (API Gateway has a timeout of 30 seconds)
layers:
- ${bref:layer.php-73-fpm}
events:
- http: 'ANY /'
- http: 'ANY /{proxy+}'
artisan:
handler: artisan
timeout: 120 # in seconds
layers:
- ${bref:layer.php-73} # PHP
- ${bref:layer.console} # The "console" layer

Now we still have a few modifications to do on the application to make it compatible with AWS Lambda.

Since the filesystem is read-only except for /tmp we need to customize where the cache files are stored. Add this line in bootstrap/app.php after $app = new Illuminate\Foundation\Application:

/*
* Allow overriding the storage path in production using an environment variable.
*/
$app->useStoragePath($_ENV['APP_STORAGE'] ?? $app->storagePath());

We will also need to customize the location for compiled views, as well as customize a few variables in the .env file:

VIEW_COMPILED_PATH=/tmp/storage/framework/views# We cannot store sessions to disk: if you don't need sessions (e.g. API)
# then use `array`, else store sessions in database or cookies
SESSION_DRIVER=array
# Logging to stderr allows the logs to end up in Cloudwatch
LOG_CHANNEL=stderr

Finally, we need to edit app/Providers/AppServiceProvider.php because Laravel will not create that directory automatically:

public function boot()
{
// Make sure the directory for compiled views exist
if (! is_dir(config('view.compiled'))) {
mkdir(config('view.compiled'), 0755, true);
}
}

Final Notes

If you intend to run this in production, you’ll want to ensure everything is packaged with the production versions.

composer install --optimize-autoloader --no-dev

If you run this command in your local installation this might break your development setup (it will remove dev dependencies). Ideally deployment should be done in a separate directory, from scratch.

--

--

Jacques Fu
StartUpward

CTO @ Healthcare Startup, Serial Entrepreneur, Time Hacker, Chief Innovator, Code Ninja, Glasshole, Professional Student, Good Listener, and Apprentice Parent.