From bd69d119a0a23381c358352b986c2466f600defb Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Mon, 27 Jan 2020 15:52:01 +0100
Subject: [PATCH] [TASK] Dissolve PersistenceDelegate for persisted mappers

The intention of PersistenceDelegate was to provide a generic API to
resolve or generate route components. However the implementation did
not provide any additional behavior. It just was used as structural
pattern which did not provide any further advantages.

That's the reason why PersistenceDelegate gets dissolved and its
database connection invocation is moved to its corresponding callers
PersistedAliasMapper and PersistedPatternMapper.

For backward-compatibility reasons previous (protected) implementation
is still kept in legacy layer for those two route aspects.

Resolves: #90218
Releases: master, 9.5
Change-Id: I49eb58372c139c9b8274593efa2892a43eb81508
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63049
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Susanne Moog <look@susi.dev>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Johannes Schlier
Reviewed-by: Susanne Moog <look@susi.dev>
---
 .../Routing/Aspect/DelegateInterface.php      |  2 +
 .../Routing/Aspect/PersistedAliasMapper.php   | 97 ++++++++-----------
 .../Routing/Aspect/PersistedPatternMapper.php | 78 +++++++--------
 .../Routing/Aspect/PersistenceDelegate.php    |  2 +
 .../PersistedAliasMapperLegacyTrait.php       | 70 +++++++++++++
 .../PersistedPatternMapperLegacyTrait.php     | 93 ++++++++++++++++++
 6 files changed, 241 insertions(+), 101 deletions(-)
 create mode 100644 typo3/sysext/core/Classes/Routing/Legacy/PersistedAliasMapperLegacyTrait.php
 create mode 100644 typo3/sysext/core/Classes/Routing/Legacy/PersistedPatternMapperLegacyTrait.php

