diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index a4fc9e03df..1df82b6b60 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -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()); diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php index 89e398c46d..d65a61fd75 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php @@ -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 { diff --git a/lib/Alchemy/Phrasea/Media/ArrayTechnicalDataSet.php b/lib/Alchemy/Phrasea/Media/ArrayTechnicalDataSet.php index 4809823fe8..e4ab83baf1 100644 --- a/lib/Alchemy/Phrasea/Media/ArrayTechnicalDataSet.php +++ b/lib/Alchemy/Phrasea/Media/ArrayTechnicalDataSet.php @@ -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(); } diff --git a/lib/Alchemy/Phrasea/Media/Factory/DbalRepositoryFactory.php b/lib/Alchemy/Phrasea/Media/Factory/DbalRepositoryFactory.php new file mode 100644 index 0000000000..5ec88a6433 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/Factory/DbalRepositoryFactory.php @@ -0,0 +1,36 @@ +connectionProvider = $connectionProvider; + } + + public function createRepositoryForDatabox($databoxId) + { + return new DbalRecordTechnicalDataSetRepository( + $this->connectionProvider->getConnection($databoxId), + new TechnicalDataFactory() + ); + } +} diff --git a/lib/Alchemy/Phrasea/Media/Factory/TechnicalDataFactory.php b/lib/Alchemy/Phrasea/Media/Factory/TechnicalDataFactory.php new file mode 100644 index 0000000000..c03f80e911 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/Factory/TechnicalDataFactory.php @@ -0,0 +1,35 @@ +recordId = (int)$recordId; + parent::__construct($technicalData); + } + + /** + * @return int + */ + public function getRecordId() + { + return $this->recordId; + } +} diff --git a/lib/Alchemy/Phrasea/Media/RecordTechnicalDataSetRepository.php b/lib/Alchemy/Phrasea/Media/RecordTechnicalDataSetRepository.php new file mode 100644 index 0000000000..46d388f29f --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/RecordTechnicalDataSetRepository.php @@ -0,0 +1,20 @@ +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]; + } +} diff --git a/lib/Alchemy/Phrasea/Media/Repository/DbalRecordTechnicalDataSetRepository.php b/lib/Alchemy/Phrasea/Media/Repository/DbalRecordTechnicalDataSetRepository.php new file mode 100644 index 0000000000..5c1e035f11 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/Repository/DbalRecordTechnicalDataSetRepository.php @@ -0,0 +1,75 @@ +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); + } +} diff --git a/lib/Alchemy/Phrasea/Media/TechnicalDataService.php b/lib/Alchemy/Phrasea/Media/TechnicalDataService.php new file mode 100644 index 0000000000..da1449cfc2 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/TechnicalDataService.php @@ -0,0 +1,52 @@ +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; + } +} diff --git a/lib/Alchemy/Phrasea/Media/TechnicalDataServiceProvider.php b/lib/Alchemy/Phrasea/Media/TechnicalDataServiceProvider.php new file mode 100644 index 0000000000..d0f19e3159 --- /dev/null +++ b/lib/Alchemy/Phrasea/Media/TechnicalDataServiceProvider.php @@ -0,0 +1,34 @@ +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 + } +} diff --git a/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php b/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php index 802d4a9e67..001c4fe93b 100644 --- a/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php +++ b/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php @@ -1,5 +1,5 @@ $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; } } diff --git a/lib/classes/record/adapter.php b/lib/classes/record/adapter.php index 1b2abc1765..88c7b3d0a1 100644 --- a/lib/classes/record/adapter.php +++ b/lib/classes/record/adapter.php @@ -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 */