From 2cf5a9131fb4ceddb7ba2d473fd3073341c2505f Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Wed, 24 Aug 2022 01:50:26 -0400 Subject: [PATCH] SOLID, DDD, Hexagonal Refactor --- public/app/.gitignore | 0 public/app/blog-post/edit.php | 38 ++++++++++ public/app/blog-post/list.php | 25 +++++++ public/app/blog-post/new.php | 27 +++++++ public/app/blog-post/show.php | 45 +++++++++++ src/App/.gitignore | 0 .../Application/Create/CreateUseCase.php | 28 +++++++ .../BlogPost/Application/Find/FindUseCase.php | 28 +++++++ .../Application/Modify/ModifyUseCase.php | 28 +++++++ .../Application/Search/SearchUseCase.php | 24 ++++++ src/App/BlogPost/Domain/Model/BlogPost.php | 71 ++++++++++++++++++ .../Domain/Model/BlogPostNotFound.php | 15 ++++ src/App/BlogPost/Domain/Model/Normalizer.php | 12 +++ src/App/BlogPost/Domain/Model/Storage.php | 19 +++++ src/App/BlogPost/Domain/Service/Factory.php | 22 ++++++ .../Normalizer/ReflectionNormalizer.php | 53 +++++++++++++ .../Persistence/Storage/FileStorage.php | 75 +++++++++++++++++++ .../Presentation/Controller/EditAction.php | 29 +++++++ .../Presentation/Controller/ListAction.php | 28 +++++++ .../Presentation/Controller/NewAction.php | 26 +++++++ .../Presentation/Controller/ShowAction.php | 24 ++++++ .../Presentation/Renderer/FormatRenderer.php | 28 +++++++ .../Presentation/Renderer/HtmlRenderer.php | 25 +++++++ .../Presentation/Renderer/JsonRenderer.php | 23 ++++++ .../Presentation/Renderer/Renderer.php | 12 +++ .../Renderer/UnsupportedRendererFormat.php | 20 +++++ .../Domain/Exception/ExceptionRenderer.php | 12 +++ .../Exception/FormatExceptionRenderer.php | 29 +++++++ .../Exception/HtmlExceptionRenderer.php | 15 ++++ .../Exception/JsonExceptionRenderer.php | 17 +++++ 30 files changed, 798 insertions(+) delete mode 100644 public/app/.gitignore create mode 100644 public/app/blog-post/edit.php create mode 100644 public/app/blog-post/list.php create mode 100644 public/app/blog-post/new.php create mode 100644 public/app/blog-post/show.php delete mode 100644 src/App/.gitignore create mode 100644 src/App/BlogPost/Application/Create/CreateUseCase.php create mode 100644 src/App/BlogPost/Application/Find/FindUseCase.php create mode 100644 src/App/BlogPost/Application/Modify/ModifyUseCase.php create mode 100644 src/App/BlogPost/Application/Search/SearchUseCase.php create mode 100644 src/App/BlogPost/Domain/Model/BlogPost.php create mode 100644 src/App/BlogPost/Domain/Model/BlogPostNotFound.php create mode 100644 src/App/BlogPost/Domain/Model/Normalizer.php create mode 100644 src/App/BlogPost/Domain/Model/Storage.php create mode 100644 src/App/BlogPost/Domain/Service/Factory.php create mode 100644 src/App/BlogPost/Infrastructure/Normalizer/ReflectionNormalizer.php create mode 100644 src/App/BlogPost/Infrastructure/Persistence/Storage/FileStorage.php create mode 100644 src/App/BlogPost/Presentation/Controller/EditAction.php create mode 100644 src/App/BlogPost/Presentation/Controller/ListAction.php create mode 100644 src/App/BlogPost/Presentation/Controller/NewAction.php create mode 100644 src/App/BlogPost/Presentation/Controller/ShowAction.php create mode 100644 src/App/BlogPost/Presentation/Renderer/FormatRenderer.php create mode 100644 src/App/BlogPost/Presentation/Renderer/HtmlRenderer.php create mode 100644 src/App/BlogPost/Presentation/Renderer/JsonRenderer.php create mode 100644 src/App/BlogPost/Presentation/Renderer/Renderer.php create mode 100644 src/App/BlogPost/Presentation/Renderer/UnsupportedRendererFormat.php create mode 100644 src/App/Shared/Domain/Exception/ExceptionRenderer.php create mode 100644 src/App/Shared/Domain/Exception/FormatExceptionRenderer.php create mode 100644 src/App/Shared/Domain/Exception/HtmlExceptionRenderer.php create mode 100644 src/App/Shared/Domain/Exception/JsonExceptionRenderer.php diff --git a/public/app/.gitignore b/public/app/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/public/app/blog-post/edit.php b/public/app/blog-post/edit.php new file mode 100644 index 0000000..9030139 --- /dev/null +++ b/public/app/blog-post/edit.php @@ -0,0 +1,38 @@ +render($exception); +} diff --git a/public/app/blog-post/list.php b/public/app/blog-post/list.php new file mode 100644 index 0000000..4b9006b --- /dev/null +++ b/public/app/blog-post/list.php @@ -0,0 +1,25 @@ + new HtmlRenderer(), + 'json' => new JsonRenderer( + $normalizer, + ), + ]), +); +$formatExceptionRenderer = new FormatExceptionRenderer([ + 'html' => new HtmlExceptionRenderer(), + 'json' => new JsonExceptionRenderer(), +]); +$format = $_GET['format'] ?? 'html'; + +try { + $action((int) ($_GET['id'] ?? 0), $format); +} catch (RuntimeException $exception) { + $formatExceptionRenderer->render($exception, $format); +} diff --git a/src/App/.gitignore b/src/App/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/App/BlogPost/Application/Create/CreateUseCase.php b/src/App/BlogPost/Application/Create/CreateUseCase.php new file mode 100644 index 0000000..582fc0a --- /dev/null +++ b/src/App/BlogPost/Application/Create/CreateUseCase.php @@ -0,0 +1,28 @@ +factory->create(); + + $this->storage->persist($blogPost); + $this->storage->flush(); + + return $blogPost; + } +} diff --git a/src/App/BlogPost/Application/Find/FindUseCase.php b/src/App/BlogPost/Application/Find/FindUseCase.php new file mode 100644 index 0000000..fdc70c4 --- /dev/null +++ b/src/App/BlogPost/Application/Find/FindUseCase.php @@ -0,0 +1,28 @@ +storage->findOne($id); + + if (null === $blogPost) { + throw BlogPostNotFound::create(); + } + + return $blogPost; + } +} diff --git a/src/App/BlogPost/Application/Modify/ModifyUseCase.php b/src/App/BlogPost/Application/Modify/ModifyUseCase.php new file mode 100644 index 0000000..bb38bc7 --- /dev/null +++ b/src/App/BlogPost/Application/Modify/ModifyUseCase.php @@ -0,0 +1,28 @@ +factory->create(); + + $blogPost->changeTitle($newSample->title()); + $blogPost->changeContent($newSample->content()); + + $this->storage->flush(); + } +} diff --git a/src/App/BlogPost/Application/Search/SearchUseCase.php b/src/App/BlogPost/Application/Search/SearchUseCase.php new file mode 100644 index 0000000..4707243 --- /dev/null +++ b/src/App/BlogPost/Application/Search/SearchUseCase.php @@ -0,0 +1,24 @@ + + */ + public function execute(): iterable + { + return $this->storage->findAll(); + } +} diff --git a/src/App/BlogPost/Domain/Model/BlogPost.php b/src/App/BlogPost/Domain/Model/BlogPost.php new file mode 100644 index 0000000..b39bb4a --- /dev/null +++ b/src/App/BlogPost/Domain/Model/BlogPost.php @@ -0,0 +1,71 @@ +createdAt = new DateTimeImmutable(); + + return $self; + } + + public function id(): int + { + return $this->id; + } + + public function author(): string + { + return $this->author; + } + + public function title(): string + { + return $this->title; + } + + public function changeTitle(string $title): void + { + $this->title = $title; + $this->updatedAt = new DateTimeImmutable(); + } + + public function content(): string + { + return $this->content; + } + + public function changeContent(string $content): void + { + $this->content = $content; + $this->updatedAt = new DateTimeImmutable(); + } + + public function createdAt(): DateTimeImmutable + { + return $this->createdAt; + } + + public function updatedAt(): ?DateTimeImmutable + { + return $this->updatedAt; + } + + private function __construct( + private readonly int $id, + private readonly string $author, + private string $title, + private string $content, + ) { + } +} diff --git a/src/App/BlogPost/Domain/Model/BlogPostNotFound.php b/src/App/BlogPost/Domain/Model/BlogPostNotFound.php new file mode 100644 index 0000000..9c92055 --- /dev/null +++ b/src/App/BlogPost/Domain/Model/BlogPostNotFound.php @@ -0,0 +1,15 @@ + + */ + public function findAll(): iterable; + + public function persist(BlogPost $blogPost): void; + + public function flush(): void; +} diff --git a/src/App/BlogPost/Domain/Service/Factory.php b/src/App/BlogPost/Domain/Service/Factory.php new file mode 100644 index 0000000..4946e94 --- /dev/null +++ b/src/App/BlogPost/Domain/Service/Factory.php @@ -0,0 +1,22 @@ + $blogPost->id(), + 'author' => $blogPost->author(), + 'title' => $blogPost->title(), + 'content' => $blogPost->content(), + 'created_at' => $blogPost->createdAt()->format('c'), + 'updated_at' => $blogPost->updatedAt()?->format('c'), + ]; + } + + public function denormalize(array $data): BlogPost + { + $blogPost = BlogPost::create( + $data['id'], + $data['author'], + $data['title'], + $data['content'], + ); + + $reflectionClass = new ReflectionClass($blogPost); + $this->setPropertyValue($reflectionClass, $blogPost, 'createdAt', new DateTimeImmutable($data['created_at'])); + $this->setPropertyValue($reflectionClass, $blogPost, 'updatedAt', $data['updated_at'] ? new DateTimeImmutable($data['updated_at']) : null); + + return $blogPost; + } + + private function setPropertyValue(ReflectionClass $reflectionClass, BlogPost $blogPost, string $property, $value): void + { + try { + $reflectionProperty = $reflectionClass->getProperty($property); + } catch (ReflectionException) { + return; + } + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($blogPost, $value); + } +} diff --git a/src/App/BlogPost/Infrastructure/Persistence/Storage/FileStorage.php b/src/App/BlogPost/Infrastructure/Persistence/Storage/FileStorage.php new file mode 100644 index 0000000..f15ff04 --- /dev/null +++ b/src/App/BlogPost/Infrastructure/Persistence/Storage/FileStorage.php @@ -0,0 +1,75 @@ + + */ + private array $collection; + private string $filename; + + public function __construct( + private readonly Normalizer $normalizer, + private readonly string $targetDir, + ) { + $this->filename = $this->targetDir.'/blog_post.dat'; + $this->load(); + } + + public function findOne(int $id): ?BlogPost + { + return $this->collection[$id] ?? null; + } + + /** + * {@inheritdoc} + */ + public function findAll(): iterable + { + return array_values($this->collection); + } + + public function persist(BlogPost $blogPost): void + { + $this->collection[$blogPost->id()] = $blogPost; + } + + public function flush(): void + { + $dataCollection = []; + foreach ($this->collection as $id => $blogPost) { + $dataCollection[$id] = $this->normalizer->normalize($blogPost); + } + + file_put_contents($this->filename, serialize($dataCollection)); + } + + private function load(): void + { + $this->collection = []; + + if (!file_exists($this->filename)) { + return; + } + + $content = file_get_contents($this->filename); + + if ('' === $content) { + return; + } + + $dataCollection = unserialize($content, ['allowed_class' => false]); + + foreach ($dataCollection as $id => $data) { + $this->collection[$id] = $this->normalizer->denormalize($data); + } + } +} diff --git a/src/App/BlogPost/Presentation/Controller/EditAction.php b/src/App/BlogPost/Presentation/Controller/EditAction.php new file mode 100644 index 0000000..ad44254 --- /dev/null +++ b/src/App/BlogPost/Presentation/Controller/EditAction.php @@ -0,0 +1,29 @@ +Edit Blog Post'; + + $blogPost = $this->findUseCase->execute($id); + $this->modifyUseCase->execute($blogPost); + + $this->renderer->render($blogPost); + } +} diff --git a/src/App/BlogPost/Presentation/Controller/ListAction.php b/src/App/BlogPost/Presentation/Controller/ListAction.php new file mode 100644 index 0000000..fc52feb --- /dev/null +++ b/src/App/BlogPost/Presentation/Controller/ListAction.php @@ -0,0 +1,28 @@ +List of Blog Posts'; + + $blogPosts = $this->searchUseCase->execute(); + + foreach ($blogPosts as $blogPost) { + $this->renderer->render($blogPost); + } + } +} diff --git a/src/App/BlogPost/Presentation/Controller/NewAction.php b/src/App/BlogPost/Presentation/Controller/NewAction.php new file mode 100644 index 0000000..c56470b --- /dev/null +++ b/src/App/BlogPost/Presentation/Controller/NewAction.php @@ -0,0 +1,26 @@ +New Blog Post'; + + $blogPost = $this->createUseCase->execute(); + + $this->renderer->render($blogPost); + } +} diff --git a/src/App/BlogPost/Presentation/Controller/ShowAction.php b/src/App/BlogPost/Presentation/Controller/ShowAction.php new file mode 100644 index 0000000..039b61d --- /dev/null +++ b/src/App/BlogPost/Presentation/Controller/ShowAction.php @@ -0,0 +1,24 @@ +findUseCase->execute($id); + + $this->renderer->render($blogPost, $format); + } +} diff --git a/src/App/BlogPost/Presentation/Renderer/FormatRenderer.php b/src/App/BlogPost/Presentation/Renderer/FormatRenderer.php new file mode 100644 index 0000000..933c859 --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/FormatRenderer.php @@ -0,0 +1,28 @@ + $renderers + */ + public function __construct(private readonly array $renderers) + { + } + + public function render(BlogPost $blogPost, string $format = 'html'): void + { + $renderer = $this->renderers[$format] ?? null; + + if (null === $renderer) { + throw UnsupportedRendererFormat::create('Unsupported format.', $format); + } + + $renderer->render($blogPost); + } +} diff --git a/src/App/BlogPost/Presentation/Renderer/HtmlRenderer.php b/src/App/BlogPost/Presentation/Renderer/HtmlRenderer.php new file mode 100644 index 0000000..2523161 --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/HtmlRenderer.php @@ -0,0 +1,25 @@ +updatedAt() + ? $blogPost->updatedAt()->format('Y-m-d H:i') + : $blogPost->createdAt()->format('Y-m-d H:i'); + + echo << +

