Dependency injection in Symfony
You work with Symfony, but the concept of dependency injection is a little blurry for you? Find out how to take advantage of the component reading this article.
Summary
This article is a follow-up to this first part which introduces serverless computing. In this second part, we will first see what the layers are in AWS Lambda and how to implement them. Then we will see how to use the Bref framework.
An AWS Lambda environment includes:
The lifecycle of a Lambda runtime consists of an initialization phase and several (as many as necessary) invocation phases.
The initialization phase represents the time between the moment when the environment starts the runtime and the moment when the code of a function is executed. This phase is performed only once during the life cycle of the environment.
After initialization, the execution environment goes into the invocation phase and will constantly check and execute tasks, until the environment shuts down.
Since November 2018, it is possible to declare your own runtimes for Lambda functions, but also to incorporate reusable components in the form of Layers.
You can implement a runtime in any language. A runtime is a program that executes the handler
of a Lambda function when it is called. A runtime can be included in the function deployment package in the form of an executable file named bootstrap
(we will see an example later in this article).
A Lambda function can be configured to download additional code and content as a layer. A layer is a ZIP archive that contains libraries, a custom runtime or other dependencies.
If you have already written serverless functions in Node.js, you know that you must package the entire node_modules
folder for every function (since they are deployed independently from each other). This slows down the deployment process and makes the builds slow.
But now, it is possible to publish the node_modules
folder as a shared and reusable layer for all our functions. This means that we could have a layer for our custom runtime, another layer which contains our dependencies and configure our functions to use these 2 layers. Note that a function has a limit of 5 layers.
PHP function
Take the following simple function as an example:
// src/profession.php
function occupation()
{
$jobs = [
'Fireman',
'Astronaut',
'Super hero',
'Pilot',
'Professional cook',
'Artist',
];
return ['occupation' => $jobs[array_rand($jobs)]];
}
PHP layer
I am going to create a layers/php
folder in my application and I will place my layer there.
To create a custom runtime, we need a bootstrap
file which will contain the logic of our runtime in charge of invoking our functions.
We also need a PHP executable capable of interpreting our code. I'm going to create a bin
folder in my layer folder to place my php
binary. To generate a binary, I recommend you read this article.
When deploying a layer, it is placed in the /opt
folder in the containers. So my bootstrap
file could look like this:
#!/bin/sh
#go into the source directory
cd $LAMBDA_TASK_ROOT
#execute the runtime
/opt/bin/php /opt/runtime.php
Here is an example of runtime.php
inspired by the article on the AWS blog.
We will use Guzzle
to make HTTP calls, therefore I will first execute the following command:
composer require guzzlehttp/guzzle
<?php
// Invoke Composer's autoloader to use Guzzle
require $_ENV['LAMBDA_TASK_ROOT'] . '/vendor/autoload.php';
// 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();
// Obtain the function name from the _HANDLER environment variable and ensure the function's code is available
list($handlerFile, $handlerFunction) = explode(".", $_ENV['_HANDLER']);
require_once $_ENV['LAMBDA_TASK_ROOT'] . '/src/' . $handlerFile . '.php';
// Execute the desired function and obtain the response
$response = $handlerFunction($request['payload']);
// Submit the response back to the runtime API
sendResponse($request['invocationId'], $response);
} while (true);
function getNextRequest()
{
$client = new \GuzzleHttp\Client();
$response = $client->get(sprintf(
'http://%s/2018-06-01/runtime/invocation/next',
$_ENV['AWS_LAMBDA_RUNTIME_API']
));
return [
'invocationId' => $response->getHeader('Lambda-Runtime-Aws-Request-Id')[0],
'payload' => json_decode((string) $response->getBody(), true),
];
}
function sendResponse($invocationId, $response)
{
$client = new \GuzzleHttp\Client();
$client->post(
sprintf(
'http://%s/2018-06-01/runtime/invocation/%s/response',
$_ENV['AWS_LAMBDA_RUNTIME_API'],
$invocationId
),
['body' => $response]
);
}
To summarize, we currently have the following file structure:
layers/
php/
bin/
php #binary file
bootstrap
runtime.php
src/
profession.php
vendor/
guzzlehttp/
Deployment
I will use the serverless framework to deploy my layer and my function:
# serverless.yml service: php-serverless provider: name: aws runtime: provided region: eu-west-3 memorySize: 512 layers: php: path: layers/php functions: occupation: handler: profession.occupation layers: - {Ref: PhpLambdaLayer}
As we can see, in my occupation
function, the handler
contains the name of my file profession.php
and the occupation
method. This is how I configured it in runtime.php
:
//... list($handlerFile, $handlerFunction) = explode(".", $_ENV['_HANDLER']); require_once $_ENV['LAMBDA_TASK_ROOT'] . '/src/' . $handlerFile . '.php'; $response = $handlerFunction($request['payload']);
It is therefore up to us to configure the way we name the handlers and the way to call them in the runtime.
The name of our layer PhpLambdaLayer
corresponds to its CloudFormation reference. You can read the details here.
To deploy the function and the layer, execute the following command:
$ sls deploy
Serverless: Packaging service...
#...
Serverless: Stack update finished...
Service Information
service: php-serverless
stage: dev
region: eu-west-3
stack: php-serverless-dev
resources: 7
api keys:
None
endpoints:
None
functions:
occupation: php-serverless-dev-occupation
layers:
php: arn:aws:lambda:eu-west-3:087017887086:layer:php:1
Finally, let's invoke the occupation
function:
$ sls invoke -f occupation -l { "occupation": "Fireman" } -------------------------------------------------------------------- START RequestId: d09f2191-7233-47d3-a4fe-8de2a621a608 Version: $LATEST END RequestId: d09f2191-7233-47d3-a4fe-8de2a621a608 REPORT RequestId: d09f2191-7233-47d3-a4fe-8de2a621a608 Duration: 38.15 ms Billed Duration: 300 ms Memory Size: 512 MB Max Memory Used: 59 MB Init Duration: 191.10 ms
Summary
So we just made a working example with a layer capable of executing PHP code.
Now imagine that you have a large application, say a REST API in Symfony, that you would like to deploy on AWS Lambda. It would be necessary to develop a much more advanced runtime capable of integrating with the front controller of Symfony, and why not with the console as well. We would also have to modify the PHP layer to add all the libraries we would need and to recompile the PHP binary.
Fortunately for us, an open source solution exists to manage all of this: Bref.
Bref is an open source Composer package that allows us to deploy PHP applications on AWS Lambda. It is developed by Matthieu Napoli.
Bref provides:
I suggest we deploy a Symfony application on AWS Lambda using Bref.
To create my application:
$ composer create-project symfony / skeleton sf-serverless-example
Next, let's modify the default controller as follows (to use the same example as above):
namespace App\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
class DefaultController
{
public function index()
{
$jobs = [
'Fireman',
'Astronaut',
'Super hero',
'Pilot',
'Professional cook',
'Artist',
];
return new JsonResponse([
'occupation' => $jobs[array_rand($jobs)],
]);
}
}
Now let's add the Bref library:
$ composer require bref/bref
Finally, let's configure the deployment with serverless framework:
# serverless.yml service: php-serverless-sf-bref provider: name: aws region: eu-west-3 runtime: provided environment: # Symfony environment variables APP_ENV: prod plugins: - ./vendor/bref/bref functions: website: handler: public/index.php timeout: 28 # API Gateway has a timeout of 29 seconds layers: - ${bref:layer.php-74-fpm} events: - http: 'ANY /' - http: 'ANY /{proxy+}' console: handler: bin/console timeout: 120 # in seconds layers: - ${bref:layer.php-74} # PHP - ${bref:layer.console} # The "console" layer
The list of layers made available by Bref can be consulted here. I also recommend that you read the Bref documentation, it is very clear and provides plenty of examples that you may need.
We need to keep in mind that with most cloud providers the filesystem is read only. Hence, we need to change the logs
and cache
folders of our application:
public function getLogDir()
{
// When on the lambda only /tmp is writeable
if (getenv('LAMBDA_TASK_ROOT') !== false) {
return '/tmp/log/';
}
return parent::getLogDir();
}
public function getCacheDir()
{
// When on the lambda only /tmp is writeable
if (getenv('LAMBDA_TASK_ROOT') !== false) {
return '/tmp/cache/'.$this->environment;
}
return parent::getCacheDir();
}
Last step, deployment:
$ sls deploy
Serverless: Packaging service...
Service Information
service: php-serverless-sf-bref
stage: dev
region: eu-west-3
stack: php-serverless-sf-bref-dev
resources: 15
api keys:
None
endpoints:
ANY - https://maeck9uwyf.execute-api.eu-west-3.amazonaws.com/dev
ANY - https://maeck9uwyf.execute-api.eu-west-3.amazonaws.com/dev/{proxy+}
functions:
website: php-serverless-sf-bref-dev-website
console: php-serverless-sf-bref-dev-console
layers:
None
My application is available on the URL indicated in the endpoints. Here is the result:
That's it. We just deployed a Symfony application to AWS Lambda using Bref! As you can see, it is a pretty straight forward process...
You can now enjoy deploying PHP applications to a serverless infrastructure :)
Author(s)
Marie Minasyan
Astronaute Raccoon @ ElevenLabs_🚀 De retour dans la Galaxie.
You wanna know more about something in particular?
Let's plan a meeting!
Our experts answer all your questions.
Contact usDiscover other content about the same topic
You work with Symfony, but the concept of dependency injection is a little blurry for you? Find out how to take advantage of the component reading this article.
Are you suffering from domain anemia? Let's look at what an anemic domain model is and how things can change.
What is the serverless architecture, and how to deploy a PHP application to it?