Refactor collection repositories: use one instance per databox

This commit is contained in:
Thibaud Fabre
2015-07-09 18:48:53 +02:00
parent 6b516618aa
commit 977e778b61
17 changed files with 407 additions and 214 deletions

View File

@@ -1,117 +0,0 @@
<?php
namespace Alchemy\Phrasea\Collection;
class ArrayCacheCollectionRepository implements CollectionRepository
{
/**
* @var CollectionRepository
*/
private $collectionRepository;
private $collectionCache = array();
private $baseIdMap = array();
private $databoxFlags = array();
public function __construct(CollectionRepository $collectionRepository)
{
$this->collectionRepository = $collectionRepository;
}
private function putCollectionsInCache(array $collections)
{
foreach ($collections as $collection) {
$this->putCollectionInCache($collection);
}
}
private function putCollectionInCache(\collection $collection = null)
{
if ($collection === null) {
return;
}
$baseId = $collection->getReference()->getBaseId();
$databoxId = $collection->getReference()->getDataboxId();
$collectionId = $collection->getReference()->getCollectionId();
if (! isset($this->collectionCache[$databoxId])) {
$this->collectionCache[$databoxId] = [];
}
$this->collectionCache[$databoxId][$collectionId] = $collection;
$this->baseIdMap[$baseId] = [ $databoxId, $collectionId ];
}
private function getCollectionInCache($databoxId, $collectionId)
{
if (isset($this->collectionCache[$databoxId][$collectionId])) {
return $this->collectionCache[$databoxId][$collectionId];
}
return null;
}
private function getCollectionInCacheByBaseId($baseId)
{
if (isset($this->baseIdMap[$baseId])) {
list ($databoxId, $collectionId) = $this->baseIdMap[$baseId];
return $this->getCollectionInCache($databoxId, $collectionId);
}
return null;
}
private function getCollectionsInCache($databoxId)
{
if (isset($this->collectionCache[$databoxId])) {
return $this->collectionCache[$databoxId];
}
return [];
}
/**
* @param int $databoxId
* @return \collection[]
*/
public function findAllByDatabox($databoxId)
{
if (! isset($this->databoxFlags[$databoxId]) || $this->databoxFlags[$databoxId] !== true) {
$this->putCollectionsInCache($this->collectionRepository->findAllByDatabox($databoxId));
$this->databoxFlags[$databoxId] = true;
}
return $this->getCollectionsInCache($databoxId);
}
/**
* @param int $baseId
* @return \collection|null
*/
public function find($baseId)
{
if (! isset($this->baseIdMap[$baseId])) {
$this->putCollectionInCache($this->collectionRepository->find($baseId));
}
return $this->getCollectionInCacheByBaseId($baseId);
}
/**
* @param int $databoxId
* @param int $collectionId
* @return \collection|null
*/
public function findByCollectionId($databoxId, $collectionId)
{
if (! isset($this->collectionCache[$databoxId][$collectionId])) {
$this->putCollectionInCache($this->collectionRepository->findByCollectionId($databoxId, $collectionId));
}
return $this->getCollectionInCache($databoxId, $collectionId);
}
}

View File

@@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\Collection;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Collection\Reference\CollectionReference;
use Assert\Assertion;
class CollectionFactory

View File

