diff --git a/src/Bridge/Symfony/DependencyInjection/CompilerPass/ConfigureDatabasesCompilerPass.php b/src/Bridge/Symfony/DependencyInjection/CompilerPass/ConfigureDatabasesCompilerPass.php index d50aa3c..a62dc3a 100644 --- a/src/Bridge/Symfony/DependencyInjection/CompilerPass/ConfigureDatabasesCompilerPass.php +++ b/src/Bridge/Symfony/DependencyInjection/CompilerPass/ConfigureDatabasesCompilerPass.php @@ -64,7 +64,7 @@ public function process(ContainerBuilder $container): void } $this->classMetadataFactory = $classMetadata; - /** @var array $configuredDatabases */ + /** @var array, options?: array}> $configuredDatabases */ $configuredDatabases = $container->getParameter('sigwin_yassg.databases_spec'); foreach ($configuredDatabases as $name => $database) { $type = $database['storage']; @@ -109,6 +109,7 @@ public function process(ContainerBuilder $container): void ->setArgument(0, new Reference($storageId)) ->setArgument(1, new Reference('sigwin_yassg.expression_language')) ->setArgument(2, $this->getProperties($databaseClass)) + ->setArgument(3, $database['page_limit']) ->addTag('sigwin_yassg.database', ['name' => $name]); $databaseId = sprintf('sigwin_yassg.database.%1$s', $name); $container->setDefinition($databaseId, $databaseDefinition); diff --git a/src/Bridge/Symfony/DependencyInjection/Configuration.php b/src/Bridge/Symfony/DependencyInjection/Configuration.php index f7b82d3..4484e85 100644 --- a/src/Bridge/Symfony/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/DependencyInjection/Configuration.php @@ -68,6 +68,11 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('class') ->isRequired() ->end() + ->scalarNode('page_limit') + ->defaultValue(20) + ->cannotBeEmpty() + ->info('How many items per page are by default used when paginating this database') + ->end() ->variableNode('options') // only with type: filesystem ->end() diff --git a/src/Bridge/Symfony/ExpressionLanguage/FunctionProvider.php b/src/Bridge/Symfony/ExpressionLanguage/FunctionProvider.php index 889ba30..911c982 100644 --- a/src/Bridge/Symfony/ExpressionLanguage/FunctionProvider.php +++ b/src/Bridge/Symfony/ExpressionLanguage/FunctionProvider.php @@ -30,6 +30,16 @@ public function getFunctions(): array return $provider->getDatabase($name)->findAll(...$arguments); }), + new ExpressionFunction('yassg_pages', static function (string $name): string { + return sprintf('$provider->getDatabase(%s)', $name); + }, static function (array $variables, string $name, ?string $condition = null, ?int $limit = null) { + /** @var DatabaseProvider $provider */ + $provider = $variables['provider']; + $database = $provider->getDatabase($name); + $count = $database->count($condition); + + return range(1, ceil($count / ($limit ?? $database->getPageLimit()))); + }), new ExpressionFunction('yassg_get', static function (string $name): string { return sprintf('$provider->getDatabase(%s)', $name); }, static function (array $variables, string $name, string $id) { diff --git a/src/Bridge/Twig/Extension/PaginatorExtension.php b/src/Bridge/Twig/Extension/PaginatorExtension.php new file mode 100644 index 0000000..c7754c0 --- /dev/null +++ b/src/Bridge/Twig/Extension/PaginatorExtension.php @@ -0,0 +1,48 @@ +provider = $provider; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('yassg_pages', function (string $name, ?string $condition = null, ?int $limit = null) { + $database = $this->provider->getDatabase($name); + $count = $database->count($condition); + + return range(1, ceil($count / ($limit ?? $database->getPageLimit()))); + }), + new TwigFunction('yassg_paginate', function (string $name, int $page, array $conditions = []) { + $database = $this->provider->getDatabase($name); + + $conditions['limit'] ??= $database->getPageLimit(); + $conditions['offset'] = ($page - 1) * $conditions['limit']; + + return $database->findAll(...$conditions); + }), + ]; + } +} diff --git a/src/Collection.php b/src/Collection.php index 37d21dc..dac2231 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -23,4 +23,6 @@ interface Collection extends \ArrayAccess, \Countable, \IteratorAggregate { public function column(string $name): array; + + public function total(): int; } diff --git a/src/Collection/ReadOnlyCollection.php b/src/Collection/ReadOnlyCollection.php index 4331955..1387a13 100644 --- a/src/Collection/ReadOnlyCollection.php +++ b/src/Collection/ReadOnlyCollection.php @@ -25,12 +25,14 @@ final class ReadOnlyCollection implements Collection private ExpressionLanguage $expressionLanguage; private array $names; private array $data; + private int $total; - public function __construct(ExpressionLanguage $expressionLanguage, array $names, array $data) + public function __construct(ExpressionLanguage $expressionLanguage, array $names, array $data, ?int $total = null) { $this->expressionLanguage = $expressionLanguage; $this->names = $names; $this->data = $data; + $this->total = $total ?? \count($data); } public function __get(string $name): object @@ -38,6 +40,11 @@ public function __get(string $name): object return $this->data[$name]; } + public function total(): int + { + return $this->total; + } + public function column(string $name): array { $expression = $this->expressionLanguage->parse($name, $this->names); diff --git a/src/Database.php b/src/Database.php index f3e62fe..e814dea 100644 --- a/src/Database.php +++ b/src/Database.php @@ -18,8 +18,19 @@ */ interface Database { + /** + * @return int<1, max> + */ + public function getPageLimit(): int; + + /** + * @return int<0, max> + */ public function count(?string $condition = null): int; + /** + * @return int<0, max> + */ public function countBy(array $condition): int; /** diff --git a/src/Database/CachingDatabase.php b/src/Database/CachingDatabase.php index ddb7353..050a299 100644 --- a/src/Database/CachingDatabase.php +++ b/src/Database/CachingDatabase.php @@ -36,8 +36,12 @@ public function __construct(string $name, Database $database, CacheItemPoolInter $this->localeContext = $localeContext; $this->expressionLanguage = $expressionLanguage; $this->names = $names; + $this->limit = $this->database->getPageLimit(); } + /** + * @return int<0, max> + */ public function count(?string $condition = null): int { $locale = $this->localeContext->getLocale()[LocaleContext::LOCALE]; @@ -45,7 +49,7 @@ public function count(?string $condition = null): int $item = $this->cacheItemPool->getItem($cacheKey); if ($item->isHit()) { - /** @var int $count */ + /** @var int<0, max> $count */ $count = $item->get(); return $count; @@ -73,7 +77,7 @@ public function findAll(?string $condition = null, ?array $sort = null, ?int $li $storage[$id] = $this->get($id); } - return $this->createCollection($storage); + return $this->createCollection($storage, $this->database->count($condition)); } $collection = $this->database->findAll($condition, $sort, $limit, $offset, $select); diff --git a/src/Database/DatabaseTrait.php b/src/Database/DatabaseTrait.php index 7d7f999..cfd8d55 100644 --- a/src/Database/DatabaseTrait.php +++ b/src/Database/DatabaseTrait.php @@ -28,6 +28,22 @@ trait DatabaseTrait private ExpressionLanguage $expressionLanguage; private array $names; + /** + * @var int<1, max> + */ + private int $limit; + + /** + * @return int<1, max> + */ + public function getPageLimit(): int + { + return $this->limit; + } + + /** + * @return int<0, max> + */ public function countBy(array $condition): int { return $this->count($this->conditionArrayToString($condition)); @@ -96,8 +112,8 @@ private function conditionArrayToString(array $condition): ?string return implode(' AND ', $condition); } - private function createCollection(array $storage): Collection + private function createCollection(array $storage, int $total): Collection { - return new Collection\ReadOnlyCollection($this->expressionLanguage, $this->names, $storage); + return new Collection\ReadOnlyCollection($this->expressionLanguage, $this->names, $storage, $total); } } diff --git a/src/Database/MemoryDatabase.php b/src/Database/MemoryDatabase.php index e2ccb12..e5890b4 100644 --- a/src/Database/MemoryDatabase.php +++ b/src/Database/MemoryDatabase.php @@ -26,12 +26,14 @@ final class MemoryDatabase implements Database /** * @param array $names + * @param int<1, max> $limit */ - public function __construct(Storage $storage, ExpressionLanguage $expressionLanguage, array $names) + public function __construct(Storage $storage, ExpressionLanguage $expressionLanguage, array $names, int $limit) { $this->storage = $storage; $this->expressionLanguage = $expressionLanguage; $this->names = $names; + $this->limit = $limit; } public function count(?string $condition = null): int @@ -75,12 +77,13 @@ public function findAll(?string $condition = null, ?array $sort = null, ?int $li }); } + $total = \count($storage); $storage = \array_slice($storage, $offset, $limit, true); if ($select !== null) { $storage = array_combine(array_keys($storage), array_column($storage, $select)); } - return $this->createCollection($storage); + return $this->createCollection($storage, $total); } public function get(string $id): object diff --git a/tests/functional/site/config/packages/yassg_databases.yaml b/tests/functional/site/config/packages/yassg_databases.yaml index dfa7071..6286e4d 100644 --- a/tests/functional/site/config/packages/yassg_databases.yaml +++ b/tests/functional/site/config/packages/yassg_databases.yaml @@ -3,6 +3,7 @@ sigwin_yassg: articles: class: Sigwin\YASSG\Test\Functional\Site\Model\Article storage: filesystem + page_limit: 2 options: root: - "%sigwin_yassg.base_dir%/content/articles" diff --git a/tests/functional/site/config/packages/yassg_routes.yaml b/tests/functional/site/config/packages/yassg_routes.yaml index 7a77da8..65104e3 100644 --- a/tests/functional/site/config/packages/yassg_routes.yaml +++ b/tests/functional/site/config/packages/yassg_routes.yaml @@ -17,6 +17,12 @@ sigwin_yassg: path: /{_locale}/article/{slug} catalog: slug: "yassg_find_all('articles').column('slug')" + articles: + path: /{_locale}/articles/{page} + defaults: + page: 1 + catalog: + page: "yassg_pages('articles', 'item.publishedAt.getTimestamp() <= 1658238497')" product: path: /{_locale}/{slug} catalog: diff --git a/tests/functional/site/content/articles/hello-world.md b/tests/functional/site/content/articles/hello-world.md index 5f0a73b..3d32e0b 100644 --- a/tests/functional/site/content/articles/hello-world.md +++ b/tests/functional/site/content/articles/hello-world.md @@ -1,8 +1,8 @@ --- +title: Hello World! slug: hello-world +publishedAt: "2022-07-18 12:44:13" --- -# Hello World! - here I am, writing some Markdown. Fine. diff --git a/tests/functional/site/content/articles/lists.md b/tests/functional/site/content/articles/lists.md new file mode 100644 index 0000000..bd2aa2a --- /dev/null +++ b/tests/functional/site/content/articles/lists.md @@ -0,0 +1,17 @@ +--- +title: Lists! +slug: lists +publishedAt: "2022-07-19 12:13:00" +--- + +## Ordered lists + +1. one +2. two +3. three + +## Unordered lists + +- a thing +- another thing +- yet another thing diff --git a/tests/functional/site/content/articles/paragraphs.md b/tests/functional/site/content/articles/paragraphs.md new file mode 100644 index 0000000..4bd7100 --- /dev/null +++ b/tests/functional/site/content/articles/paragraphs.md @@ -0,0 +1,10 @@ +--- +title: Paragraphs! +slug: paragraphs +publishedAt: "2022-07-19 12:09:00" +--- +Paragraph. + +Paragraph. + +Paragraph. diff --git a/tests/functional/site/content/articles/the-future.md b/tests/functional/site/content/articles/the-future.md new file mode 100644 index 0000000..5043ef9 --- /dev/null +++ b/tests/functional/site/content/articles/the-future.md @@ -0,0 +1,6 @@ +--- +title: Welcome to the world of tomorrow! +slug: world-of-tomorrow +publishedAt: "9999-12-31 23:59:59" +--- +Welcome! diff --git a/tests/functional/site/content/articles/titles.md b/tests/functional/site/content/articles/titles.md new file mode 100644 index 0000000..bddfd2f --- /dev/null +++ b/tests/functional/site/content/articles/titles.md @@ -0,0 +1,17 @@ +--- +title: Titles! +slug: titles +publishedAt: "2022-07-19 12:11:00" +--- + +# Title 1 + +## Title 2 + +### Title 3 + +#### Title 4 + +##### Title 5 + +###### Title 6 diff --git a/tests/functional/site/fixtures/en/article/hello-world/index.html b/tests/functional/site/fixtures/en/article/hello-world/index.html index 897a08d..6fbc841 100644 --- a/tests/functional/site/fixtures/en/article/hello-world/index.html +++ b/tests/functional/site/fixtures/en/article/hello-world/index.html @@ -5,7 +5,7 @@ - hello-world + Hello World! @@ -15,7 +15,8 @@

Hello World!

-

here I am, writing some Markdown.

+

18.07.2022. 12:44

+

here I am, writing some Markdown.

Fine.

class HelloWorld
 {
diff --git a/tests/functional/site/fixtures/en/article/lists/index.html b/tests/functional/site/fixtures/en/article/lists/index.html
new file mode 100644
index 0000000..354ebba
--- /dev/null
+++ b/tests/functional/site/fixtures/en/article/lists/index.html
@@ -0,0 +1,38 @@
+
+
+
+    
+    
+    
+
+    Lists!
+    
+    
+
+
+
+
+
+
+

Lists!

+

19.07.2022. 12:13

+

Ordered lists

+
    +
  1. one
  2. +
  3. two
  4. +
  5. three
  6. +
+

Unordered lists

+
    +
  • a thing
  • +
  • another thing
  • +
  • yet another thing
  • +
+ +
+
+
+ + + + diff --git a/tests/functional/site/fixtures/en/article/paragraphs/index.html b/tests/functional/site/fixtures/en/article/paragraphs/index.html new file mode 100644 index 0000000..51c812c --- /dev/null +++ b/tests/functional/site/fixtures/en/article/paragraphs/index.html @@ -0,0 +1,29 @@ + + + + + + + + Paragraphs! + + + + + +
+
+
+

Paragraphs!

+

19.07.2022. 12:09

+

Paragraph.

+

Paragraph.

+

Paragraph.

+ +
+
+
+ + + + diff --git a/tests/functional/site/fixtures/en/article/titles/index.html b/tests/functional/site/fixtures/en/article/titles/index.html new file mode 100644 index 0000000..288f938 --- /dev/null +++ b/tests/functional/site/fixtures/en/article/titles/index.html @@ -0,0 +1,32 @@ + + + + + + + + Titles! + + + + + +
+
+
+

Titles!

+

19.07.2022. 12:11

+

Title 1

+

Title 2

+

Title 3

+

Title 4

+
Title 5
+
Title 6
+ +
+
+
+ + + + diff --git a/tests/functional/site/fixtures/en/article/world-of-tomorrow/index.html b/tests/functional/site/fixtures/en/article/world-of-tomorrow/index.html new file mode 100644 index 0000000..86ca8b9 --- /dev/null +++ b/tests/functional/site/fixtures/en/article/world-of-tomorrow/index.html @@ -0,0 +1,27 @@ + + + + + + + + Welcome to the world of tomorrow! + + + + + +
+
+
+

Welcome to the world of tomorrow!

+

31.12.9999. 23:59

+

Welcome!

+ +
+
+
+ + + + diff --git a/tests/functional/site/fixtures/en/articles/1/index.html b/tests/functional/site/fixtures/en/articles/1/index.html new file mode 100644 index 0000000..82f336a --- /dev/null +++ b/tests/functional/site/fixtures/en/articles/1/index.html @@ -0,0 +1,37 @@ + + + + + + + + Articles, page #1 + + + + + +
+
+
+

Articles

+

Hello World!

+

18.07.2022. 12:44

+

Paragraphs!

+

19.07.2022. 12:09

+ +
    +
  • + 1 +
  • +
  • + 2 +
  • +
+
+
+
+ + + + diff --git a/tests/functional/site/fixtures/en/articles/2/index.html b/tests/functional/site/fixtures/en/articles/2/index.html new file mode 100644 index 0000000..1a68fcc --- /dev/null +++ b/tests/functional/site/fixtures/en/articles/2/index.html @@ -0,0 +1,37 @@ + + + + + + + + Articles, page #2 + + + + + +
+
+
+

Articles

+

Titles!

+

19.07.2022. 12:11

+

Lists!

+

19.07.2022. 12:13

+ +
    +
  • + 1 +
  • +
  • + 2 +
  • +
+
+
+
+ + + + diff --git a/tests/functional/site/fixtures/index.html b/tests/functional/site/fixtures/index.html index 7f55625..dd93300 100644 --- a/tests/functional/site/fixtures/index.html +++ b/tests/functional/site/fixtures/index.html @@ -35,6 +35,19 @@

Index

article
/sub/dir/another/en/article/hello-world/
+ +
/sub/dir/another/en/article/lists/
+ +
/sub/dir/another/en/article/paragraphs/
+ +
/sub/dir/another/en/article/world-of-tomorrow/
+ +
/sub/dir/another/en/article/titles/
+
articles
+ +
/sub/dir/another/en/articles/1/
+ +
/sub/dir/another/en/articles/2/
product
/sub/dir/another/en/nested-example/
diff --git a/tests/functional/site/src/Model/Article.php b/tests/functional/site/src/Model/Article.php index 0524939..2b72bd9 100644 --- a/tests/functional/site/src/Model/Article.php +++ b/tests/functional/site/src/Model/Article.php @@ -13,8 +13,14 @@ namespace Sigwin\YASSG\Test\Functional\Site\Model; +use Symfony\Component\Serializer\Annotation\Context; + final class Article { + public string $title; public string $slug; public string $body; + + #[Context(['datetime_format' => 'Y-m-d H:i:s'])] + public \DateTimeInterface $publishedAt; } diff --git a/tests/functional/site/templates/pages/article.html.twig b/tests/functional/site/templates/pages/article.html.twig index 94e9ccc..52f1e9f 100644 --- a/tests/functional/site/templates/pages/article.html.twig +++ b/tests/functional/site/templates/pages/article.html.twig @@ -3,12 +3,14 @@ {# query the database #} {% set article = yassg_find_one_by('articles', {condition: {'item.slug': slug}}) %} -{% block title %}{{ article.slug }}{% endblock %} +{% block title %}{{ article.title }}{% endblock %} {% block body %}
+

{{ article.title }}

+

{{ article.publishedAt|date('d.m.Y. H:i') }}

{{ article.body|raw }}
diff --git a/tests/functional/site/templates/pages/articles.html.twig b/tests/functional/site/templates/pages/articles.html.twig new file mode 100644 index 0000000..00c7a7b --- /dev/null +++ b/tests/functional/site/templates/pages/articles.html.twig @@ -0,0 +1,33 @@ +{% extends 'layout.html.twig' %} + +{% set condition = 'item.publishedAt.getTimestamp() <= 1658238497' %} + +{% set articles = yassg_paginate('articles', page, {condition, sort: {'item.publishedAt': 'asc'}}) %} + +{% block title %}Articles, page #{{ page }}{% endblock %} + +{% block body %} +
+
+
+

Articles

+ {% for article in articles %} +

{{ article.title }}

+

{{ article.publishedAt|date('d.m.Y. H:i') }}

+ {% endfor %} + +
    + {% for pageNumber in yassg_pages('articles', condition) %} +
  • + {% if pageNumber == page %} + {{ page }} + {% else %} + {{ pageNumber }} + {% endif %} +
  • + {% endfor %} +
+
+
+
+{% endblock %}