bootstrat rest apis with laravel 5

40
Bootstrap REST APIs with Laravel 5 Elena Kolevska

Upload: elena-kolevska

Post on 17-Jul-2015

376 views

Category:

Software


5 download

TRANSCRIPT

Page 1: Bootstrat REST APIs with Laravel 5

Bootstrap REST APIs with Laravel 5

Elena Kolevska

Page 2: Bootstrat REST APIs with Laravel 5

www.speedtocontact.comAutomated lead response system / call center in a

browser

Page 3: Bootstrat REST APIs with Laravel 5

Why REST?

Page 4: Bootstrat REST APIs with Laravel 5

SOAP!

POST http://www.stgregorioschurchdc.org/cgi/websvccal.cgi HTTP/1.1 Accept-Encoding: gzip,deflate Content-Type: text/xml;charset=UTF-8 SOAPAction: "http://www.stgregorioschurchdc.org/Calendar#easter_date" Content-Length: 479 Host: www.stgregorioschurchdc.org Connection: Keep-Alive User-Agent: Apache-HttpClient/4.1.1 (java 1.5) <?xml version="1.0"?> <soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:cal="http://www.stgregorioschurchdc.org/Calendar"> <soapenv:Header/> <soapenv:Body> <cal:easter_date soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <year xsi:type="xsd:short">2014</year> </cal:easter_date> </soapenv:Body> </soapenv:Envelope>

Page 5: Bootstrat REST APIs with Laravel 5

Basic AuthenticationALWAYS use SSL!

Page 6: Bootstrat REST APIs with Laravel 5

$header = 'Authorization: Basic' . base64_encode($username . ':' . $password);

Page 7: Bootstrat REST APIs with Laravel 5
Page 8: Bootstrat REST APIs with Laravel 5

Authentication Middleware<?php namespace App\Http\Middleware;

use App\User;use Closure;use Illuminate\Contracts\Auth\Guard;use Illuminate\Support\Facades\Hash;

class AuthenticateOnce {

public function handle($request, Closure $next) { if ($this->auth->onceBasic()) return response(['status'=>false, 'message'=>'Unauthorized'], 401, ['WWW-Authenticate' =>'Basic']);

return $next($request); }

protected $routeMiddleware = [ 'auth' => 'App\Http\Middleware\Authenticate', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', 'auth.basic.once' => 'App\Http\Middleware\AuthenticateOnce', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', ];

Page 9: Bootstrat REST APIs with Laravel 5

<?php

Route::group(array('prefix' => 'api/v1/examples','middleware' => 'auth.basic.once'), function () {

Route::get('1', 'ExamplesController@example1');

Route::get('2', 'ExamplesController@example2');

Route::get('3', 'ExamplesController@example3');

Route::get('4', 'ExamplesController@example4');

Route::get('5', 'ExamplesController@example5');

Route::get('6', 'ExamplesController@example6');

Route::get('7', 'ExamplesController@example7');

Route::get('8', 'ExamplesController@example8');

});

app/Http/routes.php

Page 10: Bootstrat REST APIs with Laravel 5

Response formatHTTP/1.1 200 OK Content-Type: application/json Connection: close X-Powered-By: PHP/5.6.3Cache-Control: no-cacheDate: Fri, 13 Apr 2015 16:37:57 GMT Transfer-Encoding: Identity

{"status":true,"data":{"k1":"value1","k2":"value2"},"message":"Zero imagination for funny messages"}

Page 11: Bootstrat REST APIs with Laravel 5

Most common HTTP status codes

• 200 OK

• 201 Created

• 204 No Content

• 400 Bad Request

• 401 Unauthorized

• 403 Forbidden

• 404 Not Found

• 409 Conflict

• 500 Internal Server Error

Page 12: Bootstrat REST APIs with Laravel 5

Response message format{ "status": true, "data": { "k1": "value1", "k2": "value2" }, "message": "Zero imagination for funny messages"}

{ "status": false, "errors": { "e1": "Nope, it can't be done", "e2": "That can't be done either" }, "error_code": 12345, "message": "Zero imagination for funny messages"}

Page 13: Bootstrat REST APIs with Laravel 5

Controller hierarchy

Page 14: Bootstrat REST APIs with Laravel 5

app/Http/Controllers/ApiController.php<?phpnamespace App\Http\Controllers;

use Illuminate\Http\Request;use Illuminate\Routing\ResponseFactory;use Illuminate\Auth\Guard;use App\User;

class ApiController extends Controller{

public $response; public $request; public $auth;

public function __construct(ResponseFactory $response, Request $request, Guard $auth) { $this->response = $response; $this->request = $request; $this->auth = $auth; $this->currentUser = $this->auth->user(); }}

Page 15: Bootstrat REST APIs with Laravel 5

app/Http/Controllers/ApiController.php

public function respond($data, $response_message, $status_code = 200) {

//If this is an internal request we only return the data if ($this->request->input('no-json')) return $data;

$message['status'] = true;

if (isset($data)) $message['data'] = $data;

if (isset($message)) $message['message'] = $response_message;

if (isset($error_code)) $message['error_code'] = $error_code;

return $this->response->json($message, $status_code);

}

Page 16: Bootstrat REST APIs with Laravel 5

app/Http/Controllers/ExamplesController.php<?php namespace App\Http\Controllers;

class ExamplesController extends ApiController {

/** * Send back response with data * * @return Response */ public function example1() { $sample_data_array = ['k1'=>'value1', 'k2'=>'value2']; return $this->respond($sample_data_array, 'Message'); }}

Page 17: Bootstrat REST APIs with Laravel 5
Page 18: Bootstrat REST APIs with Laravel 5

app/Http/Controllers/ApiController.php

public function respondWithError($errors, $error_code, $status_code = 400) { if (is_string($errors)) $errors = [$errors];

$message = [ 'status' => false, 'errors' => $errors, 'error_code' => $error_code ];

return $this->response->json($message, $status_code); }}

