A presentation at 3 Hours of Serverless by Rob Allen
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
PHP is arguably the most widely used language for writing web applications and is ideal for writing serverless applications too. This session explores PHP in a serverless environment where we’ll cover designing, building and deploying for serverless platforms. We’ll leverage the API gateway to turn incoming HTTP requests into events that trigger those serverless functions, creating robust APIs quickly and easily. By the end of the talk, you’ll appreciate that PHP and serverless really do work well together.