Slide 1

Slide 1 text

foolab.ca | @foolabca API Tips From the Frontline PHP Quebec, Montreal - April 2, 2015

Slide 2

Slide 2 text

Anna Filina • Developer • Problem solver • Coach • Advisor 2

Slide 3

Slide 3 text

What you will learn • Don't repeat my mistakes. • Overcome common obstacles. • Build elegant APIs. 3

Slide 4

Slide 4 text

Endpoints What URL to call when.

Slide 5

Slide 5 text

Lists • /products • /products?category=games,movies&lang=fr • /products?include=platforms.name&sort=-date • /products?page[number]=2&page[size]=100 5

Slide 6

Slide 6 text

Details • /products/1 • /products/1?include=photos,reviews 6

Slide 7

Slide 7 text

Write • POST /products • PUT /products/1 • DELETE /products/1 • Version your APIs: api.example.org/v1/products 7

Slide 8

Slide 8 text

Request/Response How to format stuff.

Slide 9

Slide 9 text

Multiple formats • xml, json, etc. • Can use 1 endpoint with Accept header. • Can send version, pagination & language info using headers too. 9

Slide 10

Slide 10 text

Request • Bad: Content-Type: application/x-www-form-urlencoded. • Content-Type: application/json. • Send content in body, not headers. 10

Slide 11

Slide 11 text

Request example POST /products HTTP/1.1 Host: api.example.org Content-Type: application/json; charset=UTF-8 { "data": { "name": "Skyrim", "price": 19.99 } } 11

Slide 12

Slide 12 text

Response example: list { "data": [ {"name": "Skyrim"}, {"name": "Civilization V"}, ], "page": { "number": 1, "size": 2, "pages": 50, "total": 100, } } 12

Slide 13

Slide 13 text

3 queries • Count query with same filters but no pagination. • Query with filters, pagination & sorting, but only select DISTINCT IDs. ◦ Because JOIN + LIMIT = wrong number of root records. • Query all the data by IDs and apply sorting. 13

Slide 14

Slide 14 text

Upload • Use tools, don't DIY (do it yourself). • Client-side: Plupload • Step 1: upload temp file. • Step 2: give file path to next API request. • Server-side: Guzzle. • Look under $_FILES. 14

Slide 15

Slide 15 text

Plupload var uploader = new plupload.Uploader({ runtimes: 'html5,html4', max_file_size: '5mb', url: '/api/upload', filters: [{extensions: 'jpg,png,jpeg'}] }); uploader.init(); 15

Slide 16

Slide 16 text

Guzzle createRequest('POST', $url); $postBody = $request->getBody(); $postBody->setField('data', ['first_name':'Anna']); $file = new PostFile('i.jpg', fopen('/path', 'r')); $postBody->addFile($file); $response = $client->send($request); 16

Slide 17

Slide 17 text

Status codes • Don't confuse API & HTTP codes. • 2xx success • 3xx redirect • 4xx client error • 5xx server error • Send API-specific code in body. 17

Slide 18

Slide 18 text

Example of error Status: 400 Content-Type: application/json; charset=UTF-8 { "error": { "code": 1001, "message": "Price must be greater than 0." } } 18

Slide 19

Slide 19 text

Testing Simpler than you think.

Slide 20

Slide 20 text

HTTP • Use Guzzle or built-in framework tool. • Generate HTTP request , compare output. 20

Slide 21

Slide 21 text

Guzzle test // tests/ApiProductTest.php public function testGetOneProduct() { $client = new Client(); $response = $client->get('http://api.example.org/v1/products/1', [ 'exceptions' => false, 'headers' => ['Accept' => 'application/json'] ]); $this->assertEquals(200, $response->getStatusCode()); $body = $response->getBody()->getContents(); $this->assertJsonStringEqualsJsonString('{ "data": { "id": "1", "name": "Skyrim", "price": 19.99 } }', $body); } 21

Slide 22

Slide 22 text

Testing tips • Create separate database for tests. • Write tests before you code: • TDD • Contract between you and frontend dev. 22

Slide 23

Slide 23 text

Authentication Nooo, I hate that part! Someone else code it plz.

Slide 24

Slide 24 text

Multiple methods • You can have multiple auth methods for one API. • Session can be one of them if same app. 24

Slide 25

Slide 25 text

OAuth2 • SSL required. • Advanced features like access scope. • More security: use authorization server. 25

