mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-12 12:33:26 +00:00
Remove one N+1 by fetching TechnicalDataSets
This commit is contained in:
@@ -80,6 +80,7 @@ use Alchemy\Phrasea\Form\Extension\HelpTypeExtension;
|
||||
use Alchemy\Phrasea\Media\DatafilesResolver;
|
||||
use Alchemy\Phrasea\Media\MediaAccessorResolver;
|
||||
use Alchemy\Phrasea\Media\PermalinkMediaResolver;
|
||||
use Alchemy\Phrasea\Media\TechnicalDataServiceProvider;
|
||||
use Alchemy\Phrasea\Model\Entities\User;
|
||||
use Doctrine\DBAL\Event\ConnectionEventArgs;
|
||||
use MediaVorus\Media\MediaInterface;
|
||||
@@ -190,6 +191,7 @@ class Application extends SilexApplication
|
||||
$this->register(new NotificationDelivererServiceProvider());
|
||||
$this->register(new RepositoriesServiceProvider());
|
||||
$this->register(new ManipulatorServiceProvider());
|
||||
$this->register(new TechnicalDataServiceProvider());
|
||||
$this->register(new InstallerServiceProvider());
|
||||
$this->register(new PhraseaVersionServiceProvider());
|
||||
|
||||
|
@@ -1068,7 +1068,11 @@ class V1Controller extends Controller
|
||||
/** @var SearchEngineResult $search_result */
|
||||
$references = new RecordReferenceCollection($search_result->getResults());
|
||||
|
||||
foreach ($references->toRecords($this->getApplicationBox()) as $record) {
|
||||
$technicalData = $this->app['service.technical_data']->fetchRecordsTechnicalData($references);
|
||||
|
||||
foreach ($references->toRecords($this->getApplicationBox()) as $index => $record) {
|
||||
$record->setTechnicalDataSet($technicalData[$index]);
|
||||
|
||||
if ($record->isStory()) {
|
||||
$ret['results']['stories'][] = $this->listStory($request, $record);
|
||||
} else {
|
||||
|
@@ -11,22 +11,20 @@ namespace Alchemy\Phrasea\Media;
|
||||
|
||||
use Assert\Assertion;
|
||||
|
||||
final class ArrayTechnicalDataSet implements \IteratorAggregate, TechnicalDataSet
|
||||
class ArrayTechnicalDataSet implements \IteratorAggregate, TechnicalDataSet
|
||||
{
|
||||
/** @var TechnicalData[] */
|
||||
private $data;
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* @param TechnicalData[] $data
|
||||
*/
|
||||
public function __construct($data = [])
|
||||
{
|
||||
Assertion::allIsInstanceOf($data, TechnicalData::class);
|
||||
|
||||
$this->data = [];
|
||||
Assertion::isTraversable($data);
|
||||
|
||||
foreach ($data as $technicalData) {
|
||||
$this->data[$technicalData->getName()] = $technicalData;
|
||||
$this[] = $technicalData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +39,7 @@ final class ArrayTechnicalDataSet implements \IteratorAggregate, TechnicalDataSe
|
||||
$offset = $offset->getName();
|
||||
}
|
||||
|
||||
return isset($this->data[$offset]) || array_key_exists($offset, $this->data);
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
@@ -50,7 +48,7 @@ final class ArrayTechnicalDataSet implements \IteratorAggregate, TechnicalDataSe
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param null|string $offset
|
||||
* @param TechnicalData $value
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
@@ -58,6 +56,7 @@ final class ArrayTechnicalDataSet implements \IteratorAggregate, TechnicalDataSe
|
||||
Assertion::isInstanceOf($value, TechnicalData::class);
|
||||
|
||||
$name = $value->getName();
|
||||
|
||||
if (null !== $offset) {
|
||||
Assertion::eq($name, $offset);
|
||||
}
|
||||
@@ -82,6 +81,7 @@ final class ArrayTechnicalDataSet implements \IteratorAggregate, TechnicalDataSe
|
||||
public function getValues()
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->data as $key => $value) {
|
||||
$values[$key] = $value->getValue();
|
||||
}
|
||||
|
36
lib/Alchemy/Phrasea/Media/Factory/DbalRepositoryFactory.php
Normal file
36
lib/Alchemy/Phrasea/Media/Factory/DbalRepositoryFactory.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media\Factory;
|
||||
|
||||
use Alchemy\Phrasea\Databox\DataboxConnectionProvider;
|
||||
use Alchemy\Phrasea\Media\RecordTechnicalDataSetRepositoryFactory;
|
||||
use Alchemy\Phrasea\Media\Repository\DbalRecordTechnicalDataSetRepository;
|
||||
|
||||
class DbalRepositoryFactory implements RecordTechnicalDataSetRepositoryFactory
|
||||
{
|
||||
/**
|
||||
* @var DataboxConnectionProvider
|
||||
*/
|
||||
private $connectionProvider;
|
||||
|
||||
public function __construct(DataboxConnectionProvider $connectionProvider)
|
||||
{
|
||||
$this->connectionProvider = $connectionProvider;
|
||||
}
|
||||
|
||||
public function createRepositoryForDatabox($databoxId)
|
||||
{
|
||||
return new DbalRecordTechnicalDataSetRepository(
|
||||
$this->connectionProvider->getConnection($databoxId),
|
||||
new TechnicalDataFactory()
|
||||
);
|
||||
}
|
||||
}
|
35
lib/Alchemy/Phrasea/Media/Factory/TechnicalDataFactory.php
Normal file
35
lib/Alchemy/Phrasea/Media/Factory/TechnicalDataFactory.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media\Factory;
|
||||
|
||||
use Alchemy\Phrasea\Media\FloatTechnicalData;
|
||||
use Alchemy\Phrasea\Media\IntegerTechnicalData;
|
||||
use Alchemy\Phrasea\Media\StringTechnicalData;
|
||||
use Alchemy\Phrasea\Media\TechnicalData;
|
||||
|
||||
class TechnicalDataFactory
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @return TechnicalData
|
||||
*/
|
||||
public function createFromNameAndValue($name, $value)
|
||||
{
|
||||
if (ctype_digit($value)) {
|
||||
return new IntegerTechnicalData($name, $value);
|
||||
} elseif (preg_match('/[0-9]?\.[0-9]+/', $value)) {
|
||||
return new FloatTechnicalData($name, $value);
|
||||
}
|
||||
|
||||
return new StringTechnicalData($name, $value);
|
||||
}
|
||||
}
|
37
lib/Alchemy/Phrasea/Media/RecordTechnicalDataSet.php
Normal file
37
lib/Alchemy/Phrasea/Media/RecordTechnicalDataSet.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media;
|
||||
|
||||
class RecordTechnicalDataSet extends ArrayTechnicalDataSet
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $recordId;
|
||||
|
||||
/**
|
||||
* @param int $recordId
|
||||
* @param TechnicalData[] $technicalData
|
||||
*/
|
||||
public function __construct($recordId, $technicalData = [])
|
||||
{
|
||||
$this->recordId = (int)$recordId;
|
||||
parent::__construct($technicalData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRecordId()
|
||||
{
|
||||
return $this->recordId;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media;
|
||||
|
||||
interface RecordTechnicalDataSetRepository
|
||||
{
|
||||
/**
|
||||
* @param int[] $recordIds
|
||||
* @return RecordTechnicalDataSet[]
|
||||
*/
|
||||
public function findByRecordIds(array $recordIds);
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media;
|
||||
|
||||
interface RecordTechnicalDataSetRepositoryFactory
|
||||
{
|
||||
/**
|
||||
* @param int $databoxId
|
||||
* @return RecordTechnicalDataSetRepository
|
||||
*/
|
||||
public function createRepositoryForDatabox($databoxId);
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media;
|
||||
|
||||
class RecordTechnicalDataSetRepositoryProvider
|
||||
{
|
||||
/**
|
||||
* @var RecordTechnicalDataSetRepository[]
|
||||
*/
|
||||
private $repositories = [];
|
||||
|
||||
/**
|
||||
* @var RecordTechnicalDataSetRepositoryFactory
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
public function __construct(RecordTechnicalDataSetRepositoryFactory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $databoxId
|
||||
* @return RecordTechnicalDataSetRepository
|
||||
*/
|
||||
public function getRepositoryFor($databoxId)
|
||||
{
|
||||
if (!isset($this->repositories[$databoxId])) {
|
||||
$this->repositories[$databoxId] = $this->factory->createRepositoryForDatabox($databoxId);
|
||||
}
|
||||
|
||||
return $this->repositories[$databoxId];
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media\Repository;
|
||||
|
||||
use Alchemy\Phrasea\Media\Factory\TechnicalDataFactory;
|
||||
use Alchemy\Phrasea\Media\RecordTechnicalDataSet;
|
||||
use Alchemy\Phrasea\Media\RecordTechnicalDataSetRepository;
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
class DbalRecordTechnicalDataSetRepository implements RecordTechnicalDataSetRepository
|
||||
{
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @var TechnicalDataFactory
|
||||
*/
|
||||
private $dataFactory;
|
||||
|
||||
public function __construct(Connection $connection, TechnicalDataFactory $dataFactory)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->dataFactory = $dataFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $recordIds
|
||||
* @return RecordTechnicalDataSet[]
|
||||
*/
|
||||
public function findByRecordIds(array $recordIds)
|
||||
{
|
||||
if (empty($recordIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = $this->connection->fetchAll(
|
||||
'SELECT record_id, name, value FROM technical_datas WHERE record_id IN (:recordIds)',
|
||||
['recordIds' => $recordIds],
|
||||
['recordIds' => Connection::PARAM_INT_ARRAY]
|
||||
);
|
||||
|
||||
return $this->mapSetsFromDatabaseResult($recordIds, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $recordIds
|
||||
* @param array $data
|
||||
* @return RecordTechnicalDataSet[]
|
||||
*/
|
||||
private function mapSetsFromDatabaseResult(array $recordIds, array $data)
|
||||
{
|
||||
$groups = [];
|
||||
|
||||
foreach ($recordIds as $recordId) {
|
||||
$groups[$recordId] = new RecordTechnicalDataSet($recordId);
|
||||
}
|
||||
|
||||
foreach ($data as $item) {
|
||||
$group =& $groups[$item['record_id']];
|
||||
$group[] = $this->dataFactory->createFromNameAndValue($item['name'], $item['value']);
|
||||
}
|
||||
|
||||
return array_values($groups);
|
||||
}
|
||||
}
|
52
lib/Alchemy/Phrasea/Media/TechnicalDataService.php
Normal file
52
lib/Alchemy/Phrasea/Media/TechnicalDataService.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media;
|
||||
|
||||
use Alchemy\Phrasea\Record\RecordReference;
|
||||
use Alchemy\Phrasea\Record\RecordReferenceCollection;
|
||||
|
||||
class TechnicalDataService
|
||||
{
|
||||
/**
|
||||
* @var RecordTechnicalDataSetRepositoryProvider
|
||||
*/
|
||||
private $provider;
|
||||
|
||||
public function __construct(RecordTechnicalDataSetRepositoryProvider $provider)
|
||||
{
|
||||
$this->provider = $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RecordReference[] $references
|
||||
* @return RecordTechnicalDataSet[]
|
||||
*/
|
||||
public function fetchRecordsTechnicalData($references)
|
||||
{
|
||||
if (!$references instanceof RecordReferenceCollection) {
|
||||
$references = new RecordReferenceCollection($references);
|
||||
}
|
||||
|
||||
$sets = [];
|
||||
|
||||
foreach ($references->groupPerDataboxId() as $databoxId => $indexes) {
|
||||
foreach ($this->provider->getRepositoryFor($databoxId)->findByRecordIds(array_keys($indexes)) as $set) {
|
||||
$index = $indexes[$set->getRecordId()];
|
||||
|
||||
$sets[$index] = $set;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($sets);
|
||||
|
||||
return $sets;
|
||||
}
|
||||
}
|
34
lib/Alchemy/Phrasea/Media/TechnicalDataServiceProvider.php
Normal file
34
lib/Alchemy/Phrasea/Media/TechnicalDataServiceProvider.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Media;
|
||||
|
||||
use Alchemy\Phrasea\Databox\DataboxConnectionProvider;
|
||||
use Alchemy\Phrasea\Media\Factory\DbalRepositoryFactory;
|
||||
use Silex\Application;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
class TechnicalDataServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['service.technical_data'] = $app->share(function (Application $app) {
|
||||
$connectionProvider = new DataboxConnectionProvider($app['phraseanet.appbox']);
|
||||
$repositoryFactory = new DbalRepositoryFactory($connectionProvider);
|
||||
|
||||
return new TechnicalDataService(new RecordTechnicalDataSetRepositoryProvider($repositoryFactory));
|
||||
});
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2016 Alchemy
|
||||
@@ -28,7 +28,7 @@ class RecordReferenceCollection implements \IteratorAggregate
|
||||
foreach ($records as $index => $record) {
|
||||
if (isset($record['id'])) {
|
||||
$references[$index] = RecordReference::createFromRecordReference($record['id']);
|
||||
} elseif (isset($record['databox_id']) && isset($record['record_id'])) {
|
||||
} elseif (isset($record['databox_id'], $record['record_id'])) {
|
||||
$references[$index] = RecordReference::createFromDataboxIdAndRecordId($record['databox_id'], $record['record_id']);
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,6 @@ class RecordReferenceCollection implements \IteratorAggregate
|
||||
|
||||
ksort($records);
|
||||
|
||||
return array_values($records);
|
||||
return $records;
|
||||
}
|
||||
}
|
||||
|
@@ -92,7 +92,7 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
|
||||
private $created;
|
||||
/** @var string */
|
||||
private $original_name;
|
||||
/** @var TechnicalDataSet */
|
||||
/** @var TechnicalDataSet|null */
|
||||
private $technical_data;
|
||||
/** @var string */
|
||||
private $uuid;
|
||||
@@ -727,13 +727,10 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
|
||||
*/
|
||||
public function get_technical_infos($data = '')
|
||||
{
|
||||
if (!$this->technical_data && !$this->mapTechnicalDataFromCache()) {
|
||||
$this->technical_data = [];
|
||||
$rs = $this->fetchTechnicalDataFromDb();
|
||||
if (null === $this->technical_data) {
|
||||
$sets = $this->app['service.technical_data']->fetchRecordsTechnicalData([$this]);
|
||||
|
||||
$this->mapTechnicalDataFromDb($rs);
|
||||
|
||||
$this->set_data_to_cache($this->technical_data, self::CACHE_TECHNICAL_DATA);
|
||||
$this->setTechnicalDataSet(reset($sets));
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
@@ -747,6 +744,15 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
|
||||
return $this->technical_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TechnicalDataSet $dataSet
|
||||
* @internal
|
||||
*/
|
||||
public function setTechnicalDataSet(TechnicalDataSet $dataSet)
|
||||
{
|
||||
$this->technical_data = $dataSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return caption_record
|
||||
*/
|
||||
@@ -1862,58 +1868,6 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
|
||||
$this->mime = $row['mime'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function mapTechnicalDataFromCache()
|
||||
{
|
||||
try {
|
||||
$technical_data = $this->get_data_from_cache(self::CACHE_TECHNICAL_DATA);
|
||||
} catch (Exception $e) {
|
||||
$technical_data = false;
|
||||
}
|
||||
|
||||
if (false === $technical_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->technical_data = $technical_data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|array
|
||||
*/
|
||||
private function fetchTechnicalDataFromDb()
|
||||
{
|
||||
$sql = 'SELECT name, value FROM technical_datas WHERE record_id = :record_id';
|
||||
|
||||
return $this->getDataboxConnection()
|
||||
->fetchAll($sql, ['record_id' => $this->getRecordId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rows
|
||||
*/
|
||||
private function mapTechnicalDataFromDb(array $rows)
|
||||
{
|
||||
$this->technical_data = new ArrayTechnicalDataSet();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
switch (true) {
|
||||
case ctype_digit($row['value']):
|
||||
$this->technical_data[] = new IntegerTechnicalData($row['name'], $row['value']);
|
||||
break;
|
||||
case preg_match('/[0-9]?\.[0-9]+/', $row['value']):
|
||||
$this->technical_data[] = new FloatTechnicalData($row['name'], $row['value']);
|
||||
break;
|
||||
default:
|
||||
$this->technical_data[] = new StringTechnicalData($row['name'], $row['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Connection
|
||||
*/
|
||||
|
Reference in New Issue
Block a user