Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bootstrat REST APIs with Laravel 5

Bootstrat REST APIs with Laravel 5

Let's extract all the smart code you need every time you're starting a new API, wrap it up nicely and store it in a DRY place :) Be efficient!

Elena Kolevska

April 18, 2015
Tweet

More Decks by Elena Kolevska

Other Decks in Programming

Transcript

  1. 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>
  2. 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', ];
  3. <?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
  4. Response format HTTP/1.1 200 OK Content-Type: application/json Connection: close X-Powered-By:

    PHP/5.6.3 Cache-Control: no-cache Date: Fri, 13 Apr 2015 16:37:57 GMT Transfer-Encoding: Identity {"status":true,"data":{"k1":"value1","k2":"value2"},"message":"Zero imagination for funny messages"}
  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
  6. 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" }
  7. app/Http/Controllers/ApiController.php <?php namespace 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(); } }
  8. 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); }
  9. 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'); } }
  10. 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
  11. 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); } }
  12. 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); }
  13. <?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
  14. app/Providers/RepoBindingServiceProvider.php <?php namespace 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
  15. app/Repositories/BaseRepository.php <?php namespace 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(); }
  16. 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(); }
  17. 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
  18. <?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 <?php namespace App\DataTransformers; class UserTransformer extends DataTransformer{ public function transform($user) { return [ 'id' => $user['id'], 'name' => $user['name'], 'email' => $user['email'], ]; } } app/DataTransformers/UserTransformer.php
  19. { "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" }
  20. <?php namespace 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?
  21. { "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" }
  22. 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
  23. 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" }
  24. <?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
  25. 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" }