|
1 | | -## Installation |
| 1 | +# Laravel API tool kit and best API practices |
| 2 | + |
| 3 | +Laravel api tool kit is a set of tools that will help you to build a fast and well-organized API using laravel best practices. |
| 4 | + |
| 5 | +## Contents |
| 6 | + |
| 7 | +[Installation](#installation) |
| 8 | + |
| 9 | +[Api Generator module](#api-generator-module) |
| 10 | + |
| 11 | +[Api response](#api-response) |
| 12 | + |
| 13 | +[Filters](#filters) |
| 14 | + |
| 15 | +[comment]: <> ([Out of the box permissions](#out-of-the-box-permissions)) |
| 16 | + |
| 17 | +[Actions](#actions) |
| 18 | + |
| 19 | +[Media Helper](#media-helper) |
| 20 | + |
| 21 | +[Enum](#enum) |
| 22 | + |
| 23 | +[General tips](#general-tips) |
| 24 | + |
| 25 | + |
| 26 | +### **Installation** |
| 27 | + |
| 28 | +``` |
| 29 | +composer require essa/api-tool-kit |
2 | 30 | ``` |
3 | | -composer require essa/api_generator:dev-master |
| 31 | +to publish config |
| 32 | +``` |
| 33 | +php artisan vendor:publish --provider="essa\APIToolKit\APIToolKitServiceProvider" --tag="config" |
4 | 34 | ``` |
5 | 35 |
|
| 36 | +use exception handler to standardize the error response |
6 | 37 |
|
| 38 | +```php |
7 | 39 |
|
8 | | -### Exceptions: |
| 40 | +namespace App\Exceptions; |
9 | 41 |
|
10 | | -`App\Providers\Handler.php`: |
| 42 | +use essa\APIToolKit\Exceptions\Handler; |
| 43 | +use essa\APIToolKit\Exceptions\Handler as APIHandler; |
11 | 44 |
|
12 | | -```php |
| 45 | +class Handler extends ExceptionHandler |
| 46 | +{ |
| 47 | +} |
13 | 48 |
|
| 49 | +``` |
14 | 50 |
|
| 51 | +use API Response Trait in Controller |
15 | 52 |
|
16 | | -use Throwable; |
17 | | -use essa\APIGenerator\Http\ApiResponse; |
18 | | -use Illuminate\Database\QueryException; |
19 | | -use Illuminate\Validation\ValidationException; |
20 | | -use Illuminate\Auth\Access\AuthorizationException; |
21 | | -use Illuminate\Database\Eloquent\ModelNotFoundException; |
| 53 | +`App\Http\Controllers\Controller.php`: |
22 | 54 |
|
23 | | -class Handler extends ExceptionHandler |
| 55 | +```php |
| 56 | +use essa\APIToolKit\Http\ApiResponse; |
| 57 | + |
| 58 | +class Controller extends BaseController |
24 | 59 | { |
25 | 60 | use ApiResponse; |
26 | | - |
27 | | -/** |
28 | | -* Render an exception into an HTTP response. |
29 | | -* |
30 | | -* @param \Illuminate\Http\Request $request |
31 | | -* @param \Throwable $exception |
32 | | -* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response |
33 | | -* @throws \Throwable |
34 | | -*/ |
35 | | -public function render($request, Throwable $exception) |
36 | | - { |
37 | | - if ($exception instanceof ValidationException) { |
38 | | - return $this->ResponseValidationError($exception); |
39 | | - } |
| 61 | +} |
| 62 | +``` |
| 63 | +check : [API response](#api-response) |
| 64 | + |
| 65 | +[🔝 Back to contents](#contents) |
| 66 | + |
| 67 | +### **API Generator module** |
40 | 68 |
|
41 | | - if ($exception instanceof QueryException || $exception instanceof ModelNotFoundException) { |
42 | | - return $this->responseNotFound("Record not found!", $exception->getMessage()); |
43 | | - } |
| 69 | +#### Usage : |
44 | 70 |
|
45 | | - if ($exception instanceof AuthorizationException) { |
46 | | - return $this->responseUnauthorized(); |
47 | | - } |
| 71 | +``` |
| 72 | +php artisan api:generate Car |
| 73 | +``` |
| 74 | + |
| 75 | +if you write the command it will ask you if you want to generate all default options which you can select in config/api-tool-kit file if you decide not to change the default option and decide to select all option it will generate the following files : |
| 76 | +``` |
| 77 | +-app/Models/Car.php |
| 78 | +-app/Http/Controllers/API/CarController.php |
| 79 | +-app/Http/Resources/CarResource.php |
| 80 | +-app/Http/Requests/Car/CreateCarRequest.php |
| 81 | +-app/Http/Requests/Car/UpdateCarRequest.php |
| 82 | +-app/Filters/CarFilters.php |
| 83 | +-database/seeds/CarSeeder.php |
| 84 | +-database/factories/CarFactory.php |
| 85 | +-Tests/Feature/CarTest.php |
| 86 | +-database/migrations/x_x_x_x_create_cars_table.php |
| 87 | +``` |
| 88 | +and will create api.php in routes file and will add the routes to it |
| 89 | + |
| 90 | +if you choose not to select all options it will ask you which files you want to generate |
| 91 | + |
| 92 | +[🔝 Back to contents](#contents) |
| 93 | + |
| 94 | +### **API Response** |
| 95 | + |
| 96 | +it is used to format your response to standard format and status codes |
| 97 | +for success responses, it will be |
48 | 98 |
|
49 | | - return parent::render($request, $exception); |
| 99 | +```json |
| 100 | +{ |
| 101 | + "message": "your message", |
| 102 | + "data": "your date" |
| 103 | +} |
| 104 | +``` |
| 105 | +for errors responses |
| 106 | + |
| 107 | +```json |
| 108 | +{ |
| 109 | + "errors": [ |
| 110 | + { |
| 111 | + "status": 403, |
| 112 | + "title": "unauthenticated!", |
| 113 | + "detail": "Unauthenticated." |
50 | 114 | } |
| 115 | + ] |
51 | 116 | } |
| 117 | +``` |
| 118 | + |
| 119 | +usage: |
| 120 | +you can use the trait inside the class you want to return the response form |
52 | 121 |
|
| 122 | +and use it like this |
53 | 123 |
|
| 124 | +```php |
| 125 | +$this->responseSuccess('car created successfully' , $car); |
54 | 126 | ``` |
55 | | - |
56 | | -### Controller: |
| 127 | +Available Methods |
| 128 | +```php |
57 | 129 |
|
58 | | -`App\Http\Controllers\Controller.php`: |
| 130 | +responseSuccess($message , $data) // returns a 200 HTTP status code |
| 131 | +responseCreated($message,$data) // returns a 201 HTTP status code |
| 132 | +responseDeleted() // returns empty response with a 204 HTTP status code |
| 133 | +responseNotFound($details,$message) // returns a 404 HTTP status code |
| 134 | +responseBadRequest($details,$message) // returns a 400 HTTP status code |
| 135 | +responseUnAuthorized($details,$message) // returns a 403 HTTP status code |
| 136 | +responseConflictError($details,$message) // returns a 409 HTTP status code |
| 137 | +responseUnprocessable($details,$message) // returns a 422 HTTP status code |
| 138 | +responseUnAuthenticated ($details,$message) // returns a 401 HTTP status code |
| 139 | +responseWithCustomError($title, $details, $status_code) //send custom error |
| 140 | +``` |
| 141 | +[🔝 Back to contents](#contents) |
| 142 | + |
| 143 | +### **Filters** |
| 144 | + |
| 145 | +usage: |
| 146 | + |
| 147 | +to create a filter class: |
| 148 | +``` |
| 149 | +php artisan make:action CarFilters |
| 150 | +``` |
| 151 | +to set default filters to the Car model , in Car model you will add |
| 152 | +```php |
| 153 | +protected $default_filters = CarFilters::class; |
| 154 | +``` |
| 155 | +to use it |
| 156 | + |
| 157 | +```php |
| 158 | +Car::useFilters()->get(); |
| 159 | +``` |
| 160 | +if you want to override the default filters |
59 | 161 |
|
60 | 162 | ```php |
61 | | -use essa\APIGenerator\Http\ApiResponse; |
| 163 | +Car::useFilters(SpecialCaseCarFilters::class)->get(); |
| 164 | +``` |
| 165 | +options in Filter class |
62 | 166 |
|
63 | | -class Controller extends BaseController |
| 167 | +```php |
| 168 | + //to add the attributes to filter by =>> /cars?color=red&model_id=1 |
| 169 | + protected array $allowedFilters = ['color' , 'model_id']; |
| 170 | + //to add the attributes to filter by =>> desc : ?sorts=created_at asc : ?sorts=-created_at |
| 171 | + protected array $allowedSorts = ['created_at']; |
| 172 | + //allowed relationships to be loaded |
| 173 | + protected array $allowedIncludes = ['model']; |
| 174 | + //column that will be included in search =>> ?search=tesla |
| 175 | + protected array $columnSearch = ['name','descriptions']; |
| 176 | + //relation that will be included in search =>> ?search=ahmed |
| 177 | + protected array $relationSearch = [ |
| 178 | + 'user' => ['first_name', 'last_name'] |
| 179 | + ]; |
| 180 | +``` |
| 181 | + |
| 182 | +to create a custom query you will just create a new function in the class and add your query |
| 183 | +example filter by year: |
| 184 | +```php |
| 185 | + public function year($term) |
| 186 | + { |
| 187 | + $this->builder->whereYear('created_At', $term); |
| 188 | + } |
| 189 | +``` |
| 190 | +filter by relationship : |
| 191 | +```php |
| 192 | + public function option($term) |
| 193 | + { |
| 194 | + $this->builder->whereHas('options', fn($query) => $query->where('option_id', $term)); |
| 195 | + } |
| 196 | +``` |
| 197 | + |
| 198 | + |
| 199 | +[🔝 Back to contents](#contents) |
| 200 | + |
| 201 | +### **Actions** |
| 202 | +action is a laravel implementation of command design pattern where can add the business logic in https://en.wikipedia.org/wiki/Command_pattern |
| 203 | + |
| 204 | +usage: |
| 205 | + |
| 206 | +``` |
| 207 | +php artisan make:action CreateCar |
| 208 | +``` |
| 209 | + |
| 210 | +```php |
| 211 | +<?php |
| 212 | +namespace App\Actions; |
| 213 | + |
| 214 | +class CreateCar |
64 | 215 | { |
65 | | - use ApiResponse; |
| 216 | + public function execute(array $data) |
| 217 | + { |
| 218 | + //add business logic to create a car |
| 219 | + } |
66 | 220 | } |
67 | 221 | ``` |
68 | 222 |
|
| 223 | +The best practice to use the action class is to use dependency injection |
| 224 | +you have many options |
| 225 | +1-use laravel app class |
| 226 | +```php |
| 227 | +app(CreateCar::class)->execute($data); |
| 228 | +``` |
| 229 | +2-inject it in class construct |
69 | 230 |
|
| 231 | +```php |
| 232 | + private $create_car_action ; |
| 233 | + public function __construct(CreateCar $create_car_action) |
| 234 | + { |
| 235 | + $this->create_car_action=$create_car_action; |
| 236 | + } |
| 237 | + |
| 238 | + public function doSomething() |
| 239 | + { |
| 240 | + $this->create_car_action->execute($data); |
| 241 | + } |
| 242 | +``` |
| 243 | +3-use it in laravel controller function |
70 | 244 |
|
71 | | -## Usage |
| 245 | +```php |
| 246 | + public function doSomething(CreateCar $create_car_action) |
| 247 | + { |
| 248 | + $create_car_action->execute($data); |
| 249 | + } |
| 250 | +``` |
| 251 | +[🔝 Back to contents](#contents) |
| 252 | + |
| 253 | + |
| 254 | +### **Media Helper** |
| 255 | + |
| 256 | +it is used to upload and delete an image |
| 257 | + |
| 258 | +```php |
| 259 | +// to upload image |
| 260 | +$image_path = MediaHelper::uploadImage($file ,$path); |
| 261 | +//to delete an image |
| 262 | +MediaHelper::deleteImage($path); //to delete image |
| 263 | +``` |
| 264 | +Available Methods |
| 265 | + |
| 266 | +[🔝 Back to contents](#contents) |
| 267 | + |
| 268 | +### **Enum |
| 269 | +bad practice : |
| 270 | +if I have two types of users admin and student instead of hard coding the name of user type every time using it you can simply use the enum class |
| 271 | + |
| 272 | +usage : |
| 273 | +``` |
| 274 | +php artisan make:enum UserTypes |
| 275 | +``` |
| 276 | +it will generate classes like this |
| 277 | +```php |
| 278 | +namespace App\Enums; |
72 | 279 |
|
73 | | -Create component |
| 280 | +class UserTypes extends Enum |
| 281 | +{ |
| 282 | + public const ADMIN = 'admin'; |
| 283 | + public const STUDENT = 'student'; |
| 284 | +} |
| 285 | +``` |
| 286 | +methods: |
| 287 | +```php |
| 288 | +getAll() //get all types |
| 289 | +isValid($value) //to check if this value exist in the enum |
| 290 | +toArray() //to get all enums as key and value |
74 | 291 | ``` |
75 | | -php artisan make:module Admin |
| 292 | + |
| 293 | +[🔝 Back to contents](#contents) |
| 294 | + |
| 295 | +### **General Tips** |
| 296 | + |
| 297 | +### **throw error instead of return json response** |
| 298 | + |
| 299 | +A class and a method should have only one responsibility. |
| 300 | + |
| 301 | +Bad: |
| 302 | + |
| 303 | +```php |
| 304 | +public function index() |
| 305 | +{ |
| 306 | + if (auth()->user()->not_active ) { |
| 307 | + $this->responseUnAuthorized('you cant preform this action''); |
| 308 | + } |
| 309 | +} |
76 | 310 | ``` |
| 311 | +good |
77 | 312 |
|
| 313 | +```php |
| 314 | +public function index() |
| 315 | +{ |
| 316 | + if (auth()->user()->not_active ) { |
| 317 | + throw new AuthorizationException('you cant preform this action''); |
| 318 | + } |
| 319 | +} |
78 | 320 | ``` |
79 | | -php artisan make:module Admin --with-image |
80 | | -``` |
| 321 | + |
| 322 | +[🔝 Back to contents](#contents) |
0 commit comments