Skip to content

Commit 00b0f78

Browse files
committed
Add custom API controllers
1 parent 707cd2e commit 00b0f78

File tree

3 files changed

+382
-0
lines changed

3 files changed

+382
-0
lines changed

src/README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,126 @@ Batteries included ready to use development environment for Laravel.
4646
- [browser-sync](https://github.com/Browsersync/browser-sync): Keep multiple browsers & devices in sync when building apps.
4747
- First, you must run `php artisan serve` to start the server. Then run `npm run watch` to watch for changes. Browsersync listens to laravel app 8000 port and exposes it to your browser at `localhost:3000` and Browsersync UI `localhost:3001` ports.
4848

49+
## Quick API Layer
50+
51+
Generally, we are using Laravel's [resource](https://laravel.com/docs/9.x/eloquent-resources) to create RESTful API. If somethings is going to be more complex than that, I am using respotory pattern and my custom controller to handle it. [ApiController.php](./app/Http/Controllers/Api/ApiController.php) is the API controller and [ApiResourceController.php](./app/Http/Controllers/Api/ApiResourceController.php) is the custom resource controller. So, you can create API's with the same code. When you need more custom code you can add it.
52+
53+
### Example
54+
55+
Add resource router in your `api.php` file.
56+
57+
```php
58+
Route::apiResources([
59+
'tag.tags' => Tag\TagController::class,
60+
]);
61+
```
62+
63+
Create dummy repository in your `app/Repositories/Tag/TagRepository.php` file. (Assumed model is already created.)
64+
65+
<details>
66+
<summary>Click to see repository codes</summary>
67+
68+
```php
69+
<?php
70+
71+
namespace App\Repositories\Tag;
72+
73+
use App\Models\Tag\Tag;
74+
use App\Repositories\BaseRepository;
75+
76+
class TagRepository extends BaseRepository
77+
{
78+
protected array $fieldSearchable = [];
79+
80+
public function getFieldsSearchable(): array
81+
{
82+
return $this->fieldSearchable;
83+
}
84+
85+
public function model(): string
86+
{
87+
return Tag::class;
88+
}
89+
}
90+
```
91+
</details>
92+
93+
Create your controller in your `app/Http/Controllers/Api/TagController.php` file.
94+
95+
<details>
96+
<summary>Click to see controller codes</summary>
97+
98+
We are added a little bit complex logic to handle flexible API's. Also, if you want to add custom permission validator and auto activity logger you must check these controllers. There are some mistery comment blocks.
99+
100+
```php
101+
<?php
102+
103+
namespace App\Http\Controllers\Api\Tag;
104+
105+
use App\Http\Controllers\Api\ApiResourceController;
106+
use App\Http\Requests\Api\Tag\CreateTagRequest;
107+
use App\Models\Tag\Tag;
108+
use App\Repositories\Tag\TagRepository;
109+
use Illuminate\Http\JsonResponse;
110+
use Illuminate\Http\Request;
111+
112+
class TagController extends ApiResourceController
113+
{
114+
public function __construct(TagRepository $baseRepo)
115+
{
116+
parent::__construct($baseRepo);
117+
118+
$this->sectionName = 'Tag';
119+
120+
// required spatie/laravel-permission package
121+
// $this->roleAndPermissions = [
122+
// 'index' => 'view_product',
123+
// 'show' => 'view_product',
124+
// 'store' => 'create_product',
125+
// 'update' => 'update_product',
126+
// 'destroy' => 'delete_product',
127+
// ];
128+
}
129+
130+
public function index(Request $request): JsonResponse
131+
{
132+
$all = $request->query('all');
133+
134+
$itemsQuery = $this->baseRepository->makeModel()->orderBy('created_at', 'desc');
135+
136+
$items = $all ?
137+
$itemsQuery->get() :
138+
$itemsQuery->cursorPaginate(20);
139+
140+
$this->setResponseMessage('listed');
141+
142+
return $this->sendResponse($items, $this->responseMessage);
143+
}
144+
145+
public function show(Tag $tag): JsonResponse
146+
{
147+
return $this->showResource($tag);
148+
}
149+
150+
public function store(CreateTagRequest $request): JsonResponse
151+
{
152+
return $this->storeResource($request);
153+
}
154+
155+
public function update(CreateTagRequest $request, Tag $tag): JsonResponse
156+
{
157+
return $this->updateResource($request, $tag);
158+
}
159+
160+
public function destroy(Tag $tag): JsonResponse
161+
{
162+
return $this->destroyResource($tag);
163+
}
164+
}
165+
166+
```
167+
</details>
168+
49169
## Artisan Commands
50170

51171
<details>

src/app/Http/Controllers/Api/ApiController.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,112 @@
55
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
66
use Illuminate\Foundation\Bus\DispatchesJobs;
77
use Illuminate\Foundation\Validation\ValidatesRequests;
8+
use Illuminate\Http\JsonResponse;
89
use Illuminate\Routing\Controller as BaseController;
10+
use Illuminate\Support\Facades\Auth;
11+
use Illuminate\Support\Facades\Response;
12+
use Illuminate\Support\Str;
913

1014
class ApiController extends BaseController
1115
{
1216
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
17+
18+
public string $sectionName = 'none';
19+
20+
public array $roleAndPermissions = [];
21+
22+
protected string $runningMethod = '';
23+
24+
public function __construct()
25+
{
26+
$this->runningMethod = request()->route()->getActionMethod();
27+
28+
// inline permission middleware
29+
// required spatie/laravel-permission package
30+
31+
// if ($this->roleAndPermissions && count($this->roleAndPermissions)) {
32+
// $this->middleware(function ($request, $next) {
33+
// if (! $this->checkPermissions()) {
34+
// return response()->json(['message' => __('permission.unauthorized_access')], 403);
35+
// }
36+
37+
// return $next($request);
38+
// });
39+
// }
40+
}
41+
42+
/**
43+
* Check permissions of authenticated user
44+
*
45+
* @return bool
46+
*/
47+
private function checkPermissions(): bool
48+
{
49+
if (isset($this->roleAndPermissions[$this->runningMethod])) {
50+
$permission = $this->roleAndPermissions[$this->runningMethod];
51+
$hasPermission = Auth::user()->hasPermissionTo($permission, 'api');
52+
53+
if (!$hasPermission) {
54+
return false;
55+
}
56+
}
57+
58+
return true;
59+
}
60+
61+
public function getUserRoles(): array
62+
{
63+
return Auth::user()->roles()->get()->pluck('name')->toArray();
64+
}
65+
66+
/**
67+
* Send a success response with a message and data
68+
*
69+
* @param $data
70+
* @param $message
71+
* @return JsonResponse
72+
*/
73+
public function sendResponse($data, $message): JsonResponse
74+
{
75+
return Response::json([
76+
'success' => true,
77+
'data' => $data,
78+
'message' => Str::ucfirst(Str::lower($message)) . '.',
79+
]);
80+
}
81+
82+
/**
83+
* Send a success response with a message
84+
*
85+
* @param $message
86+
* @return JsonResponse
87+
*/
88+
public function sendSuccess(string $message): JsonResponse
89+
{
90+
return Response::json([
91+
'success' => true,
92+
'message' => Str::ucfirst(Str::lower($message)) . '.',
93+
], 200);
94+
}
95+
96+
/**
97+
* Send error response with a message
98+
*
99+
* @param $error
100+
* @param int $code
101+
* @return JsonResponse
102+
*/
103+
public function sendError(string $error, int $code = 404): JsonResponse
104+
{
105+
$res = [
106+
'success' => false,
107+
'message' => $error,
108+
];
109+
110+
if (!empty($data)) {
111+
$res['data'] = $data;
112+
}
113+
114+
return Response::json($res, $code);
115+
}
13116
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api;
4+
5+
use App\Http\Requests\Api\ApiFormRequest;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
8+
use Illuminate\Foundation\Bus\DispatchesJobs;
9+
use Illuminate\Foundation\Validation\ValidatesRequests;
10+
use Illuminate\Http\JsonResponse;
11+
12+
class ApiResourceController extends ApiController
13+
{
14+
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
15+
16+
protected mixed $baseRepository;
17+
18+
private ?object $subject = null;
19+
20+
public string $responseMessage = '';
21+
22+
public function __construct(object $baseRepo)
23+
{
24+
$this->baseRepository = $baseRepo;
25+
26+
parent::__construct();
27+
}
28+
29+
/**
30+
* Display of the resource.
31+
*
32+
* @param mixed $source
33+
* @return JsonResponse
34+
*/
35+
public function showResource(mixed $source): JsonResponse
36+
{
37+
$item = $source instanceof Model ? $source : $this->baseRepository->find($source);
38+
39+
if (empty($item)) {
40+
$this->setResponseMessage('not_found');
41+
42+
return $this->sendError($this->responseMessage);
43+
}
44+
45+
$this->subject = $item;
46+
47+
$this->setResponseMessage('viewed');
48+
49+
return $this->sendResponse($item, $this->responseMessage);
50+
}
51+
52+
/**
53+
* Create a new resource in storage.
54+
*
55+
* @param ApiFormRequest $request
56+
* @return JsonResponse
57+
*/
58+
public function storeResource(ApiFormRequest $request): JsonResponse
59+
{
60+
$input = $request->validated();
61+
62+
$item = $this->baseRepository->create($input);
63+
64+
$this->subject = $item;
65+
66+
$this->setResponseMessage('created');
67+
68+
return $this->sendResponse($item, $this->responseMessage);
69+
}
70+
71+
/**
72+
* Update given resource in storage.
73+
*
74+
* @param ApiFormRequest $request
75+
* @param mixed $source
76+
* @return JsonResponse
77+
*/
78+
public function updateResource(ApiFormRequest $request, mixed $source): JsonResponse
79+
{
80+
$result = null;
81+
82+
$input = $request->validated();
83+
84+
if ($source instanceof Model) {
85+
$result = $source->update($input);
86+
87+
$this->subject = $source;
88+
} elseif (is_numeric($source)) {
89+
$item = $this->baseRepository->find($source);
90+
91+
if (empty($item)) {
92+
$this->setResponseMessage('not_found');
93+
94+
return $this->sendError($this->responseMessage);
95+
}
96+
97+
$this->subject = $item;
98+
99+
$result = $this->baseRepository->update($input, $source);
100+
}
101+
102+
$this->setResponseMessage($result ? 'updated' : 'could_not_updated');
103+
104+
return $result
105+
? $this->sendSuccess($this->responseMessage)
106+
: $this->sendError($this->responseMessage);
107+
}
108+
109+
/**
110+
* Delete given resource from storage.
111+
*
112+
* @param mixed $source
113+
* @return JsonResponse
114+
*/
115+
public function destroyResource(mixed $source): JsonResponse
116+
{
117+
$item = $source instanceof Model ? $source : $this->baseRepository->find($source);
118+
119+
if (empty($item)) {
120+
$this->setResponseMessage('not_found');
121+
122+
return $this->sendError($this->responseMessage);
123+
}
124+
125+
$this->subject = $item;
126+
127+
$result = $item->delete();
128+
129+
$this->setResponseMessage($result ? 'deleted' : 'could_not_deleted');
130+
131+
return $result
132+
? $this->sendSuccess($this->responseMessage)
133+
: $this->sendError($this->responseMessage);
134+
}
135+
136+
public function setResponseMessage(string $transKey, string $appendMessage = null): void
137+
{
138+
$this->responseMessage = "{$this->sectionName} " . __("crud.{$transKey}");
139+
140+
if ($appendMessage) {
141+
$this->responseMessage .= " {$appendMessage}";
142+
}
143+
}
144+
145+
// public function __destruct()
146+
// {
147+
// // required spatie/laravel-activitylog
148+
//
149+
// if (method_exists($this, $this->runningMethod)) {
150+
// activity()
151+
// ->useLog('API: user activity')
152+
// ->performedOn($this->subject ?? $this->baseRepository->makeModel())
153+
// ->causedBy(auth()->user())
154+
// ->event($this->runningMethod)
155+
// ->withProperties(request()->all() ?? null)
156+
// ->log($this->responseMessage);
157+
// }
158+
// }
159+
}

0 commit comments

Comments
 (0)