PHP in a Serverless World

A presentation at 3 Hours of Serverless in August 2020 in by Rob Allen

Slide 1

Slide 1

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

Slide 2

Slide 2

Rob Allen ~ @akrabat

Slide 3

Slide 3

Rob Allen ~ @akrabat

Slide 4

Slide 4

Rob Allen ~ @akrabat

Slide 5

Slide 5

Slide 6

Slide 6

Stateless Rob Allen ~ @akrabat

Slide 7

Slide 7

Synchronous Rob Allen ~ @akrabat

Slide 8

Slide 8

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

Slide 9

Slide 9

PHP 7: Faster! Rob Allen ~ @akrabat

Slide 10

Slide 10

PHP 7: Lower memory Rob Allen ~ @akrabat

Slide 11

Slide 11

Serverless platforms Rob Allen ~ @akrabat

Slide 12

Slide 12

Serverless platforms with PHP support Rob Allen ~ @akrabat

Slide 13

Slide 13

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

Slide 14

Slide 14

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

Slide 15

Slide 15

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

Slide 16

Slide 16

Hello World Google Cloud Functions (alpha): <?php function helloHttp(ServerRequest $request) { $name = $request->getQueryParams(‘name’) ?? ‘world’; return ‘Hello ’ . $name; } Rob Allen ~ @akrabat

Slide 17

Slide 17

Rob Allen ~ @akrabat

Slide 18

Slide 18

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

Slide 19

Slide 19

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

Slide 20

Slide 20

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

Slide 21

Slide 21

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

Slide 22

Slide 22

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

Slide 23

Slide 23

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

Slide 24

Slide 24

API Gateway Rob Allen ~ @akrabat

Slide 25

Slide 25

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

Slide 26

Slide 26

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

Slide 27

Slide 27

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

Slide 28

Slide 28

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

Slide 29

Slide 29

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

Slide 30

Slide 30

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

Slide 31

Slide 31

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

Slide 32

Slide 32

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

Slide 33

Slide 33

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

Slide 34

Slide 34

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

Slide 35

Slide 35

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

Slide 36

Slide 36

A Serverless API Rob Allen ~ @akrabat

Slide 37

Slide 37

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

Slide 38

Slide 38

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

Slide 39

Slide 39

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

Slide 40

Slide 40

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

Slide 41

Slide 41

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

Slide 42

Slide 42

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

Slide 43

Slide 43

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

Slide 44

Slide 44

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

Slide 45

Slide 45

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

Slide 46

Slide 46

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

Slide 47

Slide 47

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

Slide 48

Slide 48

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

Slide 49

Slide 49

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

Slide 50

Slide 50

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

Slide 51

Slide 51

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

Slide 52

Slide 52

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

Slide 53

Slide 53

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

Slide 54

Slide 54

A working API! Rob Allen ~ @akrabat

Slide 55

Slide 55

Thank you! Rob Allen ~ @akrabat

Slide 56

Slide 56

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