Skip to content

Commit

Permalink
perf: database caching
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarlovi committed Apr 6, 2022
1 parent 83746e2 commit 5e8d096
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 70 deletions.
7 changes: 6 additions & 1 deletion config/packages/cache.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
framework:
cache:
app: cache.adapter.array
app: cache.pool.database
pools:
cache.pool.database:
adapters:
- cache.adapter.array
- cache.adapter.filesystem
5 changes: 5 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
<directory name="tests/"/>
</errorLevel>
</PropertyNotSetInConstructor>
<UnusedForeachValue>
<errorLevel type="suppress">
<file name="src/Database/CachingDatabase.php"/>
</errorLevel>
</UnusedForeachValue>

<!-- false positives -->
<UnresolvableInclude>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ public function process(ContainerBuilder $container): void
$container->setDefinition($databaseId, $databaseDefinition);
$container->setAlias(sprintf('%1$s $%2$s', Database::class, $name), $databaseId);

$cachingDatabaseDefinition = new Definition(\Sigwin\YASSG\Database\CachingDatabase::class);
$cachingDatabaseDefinition
->setArgument(0, $name)
->setArgument(4, new Reference('sigwin_yassg.expression_language'))
->setArgument(5, $this->getProperties($databaseClass))
->setAutowired(true)
->setAutoconfigured(true)
->setDecoratedService($databaseId);
$container->setDefinition(sprintf('sigwin_yassg.database.cached.%1$s', $name), $cachingDatabaseDefinition);

$localizableProperties = $this->getLocalizableProperties($databaseClass);
if ($localizableProperties !== []) {
$localizableClasses[$databaseClass] = $localizableProperties;
Expand Down
96 changes: 96 additions & 0 deletions src/Database/CachingDatabase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

/*
* This file is part of the yassg project.
*
* (c) sigwin.hr
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Sigwin\YASSG\Database;

use Psr\Cache\CacheItemPoolInterface;
use Sigwin\YASSG\Collection;
use Sigwin\YASSG\Context\LocaleContext;
use Sigwin\YASSG\Database;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

final class CachingDatabase implements Database
{
use DatabaseTrait;

private string $name;
private Database $database;
private CacheItemPoolInterface $cacheItemPool;
private LocaleContext $localeContext;

public function __construct(string $name, Database $database, CacheItemPoolInterface $cacheItemPool, LocaleContext $localeContext, ExpressionLanguage $expressionLanguage, array $names)
{
$this->name = $name;
$this->database = $database;
$this->cacheItemPool = $cacheItemPool;
$this->localeContext = $localeContext;
$this->expressionLanguage = $expressionLanguage;
$this->names = $names;
}

public function count(?string $condition = null): int
{
$locale = $this->localeContext->getLocale()[LocaleContext::LOCALE];
$cacheKey = $this->name.'_count_'.$locale.'_'.$condition;

$item = $this->cacheItemPool->getItem($cacheKey);
if ($item->isHit()) {
/** @var int $count */
$count = $item->get();

return $count;
}

$count = $this->database->count($condition);
$item->set($count);
$this->cacheItemPool->save($item);

return $count;
}

public function findAll(?string $condition = null, ?array $sort = null, ?int $limit = null, int $offset = 0, ?string $select = null): Collection
{
$locale = $this->localeContext->getLocale()[LocaleContext::LOCALE];
$cacheKey = $this->name.'_find_'.$locale.'_'.$condition.'_'.json_encode($sort).'_'.$limit.'_'.$offset.'_'.$select;

$item = $this->cacheItemPool->getItem($cacheKey);
if ($item->isHit()) {
/** @var array<string> $ids */
$ids = $item->get();

$storage = [];
foreach ($ids as $id) {
$storage[$id] = $this->get($id);
}

return $this->createCollection($storage);
}

$collection = $this->database->findAll($condition, $sort, $limit, $offset, $select);

$ids = [];
$cloned = clone $collection;
foreach ($cloned as $id => $entity) {
$ids[] = $id;
}
$item->set($ids);
$this->cacheItemPool->save($item);

return $collection;
}

public function get(string $id): object
{
return $this->database->get($id);
}
}
102 changes: 102 additions & 0 deletions src/Database/DatabaseTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