diff --git a/typo3/sysext/core/Classes/Routing/Aspect/DelegateInterface.php b/typo3/sysext/core/Classes/Routing/Aspect/DelegateInterface.php
index 8145e7fd25f5..27eb7feac533 100644
--- a/typo3/sysext/core/Classes/Routing/Aspect/DelegateInterface.php
+++ b/typo3/sysext/core/Classes/Routing/Aspect/DelegateInterface.php
@@ -19,6 +19,8 @@ namespace TYPO3\CMS\Core\Routing\Aspect;
 /**
  * Interface that describes delegations of tasks to different processors
  * when resolving or generating parameters for URLs.
+ *
+ * @deprecated since TYPO3 v10.3, will be removed in TYPO3 v11.0
  */
 interface DelegateInterface
 {
diff --git a/typo3/sysext/core/Classes/Routing/Aspect/PersistedAliasMapper.php b/typo3/sysext/core/Classes/Routing/Aspect/PersistedAliasMapper.php
index ebfc87066282..9b049bfa8670 100644
--- a/typo3/sysext/core/Classes/Routing/Aspect/PersistedAliasMapper.php
+++ b/typo3/sysext/core/Classes/Routing/Aspect/PersistedAliasMapper.php
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Context\LanguageAspectFactory;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Domain\Repository\PageRepository;
+use TYPO3\CMS\Core\Routing\Legacy\PersistedAliasMapperLegacyTrait;
 use TYPO3\CMS\Core\Site\SiteLanguageAwareInterface;
 use TYPO3\CMS\Core\Site\SiteLanguageAwareTrait;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -47,6 +48,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class PersistedAliasMapper implements PersistedMappableAspectInterface, StaticMappableAspectInterface, SiteLanguageAwareInterface
 {
     use SiteLanguageAwareTrait;
+    use PersistedAliasMapperLegacyTrait;
 
     /**
      * @var array
@@ -69,14 +71,14 @@ class PersistedAliasMapper implements PersistedMappableAspectInterface, StaticMa
     protected $routeValuePrefix;
 
     /**
-     * @var PersistenceDelegate
+     * @var string[]
      */
-    protected $persistenceDelegate;
+    protected $persistenceFieldNames;
 
     /**
-     * @var string[]
+     * @var string|null
      */
-    protected $persistenceFieldNames;
+    protected $languageFieldName;
 
     /**
      * @var string|null
@@ -116,8 +118,9 @@ class PersistedAliasMapper implements PersistedMappableAspectInterface, StaticMa
         $this->tableName = $tableName;
         $this->routeFieldName = $routeFieldName;
         $this->routeValuePrefix = $routeValuePrefix;
-        $this->persistenceFieldNames = $this->buildPersistenceFieldNames();
+        $this->languageFieldName = $GLOBALS['TCA'][$this->tableName]['ctrl']['languageField'] ?? null;
         $this->languageParentFieldName = $GLOBALS['TCA'][$this->tableName]['ctrl']['transOrigPointerField'] ?? null;
+        $this->persistenceFieldNames = $this->buildPersistenceFieldNames();
     }
 
     /**
@@ -125,9 +128,7 @@ class PersistedAliasMapper implements PersistedMappableAspectInterface, StaticMa
      */
     public function generate(string $value): ?string
     {
-        $result = $this->getPersistenceDelegate()->generate([
-            'uid' => $value
-        ]);
+        $result = $this->findByIdentifier($value);
         $result = $this->resolveOverlay($result);
         if (!isset($result[$this->routeFieldName])) {
             return null;
@@ -143,9 +144,7 @@ class PersistedAliasMapper implements PersistedMappableAspectInterface, StaticMa
     public function resolve(string $value): ?string
     {
         $value = $this->routeValuePrefix . $this->purgeRouteValuePrefix($value);
-        $result = $this->getPersistenceDelegate()->resolve([
-            $this->routeFieldName => $value
-        ]);
+        $result = $this->findByRouteFieldValue($value);
         if ($result[$this->languageParentFieldName] ?? null > 0) {
             return (string)$result[$this->languageParentFieldName];
         }
@@ -164,8 +163,8 @@ class PersistedAliasMapper implements PersistedMappableAspectInterface, StaticMa
             'uid',
             'pid',
             $this->routeFieldName,
-            $GLOBALS['TCA'][$this->tableName]['ctrl']['languageField'] ?? null,
-            $GLOBALS['TCA'][$this->tableName]['ctrl']['transOrigPointerField'] ?? null,
+            $this->languageFieldName,
+            $this->languageParentFieldName,
         ]);
     }
 
@@ -181,55 +180,39 @@ class PersistedAliasMapper implements PersistedMappableAspectInterface, StaticMa
         return ltrim($value, $this->routeValuePrefix);
     }
 
-    /**
-     * @return PersistenceDelegate
-     */
-    protected function getPersistenceDelegate(): PersistenceDelegate
+    protected function findByIdentifier(string $value): ?array
     {
-        if ($this->persistenceDelegate !== null) {
-            return $this->persistenceDelegate;
-        }
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable($this->tableName)
-            ->from($this->tableName);
-        // @todo Restrictions (Hidden? Workspace?)
-
-        $resolveModifier = function (QueryBuilder $queryBuilder, array $values) {
-            return $queryBuilder->select(...$this->persistenceFieldNames)->where(
-                ...$this->createFieldConstraints($queryBuilder, $values)
-            );
-        };
-        $generateModifier = function (QueryBuilder $queryBuilder, array $values) {
-            return $queryBuilder->select(...$this->persistenceFieldNames)->where(
-                ...$this->createFieldConstraints($queryBuilder, $values)
-            );
-        };
+        $queryBuilder = $this->createQueryBuilder();
+        $result = $queryBuilder
+            ->select(...$this->persistenceFieldNames)
+            ->where($queryBuilder->expr()->eq(
+                'uid',
+                $queryBuilder->createNamedParameter($value, \PDO::PARAM_INT)
+            ))
+            ->execute()
+            ->fetch();
+        return $result !== false ? $result : null;
+    }
 
-        return $this->persistenceDelegate = new PersistenceDelegate(
-            $queryBuilder,
-            $resolveModifier,
-            $generateModifier
-        );
+    protected function findByRouteFieldValue(string $value): ?array
+    {
+        $queryBuilder = $this->createQueryBuilder();
+        $result = $queryBuilder
+            ->select(...$this->persistenceFieldNames)
+            ->where($queryBuilder->expr()->eq(
+                $this->routeFieldName,
+                $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)
+            ))
+            ->execute()
+            ->fetch();
+        return $result !== false ? $result : null;
     }
 
