PHP code example of ug-php / clean-architecture-core
1. Go to this page and download the library: Download ug-php/clean-architecture-core library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
ug-php / clean-architecture-core example snippets
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, // ' => true, // n 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();
}
}
}
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();
}
}
declare(strict_types=1);
use Urichy\Core\Exception\Exception;
final class BadRequestContentException extends Exception
{
}
final class UserNotFoundException extends Exception
{
}
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 e $exception) {
// for exception, some method are available
dd($exception->getErrors()); // print details
[
'details' => [
'email' => [
'[email] field is Message()) // '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
declare(strict_types=1);
er;
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
$controller = new BookController();
$response = $controller->registerBook($request);
$response->send();
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();
}
}
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, // ert::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();
}
}
}
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 (count($errors) > 0) {
throw new BadRequestContentException($errors);
}
}
}
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']);
}
}
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']);
}
}
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
));
}
}
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);
}
}
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();
}
}
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']);
}
}
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([]);
}
}
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([]);
}
}