/*
* This file is part of the yassg project.
*
* (c) sigwin.hr
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Sigwin\YASSG\Database;

use Sigwin\YASSG\Collection;
use Sigwin\YASSG\Exception\MoreThanOneResultException;
use Sigwin\YASSG\Exception\NoResultException;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

/**
* @internal
*
* @template T of object
*/
trait DatabaseTrait
{
private ExpressionLanguage $expressionLanguage;
private array $names;

public function countBy(array $condition): int
{
return $this->count($this->conditionArrayToString($condition));
}

/**
* @return Collection<string, T>
*/
public function findAllBy(array $condition, ?array $sort = null, ?int $limit = null, int $offset = 0, ?string $select = null): Collection
{
return $this->findAll($this->conditionArrayToString($condition), $sort, $limit, $offset, $select);
}

public function findOne(?string $condition = null, ?array $sort = null, ?string $select = null): object
{
$result = $this->findOneOrNull($condition, $sort, $select);

if ($result === null) {
throw new NoResultException();
}

return $result;
}

public function findOneOrNull(?string $condition = null, ?array $sort = null, ?string $select = null): ?object
{
return $this->oneOrFail($this->findAll($condition, $sort, null, 0, $select));
}

public function findOneBy(array $condition, ?array $sort = null, ?string $select = null): object
{
return $this->findOne($this->conditionArrayToString($condition), $sort, $select);
}

public function findOneByOrNull(array $condition, ?array $sort = null, ?string $select = null): ?object
{
return $this->findOneOrNull($this->conditionArrayToString($condition), $sort, $select);
}

private function oneOrFail(Collection $list): ?object
{
$count = \count($list);
if ($count <= 0) {
return null;
}
if ($count > 1) {
throw MoreThanOneResultException::newSelf($count);
}

$item = current(iterator_to_array($list));

return $item !== false ? $item : null;
}

private function conditionArrayToString(array $condition): ?string
{
if ($condition === []) {
return null;
}

array_walk($condition, static function (mixed &$value, string $key): void {
$value = sprintf('%1$s == "%2$s"', $key, $value);
});

return implode(' AND ', $condition);
}

private function createCollection(array $storage): Collection
{
return new Collection\ReadOnlyCollection($this->expressionLanguage, $this->names, $storage);
}
}
72 changes: 3 additions & 69 deletions src/Database/MemoryDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@

use Sigwin\YASSG\Collection;
use Sigwin\YASSG\Database;
use Sigwin\YASSG\Exception\MoreThanOneResultException;
use Sigwin\YASSG\Exception\NoResultException;
use Sigwin\YASSG\Storage;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

final class MemoryDatabase implements Database
{
use DatabaseTrait;

private Storage $storage;
private ExpressionLanguage $expressionLanguage;
private array $names;

/**
* @param array<string> $names
Expand All @@ -46,11 +44,6 @@ public function count(?string $condition = null): int
return $total;
}

public function countBy(array $condition): int
{
return $this->count($this->conditionArrayToString($condition));
}

public function findAll(?string $condition = null, ?array $sort = null, ?int $limit = null, int $offset = 0, ?string $select = null): Collection
{
$storage = [];
Expand Down Expand Up @@ -87,38 +80,7 @@ public function findAll(?string $condition = null, ?array $sort = null, ?int $li
$storage = array_combine(array_keys($storage), array_column($storage, $select));
}

return new Collection\ReadOnlyCollection($this->expressionLanguage, $this->names, $storage);
}

public function findAllBy(array $condition, ?array $sort = null, ?int $limit = null, int $offset = 0, ?string $select = null): Collection
{
return $this->findAll($this->conditionArrayToString($condition), $sort, $limit, $offset, $select);
}

public function findOne(?string $condition = null, ?array $sort = null, ?string $select = null): object
{
$result = $this->findOneOrNull($condition, $sort, $select);

if ($result === null) {
throw new NoResultException();
}

return $result;
}

public function findOneOrNull(?string $condition = null, ?array $sort = null, ?string $select = null): ?object
{
return $this->oneOrFail($this->findAll($condition, $sort, null, 0, $select));
}

public function findOneBy(array $condition, ?array $sort = null, ?string $select = null): object
{
return $this->findOne($this->conditionArrayToString($condition), $sort, $select);
}

public function findOneByOrNull(array $condition, ?array $sort = null, ?string $select = null): ?object
{
return $this->findOneOrNull($this->conditionArrayToString($condition), $sort, $select);
return $this->createCollection($storage);
}

public function get(string $id): object
Expand All @@ -142,32 +104,4 @@ private function load(?string $condition, callable $callable): void
}
}
}

private function oneOrFail(Collection $list): ?object
{
$count = \count($list);
if ($count <= 0) {
return null;
}
if ($count > 1) {
throw MoreThanOneResultException::newSelf($count);
}

$item = current(iterator_to_array($list));

return $item !== false ? $item : null;
}

private function conditionArrayToString(array $condition): ?string
{
if ($condition === []) {
return null;
}

array_walk($condition, static function (mixed &$value, string $key): void {
$value = sprintf('%1$s == "%2$s"', $key, $value);
});

return implode(' AND ', $condition);
}
}

0 comments on commit 5e8d096

Please sign in to comment.