-    /**
-     * @param QueryBuilder $queryBuilder
-     * @param array $values
-     * @return array
-     */
-    protected function createFieldConstraints(QueryBuilder $queryBuilder, array $values): array
+    protected function createQueryBuilder(): QueryBuilder
     {
-        $constraints = [];
-        foreach ($values as $fieldName => $fieldValue) {
-            $constraints[] = $queryBuilder->expr()->eq(
-                $fieldName,
-                $queryBuilder->createNamedParameter(
-                    $fieldValue,
-                    \PDO::PARAM_STR
-                )
-            );
-        }
-        return $constraints;
+        return GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($this->tableName)
+            ->from($this->tableName);
     }
 
     /**
diff --git a/typo3/sysext/core/Classes/Routing/Aspect/PersistedPatternMapper.php b/typo3/sysext/core/Classes/Routing/Aspect/PersistedPatternMapper.php
index c1a0a4c1ed65..f0c5b22a811b 100644
--- a/typo3/sysext/core/Classes/Routing/Aspect/PersistedPatternMapper.php
+++ b/typo3/sysext/core/Classes/Routing/Aspect/PersistedPatternMapper.php
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Context\LanguageAspectFactory;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Domain\Repository\PageRepository;
+use TYPO3\CMS\Core\Routing\Legacy\PersistedPatternMapperLegacyTrait;
 use TYPO3\CMS\Core\Site\SiteLanguageAwareInterface;
 use TYPO3\CMS\Core\Site\SiteLanguageAwareTrait;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -50,6 +51,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class PersistedPatternMapper implements PersistedMappableAspectInterface, StaticMappableAspectInterface, SiteLanguageAwareInterface
 {
     use SiteLanguageAwareTrait;
+    use PersistedPatternMapperLegacyTrait;
 
     protected const PATTERN_RESULT = '#\{(?P<fieldName>[^}]+)\}#';
 
@@ -78,11 +80,6 @@ class PersistedPatternMapper implements PersistedMappableAspectInterface, Static
      */
     protected $routeFieldResultNames;
 
-    /**
-     * @var PersistenceDelegate
-     */
-    protected $persistenceDelegate;
-
     /**
      * @var string|null
      */
@@ -127,9 +124,7 @@ class PersistedPatternMapper implements PersistedMappableAspectInterface, Static
      */
     public function generate(string $value): ?string
     {
-        $result = $this->getPersistenceDelegate()->generate([
-            'uid' => $value
-        ]);
+        $result = $this->findByIdentifier($value);
         $result = $this->resolveOverlay($result);
         return $this->createRouteResult($result);
     }
@@ -143,7 +138,7 @@ class PersistedPatternMapper implements PersistedMappableAspectInterface, Static
             return null;
         }
         $values = $this->filterNamesKeys($matches);
-        $result = $this->getPersistenceDelegate()->resolve($values);
+        $result = $this->findByRouteFieldValues($values);
         if ($result[$this->languageParentFieldName] ?? null > 0) {
             return (string)$result[$this->languageParentFieldName];
         }
@@ -193,51 +188,46 @@ class PersistedPatternMapper implements PersistedMappableAspectInterface, Static
         );
     }
 
-    /**
-     * @return PersistenceDelegate
-     */
-    protected function getPersistenceDelegate(): PersistenceDelegate
+    protected function findByIdentifier(string $value): ?array
     {
-        if ($this->persistenceDelegate !== null) {
-            return $this->persistenceDelegate;
-        }
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable($this->tableName)
-            ->from($this->tableName);
-        // @todo Restrictions (Hidden? Workspace?)
+        $queryBuilder = $this->createQueryBuilder();
+        $result = $queryBuilder
+            ->select('*')
+            ->where($queryBuilder->expr()->eq(
+                'uid',
+                $queryBuilder->createNamedParameter($value, \PDO::PARAM_INT)
+            ))
+            ->execute()
+            ->fetch();
+        return $result !== false ? $result : null;
+    }
 