{$blogPost->title()}

+

Author: {$blogPost->author()} | Date: {$date} | Id: {$blogPost->id()}

+

{$blogPost->content()}

+ +HTML; + } +} diff --git a/src/App/BlogPost/Presentation/Renderer/JsonRenderer.php b/src/App/BlogPost/Presentation/Renderer/JsonRenderer.php new file mode 100644 index 0000000..13ae379 --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/JsonRenderer.php @@ -0,0 +1,23 @@ +normalizer->normalize($blogPost), JSON_THROW_ON_ERROR); + } +} diff --git a/src/App/BlogPost/Presentation/Renderer/Renderer.php b/src/App/BlogPost/Presentation/Renderer/Renderer.php new file mode 100644 index 0000000..fb1198a --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/Renderer.php @@ -0,0 +1,12 @@ +format = $format; + + return $self; + } +} diff --git a/src/App/Shared/Domain/Exception/ExceptionRenderer.php b/src/App/Shared/Domain/Exception/ExceptionRenderer.php new file mode 100644 index 0000000..cde0ea1 --- /dev/null +++ b/src/App/Shared/Domain/Exception/ExceptionRenderer.php @@ -0,0 +1,12 @@ + $renderers + */ + public function __construct( + private readonly array $renderers, + ) { + } + + public function render(RuntimeException $exception, string $format = 'html'): void + { + $renderer = $this->renderers[$format] ?? null; + + if (null === $renderer) { + $this->render($exception); + } else { + $renderer->render($exception); + } + } +} diff --git a/src/App/Shared/Domain/Exception/HtmlExceptionRenderer.php b/src/App/Shared/Domain/Exception/HtmlExceptionRenderer.php new file mode 100644 index 0000000..2e10fc1 --- /dev/null +++ b/src/App/Shared/Domain/Exception/HtmlExceptionRenderer.php @@ -0,0 +1,15 @@ +'.$exception->getMessage().''; + } +} diff --git a/src/App/Shared/Domain/Exception/JsonExceptionRenderer.php b/src/App/Shared/Domain/Exception/JsonExceptionRenderer.php new file mode 100644 index 0000000..f87b656 --- /dev/null +++ b/src/App/Shared/Domain/Exception/JsonExceptionRenderer.php @@ -0,0 +1,17 @@ + $exception->getMessage()], JSON_THROW_ON_ERROR); + } +}