PHP in a Serverless World Rob Allen Nimbella’s 3 Hours of Serverless, August 2020
A presentation at 3 Hours of Serverless in August 2020 in 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