-        $resolveModifier = function (QueryBuilder $queryBuilder, array $values) {
-            return $queryBuilder->select('*')->where(
-                ...$this->createFieldConstraints($queryBuilder, $values, true)
-            );
-        };
-        $generateModifier = function (QueryBuilder $queryBuilder, array $values) {
-            return $queryBuilder->select('*')->where(
-                ...$this->createFieldConstraints($queryBuilder, $values)
-            );
-        };
+    protected function findByRouteFieldValues(array $values): ?array
+    {
+        $queryBuilder = $this->createQueryBuilder();
+        $result = $queryBuilder
+            ->select('*')
+            ->where(...$this->createRouteFieldConstraints($queryBuilder, $values))
+            ->execute()
+            ->fetch();
+        return $result !== false ? $result : null;
+    }
 
-        return $this->persistenceDelegate = new PersistenceDelegate(
-            $queryBuilder,
-            $resolveModifier,
-            $generateModifier
-        );
+    protected function createQueryBuilder(): QueryBuilder
+    {
+        return GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($this->tableName)
+            ->from($this->tableName);
     }
 
     /**
      * @param QueryBuilder $queryBuilder
      * @param array $values
-     * @param bool $resolveExpansion
      * @return array
      */
-    protected function createFieldConstraints(
-        QueryBuilder $queryBuilder,
-        array $values,
-        bool $resolveExpansion = false
-    ): array {
-        $languageExpansion = $this->languageParentFieldName
-            && $resolveExpansion
-            && isset($values['uid']);
+    protected function createRouteFieldConstraints(QueryBuilder $queryBuilder, array $values): array
+    {
+        $languageExpansion = $this->languageParentFieldName && isset($values['uid']);
 
         $constraints = [];
         foreach ($values as $fieldName => $fieldValue) {
diff --git a/typo3/sysext/core/Classes/Routing/Aspect/PersistenceDelegate.php b/typo3/sysext/core/Classes/Routing/Aspect/PersistenceDelegate.php
index a0feffeb14a3..b31b9a75e072 100644
--- a/typo3/sysext/core/Classes/Routing/Aspect/PersistenceDelegate.php
+++ b/typo3/sysext/core/Classes/Routing/Aspect/PersistenceDelegate.php
@@ -21,6 +21,8 @@ use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 /**
  * Delegate implementation in order to retrieve and generate values
  * using a database connection.
+ *
+ * @deprecated since TYPO3 v10.3, will be removed in TYPO3 v11.0
  */
 class PersistenceDelegate implements DelegateInterface
 {
diff --git a/typo3/sysext/core/Classes/Routing/Legacy/PersistedAliasMapperLegacyTrait.php b/typo3/sysext/core/Classes/Routing/Legacy/PersistedAliasMapperLegacyTrait.php
new file mode 100644
index 000000000000..735ef91c9f00
--- /dev/null
+++ b/typo3/sysext/core/Classes/Routing/Legacy/PersistedAliasMapperLegacyTrait.php
@@ -0,0 +1,70 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Routing\Legacy;
+
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Routing\Aspect\PersistenceDelegate;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+trait PersistedAliasMapperLegacyTrait
+{
+    /**
+     * @var PersistenceDelegate
+     */
+    protected $persistenceDelegate;
+
+    /**
+     * @return PersistenceDelegate
+     * @deprecated since TYPO3 v10.3, will be removed in TYPO3 v11.0
+     */
+    protected function getPersistenceDelegate(): PersistenceDelegate
+    {
+        if ($this->persistenceDelegate !== null) {
+            return $this->persistenceDelegate;
+        }
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($this->tableName)
+            ->from($this->tableName);
+        // @todo Restrictions (Hidden? Workspace?)
+
+        $resolveModifier = function (QueryBuilder $queryBuilder, array $values) {
+            return $queryBuilder->select(...$this->persistenceFieldNames)->where(
+                ...$this->createFieldConstraints($queryBuilder, $values)
+            );
+        };
+        $generateModifier = function (QueryBuilder $queryBuilder, array $values) {
+            return $queryBuilder->select(...$this->persistenceFieldNames)->where(
+                ...$this->createFieldConstraints($queryBuilder, $values)
+            );
+        };
+
+        return $this->persistenceDelegate = new PersistenceDelegate(
+            $queryBuilder,
+            $resolveModifier,
+            $generateModifier
+        );
+    }
+
+    /**
+     * @param QueryBuilder $queryBuilder
+     * @param array $values
+     * @return array
+     * @deprecated since TYPO3 v10.3, will be removed in TYPO3 v11.0
+     */
+    protected function createFieldConstraints(QueryBuilder $queryBuilder, array $values): array
+    {
+        $constraints = [];
+        foreach ($values as $fieldName => $fieldValue) {
+            $constraints[] = $queryBuilder->expr()->eq(
+                $fieldName,
+                $queryBuilder->createNamedParameter(
+                    $fieldValue,
+                    \PDO::PARAM_STR
+                )
+            );
+        }
+        return $constraints;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Routing/Legacy/PersistedPatternMapperLegacyTrait.php b/typo3/sysext/core/Classes/Routing/Legacy/PersistedPatternMapperLegacyTrait.php
new file mode 100644
index 000000000000..c33b8603d5bb
--- /dev/null
+++ b/typo3/sysext/core/Classes/Routing/Legacy/PersistedPatternMapperLegacyTrait.php
@@ -0,0 +1,93 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Routing\Legacy;
+
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Routing\Aspect\PersistenceDelegate;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+trait PersistedPatternMapperLegacyTrait
+{
+    /**
+     * @var PersistenceDelegate
+     */
+    protected $persistenceDelegate;
+
+    /**
+     * @return PersistenceDelegate
+     * @deprecated since TYPO3 v10.3, will be removed in TYPO3 v11.0
+     */
+    protected function getPersistenceDelegate(): PersistenceDelegate
+    {
+        if ($this->persistenceDelegate !== null) {
+            return $this->persistenceDelegate;
+        }
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($this->tableName)
+            ->from($this->tableName);
+        // @todo Restrictions (Hidden? Workspace?)
+
+        $resolveModifier = function (QueryBuilder $queryBuilder, array $values) {
+            return $queryBuilder->select('*')->where(
+                ...$this->createRouteFieldConstraints($queryBuilder, $values, true)
+            );
+        };
+        $generateModifier = function (QueryBuilder $queryBuilder, array $values) {
+            return $queryBuilder->select('*')->where(
+                ...$this->createRouteFieldConstraints($queryBuilder, $values)
+            );
+        };
+
+        return $this->persistenceDelegate = new PersistenceDelegate(
+            $queryBuilder,
+            $resolveModifier,
+            $generateModifier
+        );
+    }
+
+    /**
+     * @param QueryBuilder $queryBuilder
+     * @param array $values
+     * @param bool $resolveExpansion
+     * @return array
+     * @deprecated since TYPO3 v10.3, will be removed in TYPO3 v11.0
+     */
+    protected function createRouteFieldConstraints(
+        QueryBuilder $queryBuilder,
+        array $values,
+        bool $resolveExpansion = false
+    ): array {
+        $languageExpansion = $this->languageParentFieldName
+            && $resolveExpansion
+            && isset($values['uid']);
+
+        $constraints = [];
+        foreach ($values as $fieldName => $fieldValue) {
+            if ($languageExpansion && $fieldName === 'uid') {
+                continue;
+            }
+            $constraints[] = $queryBuilder->expr()->eq(
+                $fieldName,
+                $queryBuilder->createNamedParameter(
+                    $fieldValue,
+                    \PDO::PARAM_STR
+                )
+            );
+        }
+        // If requested, either match uid or language parent field value
+        if ($languageExpansion) {
+            $idParameter = $queryBuilder->createNamedParameter(
+                $values['uid'],
+                \PDO::PARAM_INT
+            );
+            $constraints[] = $queryBuilder->expr()->orX(
+                $queryBuilder->expr()->eq('uid', $idParameter),
+                $queryBuilder->expr()->eq($this->languageParentFieldName, $idParameter)
+            );
+        }
+
+        return $constraints;
+    }
+}
-- 
GitLab