public function example3() { $error = "Can't be done";

return $this->respondWithError($error, 123, 500); }

app/Http/Controllers/ExamplesController.php

Page 19: Bootstrat REST APIs with Laravel 5
Page 20: Bootstrat REST APIs with Laravel 5

app/Http/Controllers/ApiController.php } /** * @param array $errors * @param int $status_code * @return Response */ public function respondWithValidationErrors($errors, $status_code = 400) {

$message = [ 'status' => false, 'message' => "Please double check your form", 'validation_errors' => [$errors] ];

return $this->response->json($message, $status_code); }}

Page 21: Bootstrat REST APIs with Laravel 5

app/Http/Controllers/ApiController.php public function respondCreated( $message = 'Resource created') { return $this->respond($message, 201); }

public function respondUnauthorized( $error_code, $message = 'You are not authorized for this') { return $this->respondWithError($message, $error_code, 401); }

public function respondNotFound( $error_code, $message = 'Resource not found') { return $this->respondWithError($message, $error_code, 404); }

public function respondInternalError( $error_code, $message = 'Internal error') { return $this->respondWithError($message, $error_code, 500); }

public function respondOk( $message = 'Done') { return $this->respond(null, $message, 200); }

Page 22: Bootstrat REST APIs with Laravel 5

Always use fake data for testing

https://github.com/fzaninotto/Faker

Page 23: Bootstrat REST APIs with Laravel 5

<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder {

public function run() { Eloquent::unguard(); $faker = Faker\Factory::create();

for($i=1; $i < 20; $i++ ){ $data = [ 'name' => $faker->name, 'email' => $faker->email, 'password' => bcrypt('demodemo') ];

\App\User::create($data); }

}

}

database/seeds/UsersTableSeeder.php

Page 24: Bootstrat REST APIs with Laravel 5

Setting up the repositories

Page 25: Bootstrat REST APIs with Laravel 5

app/Providers/RepoBindingServiceProvider.php<?phpnamespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RepoBindingServiceProvider extends ServiceProvider { public function register() { $app = $this->app;

$app->bind('\App\Repositories\Contracts\UsersInterface', function() { $repository = new \App\Repositories\UsersRepository(new \App\User); return $repository; });

}}

* Register the service provider in the list of autoloaded service providers in config/app.php

Page 26: Bootstrat REST APIs with Laravel 5

app/Repositories/BaseRepository.php<?phpnamespace App\Repositories;

use Illuminate\Database\Eloquent\Model;

