PHP in a Serverless World Rob Allen Nimbella’s 3 Hours of Serverless, August 2020

Rob Allen ~ @akrabat

Rob Allen ~ @akrabat

Rob Allen ~ @akrabat

Stateless Rob Allen ~ @akrabat

Synchronous Rob Allen ~ @akrabat

PHP 7 This is not the PHP you remember! Rob Allen ~ @akrabat

PHP 7: Faster! Rob Allen ~ @akrabat

PHP 7: Lower memory Rob Allen ~ @akrabat

Serverless platforms Rob Allen ~ @akrabat

Serverless platforms with PHP support Rob Allen ~ @akrabat

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

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

Hello World OpenFAAS (OSS): <?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 function helloHttp(ServerRequest $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 [“message” => $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 [“message” => $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 [“message” => $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 [“message” => $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 [“message” => $message]; } Rob Allen ~ @akrabat

HTTP Access API Gateway provides: • • • • API routing Rate limiting Authentication Custom domains Rob Allen ~ @akrabat

API Gateway Rob Allen ~ @akrabat

API Gateway $ wsk api create /myapp /hello GET hello Rob Allen ~ @akrabat

API Gateway $ wsk api create /myapp /hello GET hello ok: created API /myapp/hello GET for action /_/hello Rob Allen ~ @akrabat

API Gateway $ wsk api create /myapp /hello GET hello ok: created API /myapp/hello GET for action /_/hello $ curl https://example.com/myapp/hello?name=Rob Rob Allen ~ @akrabat

API Gateway $ wsk api create /myapp /hello GET hello ok: created API /myapp/hello GET for action /_/hello $ curl https://example.com/myapp/hello?name=Rob { “message”: “Hello Rob!” } Rob Allen ~ @akrabat

Let’s talk about HTTP APIs Rob Allen ~ @akrabat

HTTP APIs Just because it’s serverless doesn’t mean we can ignore the basics! • • • • • Status codes HTTP method negotiation Content-type handling Error handling Media type format Rob Allen ~ @akrabat

Status codes Send the right one for the right situation! 1xx Informational 2xx Success 3xx Redirection 4xx Client error 5xx Server error Rob Allen ~ @akrabat

Full control over response function main(array $args) : array { // our code here to do something return [ “statusCode” => 201, “headers” => [ “Content-Type” => “application/json”, ], “body” => [ “result” => “Item created”, ], ]; } Rob Allen ~ @akrabat

HTTP verbs Method GET PUT DELETE POST PATCH Used for Retrieve data Change data Delete data Change data Update data Idempotent? Yes Yes Yes No No Prefer Idempotent whenever possible Rob Allen ~ @akrabat

HTTP request information Parameters arrive in args associative array: $args[‘name’] $args[‘date’] name parameter in body or query date parameter in body or query __ow-prefixed keys for for HTTP data: $args[‘__ow_method’] $args[‘__ow_path’] $args[‘__ow_headers’] $args[‘__ow_body’] HTTP method URL path HTTP headers Base64 encoded HTTP body Rob Allen ~ @akrabat

Let’s look at a case study Rob Allen ~ @akrabat

A Serverless API Rob Allen ~ @akrabat

Todo-Backend An OpenWhisk PHP implementation of a to-do list API Rob Allen ~ @akrabat

Serverless Framework Deployment tooling for serverless applications serverless.yml: service: ow-todo-backend provider: name: openwhisk runtime: php plugins: - serverless-openwhisk Rob Allen ~ @akrabat

Configure action functions: edit-todo: handler: “src/actions/editTodo.main” name: “todo-backend/edit-todo” Rob Allen ~ @akrabat

Configure action functions: edit-todo: handler: “src/actions/editTodo.main” name: “todo-backend/edit-todo” Rob Allen ~ @akrabat

Configure action functions: edit-todo: handler: “src/actions/editTodo.main” name: “todo-backend/edit-todo” Rob Allen ~ @akrabat

Configure API Gateway functions: edit-todo: handler: “src/actions/editTodo.main” name: “todo-backend/edit-todo” events: - http: path: /todos/{id} method: patch Rob Allen ~ @akrabat

Configure API Gateway functions: edit-todo: handler: “src/actions/editTodo.main” name: “todo-backend/edit-todo” events: - http: path: /todos/{id} method: patch Rob Allen ~ @akrabat

Project files . ├── ├── ├── ├── ├── src/ vendor/ composer.json composer.lock serverless.yml Rob Allen ~ @akrabat

Project files . ├── ├── ├── ├── ├── src/ vendor/ composer.json composer.lock serverless.yml src/ ├── Todo/ │ ├── Todo.php │ ├── TodoMapper.php │ └── TodoTransformer.php ├── actions/ │ ├── addTodo.php │ ├── deleteTodo.php │ ├── editTodo.php │ ├── listTodos.php │ └── showTodo.php └── AppContainer.php Rob Allen ~ @akrabat

editTodo.php function main(array $args) : array { try { $parts = explode(“/”, $args[‘__ow_path’]); $id = (int)array_pop($parts); $data = json_decode(base64_decode($args[‘__ow_body’]), true); if (!is_array($data)) { throw new InvalidArgumentException(‘Missing body’, 400); } $container = new AppContainer($args); $mapper = $container[TodoMapper::class]; $todo = $mapper->loadById($id); $mapper->update($todo, $data); $transformer = $container[TodoTransformer::class]; $resource = new Item($todo, $transformer, ‘todos’); $fractal = $container[Manager::class]; return [ ‘statusCode’ => 200, ‘body’ => $fractal->createData($resource)->toArray(), ]; } catch (Throwable $e) { error_log((string)$e); $code = $e->getCode() < 400 ? 500 : $e->getCode(); return [ ‘statusCode’ => $code, ‘body’ => [‘error’ => $e->getMessage()]]; } } Rob Allen ~ @akrabat

editTodo.php: Error handling function main(array $args) : array { try { // do stuff } catch (Throwable $e) { error_log((string)$e); $code = $e->getCode() < 400 ? 500 : $e->getCode(); return [ ‘statusCode’ => $code, ‘body’ => [‘error’ => $e->getMessage()]]; } } Rob Allen ~ @akrabat

editTodo.php: Error handling function main(array $args) : array { try { // do stuff } catch (Throwable $e) { error_log((string)$e); $code = $e->getCode() < 400 ? 500 : $e->getCode(); return [ ‘statusCode’ => $code, ‘body’ => [‘error’ => $e->getMessage()]]; } } Rob Allen ~ @akrabat

editTodo.php: Read input // read path parameters $parts = explode(“/”, $args[‘__ow_path’]); $id = (int)array_pop($parts); Rob Allen ~ @akrabat

editTodo.php: Read input // read path parameters $parts = explode(“/”, $args[‘__ow_path’]); $id = (int)array_pop($parts); // decode HTTP body $body = base64_decode($args[‘__ow_body’]; $data = json_decode($body), true); Rob Allen ~ @akrabat

editTodo.php: Do the work $todo = $mapper->loadById($id); $mapper->update($todo, $data); Rob Allen ~ @akrabat

editTodo.php: Present results $resource = new Fractal\Item($todo, $transformer, ‘todos’); $viewData = $fractal->createData($resource); return [ ‘statusCode’ => 200, ‘body’ => $viewData->toArray(), ]; Rob Allen ~ @akrabat

Deploy $ serverless deploy Serverless: Packaging Serverless: Compiling Serverless: Compiling Serverless: Compiling Serverless: Compiling Serverless: Compiling Serverless: Compiling Serverless: Deploying Serverless: Deploying Serverless: Deploying […] service… Functions… Packages… API Gateway definitions… Rules… Triggers & Feeds… Service Bindings… Packages… Functions… API Gateway definitions… Rob Allen ~ @akrabat

A working API! Rob Allen ~ @akrabat

Thank you! Rob Allen ~ @akrabat

Photo credits - Road sign: https://www.flickr.com/photos/ell-r-brown/6804246004 - Stars: https://www.flickr.com/photos/gsfc/19125041621 Rob Allen ~ @akrabat