@@ -6,21 +6,14 @@ interface CollectionRepository
{
/**
* @param int $databoxId
* @return \collection[]
*/
public function findAllByDatabox($databoxId);
public function findAll();
/**
* @param int $baseId
* @return \collection|null
*/
public function find($baseId);
/**
* @param int $databoxId
* @param int $collectionId
* @return \collection|null
*/
public function findByCollectionId($databoxId, $collectionId);
public function find($collectionId);
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Alchemy\Phrasea\Collection;
interface CollectionRepositoryFactory
{
/**
* @param int $databoxId
* @return CollectionRepository
*/
public function createRepositoryForDatabox($databoxId);
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Alchemy\Phrasea\Collection;
use Alchemy\Phrasea\Collection\Reference\CollectionReferenceRepository;
class CollectionRepositoryRegistry
{
private $baseIdMap = null;
/**
* @var CollectionRepository[]
*/
private $repositories = array();
/**
* @var CollectionReferenceRepository
*/
private $referenceRepository;
/**
* @var CollectionRepositoryFactory
*/
private $repositoryFactory;
/**
* @param CollectionRepositoryFactory $collectionRepositoryFactory
* @param CollectionReferenceRepository $referenceRepository
*/
public function __construct(
CollectionRepositoryFactory $collectionRepositoryFactory,
CollectionReferenceRepository $referenceRepository
) {
$this->repositoryFactory = $collectionRepositoryFactory;
$this->referenceRepository = $referenceRepository;
}
/**
* @param $databoxId
* @return CollectionRepository
*/
public function getRepositoryByDatabox($databoxId)
{
if (!isset($this->repositories[$databoxId])) {
$this->repositories[$databoxId] = $this->repositoryFactory->createRepositoryForDatabox($databoxId);
}
return $this->repositories[$databoxId];
}
/**
* @param int $baseId
* @return CollectionRepository
* @throws \OutOfBoundsException if no repository was found for the given baseId.
*/
public function getRepositoryByBase($baseId)
{
if ($this->baseIdMap === null) {
$this->loadBaseIdMap();
}
if (isset($this->baseIdMap[$baseId])) {
return $this->getRepositoryByDatabox($this->baseIdMap[$baseId]);
}
throw new \OutOfBoundsException('No repository available for given base [baseId: ' . $baseId . ' ].');
}
private function loadBaseIdMap()
{
$references = $this->referenceRepository->findAll();
$this->baseIdMap = [];
foreach ($references as $reference) {
$this->baseIdMap[$reference->getBaseId()] = $reference->getDataboxId();
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Alchemy\Phrasea\Collection\Factory;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Alchemy\Phrasea\Collection\CollectionRepositoryFactory;
use Alchemy\Phrasea\Collection\Repository\ArrayCacheCollectionRepository;
class ArrayCachedCollectionRepositoryFactory implements CollectionRepositoryFactory
{
/**
* @var CollectionRepositoryFactory
*/
private $collectionRepositoryFactory;
/**
* @param CollectionRepositoryFactory $collectionRepositoryFactory
*/
public function __construct(CollectionRepositoryFactory $collectionRepositoryFactory)
{
$this->collectionRepositoryFactory = $collectionRepositoryFactory;
}
/**
* @param int $databoxId
* @return CollectionRepository
*/
public function createRepositoryForDatabox($databoxId)
{
$repository = $this->collectionRepositoryFactory->createRepositoryForDatabox($databoxId);
return new ArrayCacheCollectionRepository($repository);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Alchemy\Phrasea\Collection\Factory;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Alchemy\Phrasea\Collection\CollectionRepositoryFactory;
use Alchemy\Phrasea\Collection\Repository\CachedCollectionRepository;
use Doctrine\Common\Cache\Cache;
class CachedCollectionRepositoryFactory implements CollectionRepositoryFactory
{
/**
* @var Application
*/
private $application;
/**
* @var CollectionRepositoryFactory
*/
private $collectionRepositoryFactory;
/**
* @var Cache
*/
private $cache;
/**
* @var string
*/
private $baseCacheKey;
/**
* @param Application $application
* @param CollectionRepositoryFactory $collectionRepositoryFactory
* @param Cache $cache
* @param string $baseCacheKey
*/
public function __construct(
Application $application,
CollectionRepositoryFactory $collectionRepositoryFactory,
Cache $cache,
$baseCacheKey
) {
$this->application = $application;
$this->collectionRepositoryFactory = $collectionRepositoryFactory;
$this->cache = $cache;
$this->baseCacheKey = (string)$baseCacheKey;
}
/**
* @param int $databoxId
* @return CollectionRepository
*/
public function createRepositoryForDatabox($databoxId)
{
$repository = $this->collectionRepositoryFactory->createRepositoryForDatabox($databoxId);
return new CachedCollectionRepository(
$this->application,
$repository,
$this->cache,
$this->baseCacheKey . '.' . $databoxId
);
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Alchemy\Phrasea\Collection\Factory;
use Alchemy\Phrasea\Collection\CollectionFactory;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Alchemy\Phrasea\Collection\CollectionRepositoryFactory;
use Alchemy\Phrasea\Collection\Reference\CollectionReferenceRepository;
use Alchemy\Phrasea\Collection\Repository\DbalCollectionRepository;
use Alchemy\Phrasea\Databox\DataboxConnectionProvider;
class DbalCollectionRepositoryFactory implements CollectionRepositoryFactory
{
/**
* @var CollectionReferenceRepository
*/
private $collectionReferenceRepository;
/**
* @var DataboxConnectionProvider
*/
private $databoxConnectionProvider;
/**
* @var CollectionFactory
*/
private $collectionFactory;
/**
* @param DataboxConnectionProvider $connectionProvider
* @param CollectionFactory $collectionFactory
* @param CollectionReferenceRepository $referenceRepository
*/
public function __construct(
DataboxConnectionProvider $connectionProvider,
CollectionFactory $collectionFactory,
CollectionReferenceRepository $referenceRepository
) {
$this->databoxConnectionProvider = $connectionProvider;
$this->collectionFactory = $collectionFactory;
$this->collectionReferenceRepository = $referenceRepository;
}
/**
* @param int $databoxId
* @return CollectionRepository
*/
public function createRepositoryForDatabox($databoxId)
{
$connection = $this->databoxConnectionProvider->getConnection($databoxId);
return new DbalCollectionRepository(
$databoxId,
$connection,
$this->collectionReferenceRepository,
$this->collectionFactory
);
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace Alchemy\Phrasea\Collection;
namespace Alchemy\Phrasea\Collection\Reference;
class CollectionReference
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Alchemy\Phrasea\Collection;
namespace Alchemy\Phrasea\Collection\Reference;
interface CollectionReferenceRepository
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Alchemy\Phrasea\Collection;
namespace Alchemy\Phrasea\Collection\Reference;
use Doctrine\DBAL\Connection;

View File

@@ -0,0 +1,50 @@
<?php
namespace Alchemy\Phrasea\Collection\Repository;
use Alchemy\Phrasea\Collection\CollectionRepository;
class ArrayCacheCollectionRepository implements CollectionRepository
{
/**
* @var CollectionRepository
*/
private $collectionRepository;
/**
* @var \collection[]
*/
private $collectionCache = null;
public function __construct(CollectionRepository $collectionRepository)
{
$this->collectionRepository = $collectionRepository;
}
/**
* @return \collection[]
*/
public function findAll()
{
if ($this->collectionCache === null) {
$this->collectionCache = $this->collectionRepository->findAll();
}
return $this->collectionCache;
}
/**
* @param int $collectionId
* @return \collection|null
*/
public function find($collectionId)
{
$collections = $this->findAll();
if (isset($collections[$collectionId])) {
return $collections[$collectionId];
}
return null;
}
}

View File

@@ -7,9 +7,10 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Collection;
namespace Alchemy\Phrasea\Collection\Repository;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Doctrine\Common\Cache\Cache;
final class CachedCollectionRepository implements CollectionRepository
@@ -50,17 +51,17 @@ final class CachedCollectionRepository implements CollectionRepository
}
/**
* @param int $databoxId
* @return \collection[]
*/
public function findAllByDatabox($databoxId)
public function findAll()
{
$cacheKey = hash('sha256', $this->cacheKey . '.findAll.' . $databoxId);
$cacheKey = hash('sha256', $this->cacheKey . '.findAll');
/** @var \collection[] $collections */
$collections = $this->cache->fetch($cacheKey);
if ($collections === false) {
$collections = $this->repository->findAllByDatabox($databoxId);
$this->save($cacheKey, $collections);
$collections = $this->repository->findAll();
$this->putInCache($cacheKey, $collections);
} else {
foreach ($collections as $collection) {
$collection->hydrate($this->app);
@@ -71,45 +72,21 @@ final class CachedCollectionRepository implements CollectionRepository
}
/**
* @param int $baseId
* @return \collection|null
*/
public function find($baseId)
{
$cacheKey = hash('sha256', $this->cacheKey . '.find.' . $baseId);
$collection = $this->cache->fetch($cacheKey);
if ($collection === false) {
$collection = $this->repository->find($baseId);
$this->save($cacheKey, $collection);
} else {
$collection->hydrate($this->app);
}
return $collection;
}
/**
* @param int $databoxId
* @param int $collectionId
* @return \collection|null
*/
public function findByCollectionId($databoxId, $collectionId)
public function find($collectionId)
{
$cacheKey = hash('sha256', $this->cacheKey . '.findByCollection.' . $databoxId . $collectionId);
$collection = $this->cache->fetch($cacheKey);
$collections = $this->findAll();
if ($collection === false) {
$collection = $this->repository->findByCollectionId($databoxId, $collectionId);
$this->save($cacheKey, $collection);
} else {
$collection->hydrate($this->app);
if (isset($collections[$collectionId])) {
return $collections[$collectionId];
}
return $collection;
return null;
}
private function save($key, $value)
private function putInCache($key, $value)
{
$this->cache->save($key, $value);
}

View File

@@ -1,8 +1,11 @@
<?php
namespace Alchemy\Phrasea\Collection;
namespace Alchemy\Phrasea\Collection\Repository;
use Alchemy\Phrasea\Databox\DataboxConnectionProvider;
use Alchemy\Phrasea\Collection\CollectionFactory;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Alchemy\Phrasea\Collection\Reference\CollectionReferenceRepository;
use Doctrine\DBAL\Connection;
class DbalCollectionRepository implements CollectionRepository
{
@@ -10,15 +13,20 @@ class DbalCollectionRepository implements CollectionRepository
private static $query = 'SELECT coll_id, asciiname, label_en, label_fr, label_de, label_nl, prefs, logo, majLogo, pub_wm
FROM coll';
/**
* @var int
*/
private $databoxId;
/**
* @var CollectionReferenceRepository
*/
private $referenceRepository;
/**
* @var DataboxConnectionProvider
* @var Connection
*/
private $connectionProvider;
private $connection;
/**
* @var CollectionFactory
@@ -26,24 +34,23 @@ class DbalCollectionRepository implements CollectionRepository
private $collectionFactory;
public function __construct(
DataboxConnectionProvider $connectionProvider,
$databoxId,
Connection $connection,
CollectionReferenceRepository $referenceRepository,
CollectionFactory $collectionFactory
) {
$this->connectionProvider = $connectionProvider;
$this->databoxId = (int) $databoxId;
$this->connection = $connection;
$this->referenceRepository = $referenceRepository;
$this->collectionFactory = $collectionFactory;
}
/**
* @param int $databoxId
* @return \collection[]
*/
public function findAllByDatabox($databoxId)
public function findAll()
{
$references = $this->referenceRepository->findAllByDatabox($databoxId);
$connection = $this->connectionProvider->getConnection($databoxId);
$references = $this->referenceRepository->findAllByDatabox($this->databoxId);
$params = [];
foreach ($references as $reference) {
@@ -51,9 +58,9 @@ class DbalCollectionRepository implements CollectionRepository
}
$query = self::$query . sprintf(' WHERE coll_id IN (%s)', implode(', ', array_keys($params)));
$rows = $connection->fetchAll($query, $params);
$rows = $this->connection->fetchAll($query, $params);
return $this->collectionFactory->createMany($databoxId, $references, $rows);
return $this->collectionFactory->createMany($this->databoxId, $references, $rows);
}
/**
@@ -68,13 +75,11 @@ class DbalCollectionRepository implements CollectionRepository
return null;
}
$connection = $this->connectionProvider->getConnection($reference->getDataboxId());
$query = self::$query . ' WHERE coll_id = :collectionId';
$row = $connection->fetchAssoc($query, [ ':collectionId' => $reference->getCollectionId() ]);
$row = $this->connection->fetchAssoc($query, [ ':collectionId' => $reference->getCollectionId() ]);
if ($row !== false) {
return $this->collectionFactory->create($reference->getDataboxId(), $reference, $row);
return $this->collectionFactory->create($this->databoxId, $reference, $row);
}
return null;
@@ -93,13 +98,11 @@ class DbalCollectionRepository implements CollectionRepository
return null;
}
$connection = $this->connectionProvider->getConnection($databoxId);
$query = self::$query . ' WHERE coll_id = :collectionId';
$row = $connection->fetchAssoc($query, [ ':collectionId' => $reference->getCollectionId() ]);
$row = $this->connection->fetchAssoc($query, [ ':collectionId' => $reference->getCollectionId() ]);
if ($row !== false) {
return $this->collectionFactory->create($databoxId, $reference, $row);
return $this->collectionFactory->create($this->databoxId, $reference, $row);
}
return null;

View File

@@ -12,11 +12,15 @@
namespace Alchemy\Phrasea\Core\Provider;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Collection\ArrayCacheCollectionRepository;
use Alchemy\Phrasea\Collection\CollectionFactory;
use Alchemy\Phrasea\Collection\DbalCollectionReferenceRepository;
use Alchemy\Phrasea\Collection\DbalCollectionRepository;
use Alchemy\Phrasea\Collection\CachedCollectionRepository;
use Alchemy\Phrasea\Collection\CollectionRepositoryRegistry;
use Alchemy\Phrasea\Collection\Factory\ArrayCachedCollectionRepositoryFactory;
use Alchemy\Phrasea\Collection\Factory\CachedCollectionRepositoryFactory;
use Alchemy\Phrasea\Collection\Factory\DbalCollectionRepositoryFactory;
use Alchemy\Phrasea\Collection\Reference\DbalCollectionReferenceRepository;
use Alchemy\Phrasea\Collection\Repository\ArrayCacheCollectionRepository;
use Alchemy\Phrasea\Collection\Repository\CachedCollectionRepository;
use Alchemy\Phrasea\Collection\Repository\DbalCollectionRepository;
use Alchemy\Phrasea\Databox\CachingDataboxRepositoryDecorator;
use Alchemy\Phrasea\Databox\DataboxConnectionProvider;
use Alchemy\Phrasea\Databox\DataboxFactory;
@@ -149,19 +153,26 @@ class RepositoriesServiceProvider implements ServiceProviderInterface
return new DbalCollectionReferenceRepository($app->getApplicationBox()->get_connection());
});
$app['repo.collections'] = $app->share(function (PhraseaApplication $app) {
$appbox = $app->getApplicationBox();
$app['repo.collections-registry'] = $app->share(function (PhraseaApplication $app) {
$factory = new CollectionFactory($app);
$connectionProvider = new DataboxConnectionProvider($appbox);
$repository = new DbalCollectionRepository(
$connectionProvider = new DataboxConnectionProvider($app->getApplicationBox());
$repositoryFactory = new DbalCollectionRepositoryFactory(
$connectionProvider,
$app['repo.collection-references'],
$factory
$factory,
$app['repo.collection-references']
);
$repository = new CachedCollectionRepository($app, $repository, $app['cache'], 'collection_');
$repositoryFactory = new CachedCollectionRepositoryFactory(
$app,
$repositoryFactory,
$app['cache'],
'phrasea.collections'
);
return new ArrayCacheCollectionRepository($repository);
$repositoryFactory = new ArrayCachedCollectionRepositoryFactory($repositoryFactory);
return new CollectionRepositoryRegistry($repositoryFactory, $app['repo.collection-references']);
});
}

View File

@@ -10,8 +10,9 @@
*/
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Collection\CollectionReference;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Alchemy\Phrasea\Collection\CollectionRepositoryRegistry;
use Alchemy\Phrasea\Collection\Reference\CollectionReference;
use Alchemy\Phrasea\Collection\Reference\CollectionReferenceRepository;
use Alchemy\Phrasea\Core\Event\Collection\CollectionEvent;
use Alchemy\Phrasea\Core\Event\Collection\CollectionEvents;
use Alchemy\Phrasea\Core\Event\Collection\CreatedEvent;
@@ -196,9 +197,18 @@ EOT;
*/
public static function getByBaseId(Application $app, $base_id)
{
/** @var CollectionRepository $repository */
$repository = $app['repo.collections'];
$collection = $repository->find($base_id);
/** @var CollectionReferenceRepository $referenceRepository */
$referenceRepository = $app['repo.collection-references'];
$reference = $referenceRepository->find($base_id);
if (! $reference) {
throw new Exception_Databox_CollectionNotFound(sprintf("Collection with base_id %s could not be found", $base_id));
}
/** @var CollectionRepositoryRegistry $registry */
$registry = $app['repo.collections-registry'];
$repository = $registry->getRepositoryByDatabox($reference->getDataboxId());
$collection = $repository->find($reference->getCollectionId());
if (! $collection) {
throw new Exception_Databox_CollectionNotFound(sprintf("Collection with base_id %s could not be found", $base_id));
@@ -221,16 +231,23 @@ EOT;
{
assert(is_int($coll_id));
/** @var CollectionRepository $repository */
$repository = $app['repo.collections'];
$collection = $repository->findByCollectionId($databox->get_sbas_id(), $coll_id);
/** @var CollectionRepositoryRegistry $registry */
$registry = $app['repo.collections-registry'];
$repository = $registry->getRepositoryByDatabox($databox->get_sbas_id());
$collection = $repository->find($coll_id);
if (!$collection) {
throw new Exception_Databox_CollectionNotFound(sprintf("Collection with base_id %s could not be found", $base_id));
throw new Exception_Databox_CollectionNotFound(sprintf(
"Collection with collection ID %d could not be found",
$coll_id
));
}
if (!$app['conf.restrictions']->isCollectionAvailable($collection)) {
throw new Exception_Databox_CollectionNotFound('Collection `' . $collection->get_base_id() . '` is not available here.');
throw new Exception_Databox_CollectionNotFound(sprintf(
'Collection `%d` is not available here.',
$collection->get_base_id())
);
}
return $collection;
@@ -344,6 +361,7 @@ EOT;
$this->delete_data_from_cache();
$appbox->delete_data_from_cache(appbox::CACHE_LIST_BASES);
$this->databox->delete_data_from_cache(databox::CACHE_COLLECTIONS);
cache_databox::update($this->app, $this->databox->get_sbas_id(), 'structure');
return $this;

View File

@@ -10,7 +10,7 @@
*/
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Collection\CollectionRepository;
use Alchemy\Phrasea\Collection\CollectionRepositoryRegistry;
use Alchemy\Phrasea\Core\Connection\ConnectionSettings;
use Alchemy\Phrasea\Core\PhraseaTokens;
use Alchemy\Phrasea\Core\Version\DataboxVersionRepository;
@@ -155,14 +155,19 @@ class databox extends base implements \Alchemy\Phrasea\Core\Thumbnail\Thumbnaile
static $collections;
if ($collections === null) {
/** @var CollectionRepository $collectionsRepository */
$collectionsRepository = $this->app['repo.collections'];
$collections = $collectionsRepository->findAllByDatabox($this->get_sbas_id());
/** @var CollectionRepositoryRegistry $repositoryRegistry */
$repositoryRegistry = $this->app['repo.collections-registry'];
$repository = $repositoryRegistry->getRepositoryByDatabox($this->get_sbas_id());
$collections = $repository->findAll();
}
return $collections;
}
/**
* @return int[]
*/
public function get_collection_unique_ids()
{
static $collectionsIds;