From 931482539f9942b4517f94dc93906a7f6dbe21b2 Mon Sep 17 00:00:00 2001 From: Claus Due Date: Thu, 24 Oct 2024 14:31:09 +0200 Subject: [PATCH] [FEATURE] Introduce Doctrine proxy Allows Doctrine usage on different major versions which change the way queries are executed and fetched. --- Classes/Content/ContentTypeValidator.php | 3 +- ...rdBasedContentTypeDefinitionRepository.php | 12 ++--- .../Transformer/FileTransformer.php | 10 +++-- .../Integration/FormEngine/UserFunctions.php | 3 +- .../HookSubscribers/DataHandlerSubscriber.php | 22 ++++++--- .../Converter/InlineRecordDataConverter.php | 26 +++++++---- .../Overrides/BackendLayoutView.php | 3 +- Classes/Service/RecordService.php | 27 +++++------ Classes/Utility/DoctrineQueryProxy.php | 45 +++++++++++++++++++ Tests/Mock/QueryBuilder.php | 15 +++++++ Tests/Mock/Result.php | 5 +++ phpstan-baseline.neon | 4 ++ 12 files changed, 135 insertions(+), 40 deletions(-) create mode 100644 Classes/Utility/DoctrineQueryProxy.php diff --git a/Classes/Content/ContentTypeValidator.php b/Classes/Content/ContentTypeValidator.php index 96d68973b..237e59ede 100644 --- a/Classes/Content/ContentTypeValidator.php +++ b/Classes/Content/ContentTypeValidator.php @@ -13,6 +13,7 @@ use FluidTYPO3\Flux\Content\TypeDefinition\ContentTypeDefinitionInterface; use FluidTYPO3\Flux\Content\TypeDefinition\FluidRenderingContentTypeDefinitionInterface; use FluidTYPO3\Flux\Service\TemplateValidationService; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use FluidTYPO3\Flux\Utility\ExtensionNamingUtility; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -127,7 +128,7 @@ protected function countUsages(ContentTypeDefinitionInterface $definition): int $queryBuilder->createNamedParameter($definition->getContentTypeName(), Connection::PARAM_STR) ) ); - return (integer) $queryBuilder->execute()->rowCount(); + return (integer) DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder)->rowCount(); } /** diff --git a/Classes/Content/TypeDefinition/RecordBased/RecordBasedContentTypeDefinitionRepository.php b/Classes/Content/TypeDefinition/RecordBased/RecordBasedContentTypeDefinitionRepository.php index 0f42d16b8..baeb09084 100644 --- a/Classes/Content/TypeDefinition/RecordBased/RecordBasedContentTypeDefinitionRepository.php +++ b/Classes/Content/TypeDefinition/RecordBased/RecordBasedContentTypeDefinitionRepository.php @@ -10,6 +10,7 @@ */ use Doctrine\DBAL\Exception\TableNotFoundException; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use FluidTYPO3\Flux\Utility\ExtensionNamingUtility; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -35,16 +36,17 @@ public function fetchContentTypeDefinitions(): array $queryBuilder = $this->connectionPool->getQueryBuilderForTable('content_types'); /** @var string[] $keys */ $keys = array_keys($GLOBALS['TCA']['content_types']['columns'] ?? ['*' => '']); - /** @var array[] $typeRecords */ - $typeRecords = $queryBuilder->select(...$keys) + $queryBuilder->select(...$keys) ->from('content_types') ->where( $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)), $queryBuilder->expr()->eq('hidden', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)) ) - ->orderBy('sorting', 'ASC') - ->execute() - ->fetchAll(); + ->orderBy('sorting', 'ASC'); + + $result = $typeRecords = DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder); + /** @var array[] $typeRecords */ + $typeRecords = DoctrineQueryProxy::fetchAllAssociative($result); } catch (TableNotFoundException $exception) { $typeRecords = []; } diff --git a/Classes/Form/Transformation/Transformer/FileTransformer.php b/Classes/Form/Transformation/Transformer/FileTransformer.php index 9d274d09b..11fac9ab4 100644 --- a/Classes/Form/Transformation/Transformer/FileTransformer.php +++ b/Classes/Form/Transformation/Transformer/FileTransformer.php @@ -15,6 +15,7 @@ use FluidTYPO3\Flux\Form\FormInterface; use FluidTYPO3\Flux\Form\OptionCarryingInterface; use FluidTYPO3\Flux\Form\Transformation\DataTransformerInterface; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use FluidTYPO3\Flux\Utility\ExtensionConfigurationUtility; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException; @@ -92,7 +93,7 @@ public function transform(FormInterface $component, string $type, $value) protected function fetchFileReferences(string $table, string $fieldName, int $recordUid): array { $queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_file_reference'); - $result = $queryBuilder + $queryBuilder ->select('uid') ->from('sys_file_reference') ->where( @@ -100,11 +101,12 @@ protected function fetchFileReferences(string $table, string $fieldName, int $re $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter($table)), $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter($fieldName)) ) - ->orderBy('sorting_foreign') - ->execute(); + ->orderBy('sorting_foreign'); + + $result = DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder); $references = []; - while ($row = $result->fetchAssociative()) { + while ($row = DoctrineQueryProxy::fetchAssociative($result)) { /** @var array $row */ try { $references[] = $this->resourceFactory->getFileReferenceObject($row['uid']); diff --git a/Classes/Integration/FormEngine/UserFunctions.php b/Classes/Integration/FormEngine/UserFunctions.php index 485a4fc98..e1b637944 100644 --- a/Classes/Integration/FormEngine/UserFunctions.php +++ b/Classes/Integration/FormEngine/UserFunctions.php @@ -10,6 +10,7 @@ use FluidTYPO3\Flux\Provider\ProviderResolver; use FluidTYPO3\Flux\Utility\ColumnNumberUtility; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -121,7 +122,7 @@ protected function determineTakenColumnPositionsWithinParent(string $table, int $queryBuilder->expr()->gte('colPos', $minimumColPosValue), $queryBuilder->expr()->lt('colPos', $maximumColPosValue) ); - $rows = $query->execute()->fetchAll(); + $rows = DoctrineQueryProxy::fetchAllAssociative(DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder)); return empty($rows) ? [] : array_map(function ($colPos) { return ColumnNumberUtility::calculateLocalColumnNumber($colPos); }, array_unique(array_column($rows, 'colPos'))); diff --git a/Classes/Integration/HookSubscribers/DataHandlerSubscriber.php b/Classes/Integration/HookSubscribers/DataHandlerSubscriber.php index aa31b314b..775ca8458 100644 --- a/Classes/Integration/HookSubscribers/DataHandlerSubscriber.php +++ b/Classes/Integration/HookSubscribers/DataHandlerSubscriber.php @@ -16,6 +16,7 @@ use FluidTYPO3\Flux\Provider\PageProvider; use FluidTYPO3\Flux\Provider\ProviderResolver; use FluidTYPO3\Flux\Utility\ColumnNumberUtility; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use FluidTYPO3\Flux\Utility\ExtensionConfigurationUtility; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Cache\CacheManager; @@ -161,7 +162,8 @@ public function processDatamap_afterDatabaseOperations($command, $table, $id, $f $queryBuilder->createNamedParameter($GLOBALS['BE_USER']->workspace, Connection::PARAM_INT) ) ) - )->execute(); + ); + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder); } static::$copiedRecords[$fieldArray['t3_origuid']] = true; @@ -500,7 +502,9 @@ protected function getSingleRecordWithRestrictions(string $table, int $uid, stri ->from($table) ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT))); /** @var array|false $firstResult */ - $firstResult = $queryBuilder->execute()->fetch(); + $firstResult = DoctrineQueryProxy::fetchAssociative( + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder) + ); return $firstResult ?: null; } @@ -515,7 +519,9 @@ protected function getSingleRecordWithoutRestrictions(string $table, int $uid, s ->from($table) ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT))); /** @var array|false $firstResult */ - $firstResult = $queryBuilder->execute()->fetch(); + $firstResult = DoctrineQueryProxy::fetchAssociative( + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder) + ); return $firstResult ?: null; } @@ -535,7 +541,9 @@ protected function getMostRecentCopyOfRecord(int $uid, string $fieldsToSelect = $queryBuilder->expr()->neq('t3ver_state', -1) ); /** @var array|false $firstResult */ - $firstResult = $queryBuilder->execute()->fetch(); + $firstResult = DoctrineQueryProxy::fetchAssociative( + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder) + ); return $firstResult ?: null; } @@ -568,7 +576,9 @@ protected function getTranslatedVersionOfParentInLanguageOnPage( ) ); /** @var array|false $firstResult */ - $firstResult = $queryBuilder->execute()->fetch(); + $firstResult = DoctrineQueryProxy::fetchAssociative( + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder) + ); return $firstResult ?: null; } @@ -642,7 +652,7 @@ protected function getParentAndRecordsNestedInGrid( $query->andWhere($queryBuilder->expr()->neq('pid', -1)); } - $records = $query->execute()->fetchAll(); + $records = DoctrineQueryProxy::fetchAllAssociative(DoctrineQueryProxy::executeQueryOnQueryBuilder($query)); // Selecting records to return. The "sorting DESC" is very intentional; copy operations will place records // into the top of columns which means reading records in reverse order causes the correct final order. diff --git a/Classes/Integration/NormalizedData/Converter/InlineRecordDataConverter.php b/Classes/Integration/NormalizedData/Converter/InlineRecordDataConverter.php index b1f1cea5f..0bae8826d 100644 --- a/Classes/Integration/NormalizedData/Converter/InlineRecordDataConverter.php +++ b/Classes/Integration/NormalizedData/Converter/InlineRecordDataConverter.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace FluidTYPO3\Flux\Integration\NormalizedData\Converter; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -193,7 +194,8 @@ protected function insertFieldData(array $fieldData): int { $connection = $this->createConnectionForTable('flux_field'); $queryBuilder = $connection->createQueryBuilder(); - $queryBuilder->insert('flux_field')->values($fieldData)->execute(); + $queryBuilder->insert('flux_field')->values($fieldData); + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder); return (int) $connection->lastInsertId('flux_field'); } @@ -204,7 +206,8 @@ protected function insertSheetData(array $sheetData): int { $connection = $this->createConnectionForTable('flux_sheet'); $queryBuilder = $connection->createQueryBuilder(); - $queryBuilder->insert('flux_sheet')->values($sheetData)->execute(); + $queryBuilder->insert('flux_sheet')->values($sheetData); + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder); return (int) $connection->lastInsertId('flux_sheet'); } @@ -215,10 +218,13 @@ protected function fetchFieldData(int $uid): array { $settings = []; $queryBuilder = $this->createQueryBuilderForTable('flux_field'); - /** @var array[] $result */ - $result = $queryBuilder->select('uid', 'field_name', 'field_value')->from('flux_field')->where( + $queryBuilder->select('uid', 'field_name', 'field_value')->from('flux_field')->where( $queryBuilder->expr()->eq('sheet', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)) - )->execute()->fetchAllAssociative(); + ); + /** @var array[] $result */ + $result = DoctrineQueryProxy::fetchAllAssociative( + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder) + ); foreach ($result as $fieldRecord) { $settings = $this->assignVariableByDottedPath( $settings, @@ -235,7 +241,7 @@ protected function fetchFieldData(int $uid): array protected function fetchConfigurationRecords(): array { $queryBuilder = $this->createQueryBuilderForTable('flux_sheet'); - return $queryBuilder->select('*')->from('flux_sheet')->where( + $queryBuilder->select('*')->from('flux_sheet')->where( $queryBuilder->expr()->eq( 'source_table', $queryBuilder->createNamedParameter($this->table, Connection::PARAM_STR) @@ -244,7 +250,8 @@ protected function fetchConfigurationRecords(): array 'source_field', $queryBuilder->createNamedParameter($this->field, Connection::PARAM_STR) ), - )->execute()->fetchAllAssociative(); + ); + return DoctrineQueryProxy::fetchAllAssociative(DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder)); } /** @@ -253,9 +260,10 @@ protected function fetchConfigurationRecords(): array protected function fetchSheetRecord(string $sheetName): ?array { $queryBuilder = $this->createQueryBuilderForTable('flux_sheet'); - return $queryBuilder->select('uid', 'name')->from('flux_sheet')->where( + $queryBuilder->select('uid', 'name')->from('flux_sheet')->where( $queryBuilder->expr()->eq('name', $queryBuilder->createNamedParameter($sheetName, Connection::PARAM_STR)) - )->execute()->fetchAssociative() ?: null; + ); + return DoctrineQueryProxy::fetchAssociative(DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder)); } /** diff --git a/Classes/Integration/Overrides/BackendLayoutView.php b/Classes/Integration/Overrides/BackendLayoutView.php index 6a10ecb4b..c91988be5 100644 --- a/Classes/Integration/Overrides/BackendLayoutView.php +++ b/Classes/Integration/Overrides/BackendLayoutView.php @@ -13,6 +13,7 @@ use FluidTYPO3\Flux\Provider\Interfaces\GridProviderInterface; use FluidTYPO3\Flux\Provider\ProviderResolver; use FluidTYPO3\Flux\Utility\ColumnNumberUtility; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -152,7 +153,7 @@ protected function loadRecordFromTable(string $table, int $uid): ?array ->where($queryBuilder->expr()->eq('uid', $uid)); $query->getRestrictions()->removeAll(); /** @var array[] $results */ - $results = $query->execute()->fetchAll(); + $results = DoctrineQueryProxy::fetchAllAssociative(DoctrineQueryProxy::executeQueryOnQueryBuilder($query)); return $results[0] ?? null; } diff --git a/Classes/Service/RecordService.php b/Classes/Service/RecordService.php index e115dac7e..c549b9996 100644 --- a/Classes/Service/RecordService.php +++ b/Classes/Service/RecordService.php @@ -11,6 +11,7 @@ use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Result; +use FluidTYPO3\Flux\Utility\DoctrineQueryProxy; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\VisibilityAspect; @@ -55,7 +56,7 @@ public function get( $statement->setFirstResult($offset); } - return $statement->execute()->fetchAll(); + return DoctrineQueryProxy::fetchAllAssociative(DoctrineQueryProxy::executeQueryOnQueryBuilder($statement)); } public function getSingle(string $table, string $fields, int $uid): ?array @@ -64,11 +65,12 @@ public function getSingle(string $table, string $fields, int $uid): ?array return BackendUtility::getRecord($table, $uid, $fields); } $queryBuilder = $this->getQueryBuilder($table); - $results = $queryBuilder->from($table) + $queryBuilder->from($table) ->select(...explode(',', $fields)) - ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid))) - ->execute() - ->fetchAll() ?: []; + ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid))); + $results = DoctrineQueryProxy::fetchAllAssociative( + DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder) + ); $firstResult = reset($results); return $firstResult ? (array) $firstResult : null; } @@ -84,7 +86,7 @@ public function update(string $table, array $record) foreach ($record as $name => $value) { $builder->set($name, $value); } - return $builder->execute(); + return DoctrineQueryProxy::executeQueryOnQueryBuilder($builder); } /** @@ -94,20 +96,19 @@ public function delete(string $table, $recordOrUid): bool { $clauseUid = true === is_array($recordOrUid) ? $recordOrUid['uid'] : $recordOrUid; $queryBuilder = $this->getQueryBuilder($table); - return (bool) $queryBuilder->delete($table) - ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($clauseUid))) - ->execute(); + $queryBuilder->delete($table) + ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($clauseUid))); + return (bool) DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder); } public function preparedGet(string $table, string $fields, string $condition, array $values = []): array { - return $this->getQueryBuilder($table) + $queryBuilder = $this->getQueryBuilder($table) ->select(...explode(',', $fields)) ->from($table) ->where($condition) - ->setParameters($values) - ->execute() - ->fetchAll(); + ->setParameters($values); + return DoctrineQueryProxy::fetchAllAssociative(DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder)); } protected function getQueryBuilder(string $table): QueryBuilder diff --git a/Classes/Utility/DoctrineQueryProxy.php b/Classes/Utility/DoctrineQueryProxy.php new file mode 100644 index 000000000..1f6627bf2 --- /dev/null +++ b/Classes/Utility/DoctrineQueryProxy.php @@ -0,0 +1,45 @@ +executeQuery(); + } + /** @var Result $result */ + $result = $queryBuilder->execute(); + return $result; + } + + public static function fetchAssociative(Result $result): ?array + { + if (method_exists($result, 'fetchAssociative')) { + /** @var array|null $output */ + $output = $result->fetchAssociative() ?: null; + } else { + /** @var array|null $output */ + $output = $result->fetch(FetchMode::ASSOCIATIVE); + } + + return $output; + } + + public static function fetchAllAssociative(Result $result): array + { + if (method_exists($result, 'fetchAllAssociative')) { + return $result->fetchAllAssociative() ?: []; + } + return $result->fetchAll(FetchMode::ASSOCIATIVE) ?: []; + } +} diff --git a/Tests/Mock/QueryBuilder.php b/Tests/Mock/QueryBuilder.php index d15b3ab8b..d7db20727 100644 --- a/Tests/Mock/QueryBuilder.php +++ b/Tests/Mock/QueryBuilder.php @@ -20,6 +20,11 @@ public function execute(): Result return $this->result; } + public function executeStatement(): int + { + return $this->result->rowCount(); + } + public function expr(): ExpressionBuilder { return $this->expressionBuilder; @@ -98,4 +103,14 @@ public function addOrderBy(string $fieldName, string $order = null): \TYPO3\CMS\ { return $this; } + + public function getQueryPart(string $queryPartName) + { + return ''; + } + + public function getQueryParts(): array + { + return []; + } } diff --git a/Tests/Mock/Result.php b/Tests/Mock/Result.php index 507a76412..b08a9a0b8 100644 --- a/Tests/Mock/Result.php +++ b/Tests/Mock/Result.php @@ -38,4 +38,9 @@ public function fetchAllAssociative(): array { return $this->returns; } + + public function rowCount(): int + { + return count($this->returns); + } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4b6ccaeb6..10c5a2751 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,9 @@ parameters: ignoreErrors: + - + message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Result\\:\\:fetch(All)?\\(\\)\\.$#" + count: 2 + path: Classes/Utility/DoctrineQueryProxy.php - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Package\\\\MetaData\\:\\:getTitle\\(\\)\\.$#" count: 1