Dokumentasi ini memandu Anda melalui pemanfaatan perpustakaan inti untuk menerapkan arsitektur bersih di PHP. Kami akan mengeksplorasi pembuatan permintaan aplikasi khusus dan menggunakan kasus, memberikan perhatian khusus untuk menangani bidang yang hilang dan tidak sah.
Contoh -contoh praktis disediakan menggunakan cuplikan kode untuk memamerkan penggunaan perpustakaan dalam membangun aplikasi PHP yang modular dan bersih.
Pastikan bahwa Anda memiliki yang berikut:
PHP diinstal pada mesin Anda (versi 8.2.0 or higher ).Composer yang diinstal untuk manajemen ketergantungan. Untuk menginstal Perpustakaan Inti, jalankan perintah berikut di Direktori Proyek Anda:
composer require ug-php/clean-architecture-core Permintaan berfungsi sebagai objek input, merangkum data dari pengontrol HTTP Anda. Di pustaka inti, gunakan kelas UrichyCoreRequestRequest sebagai yayasan untuk membuat objek permintaan aplikasi khusus. Tentukan bidang yang diharapkan menggunakan properti requestPossibleFields .
<?php
declare (strict_types= 1 );
use Urichy Core Request Request ;
use Urichy Core Request RequestInterface ;
use Assert Assert ;
final class PatientRecordRequest extends Request
{
protected static array $ requestPossibleFields = [
' patient_name ' => true , // required parameter
' old ' => true , // required parameter
' medical_history ' => [
' allergies ' => false , // optional parameter
' current_medications ' => true , // required nested parameter
' past_surgeries ' => [
' surgery_name ' => true , // required nested parameter
' surgery_date ' => true , // required nested parameter
],
],
];
protected static function applyConstraintsOnRequestFields ( array $ requestData ): void
{
Assert:: that ( $ requestData [ ' patient_name ' ], ' [patient_name] must not be an empty string. ' )-> notEmpty ()-> string ();
Assert:: that ( $ requestData [ ' old ' ], ' [old] must be an integer. ' )-> integer ()-> greaterThan ( 0 );
Assert:: that ( $ requestData [ ' medical_history ' ][ ' current_medications ' ], ' [current_medications] must not be an empty string. ' )-> notEmpty ()-> string ();
Assert:: that ( $ requestData [ ' medical_history ' ][ ' past_surgeries ' ][ ' surgery_name ' ], ' [surgery_name] must not be an empty string. ' )-> notEmpty ()-> string ();
Assert:: that ( $ requestData [ ' medical_history ' ][ ' past_surgeries ' ][ ' surgery_date ' ], ' [surgery_date] must be a valid date. ' )-> date ();
// Optional field constraint
if ( isset ( $ requestData [ ' medical_history ' ][ ' allergies ' ])) {
Assert:: that ( $ requestData [ ' medical_history ' ][ ' allergies ' ], ' [allergies] must be a string. ' )-> string ();
}
}
}Menangani bidang yang tidak sah:
<?php
declare (strict_types= 1 );
try {
PatientRecordRequest:: createFromPayload ([
' patient_name ' => ' Jane Doe ' ,
' old ' => 45 ,
' medical_history ' => [
' current_medications ' => ' aspirin ' ,
' past_surgeries ' => [
' surgery_name ' => ' Appendectomy ' ,
' surgery_date ' => ' 2022-01-01 ' ,
],
' extra_field ' => ' unexpected ' ,
],
]);
} catch ( BadRequestContentException $ exception ) {
// Handle unauthorized fields
dd ( $ exception -> getErrors ()); // ["medical_history.extra_field"]
}Menangani bidang yang hilang:
<?php
declare (strict_types= 1 );
try {
PatientRecordRequest:: createFromPayload ([
' patient_name ' => ' Jane Doe ' ,
' medical_history ' => [
' current_medications ' => ' aspirin ' ,
' past_surgeries ' => [
' surgery_name ' => ' Appendectomy ' ,
],
],
]);
} catch ( BadRequestContentException $ exception ) {
// Handle missing fields
dd ( $ exception -> getErrors ()); // ["old" => "required", "medical_history.past_surgeries.surgery_date" => "required"]
}Saat permintaan berhasil dibuat.
<?php
declare (strict_types= 1 );
$ request = PatientRecordRequest:: createFromPayload ([
' patient_name ' => ' Jane Doe ' ,
' old ' => 45 ,
' medical_history ' => [
' current_medications ' => ' aspirin ' ,
' past_surgeries ' => [
' surgery_name ' => ' Appendectomy ' ,
' surgery_date ' => ' 2022-01-01 ' ,
],
],
]);
dd ( $ request -> getRequestId ()); // 6d326314-f527-483c-80df-7c157acdb95b
dd ([
' patient_name ' => $ request -> get ( ' patient_name ' ),
' current_medications ' => $ request -> get ( ' medical_history.current_medications ' ),
' unknown ' => $ request -> get ( ' unknown ' , ' default_value ' ),
]); // ['patient_name' => 'Jane Doe', 'current_medications' => 'aspirin', 'unknown' => 'default_value']
dd ( $ request -> toArray ());
/*
[
'patient_name' => 'Jane Doe',
'old' => 45,
'medical_history' => [
'current_medications' => 'aspirin',
'past_surgeries' => [
'surgery_name' => 'Appendectomy',
'surgery_date' => '2022-01-01',
],
],
]*/ Presenter menangani logika output dari kasus penggunaan Anda. Perpanjakan UrichyCorePresenterPresenter dan implement UrichyCorePresenterPresenterInterface .
<?php
declare (strict_types= 1 );
use Urichy Core Presenter Presenter ;
use Urichy Core Presenter PresenterInterface ;
use Urichy Core Response ResponseInterface ;
final class ArrayResponsePresenter extends Presenter
{
public function getResponse (): array
{
return $ this -> response -> output ();
}
} Respons merangkum data yang dikembalikan oleh kasus penggunaan. Mereka termasuk informasi status, pesan, dan data yang relevan. Gunakan UrichyCoreResponseResponse untuk membuat respons kasus penggunaan.
<?php
declare (strict_types= 1 );
use Urichy Core Response Response ;
// success response
$ response = Response:: create (
success: true ,
statusCode: StatusCode:: OK -> value ,
message: ' success.response ' ,
data: [
' user_id ' => ' 6d326314-f527-483c-80df-7c157acdb95b ' ,
]
)
// or failed response
$ response = Response:: create (
success: false ,
statusCode: StatusCode:: NOT_FOUND -> value ,
message: ' failed.response ' ,
data: [
' field ' => ' value ' ,
]
)
dd ( $ response -> isSuccess ()); // true or false
dd ( $ response -> getStatusCode ()); // 200 or 404
dd ( $ response -> getMessage ()); // 'success.response' or 'failed.response'
dd ( $ response -> getData ()); // ['field' => 'value'] or ['user_id' => '6d326314-f527-483c-80df-7c157acdb95b']
dd ( $ response -> get ( ' field ' )); // 'value'
dd ( $ response -> get ( ' unknown_field ' )); // null Gunakan kasus merangkum logika bisnis dan mengatur aliran data antara permintaan, entitas, dan presenter. Perpanjang kelas UrichyCoreUsecaseUsecase dan mengimplementasikan UrichyCoreUsecaseUsecaseInterface dengan metode execute .
@see example below
Ketika pengecualian dilemparkan selama pemrosesan, Anda dapat menggunakan beberapa metode untuk menangani data pengecualian.
Bagaimana cara membuat pengecualian?
UrichyCoreExceptionException <?php
declare (strict_types= 1 );
use Urichy Core Exception Exception ;
final class BadRequestContentException extends Exception
{
}
final class UserNotFoundException extends Exception
{
} <?php
declare (strict_types= 1 );
use Urichy Core Exception Exception ;
use Urichy Core Exception BadRequestContentException ;
use Urichy Core Exception UserNotFoundException ;
try {
//...
throw new BadRequestContentException ([
' message ' => ' bad.request.content ' ,
' details ' => [
' email ' => [
' [email] field is required. ' ,
' [email] must be a valid email. ' ,
]
] // array with error contexts
]);
// or
throw new UserNotFoundException ([
' message ' => ' user.not.found ' ,
' details ' => [
' error ' => ' User with [ulrich] username not found. '
] // array with error contexts
]);
} catch ( ExceptionInterface $ exception ) {
// for exception, some method are available
dd ( $ exception -> getErrors ()); // print details
[
' details ' => [
' email ' => [
' [email] field is required. ' ,
' [email] must be a valid email. ' ,
]
],
]
// or
[
' details ' => [
' error ' => ' User with [ulrich] username not found. ' ,
],
]
dd ( $ exception -> getDetails ()); // print error details
[
' email ' => [
' [email] field is required. ' ,
' [email] must be a valid email. ' ,
]
]
// or
[
' error ' => ' User with [ulrich] username not found. ' ,
],
dd ( $ exception -> getMessage ()) // 'error.message'
dd ( $ exception -> getDetailsMessage ()) // 'User with [ulrich] username not found.', only if 'error' key is defined in details.
dd ( $ exception -> getErrorsForLog ()) // print error with more context
[
' message ' => $ this -> getMessage (),
' code ' => $ this -> getCode (),
' errors ' => $ this -> errors ,
' file ' => $ this -> getFile (),
' line ' => $ this -> getLine (),
' previous ' => $ this -> getPrevious (),
' trace_as_array ' => $ this -> getTrace (),
' trace_as_string ' => $ this -> getTraceAsString (),
]
dd ( $ exception -> format ());
[
' status ' => ' success ' or ' error ' ,
' error_code ' => 400 ,
' message ' => ' throw.error ' ,
' details ' => [
' email ' => [
' [email] field is required. ' ,
' [email] must be a valid email. ' ,
],
' lastname ' => [
' [lastname] field is required. ' ,
]
],
]
} ├── src
│ ├── Controller
│ │ └── BookController.php
│ ├── Request
│ │ └── BookRecordRequest.php
│ ├── Presenter
│ │ └── JsonResponsePresenter.php
│ ├── UseCase
│ │ └── RegisterBookUsecase.php
│ └── Response
│ └── Response.php
├── public
│ └── index.php
└── composer.json
public/index.php <?php
declare (strict_types= 1 );
require ' ../vendor/autoload.php ' ;
use App Controller BookController ;
use Symfony Component HttpFoundation Request ;
$ request = Request:: createFromGlobals ();
$ controller = new BookController ();
$ response = $ controller -> registerBook ( $ request );
$ response -> send ();src/Controller/BookController.php <?php
declare (strict_types= 1 );
namespace App Controller ;
use App Request BookRecordRequest ;
use App Presenter JsonResponsePresenter ;
use App UseCase RegisterBookUsecase ;
use Symfony Component HttpFoundation Request as SymfonyRequest ;
use Symfony Component HttpFoundation JsonResponse ;
final class BookController
{
public function registerBook ( SymfonyRequest $ request ): JsonResponse
{
$ bookRequest = BookRecordRequest:: createFromPayload ([
' title ' => $ request -> get ( ' title ' ),
' publication ' => [
' date ' => $ request -> get ( ' published_date ' ),
' publisher ' => $ request -> get ( ' publisher ' ),
],
' isbn ' => $ request -> get ( ' isbn ' ),
]);
// you can also use $request->toArray() (in createFromPayload method) to get request payload if POST request
$ presenter = new JsonResponsePresenter ();
$ useCase = new RegisterBookUsecase ();
$ useCase
-> withRequest ( $ bookRequest )
-> withPresenter ( $ presenter )
-> execute ();
return $ presenter -> getResponse ();
}
}src/Request/BookRecordRequest.php beberlei/assert Validation Library <?php
declare (strict_types= 1 );
namespace App Request ;
use Urichy Core Request Request ;
use Urichy Core Request RequestInterface ;
use Assert Assert ;
// interface is optional. You can directly use the implementation
interface BookRecordRequestInterface extends RequestInterface {}
final class BookRecordRequest extends Request implements BookRecordRequestInterface
{
protected static array $ requestPossibleFields = [
' title ' => true , // required parameters
' publication ' => [
' date ' => true ,
' publisher ' => false , // optional parameters
],
' isbn ' => true ,
];
/**
* @param array<string, mixed> $requestData
* @return void
*/
protected static function applyConstraintsOnRequestFields ( array $ requestData ): void
{
Assert:: that ( $ requestData [ ' title ' ], ' [title] must not be an empty string. ' )-> notEmpty ()-> string ();
Assert:: that ( $ requestData [ ' publication ' ][ ' date ' ], ' [date] must be a valid date. ' )-> date ();
Assert:: that ( $ requestData [ ' isbn ' ], ' [isbn] must not be an empty string. ' )-> notEmpty ()-> string ();
if ( isset ( $ requestData [ ' publication ' ][ ' publisher ' ])) {
Assert:: that ( $ requestData [ ' publication ' ][ ' publisher ' ], ' [publisher] must be a string. ' )-> string ();
}
}
}Symfony Validator <?php
declare (strict_types= 1 );
namespace App Request ;
use Urichy Core Request Request ;
use Urichy Core Request RequestInterface ;
use Symfony Component Validator Validation ;
use Symfony Component Validator Constraints as SymfonyAssert ;
use Symfony Component Validator ConstraintViolationListInterface ;
// interface is optional. You can directly use the implementation
interface BookRecordRequestInterface extends RequestInterface {}
final class BookRecordRequest extends Request implements BookRecordRequestInterface
{
protected static array $ requestPossibleFields = [
' title ' => true ,
' publication ' => [
' date ' => true ,
' publisher ' => false ,
],
' isbn ' => true ,
];
/**
* @param array<string, mixed> $requestData
* @return void
*/
protected static function applyConstraintsOnRequestFields ( array $ requestData ): void
{
$ validator = Validation:: createValidator ();
$ constraints = [
' title ' => [
new SymfonyAssert NotBlank (message: ' [title] cannot be blank ' ),
new SymfonyAssert Type (type: ' string ' , message: ' [title] must be a string ' ),
],
' publication ' => new SymfonyAssert Collection ([
' date ' => [
new SymfonyAssert NotBlank (message: ' [date] cannot be blank ' ),
new SymfonyAssert Date (message: ' [date] must be a valid date ' ),
]
]),
' isbn ' => [
new SymfonyAssert NotBlank (message: ' [isbn] cannot be blank ' ),
new SymfonyAssert Type (type: ' string ' , message: ' [isbn] must be a string ' ),
],
];
if ( isset ( $ requestData [ ' publication ' ][ ' publisher ' ])) {
$ constraints [ ' publication ' ][ ' publisher ' ] = [
new SymfonyAssert Type (type: ' string ' , message: ' [publisher] must be a string ' ),
];
}
$ violations = $ validator -> validate ( $ requestData , new SymfonyAssert Collection ( $ constraints ));
self :: throwViolationsWhenErrors ( $ violations );
}
private static function throwViolationsWhenErrors ( ConstraintViolationListInterface $ violations ): void
{
$ errors = [];
foreach ( $ violations as $ violation ) {
$ propertyPath = $ violation -> getPropertyPath ();
$ errors [ $ propertyPath ][] = $ violation -> getMessage ();
}
if (! empty ( $ errors )) {
$ errors [ ' message ' ] = ' invalid.request.field ' ;
throw new BadRequestContentException ( $ errors );
}
}
}src/Presenter/JsonResponsePresenter.php <?php
declare (strict_types= 1 );
namespace App Presenter ;
use Urichy Core Presenter Presenter ;
use Urichy Core Presenter PresenterInterface ;
use Symfony Component HttpFoundation JsonResponse ;
final class JsonResponsePresenter extends Presenter implements PresenterInterface
{
public function getResponse (): JsonResponse
{
$ responseData = $ this -> response -> output ();
return new JsonResponse ( $ responseData , $ responseData [ ' code ' ]);
}
}src/Presenter/HtmlResponsePresenter.php <?php
declare (strict_types= 1 );
namespace App Presenter ;
use Urichy Core Presenter Presenter ;
use Urichy Core Presenter PresenterInterface ;
use Symfony Component HttpFoundation Response ;
final class HtmlResponsePresenter extends Presenter implements PresenterInterface
{
public function getResponse (): Response
{
$ responseData = $ this -> response -> output ();
$ htmlContent = " <html><body><h1> { $ responseData [ ' message ' ]} </h1><p> " . json_encode ( $ responseData [ ' data ' ]) . " </p></body></html> " ;
return new Response ( $ htmlContent , $ responseData [ ' code ' ]);
}
}src/UseCase/RegisterBookUsecase.php <?php
declare (strict_types= 1 );
namespace App UseCase ;
use Urichy Core Usecase Usecase ;
use Urichy Core Usecase UsecaseInterface ;
use Urichy Core Response Response ;
use Urichy Core Response StatusCode ;
interface RegisterBookUsecaseInterface extends UsecaseInterface {}
final class RegisterBookUsecase extends Usecase implements RegisterBookUsecaseInterface
{
public function __construct (
// inject your dependencies here (always use dependencie interface, not implementation)
private BookRepositoryInterface $ bookRepository
) {}
public function execute (): void
{
$ requestData = $ this -> getRequestData ();
$ requestId = $ this -> getRequestId ();
$ book = [
' title ' => $ this -> getField ( ' title ' ),
' author ' => $ this -> getField ( ' publication.publisher ' ),
' publication_date ' => $ this -> getField ( ' publication.date ' ),
' isbn ' => $ this -> getField ( ' isbn ' ),
];
// process your business logic here
try {
$ this -> bookRepository -> save (Book:: from ( $ book ))
} catch ( PersistenceException $ e ) {
// handle persistence exception here or log it or send failed response.
}
$ this -> presentResponse (Response:: create (
success: true ,
statusCode: StatusCode:: OK -> value ,
message: ' book.registered.successfully. ' ,
data: $ book
));
}
}src/Response/Response.php <?php
declare (strict_types= 1 );
namespace App Response ;
use Urichy Core Response Response as LibResponse ;
use Urichy Core Response StatusCode ;
abstract class Response extends LibResponse
{
public static function createSuccessResponse ( array $ data , StatusCode $ statusCode , ? string $ message = null ): self
{
return new self ( true , $ statusCode -> value , $ message , $ data );
}
public static function createFailedResponse ( array $ errors = [], StatusCode $ statusCode , ? string $ message = null ): self
{
return new self ( false , $ statusCode -> value , $ message , $ errors );
}
} ├── src
│ ├── Controller
│ │ └── BookController.php
│ ├── Request
│ │ └── BookRecordRequest.php
│ ├── Presenter
│ │ └── JsonResponsePresenter.php
| | └── HtmlResponsePresenter.php
│ ├── UseCase
│ │ └── RegisterBookUsecase.php
│ └── Response
│ └── Response.php
├── public
│ └── index.php
├── config
│ └── services.yaml
└── composer.json
src/Controller/BookController.php <?php
declare (strict_types= 1 );
namespace App Controller ;
use App Request BookRecordRequest ;
use App Presenter JsonResponsePresenter ;
use App Presenter HtmlResponsePresenter ;
use App UseCase RegisterBookUsecase ;
use Symfony Component HttpFoundation Request as SymfonyRequest ;
use Symfony Component HttpFoundation JsonResponse ;
use Symfony Bundle FrameworkBundle Controller AbstractController ;
use Symfony Component Routing Annotation Route ;
#[Route( ' /register-book ' , name: ' register_book ' , methods: ' POST ' )]
final class BookController extends AbstractController
{
public function __construct (
private readonly RegisterBookUsecase $ registerBookUsecase
) {}
public function __invoke ( SymfonyRequest $ request ): JsonResponse
{
try {
$ bookRequest = BookRecordRequest:: createFromPayload ([
' title ' => $ request -> get ( ' title ' ),
' author ' => $ request -> get ( ' author ' ),
' publication ' => [
' published_date ' => $ request -> get ( ' published_date ' ),
' publisher ' => $ request -> get ( ' publisher ' ),
],
' isbn ' => $ request -> get ( ' isbn ' ),
]);
$ presenter = $ this - getPresenterAccordingToRequestContentType ( $ request -> getContentType ());
$ this -> registerBookUsecase
-> withRequest ( $ bookRequest )
-> withPresenter ( $ presenter )
-> execute ();
$ response = $ presenter -> getResponse ()-> output ();
} catch ( Exception $ exception ) {
return $ this -> json ( $ exception -> format (), $ exception -> getCode ());
}
return $ this -> json ( $ response , $ response [ ' code ' ]);
}
// you can instanciate presenter according to the request context
private function getPresenterAccordingToRequestContentType ( string $ contentType ): PresenterInterface
{
switch ( $ contentType ) {
case ' text/html ' :
return new HtmlResponsePresenter ();
default :
break ;
}
return new JsonResponsePresenter ();
}
} ├── app
│ ├── Http
│ │ └── Controllers
│ │ └── BookController.php
│ ├── Requests
│ │ └── BookRecordRequest.php
│ ├── Presenters
│ │ └── JsonResponsePresenter.php
│ ├── UseCases
│ │ └── RegisterBookUsecase.php
│ └── Responses
│ └── Response.php
├── public
│ └── index.php
└── composer.json
app/Http/Controllers/BookController.phpDengan permintaan dan presenter
<?php
declare (strict_types= 1 );
namespace App Http Controllers ;
use App Requests BookRecordRequest ;
use App Presenters JsonResponsePresenter ;
use App Presenters HtmlResponsePresenter ;
use App UseCases RegisterBookUsecase ;
use Illuminate Http Request as LaravelRequest ;
use Illuminate Http JsonResponse ;
final class BookController extends Controller
{
public function __construct (
private readonly RegisterBookUsecase $ registerBookUsecase
) {}
public function __invoke ( LaravelRequest $ request ): JsonResponse
{
try {
$ bookRequest = BookRecordRequest:: createFromPayload ([
' title ' => $ request -> input ( ' title ' ),
' author ' => $ request -> input ( ' author ' ),
' publication ' => [
' published_date ' => $ request -> input ( ' published_date ' ),
' publisher ' => $ request -> input ( ' publisher ' ),
],
' isbn ' => $ request -> input ( ' isbn ' ),
]);
$ jsonPresenter = new JsonResponsePresenter ();
$ this
-> registerBookUsecase
-> withRequest ( $ bookRequest )
-> withPresenter ( $ jsonPresenter )
-> execute ();
$ response = $ jsonPresenter -> getResponse ()-> output ();
} catch ( Exception $ exception ) {
return response ()-> json ( $ exception -> format (), $ exception -> getCode ());
}
return response ()-> json ( $ response , $ response [ ' code ' ]);
}
}Tanpa presenter, tetapi dengan permintaan.
<?php
declare (strict_types= 1 );
namespace App Http Controllers ;
use App Requests BookRecordRequest ;
use App UseCases RegisterBookUsecase ;
use Illuminate Http Request as LaravelRequest ;
use Illuminate Http JsonResponse ;
final class BookController extends Controller
{
public function __construct (
private readonly RegisterBookUsecase $ registerBookUsecase
) {}
public function __invoke ( LaravelRequest $ request ): JsonResponse
{
try {
$ bookRequest = BookRecordRequest:: createFromPayload ([
' title ' => $ request -> input ( ' title ' ),
' author ' => $ request -> input ( ' author ' ),
' publication ' => [
' published_date ' => $ request -> input ( ' published_date ' ),
' publisher ' => $ request -> input ( ' publisher ' ),
],
' isbn ' => $ request -> input ( ' isbn ' ),
]);
$ this
-> registerBookUsecase
-> withRequest ( $ bookRequest )
-> execute ();
} catch ( Exception $ exception ) {
return response ()-> json ( $ exception -> format (), $ exception -> getCode ());
}
return response ()-> json ([]);
}
}Tanpa permintaan dan presenter
<?php
declare (strict_types= 1 );
namespace App Http Controllers ;
use App Requests BookRecordRequest ;
use App UseCases RegisterBookUsecase ;
use Illuminate Http Request as LaravelRequest ;
use Illuminate Http JsonResponse ;
final class BookController extends Controller
{
public function __construct (
private readonly RegisterBookUsecase $ registerBookUsecase
) {}
public function __invoke (): JsonResponse
{
try {
$ this
-> registerBookUsecase
-> execute ();
} catch ( Exception $ exception ) {
return response ()-> json ( $ exception -> format (), $ exception -> getCode ());
}
return response ()-> json ([]);
}
}$ make tests