From 49fd0dd947a403214901e05f6fbc48d5275c66a6 Mon Sep 17 00:00:00 2001 From: Thomas Jarrand Date: Wed, 12 Oct 2022 10:41:44 +0200 Subject: [PATCH] Allowing list files that contains a list of object (e.g. CSV) --- src/Content.php | 10 +- src/ContentBag.php | 91 +++++++++++++++++++ src/ContentManager.php | 61 ++++--------- src/ContentManagerInterface.php | 7 +- .../LocalFilesystemProviderFactory.php | 3 +- src/Provider/LocalFilesystemProvider.php | 8 +- .../Provider/LocalFilesystemProviderTest.php | 1 + 7 files changed, 131 insertions(+), 50 deletions(-) create mode 100644 src/ContentBag.php diff --git a/src/Content.php b/src/Content.php index 99b34442..d96d1658 100644 --- a/src/Content.php +++ b/src/Content.php @@ -17,6 +17,7 @@ final class Content private ?\DateTimeImmutable $lastModified; private ?\DateTimeImmutable $createdAt; private array $metadata; + private bool $list; public function __construct( string $slug, @@ -25,7 +26,8 @@ public function __construct( string $format, ?\DateTimeImmutable $lastModified = null, ?\DateTimeImmutable $createdAt = null, - array $metadata = [] + array $metadata = [], + bool $list = false, ) { $this->slug = $slug; $this->type = $type; @@ -34,6 +36,7 @@ public function __construct( $this->lastModified = $lastModified; $this->createdAt = $createdAt; $this->metadata = $metadata; + $this->list = $list; } public function getSlug(): string @@ -70,4 +73,9 @@ public function getMetadata(): array { return $this->metadata; } + + public function isList(): bool + { + return $this->list; + } } diff --git a/src/ContentBag.php b/src/ContentBag.php new file mode 100644 index 00000000..641ede34 --- /dev/null +++ b/src/ContentBag.php @@ -0,0 +1,91 @@ + + */ + +namespace Stenope\Bundle; + +use Stenope\Bundle\Exception\RuntimeException; + +class ContentBag +{ + private array $contents = []; + private bool $locked = false; + private string $type; + + /** @var callable Sorting function */ + private $sorter = null; + + /** @var callable Filter function */ + private $filter = null; + + public function __construct(string $type, ?callable $sorter = null, ?callable $filter = null) + { + $this->type = $type; + $this->sorter = $sorter; + $this->filter = $filter; + } + + public function add(string $identifier, object $object): void + { + if ($this->locked) { + throw new RuntimeException('Contents have already been filtered and sorted.', 1); + } + + if (isset($this->contents[$identifier])) { + throw new RuntimeException(sprintf( + 'Found multiple contents of type "%s" with the same "%s" identifier.', + $this->type, + $identifier + )); + } + + $this->contents[$identifier] = $object; + } + + public function getContents(): array + { + try { + $this->applyFilter(); + } catch (\Throwable $exception) { + throw new RuntimeException(sprintf('There was a problem filtering %s.', $this->type), 0, $exception); + } + + try { + $this->applySort(); + } catch (\Throwable $exception) { + throw new RuntimeException(sprintf('There was a problem sorting %s.', $this->type), 0, $exception); + } + + $this->locked = true; + + return $this->contents; + } + + private function applyFilter(): void + { + if ($this->filter === null) { + return; + } + + $this->contents = array_filter($this->contents, $this->filter); + } + + private function applySort(): void + { + if ($this->sorter === null) { + return; + } + + set_error_handler(static function (int $severity, string $message, ?string $file, ?int $line): void { + throw new \ErrorException($message, $severity, $severity, $file, $line); + }); + + uasort($this->contents, $this->sorter); + + restore_error_handler(); + } +} diff --git a/src/ContentManager.php b/src/ContentManager.php index 7881d364..efb21471 100644 --- a/src/ContentManager.php +++ b/src/ContentManager.php @@ -82,60 +82,27 @@ public function __construct( */ public function getContents(string $type, $sortBy = null, $filterBy = null): array { - $contents = []; + $contents = new ContentBag($type, $this->getSortFunction($sortBy), $this->getFilterFunction($filterBy)); foreach ($this->getProviders($type) as $provider) { foreach ($provider->listContents() as $content) { - if (isset($contents[$content->getSlug()])) { - throw new RuntimeException(sprintf( - 'Found multiple contents of type "%s" with the same "%s" identifier.', - $content->getType(), - $content->getSlug() - )); + if ($content->isList()) { + foreach ($this->load($content) as $index => $object) { + $contents->add("{$content->getSlug()}[{$index}]", $object); + } + } else { + $contents->add($content->getSlug(), $this->load($content)); } - $contents[$content->getSlug()] = $this->load($content); } } - try { - $this->filterBy($contents, $filterBy); - } catch (\Throwable $exception) { - throw new RuntimeException(sprintf('There was a problem filtering %s.', $type), 0, $exception); - } - - try { - $this->sortBy($contents, $sortBy); - } catch (\Throwable $exception) { - throw new RuntimeException(sprintf('There was a problem sorting %s.', $type), 0, $exception); - } - - return $contents; - } - - private function filterBy(array &$contents, $filterBy = null): void - { - if ($filter = $this->getFilterFunction($filterBy)) { - $contents = array_filter($contents, $filter); - } - } - - private function sortBy(array &$contents, $sortBy = null): void - { - if ($sorter = $this->getSortFunction($sortBy)) { - set_error_handler(static function (int $severity, string $message, ?string $file, ?int $line): void { - throw new \ErrorException($message, $severity, $severity, $file, $line); - }); - - uasort($contents, $sorter); - - restore_error_handler(); - } + return $contents->getContents(); } /** * {@inheritdoc} */ - public function getContent(string $type, string $id): object + public function getContent(string $type, string $id, ?int $index = null): object { if ($this->stopwatch) { $event = $this->stopwatch->start('get_content', 'stenope'); @@ -149,6 +116,14 @@ public function getContent(string $type, string $id): object $event->stop(); } + if ($content->isList()) { + if ($index === null) { + throw new RuntimeException("No index provided for list content \"$id\".", 1); + } + + return $loaded[$index]; + } + return $loaded; } } @@ -228,7 +203,7 @@ private function load(Content $content) $this->crawlers->saveAll($content, $data); - $data = $this->denormalizer->denormalize($data, $content->getType(), $content->getFormat(), [ + $data = $this->denormalizer->denormalize($data, $content->getType() . ($content->isList() ? '[]' : ''), $content->getFormat(), [ SkippingInstantiatedObjectDenormalizer::SKIP => true, ]); diff --git a/src/ContentManagerInterface.php b/src/ContentManagerInterface.php index 961cdf5b..56bae526 100644 --- a/src/ContentManagerInterface.php +++ b/src/ContentManagerInterface.php @@ -33,12 +33,13 @@ public function getContents(string $type, $sortBy = null, $filterBy = null): arr * * @template T of object * - * @param class-string $type Model FQCN e.g. "App/Model/Article" - * @param string $id Unique identifier (slug) + * @param class-string $type Model FQCN e.g. "App/Model/Article" + * @param string $id Unique identifier (slug) + * @param int $index Index (for content lists) * * @return T An object of the given type. */ - public function getContent(string $type, string $id): object; + public function getContent(string $type, string $id, ?int $index = null): object; /** * Attempt to reverse resolve a content according to a context. diff --git a/src/Provider/Factory/LocalFilesystemProviderFactory.php b/src/Provider/Factory/LocalFilesystemProviderFactory.php index a25bb5ef..cef4c8ee 100644 --- a/src/Provider/Factory/LocalFilesystemProviderFactory.php +++ b/src/Provider/Factory/LocalFilesystemProviderFactory.php @@ -18,11 +18,12 @@ class LocalFilesystemProviderFactory implements ContentProviderFactoryInterface public function create(string $type, array $config): ContentProviderInterface { return new LocalFilesystemProvider( - $config['class'], + trim($config['class'], '[]'), $config['path'], $config['depth'] ?? null, $config['excludes'] ?? [], $config['patterns'] ?? ['*'], + str_ends_with($config['class'], '[]') ); } diff --git a/src/Provider/LocalFilesystemProvider.php b/src/Provider/LocalFilesystemProvider.php index 2eab9d6d..4eab52ba 100644 --- a/src/Provider/LocalFilesystemProvider.php +++ b/src/Provider/LocalFilesystemProvider.php @@ -21,6 +21,7 @@ class LocalFilesystemProvider implements ReversibleContentProviderInterface private string $path; private ?string $depth; private array $excludes; + private bool $list; /** @var string[] */ private array $patterns; @@ -30,13 +31,15 @@ public function __construct( string $path, ?string $depth = null, array $excludes = [], - array $patterns = ['*'] + array $patterns = ['*'], + bool $list = false, ) { $this->supportedClass = $supportedClass; $this->path = $path; $this->depth = $depth; $this->excludes = $excludes; $this->patterns = $patterns; + $this->list = $list; } /** @@ -106,7 +109,8 @@ private function fromFile(\SplFileInfo $file): Content [ 'path' => $file->getRealPath(), 'provider' => LocalFilesystemProviderFactory::TYPE, - ] + ], + $this->list, ); } diff --git a/tests/Unit/Provider/LocalFilesystemProviderTest.php b/tests/Unit/Provider/LocalFilesystemProviderTest.php index 5f32d44c..04043652 100644 --- a/tests/Unit/Provider/LocalFilesystemProviderTest.php +++ b/tests/Unit/Provider/LocalFilesystemProviderTest.php @@ -81,6 +81,7 @@ public function testGetContent(): void "path" => "$basePath/content/foo/foo.md", "provider" => "files", ] + -list: false } DUMP, $provider->getContent('foo'),