From 4431f4dfd9f908b937e4ad7fb8a3bdf07c891968 Mon Sep 17 00:00:00 2001 From: fsecnd <92017404+fsecnd@users.noreply.github.com> Date: Fri, 9 Sep 2022 16:34:29 +0300 Subject: [PATCH] refactor attempt --- composer.json | 3 +- public/app/.gitignore | 0 public/app/blog-post/edit.php | 27 +++++++ public/app/blog-post/list.php | 25 ++++++ public/app/blog-post/new.php | 25 ++++++ public/app/blog-post/show.php | 40 +++++++++ src/App/.gitignore | 0 .../Application/Create/CreateHandler.php | 35 ++++++++ .../BlogPost/Application/Edit/EditHandler.php | 34 ++++++++ .../BlogPost/Application/Find/FindHandler.php | 29 +++++++ .../Application/Search/SearchHandler.php | 28 +++++++ src/App/BlogPost/Domain/Entity/BlogPost.php | 78 ++++++++++++++++++ .../Domain/Serializer/ObjectSerializer.php | 31 +++++++ .../BlogPost/Domain/Serializer/Serializer.php | 21 +++++ src/App/BlogPost/Domain/Storage/Storage.php | 25 ++++++ .../Infrastructure/Hydration/Hydrator.php | 19 +++++ .../Hydration/ObjectHydrator.php | 39 +++++++++ .../Infrastructure/Storage/FileStorage.php | 81 +++++++++++++++++++ .../Presentation/Controller/CreateAction.php | 37 +++++++++ .../Presentation/Controller/EditAction.php | 40 +++++++++ .../Presentation/Controller/ListAction.php | 38 +++++++++ .../Presentation/Controller/ShowAction.php | 34 ++++++++ .../BlogPost/Presentation/Http/Response.php | 30 +++++++ .../Presentation/Renderer/HtmlRenderer.php | 41 ++++++++++ .../Presentation/Renderer/JsonRenderer.php | 38 +++++++++ .../Presentation/Renderer/Renderer.php | 23 ++++++ .../Renderer/RendererStrategy.php | 29 +++++++ .../Presentation/Renderer/XmlRenderer.php | 44 ++++++++++ 28 files changed, 893 insertions(+), 1 deletion(-) 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/CreateHandler.php create mode 100644 src/App/BlogPost/Application/Edit/EditHandler.php create mode 100644 src/App/BlogPost/Application/Find/FindHandler.php create mode 100644 src/App/BlogPost/Application/Search/SearchHandler.php create mode 100644 src/App/BlogPost/Domain/Entity/BlogPost.php create mode 100644 src/App/BlogPost/Domain/Serializer/ObjectSerializer.php create mode 100644 src/App/BlogPost/Domain/Serializer/Serializer.php create mode 100644 src/App/BlogPost/Domain/Storage/Storage.php create mode 100644 src/App/BlogPost/Infrastructure/Hydration/Hydrator.php create mode 100644 src/App/BlogPost/Infrastructure/Hydration/ObjectHydrator.php create mode 100644 src/App/BlogPost/Infrastructure/Storage/FileStorage.php create mode 100644 src/App/BlogPost/Presentation/Controller/CreateAction.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/ShowAction.php create mode 100644 src/App/BlogPost/Presentation/Http/Response.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/RendererStrategy.php create mode 100644 src/App/BlogPost/Presentation/Renderer/XmlRenderer.php diff --git a/composer.json b/composer.json index 513b4df..a9c0009 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ } ], "require": { - "php": ">=8.1" + "php": ">=8.1", + "ext-simplexml": "*" } } 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..6dffcc7 --- /dev/null +++ b/public/app/blog-post/edit.php @@ -0,0 +1,27 @@ +send(); diff --git a/public/app/blog-post/list.php b/public/app/blog-post/list.php new file mode 100644 index 0000000..4969fd2 --- /dev/null +++ b/public/app/blog-post/list.php @@ -0,0 +1,25 @@ +send(); diff --git a/public/app/blog-post/new.php b/public/app/blog-post/new.php new file mode 100644 index 0000000..d6eae1d --- /dev/null +++ b/public/app/blog-post/new.php @@ -0,0 +1,25 @@ +send(); diff --git a/public/app/blog-post/show.php b/public/app/blog-post/show.php new file mode 100644 index 0000000..5f534cd --- /dev/null +++ b/public/app/blog-post/show.php @@ -0,0 +1,40 @@ + new HtmlRenderer(), + 'json' => new JsonRenderer(new ObjectSerializer()), + 'xml' => new XmlRenderer(new ObjectSerializer()), + ]))->find($_GET['format'] ?? 'html'); +} catch (InvalidArgumentException $e) { + echo $e->getMessage(); + + return; +} + +$storage = new FileStorage( + __DIR__.'/../../../var/data/blog_post.dat', + new ObjectHydrator(), + new ObjectSerializer(), +); + +$controller = new ShowAction( + new FindHandler($storage), + $renderer, +); +$response = $controller(intval($_GET['id'] ?? 0)); +$response->send(); 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/CreateHandler.php b/src/App/BlogPost/Application/Create/CreateHandler.php new file mode 100644 index 0000000..7f12396 --- /dev/null +++ b/src/App/BlogPost/Application/Create/CreateHandler.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Application\Create; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; +use Solid\App\BlogPost\Domain\Storage\Storage; + +class CreateHandler +{ + public function __construct(private readonly Storage $storage) + { + } + + public function handle(): BlogPost + { + $loripsum = explode("\n", file_get_contents('https://loripsum.net/api/1/long/headers')); + + $blogPost = new BlogPost(random_int(1, 1000), strip_tags($loripsum[0]), $loripsum[2], 'John Doe'); + + $this->storage->save($blogPost); + + return $blogPost; + } +} diff --git a/src/App/BlogPost/Application/Edit/EditHandler.php b/src/App/BlogPost/Application/Edit/EditHandler.php new file mode 100644 index 0000000..ff9eb7e --- /dev/null +++ b/src/App/BlogPost/Application/Edit/EditHandler.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Application\Edit; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; +use Solid\App\BlogPost\Domain\Storage\Storage; + +class EditHandler +{ + public function __construct(private readonly Storage $storage) + { + } + + public function handle(BlogPost $blogPost): void + { + $loripsum = explode("\n", file_get_contents('https://loripsum.net/api/1/long/headers')); + + $blogPost->setTitle(strip_tags($loripsum[0])); + $blogPost->setContent($loripsum[2]); + + $this->storage->save($blogPost); + } +} diff --git a/src/App/BlogPost/Application/Find/FindHandler.php b/src/App/BlogPost/Application/Find/FindHandler.php new file mode 100644 index 0000000..5eb6457 --- /dev/null +++ b/src/App/BlogPost/Application/Find/FindHandler.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Application\Find; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; +use Solid\App\BlogPost\Domain\Storage\Storage; + +class FindHandler +{ + public function __construct(private readonly Storage $storage) + { + } + + public function handle(int $id): ?BlogPost + { + return $this->storage->find($id); + } +} diff --git a/src/App/BlogPost/Application/Search/SearchHandler.php b/src/App/BlogPost/Application/Search/SearchHandler.php new file mode 100644 index 0000000..bb7a843 --- /dev/null +++ b/src/App/BlogPost/Application/Search/SearchHandler.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Application\Search; + +use Solid\App\BlogPost\Domain\Storage\Storage; + +class SearchHandler +{ + public function __construct(private readonly Storage $storage) + { + } + + public function handle(): array + { + return $this->storage->findAll(); + } +} diff --git a/src/App/BlogPost/Domain/Entity/BlogPost.php b/src/App/BlogPost/Domain/Entity/BlogPost.php new file mode 100644 index 0000000..9fbe7e7 --- /dev/null +++ b/src/App/BlogPost/Domain/Entity/BlogPost.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Domain\Entity; + +use DateTimeImmutable; + +class BlogPost +{ + private readonly DateTimeImmutable $created_at; + private ?DateTimeImmutable $updated_at = null; + + public function __construct( + private readonly int $id, + private string $title, + private string $content, + private string $author, + ) { + $this->created_at = new DateTimeImmutable(); + } + + public function getId(): int + { + return $this->id; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getContent(): string + { + return $this->content; + } + + public function getAuthor(): string + { + return $this->author; + } + + public function getCreatedAt(): DateTimeImmutable + { + return $this->created_at; + } + + public function getUpdatedAt(): ?DateTimeImmutable + { + return $this->updated_at; + } + + public function setTitle(string $title): void + { + $this->title = $title; + $this->updateAt(); + } + + public function setContent(string $content): void + { + $this->content = $content; + $this->updateAt(); + } + + private function updateAt(): void + { + $this->updated_at = new DateTimeImmutable(); + } +} diff --git a/src/App/BlogPost/Domain/Serializer/ObjectSerializer.php b/src/App/BlogPost/Domain/Serializer/ObjectSerializer.php new file mode 100644 index 0000000..0f6ec6c --- /dev/null +++ b/src/App/BlogPost/Domain/Serializer/ObjectSerializer.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Domain\Serializer; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; + +class ObjectSerializer implements Serializer +{ + public function serialize(BlogPost $blogPost): array + { + return [ + 'id' => $blogPost->getId(), + 'author' => $blogPost->getAuthor(), + 'title' => $blogPost->getTitle(), + 'content' => $blogPost->getContent(), + 'created_at' => $blogPost->getCreatedAt()->format('c'), + 'updated_at' => $blogPost->getUpdatedAt()?->format('c'), + ]; + } +} diff --git a/src/App/BlogPost/Domain/Serializer/Serializer.php b/src/App/BlogPost/Domain/Serializer/Serializer.php new file mode 100644 index 0000000..fb84447 --- /dev/null +++ b/src/App/BlogPost/Domain/Serializer/Serializer.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Domain\Serializer; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; + +interface Serializer +{ + public function serialize(BlogPost $blogPost): array; +} diff --git a/src/App/BlogPost/Domain/Storage/Storage.php b/src/App/BlogPost/Domain/Storage/Storage.php new file mode 100644 index 0000000..d2bc24d --- /dev/null +++ b/src/App/BlogPost/Domain/Storage/Storage.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Domain\Storage; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; + +interface Storage +{ + public function find(int $id): ?BlogPost; + + public function findAll(): array; + + public function save(BlogPost $blogPost): void; +} diff --git a/src/App/BlogPost/Infrastructure/Hydration/Hydrator.php b/src/App/BlogPost/Infrastructure/Hydration/Hydrator.php new file mode 100644 index 0000000..ee68dc4 --- /dev/null +++ b/src/App/BlogPost/Infrastructure/Hydration/Hydrator.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Infrastructure\Hydration; + +interface Hydrator +{ + public function hydrate(string $className, array $data): object; +} diff --git a/src/App/BlogPost/Infrastructure/Hydration/ObjectHydrator.php b/src/App/BlogPost/Infrastructure/Hydration/ObjectHydrator.php new file mode 100644 index 0000000..6c978e7 --- /dev/null +++ b/src/App/BlogPost/Infrastructure/Hydration/ObjectHydrator.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Infrastructure\Hydration; + +use DateTimeImmutable; +use ReflectionClass; + +class ObjectHydrator implements Hydrator +{ + public function hydrate(string $className, array $data): object + { + $reflectionClass = new ReflectionClass($className); + $object = $reflectionClass->newInstanceWithoutConstructor(); + + foreach ($data as $key => $value) { + $property = $reflectionClass->getProperty($key); + + $value = match ($property->getType()->getName()) { + 'DateTimeImmutable' => $value ? new DateTimeImmutable($value) : null, + default => $value, + }; + + $property->setValue($object, $value); + } + + return $object; + } +} diff --git a/src/App/BlogPost/Infrastructure/Storage/FileStorage.php b/src/App/BlogPost/Infrastructure/Storage/FileStorage.php new file mode 100644 index 0000000..dafbc68 --- /dev/null +++ b/src/App/BlogPost/Infrastructure/Storage/FileStorage.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Infrastructure\Storage; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; +use Solid\App\BlogPost\Domain\Serializer\Serializer; +use Solid\App\BlogPost\Domain\Storage\Storage; +use Solid\App\BlogPost\Infrastructure\Hydration\Hydrator; + +class FileStorage implements Storage +{ + private ?array $collection = null; + + public function __construct( + private readonly string $filename, + private readonly Hydrator $hydrator, + private readonly Serializer $serializer, + ) { + } + + public function findAll(): array + { + $this->load(); + + return array_values($this->collection); + } + + public function find(int $id): ?BlogPost + { + $this->load(); + + return $this->collection[$id] ?? null; + } + + public function save(BlogPost $blogPost): void + { + $this->load(); + + $this->collection[$blogPost->getId()] = $blogPost; + + $data = []; + foreach ($this->collection as $id => $blogPost) { + $data[$id] = $this->serializer->serialize($blogPost); + } + + file_put_contents($this->filename, serialize($data)); + } + + private function load(): void + { + if (null !== $this->collection) { + return; + } + + if (!file_exists($this->filename)) { + throw new \RuntimeException('Invalid database file'); + } + + $this->collection = []; + + $content = file_get_contents($this->filename); + if ('' === $content) { + return; + } + + foreach (unserialize($content, ['allowed_class' => false]) as $id => $data) { + $this->collection[$id] = $this->hydrator->hydrate(BlogPost::class, $data); + } + } +} diff --git a/src/App/BlogPost/Presentation/Controller/CreateAction.php b/src/App/BlogPost/Presentation/Controller/CreateAction.php new file mode 100644 index 0000000..67ca692 --- /dev/null +++ b/src/App/BlogPost/Presentation/Controller/CreateAction.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Presentation\Controller; + +use Solid\App\BlogPost\Application\Create\CreateHandler; +use Solid\App\BlogPost\Presentation\Http\Response; +use Solid\App\BlogPost\Presentation\Renderer\Renderer; + +class CreateAction +{ + public function __construct( + private readonly CreateHandler $createHandler, + private readonly Renderer $renderer, + ) { + } + + public function __invoke(int $id): Response + { + $blogPost = $this->createHandler->handle(); + + $content = '
Author: {$blogPost->getAuthor()} | Date: {$date} | Id: {$blogPost->getId()}
+{$blogPost->getContent()}
+ +HTML; + } + + public function contentType(): string + { + return 'text/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..3807e00 --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/JsonRenderer.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Presentation\Renderer; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; +use Solid\App\BlogPost\Domain\Serializer\Serializer; + +class JsonRenderer implements Renderer +{ + public function __construct(private readonly Serializer $serializer) + { + } + + public function render(?BlogPost $blogPost): string + { + if (null === $blogPost) { + return json_encode('Blog Post not found'); + } + + return json_encode($this->serializer->serialize($blogPost)); + } + + public function contentType(): string + { + return 'application/json'; + } +} diff --git a/src/App/BlogPost/Presentation/Renderer/Renderer.php b/src/App/BlogPost/Presentation/Renderer/Renderer.php new file mode 100644 index 0000000..fb238fb --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/Renderer.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Presentation\Renderer; + +use Solid\App\BlogPost\Domain\Entity\BlogPost; + +interface Renderer +{ + public function render(?BlogPost $blogPost): string; + + public function contentType(): string; +} diff --git a/src/App/BlogPost/Presentation/Renderer/RendererStrategy.php b/src/App/BlogPost/Presentation/Renderer/RendererStrategy.php new file mode 100644 index 0000000..5e04908 --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/RendererStrategy.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Presentation\Renderer; + +use InvalidArgumentException; + +class RendererStrategy +{ + public function __construct(private array $rendererCollection) + { + } + + public function find(string $format): Renderer + { + return $this->rendererCollection[$format] + ?? throw new InvalidArgumentException('Unsupported format. The Blog Post cannot be renderer in the given format.'); + } +} diff --git a/src/App/BlogPost/Presentation/Renderer/XmlRenderer.php b/src/App/BlogPost/Presentation/Renderer/XmlRenderer.php new file mode 100644 index 0000000..0799c48 --- /dev/null +++ b/src/App/BlogPost/Presentation/Renderer/XmlRenderer.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Solid\App\BlogPost\Presentation\Renderer; + +use SimpleXMLElement; +use Solid\App\BlogPost\Domain\Entity\BlogPost; +use Solid\App\BlogPost\Domain\Serializer\Serializer; + +class XmlRenderer implements Renderer +{ + public function __construct(private readonly Serializer $serializer) + { + } + + public function render(?BlogPost $blogPost): string + { + if (null === $blogPost) { + return '