Custom PHP Runtime Layer in AWS Lambda Functions

Run PHP in AWS lambda
Nov 26 2020 · 4 min read

Introduction 

I wanted to deploy Serverless Microservices(PHP) using AWS Lambda for one of my projects. Through research, I found some existing packages that can accomplish this task.

I was curious about the process inside the lambda container. Thus, I decided to do it on my own.

So Let’s start the Adventure….

We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!

Prerequisites

We will create a layer of runtime in AWS lambda which contains

  • PHP binary
  • runtime.php
  • bootstrap

And we will reference it in the lambda function.

PHP Runtime

We are using guzzlehttp for handling incoming lambda requests.

You can install it using,

composer require guzzlehttp/guzzle

In the runtime.php, add vendor dependency for guzzlehttp.

require __DIR__ . '/vendor/autoload.php';

Then, create the method getNextRequest() for capturing lambda requests. In this method, we will initialize the guzzlehttp client, and send a request to AWS lambda.

function getNextRequest()
{
    $client = new \GuzzleHttp\Client();
    return $client->get('http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/next');
}

After this, add a method to send a response to the AWS lambda.

function sendResponse($invocationId, $response)
{
    $client = new \GuzzleHttp\Client();
    $client->post(
        'http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/' . $invocationId . '/response',
        ['body' => json_encode($response)]
    );
}

AWS_LAMBDA_RUNTIME_API is AWS lambda’s default environment variable, we don’t need to set it. I’ll explain later, why we should send a JSON response to the AWS Lambda.

Finally, use the do-while loop, which will run until AWS Lambda’s environment is shut down. Then, call both of the above methods.

do {
      $request = getNextRequest();
      $invocationId = $request->getHeader('Lambda-Runtime-Aws-Request-Id')[0];
      $payLoad =  json_decode((string) $request->getBody(), true);
      $data = [];
      sendResponse($invocationId, $data);
}while(true)

Here data is an empty array, so let’s prepare data to send a response to AWS Lambda.

Create an index.php file. It contains a simple PHP code that we want to run in AWS lambda. You can add your code and pass the result as $response to runtime.php.

<?php
   $response = "Hello World";
?>

Now we’ll include it in runtime.php as,

require_once $_ENV['LAMBDA_TASK_ROOT'].'/index.php';

Then prepare $data as follows,

$headers['Content-type'] = "text/html; charset=UTF-8";
$statusCode = 200;
$content = $response;

I took Content-type as text/html because I wanted an HTML view for my response. You can take content type according to your requirements.

$data will look like this. We will encode it using PHP's json_encode in sendResponse() to get JSON response.

$data = [
        'statusCode' => $statusCode,
        'isBase64Encoded' => false,
        'headers' => $headers,
        'body' => $content
    ];

Full code of runtime.php will look like below,

<?php

// This invokes Composer's autoloader so that we'll be able to use Guzzle and any other 3rd party libraries we need.
require __DIR__ . '/vendor/autoload.php';

// This is the request processing loop. Barring unrecoverable failure, this loop runs until the environment shuts down.
do {

    // Ask the runtime API for a request to handle.
    $request = getNextRequest();
    
    //Get invocationId and Payload from request.
    $invocationId = $request->getHeader('Lambda-Runtime-Aws-Request-   Id')[0];
    $payLoad =  json_decode((string) $request->getBody(), true);
    
    //include index.php file
    require_once $_ENV['LAMBDA_TASK_ROOT'].'/index.php';

    $headers['Content-type'] = "text/html; charset=UTF-8";
    $statusCode = 200;
    $content = $response;
    
    //Prepare response for runtime API.
    $data = [
        'statusCode' => $statusCode,
        'isBase64Encoded' => false,
        'headers' => $headers,
        'body' => $content
    ];
    
    // Submit the response back to the runtime API.
    sendResponse($invocationId, $data);
} while (true);


function getNextRequest()
{
    $client = new \GuzzleHttp\Client();
    
    //Send request to lambda function.
    return $client->get('http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/next');
}

function sendResponse($invocationId, $response)
{
    $client = new \GuzzleHttp\Client();
    
    //Get response from lambda function and send it to client.
    $client->post(
        'http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/' . $invocationId . '/response',
        ['body' => json_encode($response)]
    );
}

To execute runtime.php in the Lambda environment, we need PHP binary. As I need a UI interface, I’ll use PHP_CGI. We can use PHP if an interface is not required.

PHP Binary 

I have built PHP binary using amazonlinux:2 docker image. You can find the Full Code of it in Dockerfile.

Bootstrap

It contains a command to execute runtime.php

Layer’s files reside in /opt directory and Function’s files reside in /var/task directory.

#go into the source directory
cd $LAMBDA_TASK_ROOT

#execute the runtime
/opt/php-cgi /opt/runtime.php

Again, LAMBDA_TASK_ROOT is Lambda’s predefined env variable, valued as the root directory of lambda function which is /var/task.

Deploy using the AWS console

Create a Lambda layer and a lambda function using AWS-CLI and deploy PHP code. Deploy.sh contains a script of it.

I created a GitHub repo for serverless-php, which contains a lambda function example.

After deploying both layer and function, it will look like below,

1_ENvVm1Xz2zsOS3OFxwrElg.png
AWS Lambda Layer


 

1_051TqccTCFf-atG9vvirRw.png
AWS Lambda Function

Lambda Function has a layer of PHP runtime as,

1_yDrPsDhVtgG3mKK5ydogew.png
Layer Reference in AWS Lambda function

 

Now it's time to run the deployed Lambda function.

Open the created Lambda Function and Test it. If everything’s fine, it will show output like,

1_xQLOE_9W41-OSwPJG26Lyg.png
Lambda Function Response

 

If we want to use an API gateway with a Lambda function, As mentioned here at Method Response, the response should be in JSON format.

You can configure CloudWatch to trace the logs of PHP applications.

Our custom PHP runtime also allows the deployment of PHP Laravel and CodeIgniter applications by doing some minor configurations like changing Session, Cache, and Logs drivers.

We’re Grateful to have you with us on this journey!

Suggestions and feedback are more than welcome! 

Please reach us at Canopas Twitter handle @canopas_eng with your content or feedback. Your input enriches our content and fuels our motivation to create more valuable and informative articles for you.

Similar Useful Articles


Code, Build, Repeat.
Stay updated with the latest trends and tutorials in Android, iOS, and web development.
sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.
sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.
canopas-logo
We build products that customers can't help but love!
Get in touch

Whether you need...

  • *
    High-performing mobile apps
  • *
    Bulletproof cloud solutions
  • *
    Custom solutions for your business.
Bring us your toughest challenge and we'll show you the path to a sleek solution.
Talk To Our Experts
footer
Follow us on
2024 Canopas Software LLP. All rights reserved.