class BaseRepository { public function __construct(Model $model) { $this->model = $model; }

public function create($data) { return $this->model->create($data); }

public function find($id) { return $this->model->find($id); }

public function delete($id) { return $this->model->destroy($id); }

public function all() { return $this->model->all(); }

Page 27: Bootstrat REST APIs with Laravel 5

app/Repositories/BaseRepository.php public function update($record, $data) { if (is_int($record)){ $this->model->find($record); $id = $record; } else { $this->model = $record; $id = $record->id; } return $this->model->where('id',$id)->update($data); }

public function getById($id, $user_id = null, $with = null) { if (is_array($id)){ $result = $this->model->whereIn('id', $id); }else{ $result = $this->model->where('id', $id); }

if ($user_id) $result->where('user_id', $user_id);

if ($with) $result->with($with);

if (is_array($id)){ return $result->get();

return $result->first(); }

Page 28: Bootstrat REST APIs with Laravel 5

public function example5() { $data = \App::make('\App\Repositories\Contracts\UsersInterface')->getById(3,null,['courses']); return $this->respond($data,"All users"); }

{ "status": true, "data": { "id": 3, "name": "Asia Towne DVM", "email": "[email protected]", "api_token":"543bjk6h3uh34n5j45nlk34j5k43n53j4b5jk34b5jk34", "created_at": "2015-04-14 18:09:48", "updated_at": "2015-04-14 18:09:48", "courses": [ { "id": 3, "name": "Forro", "pivot": { "user_id": 3, "course_id": 3 } }, { "id": 4, "name": "Jiu Jitsu", "pivot": { "user_id": 3, "course_id": 4 } } ] }, "message": "User with the id of 3"}

app/Http/Controllers/ExamplesController.php

Page 29: Bootstrat REST APIs with Laravel 5

Data TransformersYou need them

Page 30: Bootstrat REST APIs with Laravel 5

<?php

namespace App\DataTransformers;

abstract class DataTransformer {

public function transformCollection($items, $method = 'transform') { return array_map([$this, $method], $items); }

public abstract function transform($item);}

app/DataTransformers/DataTransformer.php

<?phpnamespace App\DataTransformers;

class UserTransformer extends DataTransformer{

public function transform($user) { return [ 'id' => $user['id'], 'name' => $user['name'], 'email' => $user['email'], ]; }}

app/DataTransformers/UserTransformer.php

Page 31: Bootstrat REST APIs with Laravel 5

{ "status": true, "data": [ { "id": 20, "name": "Vance Jacobs", "email": "[email protected]" }, { "id": 19, "name": "Chesley Swift", "email": "[email protected]" }, { "id": 18, "name": "Frederick Hilpert", "email": "[email protected]" } ], "message": "Latest 3 users"}

Page 32: Bootstrat REST APIs with Laravel 5

<?phpnamespace App\DataTransformers;

class UserTransformer extends DataTransformer{

public function transform($user) { return [ 'id' => $user['id'], 'name' => $user['name'], 'email' => $user['email'], 'courses' => (isset($user['courses']) && count($user['courses'])) ? array_map([$this,'transformCourses'], $user['courses']) : null, ]; }

public function transformCourses($course){ return [ 'id' => $course['id'], 'name' => $course['name'] ]; }}

What about nested resources?

Page 33: Bootstrat REST APIs with Laravel 5

{ "status": true, "data": [ { "id": 20, "name": "Vance Jacobs", "email": "[email protected]", "courses": null }, { "id": 19, "name": "Chesley Swift", "email": "[email protected]", "courses": [ { "id": 2, "name": "Samba" }, { "id": 3, "name": "Forro" } ] }, { "id": 18, "name": "Frederick Hilpert", "email": "[email protected]", "courses": [

{ "id": 4, "name": "Jiu Jitsu" } ] } ], "message": "Latest 3 users"}

Page 34: Bootstrat REST APIs with Laravel 5

Pretty error messages

Page 35: Bootstrat REST APIs with Laravel 5

public function render($request, Exception $e) { if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException){ $message = [ 'status' => false, 'error_code' => 2234, 'errors' => ["That resource doesn't exist"] ]; return response($message, 404); }

if ($e instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException){ $message = [ 'status' => false, 'error_code' => 1235, 'errors' => ["We don't have that kind of resources"] ]; return response($message, 404); }

if ($e instanceof \Exception){ $message = [ 'status' => false, 'message' => $e->getMessage() ]; return response($message, $e->getCode()); }

return parent::render($request, $e); }

}

app/Exceptions/Handler.php

Page 36: Bootstrat REST APIs with Laravel 5

STATUS 404 Not Found

{ "status": false, "error_code": 1235, "errors": [ "We don't have that kind of resources" ]}

STATUS 404 Not Found

{ "status": false, "error_code": 2234, "errors": [ "That resource doesn't exist" ]}

STATUS 418 I'm a teapot

{ "status": false, "message": "I'm a teapot"}

STATUS 500 Internal Server Error

{ "status": false, "message": "Class 'User' not found"}

Page 37: Bootstrat REST APIs with Laravel 5

Internal dispatcherFor your (probably) most important consumer

Page 38: Bootstrat REST APIs with Laravel 5

<?php

namespace App\Http;

class InternalDispatcher {

public function release( $url, $method = 'GET', $input, $no_json) { // Store the original input of the request $originalInput = \Request::input();

// Create request to the API, adding the no-json parameter, since it's an internal request $request = \Request::create($url, $method);

// Replace the input with the request instance input \Request::replace($input);

// Fetch the response if ($no_json){ $content = \Route::dispatch($request)->getContent(); $result = json_decode($content, 1); }else{ $result = \Route::dispatch($request)->getContent(); }

// Replace the input again with the original request input. \Request::replace($originalInput);

return $result; }

public function withNoInput($url, $method = 'GET', $no_json = true){ $input = ['no-json'=>$no_json]; return $this->release($url, $method = 'GET', $input, $no_json); }}

app/Http/InternalDispatcher.php

Page 39: Bootstrat REST APIs with Laravel 5

STATUS 200 OK{ "status": true, "data": { "latest_users": [ { "id": 20, "name": "Vance Jacobs", "email": "[email protected]", "courses": [] }, { "id": 18, "name": "Frederick Hilpert", "email": "[email protected]", "courses": [] } ], "youngest_user": { "id": 3, "name": "Asia Towne DVM", "email": "[email protected]", "courses": [ { "id": 3, "name": "Forro" }, { "id": 4, "name": "Jiu Jitsu" } ] } }, "message": "Good data"}

Page 40: Bootstrat REST APIs with Laravel 5

Thank you for listening!

Let the discussion begin :)