diff --git a/bootstrap/middlewares.php b/bootstrap/middlewares.php index 20ca07b..90fd85d 100644 --- a/bootstrap/middlewares.php +++ b/bootstrap/middlewares.php @@ -3,17 +3,18 @@ declare(strict_types=1); use Illuminate\Foundation\Configuration\Middleware; +use Presentation\Api\V1\Http\Middleware\TransformApiV1Response; return function (Middleware $middleware): void { $middleware->web(append: [ 'throttle:web', ]); - $middleware->api(append: [ - 'throttle:api.v1', - ]); + $middleware->api(append: []); - $middleware->alias([ + $middleware->group('api.v1', [ + 'throttle:api.v1', + TransformApiV1Response::class, ]); $middleware->preventRequestsDuringMaintenance(except: [ diff --git a/routes/api.php b/routes/api.php index f2cf552..66cfe54 100644 --- a/routes/api.php +++ b/routes/api.php @@ -19,6 +19,9 @@ Route::get( Route::get('/ping', fn () => response()->json('pong'))->name('api.ping'); // * V1 API -Route::prefix('v1')->name('api.v1.')->group(function () { - require __DIR__ . '/apiv1.php'; -}); +Route::prefix('v1') + ->name('api.v1.') + ->middleware('api.v1') + ->group(function (): void { + require __DIR__ . '/apiv1.php'; + }); diff --git a/src/Infra/Usecase/LaravelExecutor.php b/src/Infra/Usecase/LaravelExecutor.php index 4c06696..78625bf 100644 --- a/src/Infra/Usecase/LaravelExecutor.php +++ b/src/Infra/Usecase/LaravelExecutor.php @@ -11,7 +11,7 @@ use Shared\DDD\Usecase\Input; use Shared\DDD\Usecase\Presenter; use Shared\DDD\Usecase\Usecase; -/** @implements UsecaseExecutor */ +/** @implements UsecaseExecutor */ final readonly class LaravelExecutor implements UsecaseExecutor { public function __construct( @@ -21,8 +21,11 @@ final readonly class LaravelExecutor implements UsecaseExecutor } /** - * @param Usecase $usecase - * @param Presenter<\Shared\DDD\Usecase\Output,object> $presenter + * @template TView of object + * + * @param Usecase $usecase + * @param Presenter<\Shared\DDD\Usecase\Output, TView> $presenter + * @return TView */ #[\Override] public function execute(Usecase $usecase, Input $input, Presenter $presenter): object @@ -36,7 +39,7 @@ final readonly class LaravelExecutor implements UsecaseExecutor $this->logger->debug('Usecase execution started', $context); try { - /** @var object $result */ + /** @var TView $result */ $result = $this->database->transaction(function () use ($usecase, $input, $presenter): object { $output = $usecase->execute($input); diff --git a/src/Presentation/Api/V1/Contracts/ApiResponse.php b/src/Presentation/Api/V1/Contracts/ApiResponse.php index 352e467..62f77c0 100644 --- a/src/Presentation/Api/V1/Contracts/ApiResponse.php +++ b/src/Presentation/Api/V1/Contracts/ApiResponse.php @@ -4,9 +4,10 @@ declare(strict_types=1); namespace Presentation\Api\V1\Contracts; +use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\JsonResponse; -interface ApiResponse +interface ApiResponse extends Responsable { public function present(): JsonResponse; } diff --git a/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeeShowController.php b/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeeShowController.php index 42ae0fa..1a3066e 100644 --- a/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeeShowController.php +++ b/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeeShowController.php @@ -6,7 +6,7 @@ namespace Presentation\Api\V1\Http\Controllers\Employees; use Application\Usecase\Employees\Find\FindEmployee; use Application\Usecase\Employees\Find\Input; -use Illuminate\Http\JsonResponse; +use Presentation\Api\V1\Contracts\ApiResponse; use Presentation\Api\V1\Http\Requests\Employees\EmployeeShowRequest; use Presentation\Api\V1\Http\Responses\Employees\EmployeeShowResponse; use Presentation\Api\V1\Presenters\Employees\EmployeeShowPresenter; @@ -23,17 +23,15 @@ final readonly class EmployeeShowController ) { } - public function __invoke(EmployeeShowRequest $request): JsonResponse + public function __invoke(EmployeeShowRequest $request): ApiResponse { - - /** @var \Presentation\Api\V1\Presenters\Employees\EmployeeShowView $response */ $response = UsecaseBuilder::using($this->executor) ->do($this->usecase) ->from($this->toInput($request)) ->with($this->presenter) ->run(); - return (new EmployeeShowResponse($response))->present(); + return new EmployeeShowResponse($response); } private function toInput(EmployeeShowRequest $request): Input diff --git a/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php b/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php index ea25050..d594fdc 100644 --- a/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php +++ b/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php @@ -6,7 +6,7 @@ namespace Presentation\Api\V1\Http\Controllers\Employees; use Application\Usecase\Employees\FindAll\FindAllEmployees; use Application\Usecase\Employees\FindAll\Input; -use Illuminate\Http\JsonResponse; +use Presentation\Api\V1\Contracts\ApiResponse; use Presentation\Api\V1\Http\Requests\Employees\EmployeesIndexRequest; use Presentation\Api\V1\Http\Responses\Employees\EmployeesIndexResponse; use Presentation\Api\V1\Presenters\Employees\EmployeesIndexPresenter; @@ -23,21 +23,25 @@ final readonly class EmployeesIndexController ) { } - public function __invoke(EmployeesIndexRequest $request): JsonResponse + public function __invoke(EmployeesIndexRequest $request): ApiResponse { - $input = new Input( - pagination: $request->pagination(), - sorter: $request->sorter(), - filter: $request->filter(), - ); + $input = $this->inputFrom($request); - /** @var \Presentation\Api\V1\Presenters\Employees\EmployeesIndexView $response */ $response = UsecaseBuilder::using($this->executor) ->do($this->usecase) ->from($input) ->with($this->presenter) ->run(); - return (new EmployeesIndexResponse($response, $request->toLeagueUri()))->present(); + return new EmployeesIndexResponse($response, $request->toLeagueUri()); + } + + private function inputFrom(EmployeesIndexRequest $request): Input + { + return new Input( + pagination: $request->pagination(), + sorter: $request->sorter(), + filter: $request->filter(), + ); } } diff --git a/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsIndexController.php b/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsIndexController.php index 82ffac6..25146a6 100644 --- a/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsIndexController.php +++ b/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsIndexController.php @@ -6,7 +6,7 @@ namespace Presentation\Api\V1\Http\Controllers\IncoSheets; use Application\Usecase\IncoSheets\FindAll\FindAllIncoSheets; use Application\Usecase\IncoSheets\FindAll\Input; -use Illuminate\Http\JsonResponse; +use Presentation\Api\V1\Contracts\ApiResponse; use Presentation\Api\V1\Http\Requests\IncoSheets\IncoSheetsIndexRequest; use Presentation\Api\V1\Http\Responses\IncoSheets\IncoSheetsIndexResponse; use Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsIndexPresenter; @@ -23,7 +23,7 @@ final readonly class IncoSheetsIndexController ) { } - public function __invoke(IncoSheetsIndexRequest $request): JsonResponse + public function __invoke(IncoSheetsIndexRequest $request): ApiResponse { $input = new Input( pagination: $request->pagination(), @@ -31,13 +31,12 @@ final readonly class IncoSheetsIndexController filter: $request->filter(), ); - /** @var \Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsIndexView $response */ $response = UsecaseBuilder::using($this->executor) ->do($this->usecase) ->from($input) ->with($this->presenter) ->run(); - return (new IncoSheetsIndexResponse($response, $request->toLeagueUri(), $request->isShort()))->present(); + return new IncoSheetsIndexResponse($response, $request->toLeagueUri()); } } diff --git a/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsShowController.php b/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsShowController.php index 0e08ad5..ca9c3f7 100644 --- a/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsShowController.php +++ b/src/Presentation/Api/V1/Http/Controllers/IncoSheets/IncoSheetsShowController.php @@ -6,7 +6,7 @@ namespace Presentation\Api\V1\Http\Controllers\IncoSheets; use Application\Usecase\IncoSheets\Find\FindIncoSheet; use Application\Usecase\IncoSheets\Find\Input; -use Illuminate\Http\JsonResponse; +use Presentation\Api\V1\Contracts\ApiResponse; use Presentation\Api\V1\Http\Requests\IncoSheets\IncoSheetsShowRequest; use Presentation\Api\V1\Http\Responses\IncoSheets\IncoSheetsShowResponse; use Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsShowPresenter; @@ -23,19 +23,18 @@ final readonly class IncoSheetsShowController ) { } - public function __invoke(IncoSheetsShowRequest $request): JsonResponse + public function __invoke(IncoSheetsShowRequest $request): ApiResponse { $input = new Input( id: $request->incoId(), ); - /** @var \Presentation\Api\V1\Presenters\IncoSheets\IncoSheetsShowView $response */ $response = UsecaseBuilder::using($this->executor) ->do($this->usecase) ->from($input) ->with($this->presenter) ->run(); - return (new IncoSheetsShowResponse($response))->present(); + return new IncoSheetsShowResponse($response); } } diff --git a/src/Presentation/Api/V1/Http/Middleware/TransformApiV1Response.php b/src/Presentation/Api/V1/Http/Middleware/TransformApiV1Response.php new file mode 100644 index 0000000..c27275c --- /dev/null +++ b/src/Presentation/Api/V1/Http/Middleware/TransformApiV1Response.php @@ -0,0 +1,25 @@ +present(); + } + + /** @var Response $response */ + return $response; + } +} diff --git a/src/Presentation/Api/V1/Http/Responses/Employees/EmployeeShowResponse.php b/src/Presentation/Api/V1/Http/Responses/Employees/EmployeeShowResponse.php index e5296ec..4c0cf48 100644 --- a/src/Presentation/Api/V1/Http/Responses/Employees/EmployeeShowResponse.php +++ b/src/Presentation/Api/V1/Http/Responses/Employees/EmployeeShowResponse.php @@ -16,6 +16,12 @@ final readonly class EmployeeShowResponse implements ApiResponse ) { } + #[\Override] + public function toResponse($request): JsonResponse + { + return $this->present(); + } + #[\Override] public function present(): JsonResponse { diff --git a/src/Presentation/Api/V1/Http/Responses/Employees/EmployeesIndexResponse.php b/src/Presentation/Api/V1/Http/Responses/Employees/EmployeesIndexResponse.php index a530e78..1dcae08 100644 --- a/src/Presentation/Api/V1/Http/Responses/Employees/EmployeesIndexResponse.php +++ b/src/Presentation/Api/V1/Http/Responses/Employees/EmployeesIndexResponse.php @@ -19,6 +19,12 @@ final readonly class EmployeesIndexResponse implements ApiResponse ) { } + #[\Override] + public function toResponse($request): JsonResponse + { + return $this->present(); + } + #[\Override] public function present(): JsonResponse { diff --git a/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsIndexResponse.php b/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsIndexResponse.php index 4b86d70..61c4b6d 100644 --- a/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsIndexResponse.php +++ b/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsIndexResponse.php @@ -18,10 +18,15 @@ final readonly class IncoSheetsIndexResponse implements ApiResponse public function __construct( private IncoSheetsIndexView $view, private Http $baseUri, - private bool $isShort = false, ) { } + #[\Override] + public function toResponse($request): JsonResponse + { + return $this->present(); + } + #[\Override] public function present(): JsonResponse { @@ -36,31 +41,15 @@ final readonly class IncoSheetsIndexResponse implements ApiResponse $prevUrl = $page > 1 ? (string) $this->baseUri->withQuery(http_build_query(['page' => $page - 1, 'pageSize' => $pageSize])) : null; $nextUrl = $page < $totalPages ? (string) $this->baseUri->withQuery(http_build_query(['page' => $page + 1, 'pageSize' => $pageSize])) : null; - $isShort = $this->isShort; - - $incoSheets = array_map( - static function (PaginatedIncoSheet | PaginatedShortIncoSheet $sheet) use ($isShort): array { - if ($isShort || $sheet instanceof PaginatedShortIncoSheet) { - $shortSheet = $sheet instanceof PaginatedShortIncoSheet - ? $sheet - : new PaginatedShortIncoSheet( - id: $sheet->id, - name: $sheet->name, - label: $sheet->label, - createdAt: $sheet->createdAt, - updatedAt: $sheet->updatedAt, - ); - - return get_object_vars(IncoShortSheetDto::fromPaginatedShortIncoSheet($shortSheet)); - } - - return get_object_vars(IncoSheetDto::fromPaginatedIncoSheet($sheet)); - }, + $items = array_map( + fn (PaginatedIncoSheet|PaginatedShortIncoSheet $sheet): array => $sheet instanceof PaginatedShortIncoSheet + ? get_object_vars(IncoShortSheetDto::fromPaginatedShortIncoSheet($sheet)) + : get_object_vars(IncoSheetDto::fromPaginatedIncoSheet($sheet)), $this->view->incoSheets ); return response()->json([ - 'incoSheets' => $incoSheets, + 'incoSheets' => $items, 'pagination' => [ 'page' => $page, 'pageSize' => $pageSize, diff --git a/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsShowResponse.php b/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsShowResponse.php index 9615324..113a560 100644 --- a/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsShowResponse.php +++ b/src/Presentation/Api/V1/Http/Responses/IncoSheets/IncoSheetsShowResponse.php @@ -16,6 +16,12 @@ final readonly class IncoSheetsShowResponse implements ApiResponse ) { } + #[\Override] + public function toResponse($request): JsonResponse + { + return $this->present(); + } + #[\Override] public function present(): JsonResponse { diff --git a/src/Shared/DDD/Usecase/Builder/BuilderStepBuild.php b/src/Shared/DDD/Usecase/Builder/BuilderStepBuild.php index 4cee856..70a60d4 100644 --- a/src/Shared/DDD/Usecase/Builder/BuilderStepBuild.php +++ b/src/Shared/DDD/Usecase/Builder/BuilderStepBuild.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace Shared\DDD\Usecase\Builder; +/** @template-covariant TView of object */ interface BuilderStepBuild { + /** @return TView */ public function run(): object; } diff --git a/src/Shared/DDD/Usecase/Builder/BuilderStepPresenter.php b/src/Shared/DDD/Usecase/Builder/BuilderStepPresenter.php index ef8728b..5334c02 100644 --- a/src/Shared/DDD/Usecase/Builder/BuilderStepPresenter.php +++ b/src/Shared/DDD/Usecase/Builder/BuilderStepPresenter.php @@ -9,6 +9,11 @@ use Shared\DDD\Usecase\Presenter; /** @template TOutput of \Shared\DDD\Usecase\Output */ interface BuilderStepPresenter { - /** @param Presenter $presenter */ + /** + * @template TView of object + * + * @param Presenter $presenter + * @return BuilderStepBuild + */ public function with(Presenter $presenter): BuilderStepBuild; } diff --git a/src/Shared/DDD/Usecase/Builder/BuilderStepUsecase.php b/src/Shared/DDD/Usecase/Builder/BuilderStepUsecase.php index ad0cc80..3aa7a60 100644 --- a/src/Shared/DDD/Usecase/Builder/BuilderStepUsecase.php +++ b/src/Shared/DDD/Usecase/Builder/BuilderStepUsecase.php @@ -6,11 +6,14 @@ namespace Shared\DDD\Usecase\Builder; use Shared\DDD\Usecase\Usecase; -/** @template TOutput of \Shared\DDD\Usecase\Output */ +/** + * @template TInput of \Shared\DDD\Usecase\Input + * @template TOutput of \Shared\DDD\Usecase\Output + */ interface BuilderStepUsecase { /** - * @param Usecase<\Shared\DDD\Usecase\Input,TOutput> $usecase + * @param Usecase $usecase * @return BuilderStepInput */ public function do(Usecase $usecase): BuilderStepInput; diff --git a/src/Shared/DDD/Usecase/Builder/UsecaseBuilder.php b/src/Shared/DDD/Usecase/Builder/UsecaseBuilder.php index 835a013..e2d28ef 100644 --- a/src/Shared/DDD/Usecase/Builder/UsecaseBuilder.php +++ b/src/Shared/DDD/Usecase/Builder/UsecaseBuilder.php @@ -10,8 +10,8 @@ final class UsecaseBuilder * @template TInput of \Shared\DDD\Usecase\Input * @template TOutput of \Shared\DDD\Usecase\Output * - * @param UsecaseExecutor $executor - * @return BuilderStepUsecase + * @param UsecaseExecutor $executor + * @return BuilderStepUsecase */ public static function using(UsecaseExecutor $executor): BuilderStepUsecase { diff --git a/src/Shared/DDD/Usecase/Builder/UsecaseExecutionBuilder.php b/src/Shared/DDD/Usecase/Builder/UsecaseExecutionBuilder.php index fb6ca89..6a3bfbf 100644 --- a/src/Shared/DDD/Usecase/Builder/UsecaseExecutionBuilder.php +++ b/src/Shared/DDD/Usecase/Builder/UsecaseExecutionBuilder.php @@ -12,28 +12,30 @@ use Shared\DDD\Usecase\Usecase; /** * @template TInput of \Shared\DDD\Usecase\Input * @template TOutput of \Shared\DDD\Usecase\Output + * @template TView of object * * @implements BuilderStepInput * @implements BuilderStepPresenter - * @implements BuilderStepUsecase + * @implements BuilderStepUsecase + * @implements BuilderStepBuild */ final class UsecaseExecutionBuilder implements BuilderStepBuild, BuilderStepInput, BuilderStepPresenter, BuilderStepUsecase { - /** @param UsecaseExecutor $executor */ + /** @param UsecaseExecutor $executor */ public function __construct( private readonly UsecaseExecutor $executor, ) { } - /** @var Usecase|null */ + /** @var Usecase|null */ private ?Usecase $usecase = null; private ?Input $input = null; - /** @var Presenter|null */ + /** @var Presenter|null */ private ?Presenter $presenter = null; - /** @param Usecase $usecase */ + /** @param Usecase $usecase */ #[\Override] public function do(Usecase $usecase): BuilderStepInput { @@ -54,7 +56,12 @@ final class UsecaseExecutionBuilder implements BuilderStepBuild, BuilderStepInpu return $this; } - /** @param Presenter $presenter */ + /** + * @template TNextView of object + * + * @param Presenter $presenter + * @return BuilderStepBuild + */ #[\Override] public function with(Presenter $presenter): BuilderStepBuild { @@ -69,6 +76,7 @@ final class UsecaseExecutionBuilder implements BuilderStepBuild, BuilderStepInpu return $this; } + /** @return TView */ #[\Override] public function run(): object { @@ -76,6 +84,7 @@ final class UsecaseExecutionBuilder implements BuilderStepBuild, BuilderStepInpu throw new LogicException('Usecase, input and presenter are all required.'); } + /** @var TView */ return $this->executor->execute( usecase: $this->usecase, input: $this->input, diff --git a/src/Shared/DDD/Usecase/Builder/UsecaseExecutor.php b/src/Shared/DDD/Usecase/Builder/UsecaseExecutor.php index 7d797b1..2e5b246 100644 --- a/src/Shared/DDD/Usecase/Builder/UsecaseExecutor.php +++ b/src/Shared/DDD/Usecase/Builder/UsecaseExecutor.php @@ -15,8 +15,11 @@ use Shared\DDD\Usecase\Usecase; interface UsecaseExecutor { /** - * @param Usecase $usecase - * @param Presenter $presenter + * @template TView of object + * + * @param Usecase $usecase + * @param Presenter $presenter + * @return TView */ public function execute(Usecase $usecase, Input $input, Presenter $presenter): object; }