The Serverless PHP Application Rob Allen PHPUGFFM, September 2021

Serverless? Rob Allen ~ @akrabat

Platform options Rob Allen ~ @akrabat

Platform options Rob Allen ~ @akrabat

Platform options Rob Allen ~ @akrabat

Platform options Rob Allen ~ @akrabat

Platform options Rob Allen ~ @akrabat

Platform options Rob Allen ~ @akrabat

Platform options Rob Allen ~ @akrabat

Serverless Serverless is all about composing software systems from a collection of cloud services. With serverless, you can lean on off-the-shelf cloud services resources for your application architecture, focus on business logic and application needs. Nate Taggart, CEO Stackery Rob Allen ~ @akrabat

FaaS Your code Rob Allen ~ @akrabat

FaaS Deployed to the cloud Rob Allen ~ @akrabat

FaaS Runs when needed Rob Allen ~ @akrabat

FaaS Scaled automatically Rob Allen ~ @akrabat

FaaS Pay only for execution Rob Allen ~ @akrabat

Where are the servers? Rob Allen ~ @akrabat

Rob Allen ~ @akrabat

Rob Allen ~ @akrabat

Use-cases Rob Allen ~ @akrabat

Use-cases Synchronous Service is invoked and provides immediate response (HTTP requests: APIs, chat bots) Rob Allen ~ @akrabat

Use-cases Synchronous Service is invoked and provides immediate response (HTTP requests: APIs, chat bots) Asynchronous Push a message which drives an action later (web hooks, timed events, database changes) Rob Allen ~ @akrabat

Benefits Rob Allen ~ @akrabat

Benefits • No need to maintain infrastructure Rob Allen ~ @akrabat

Benefits • No need to maintain infrastructure • Concentrate on application code Rob Allen ~ @akrabat

Benefits • No need to maintain infrastructure • Concentrate on application code • Pay only for what you use, when you use it Rob Allen ~ @akrabat

Benefits • • • • No need to maintain infrastructure Concentrate on application code Pay only for what you use, when you use it Language agnostic Rob Allen ~ @akrabat

Challenges Rob Allen ~ @akrabat

Challenges • Start up latency Rob Allen ~ @akrabat

Challenges • Start up latency • Time limit Rob Allen ~ @akrabat

Challenges • Start up latency • Time limit • State is external Rob Allen ~ @akrabat

Challenges • • • • Start up latency Time limit State is external Different way of thinking Rob Allen ~ @akrabat

When should you use serverless? Rob Allen ~ @akrabat

When should you use serverless? • Responding to web hooks Rob Allen ~ @akrabat

When should you use serverless? • Responding to web hooks • Additional features without extending current platform Rob Allen ~ @akrabat

When should you use serverless? • Responding to web hooks • Additional features without extending current platform • PWA/Static site contact form, et al. Rob Allen ~ @akrabat

When should you use serverless? • • • • Responding to web hooks Additional features without extending current platform PWA/Static site contact form, et al. Variable traffic levels Rob Allen ~ @akrabat

When should you use serverless? • • • • • Responding to web hooks Additional features without extending current platform PWA/Static site contact form, et al. Variable traffic levels When you want your costs to scale with traffic Rob Allen ~ @akrabat

It’s about value Rob Allen ~ @akrabat

Serverless platforms Rob Allen ~ @akrabat

Serverless languages Rob Allen ~ @akrabat

Serverless platforms with PHP support Rob Allen ~ @akrabat

Hello World AWS Lambda (Bref): <?php return function ($event) { $name = $event[‘name’] ?? ‘world’; return ‘Hello ’ . $name; }; Rob Allen ~ @akrabat

Hello World Apache OpenWhisk: <?php function main(array $args): array { $name = $args[‘name’] ?? ‘world’; return [“greeting” => ‘Hello ’ . $name]; } Rob Allen ~ @akrabat

Hello World OpenFAAS <?php class Handler { public function handle(string $data): void { $decoded = json_decode($data, true); $name = $decoded[‘name’] ?? ‘world’; return ‘Hello ’ . $name; } } Rob Allen ~ @akrabat

Hello World Google Cloud Functions (alpha) <?php use Psr\Http\Message\ServerRequestInterface as Request; function helloHttp(Request $request) { $name = $request->getQueryParams(‘name’) ?? ‘world’; return ‘Hello ’ . $name; } Rob Allen ~ @akrabat

Rob Allen ~ @akrabat

The anatomy of an action function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat

Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat

Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat

Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat

Hello World function main(array $args): array { // Marshall inputs from event parameters $name = $args[‘name’] ?? ‘world’; // Do the work $message = ‘Hello ’ . $name // Return result return [“body” => $message]; } Rob Allen ~ @akrabat

Deploy to OpenWhisk $ zip -q hello.zip hello.php Rob Allen ~ @akrabat

Deploy to OpenWhisk $ zip -q hello.zip hello.php $ wsk action update —kind php:7.4 hello hello.zip ok: updated action hello Rob Allen ~ @akrabat

Run it $ wsk action invoke hello —result —param name Rob Rob Allen ~ @akrabat

Run it $ wsk action invoke hello —result —param name Rob { “body”: “Hello Rob!” } Rob Allen ~ @akrabat

Under the hood Rob Allen ~ @akrabat

OpenWhisk’s architecture Rob Allen ~ @akrabat

Create an action Rob Allen ~ @akrabat

Invoke an action Rob Allen ~ @akrabat

Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run Rob Allen ~ @akrabat

Action container lifecycle • Hosts the user-written code • Controlled via two end points: /init & /run Rob Allen ~ @akrabat

Architecture Rob Allen ~ @akrabat

Monolith architecture Rob Allen ~ @akrabat

Serverless architecture Rob Allen ~ @akrabat

Serverless architecture pattern Rob Allen ~ @akrabat

Functions are key Rob Allen ~ @akrabat

Functions are the Unit of Deployment Rob Allen ~ @akrabat

Functions are the Unit of Scale Rob Allen ~ @akrabat

Functions are Stateless Rob Allen ~ @akrabat

Functions have Structure Rob Allen ~ @akrabat

Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods Rob Allen ~ @akrabat

Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods • Use multiple files Rob Allen ~ @akrabat

Structure If it’s non-trivial, software engineering principles apply! • Use multiple methods • Use multiple files • Integrate reusable dependencies Rob Allen ~ @akrabat

Rob Allen ~ @akrabat

AWS Lambda with PHP Relies on Lambda’s layers system Process: 1. Create a layer containing: 1. the PHP executable 2. a bootstrap script 2. Write the PHP function! Rob Allen ~ @akrabat

.

Bref PHP function index.php: <?php declare(strict_types=1); require DIR . ‘/vendor/autoload.php’; return function($event) { return ‘Hello ’ . ($event[‘name’] ?? ‘world’); } Rob Allen ~ @akrabat

serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80} Rob Allen ~ @akrabat

serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80} Rob Allen ~ @akrabat

serverless.yml service: helloapp provider: name: aws runtime: provided.al2 functions: hello: handler: index.php layers: - ${bref:layer.php-80} Rob Allen ~ @akrabat

Deploy $ serverless deploy Serverless: Packaging service… … Serverless: Stack update finished… Service Information service: helloapp stage: dev region: eu-west-2 stack: helloapp-dev functions: hello: helloapp-dev-hello Rob Allen ~ @akrabat

Run $ serverless invoke -f hello “Hello world” Rob Allen ~ @akrabat

Run $ serverless invoke -f hello “Hello world” $ serverless invoke -f hello -d ‘{“name”: “Rob”}’ “Hello Rob” Rob Allen ~ @akrabat

Run locally in Docker $ serverless invoke local —docker -f hello Serverless: Building Docker image… … REPORT RequestId: 6a653a94-ee51-1f3e-65c7-1f1954842f29 Init Duration: 265.93 ms Duration: 145.37 ms Billed Duration: 200 ms Memory Size: 1024 MB Max Memory Used: 27 MB “Hello world” Xdebug also works! Rob Allen ~ @akrabat

Add AWS API Gateway serverless.yml: functions: hello: handler: index.php … events: - http: “GET /hello” - http: “GET /hi/{name}” Rob Allen ~ @akrabat

Return a PSR-15 RequestHandler class HelloHandler implements RequestHandlerInterface { public function handle(ServerRequest $request): Response { $name = ($request->getQueryParams()[‘name’] ?? ‘world’); $body = ‘Hello ’ . $name; return new Response(200, [‘Content-Type’ => ‘text/plain’], $body); } } Rob Allen ~ @akrabat

Deploy $ serverless deploy Serverless: Packaging service… … Service Information service: helloapp stage: dev stack: helloapp-dev endpoints: GET - https://l1v6cz13zb.execute-api.eu-west-2 .amazonaws.com/dev/hello Rob Allen ~ @akrabat

Test $ curl -i https://l1v6cz…naws.com/dev/hello?name=Rob HTTP/2 200 content-type: text/plain content-length: 9 Hello Rob Rob Allen ~ @akrabat

Case study Project 365 photo website Rob Allen ~ @akrabat

Project 365 Static website to display my photo-a-day picture for each day of the year. • Hosted on S3 • CloudFront CDN • Lambda/PHP function Rob Allen ~ @akrabat

Lambda/PHP function Rob Allen ~ @akrabat

Infrastructure as code serverless.yml: functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Infrastructure as code functions: update: handler: update.php layers: - ${bref:layer.php-80} events: - schedule: rate: cron(0 */2 * * ? *) Rob Allen ~ @akrabat

Process 1. 2. 3. 4. 5. Gather credentials from environment Download photos from Flickr API Create HTML page Upload to S3 Invalidate CloudFront cache Rob Allen ~ @akrabat

main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat

main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat

main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat

main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat

main() return function ($event) { $year = $event[‘year’] ?? date(‘Y’); $photos = (new PhotoFetcher())->fetchForYear($year); $html = (new PageCreator())->create($year, $photos); $filename = “$year.html”; $uploader = new Uploader(); $uploader->uploadOne($filename, $html, $s3Bucket); $uploader->invalidateCache($filename); } Rob Allen ~ @akrabat

The finished website Rob Allen ~ @akrabat

To sum up Rob Allen ~ @akrabat

Thank you! Rob Allen ~ @akrabat

Photo credits - Assembly line: https://www.flickr.com/photos/adiram/3886212918 - Under the hood: https://www.flickr.com/photos/atomichotlinks/7736849388 - Pantheon: https://www.flickr.com/photos/shawnstilwell/4335732627 - Watch mechanism: https://www.flickr.com/photos/shinythings/2168994732 - Holiday snaps: https://www.flickr.com/photos/kjgarbutt/5358075923 - Rocket launch: https://www.flickr.com/photos/gsfc/16495356966 - Stars: https://www.flickr.com/photos/gsfc/19125041621 Rob Allen ~ @akrabat