Slide 26

Slide 26 text

OAuth2 - Step 1 (simplified approach) 26 Login User Client app
 (php/js/mobile) Forward API Validate Create token Store token user/pass user/pass token

Slide 27

Slide 27 text

OAuth2 - Step 2 27 Fetch Display page Done token data page Access page Request data Validate token User Client app
 (php/js/mobile) API Authorization: Bearer zaVOP1lmQja4Lz-^RLwvzapvjd

Slide 28

Slide 28 text

OAuth2 - Auth server 28 User Client app
 (php/js/mobile) API Auth Request Forward Validate Create token Store token token Login form Login Store token token user/pass

Slide 29

Slide 29 text

Refactoring to API Don't rewrite all the legacy at once.

Slide 30

Slide 30 text

Micro-services 30 Products Orders Inventory /categories /products /reviews /customers /orders /warehouses /sales-reps get info update
 stock get info

Slide 31

Slide 31 text

Progressive rewrite • Rewrite one component at a time. • Start with whatever has fewer dependencies (or critical). • Will have duplicate functionality for a time. ○ Example: orders will still fetch products the old way. • Copy production data for dev environment. 31

Slide 32

Slide 32 text

Services vs libraries • Services: GET /products?id=1,5 • Libraries: • $db->getList( ["id" => "1,5"] ); • Can fetch without pagination. • Can hydrate to models. • Bypass HTTP. 32

Slide 33

Slide 33 text

Implementation tips Is this a good way to code it?

Slide 34

Slide 34 text

Libraries • I use Symfony & Doctrine. • Aside from frameworks, I prefer small tools that don't do too much magic. • OptionsResolver component to validate parameters. • Validator to validate entities (models). 34

Slide 35

Slide 35 text

OptionsResolver $resolver = new OptionsResolver(); $resolver->setDefaults([ 'sort' => null, 'lang' => 'en', ]); $resolver->setAllowedTypes('lang','string'); $resolver->setAllowedValues('lang',['en', 'fr']); $resolver->resolve($options); 35

Slide 36

Slide 36 text

Validator $validator = Validation::createValidator(); $constraint = new Assert\Collection([ 'name' => new Assert\Length(['min'=>2]), 'photos' => new Assert\Collection([ 'file' => new Assert\Image(), ]), ]); $violations = $validator->validateValue($input, $constraint); 36

Slide 37

Slide 37 text

Validator • Can read constraints from model annotations.
 /** @Assert\ Length(min=5, minMessage="Too short") **/ • List of violations for you to forward to client. • Can validate files, emails, IPs, credit cards, dates, etc. 37

Slide 38

Slide 38 text

Reuse & standardize • Goal: streamline endpoint creation. • Base controller for common request processing. • Base repository for querying with filters. • Keep things fully customizable. ◦ Not too much magic. ◦ I avoid out-of-the-box solutions. 38

Slide 39

Slide 39 text

Performance Make things faster. Much faster.

Slide 40

Slide 40 text

Benchmark • Make performance part of your test suite. • Give Blackfire a spin. • In dev/test mode, use a meta block (or add to headers). 40

Slide 41

Slide 41 text

Example { "meta": { "perf": { "db_time": 0.00367, "total_time": 0.3120, "memory": 19661 } } } 41

Slide 42

Slide 42 text

Perfomance tips • Don't use lazy loading. Example: $product->getPhotos(). • Craft your own joins and carefully select fields. • Avoid ORM built-in hydration for read operations. • Use API keys even for public endpoints (DDoS mitigation). • Put stuff in Memcached/Redis (especially blocked keys). 42

Slide 43

Slide 43 text

Useful links • Standard API format
 http://jsonapi.org/format/ • Blackfire
 https://blackfire.io • OAuth2
 http://oauth2.thephpleague.com/ • OAuth2 with mobile
 http://www.slideshare.net/briandavidcampbell/... • Symfony components
 http://symfony.com/doc/current/components/index.html • Book "Build APIs You Won't Hate"
 https://leanpub.com/build-apis-you-wont-hate 43

Slide 44

Slide 44 text

Anna Filina • @afilina • afilina.com • Development: PHP, JS, etc. • Fix problems: bugs, performance, etc. • Workshops: testing, Symfony, AngularJS, API, etc. • Advisor: testing strategy, legacy code, etc. 44

Slide 45

Slide 45 text

@afilina afilina.com