[TASK] Clean up TSFE constructor initialization 89/61289/4
authorBenni Mack <benni@typo3.org>
Sat, 13 Jul 2019 17:03:41 +0000 (19:03 +0200)
committerBenni Mack <benni@typo3.org>
Thu, 18 Jul 2019 12:50:24 +0000 (14:50 +0200)
Due to e50b1c1acdd5da514a35f837d9b853692bcfa16d
the TypoScriptFrontendController requires four objects
as constructor arguments while keeping maximum
compatibility by fetching fallback information from the
current PSR-7 object or setting data from the legacy arguments.

The constructor is cleaned up with special initialize methods
for readability.

Resolves: #88747
Related: #88717
Releases: master
Change-Id: I734d1184780b8bfdf0e9638aca75c6a4fc0f7e2c
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61289
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog <look@susi.dev>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Susanne Moog <look@susi.dev>
Reviewed-by: Oliver Klee <typo3-coding@oliverklee.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
typo3/sysext/core/Documentation/Changelog/master/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.rst
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php

index 94abba3..111d1f1 100644 (file)
@@ -55,7 +55,7 @@ The new request workflow looks like this (simplified):
 3. Resolving Site configuration and Language from URL if possible
 4. Resolving logged-in Backend User Authentication for previewing hidden pages or languages
 5. Authentication of Website Users ("Frontend Users")
-6. Executing various static routes and redirct functionality
+6. Executing various static routes and redirect functionality
 7. Resolving Target Page ID and URL parameters based on Routing, Validation of Page Arguments based on "cHash"
 8. Setting up global $TSFE object, injecting previously resolved settings into TSFE.
 9. Resolving the Rootline for the page
index 4dcc4eb..e7a061a 100644 (file)
@@ -660,21 +660,60 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     protected $context;
 
     /**
-     * Class constructor
-     * Takes a number of GET/POST input variable as arguments and stores them internally.
-     * The processing of these variables goes on later in this class.
+     * Since TYPO3 v10.0, TSFE is composed out of
+     *  - Context
+     *  - Site
+     *  - SiteLanguage
+     *  - PageArguments (containing ID, Type, cHash and MP arguments)
+     *
+     * With TYPO3 v11, they will become mandatory and the method arguments will become strongly typed.
+     * For TYPO3 v10 this is built in a way to ensure maximum compatibility.
+     *
      * Also sets a unique string (->uniqueString) for this script instance; A md5 hash of the microtime()
      *
-     * @param Context|array $context the Context object to work on, previously defined to set TYPO3_CONF_VARS
+     * @param Context|array|null $context the Context object to work on, previously defined to set TYPO3_CONF_VARS
      * @param mixed|SiteInterface $siteOrId The resolved site to work on, previously this was the value of GeneralUtility::_GP('id')
-     * @param int|SiteLanguage $siteLanguageOrType The resolved language to work on, previously the value of GeneralUtility::_GP('type')
-     * @param bool|string|PageArguments $pageArguments The PageArguments object containing ID, type and GET parameters, previously unused or the value of GeneralUtility::_GP('no_cache')
-     * @param string|FrontendUserAuthentication $cHashOrFrontendUser FrontendUserAuthentication object, previously the value of GeneralUtility::_GP('cHash'), use the PageArguments object instead, will be removed in TYPO3 v11.0
-     * @param string $_2 previously was used to define the jumpURL, use the PageArguments object instead, will be removed in TYPO3 v11.0
-     * @param string $MP The value of GeneralUtility::_GP('MP'), use the PageArguments object instead, will be removed in TYPO3 v11.0
+     * @param SiteLanguage|int|string $siteLanguageOrType The resolved language to work on, previously the value of GeneralUtility::_GP('type')
+     * @param bool|string|PageArguments|null $pageArguments The PageArguments object containing ID, type and GET parameters, previously unused or the value of GeneralUtility::_GP('no_cache')
+     * @param string|FrontendUserAuthentication|null $cHashOrFrontendUser FrontendUserAuthentication object, previously the value of GeneralUtility::_GP('cHash'), use the PageArguments object instead, will be removed in TYPO3 v11.0
+     * @param string|null $_2 previously was used to define the jumpURL, use the PageArguments object instead, will be removed in TYPO3 v11.0
+     * @param string|null $MP The value of GeneralUtility::_GP('MP'), use the PageArguments object instead, will be removed in TYPO3 v11.0
      */
     public function __construct($context = null, $siteOrId = null, $siteLanguageOrType = null, $pageArguments = null, $cHashOrFrontendUser = null, $_2 = null, $MP = null)
     {
+        $this->initializeContextWithGlobalFallback($context);
+
+        // Fetch the request for fetching data (site/language/pageArguments) for compatibility reasons, not needed
+        // in TYPO3 v11.0 anymore.
+        /** @var ServerRequestInterface $request */
+        $request = $GLOBALS['TYPO3_REQUEST'] ?? ServerRequestFactory::fromGlobals();
+
+        $this->initializeSiteWithCompatibility($siteOrId, $request);
+        $this->initializeSiteLanguageWithCompatibility($siteLanguageOrType, $request);
+        $pageArguments = $this->buildPageArgumentsWithFallback($pageArguments, $request);
+        $pageArguments = $this->initializeFrontendUserOrUpdateCHashArgument($cHashOrFrontendUser, $pageArguments);
+        $pageArguments = $this->initializeLegacyMountPointArgument($MP, $pageArguments);
+
+        $this->setPageArguments($pageArguments);
+
+        $this->domainStartPage = $this->site->getRootPageId();
+        $this->uniqueString = md5(microtime());
+        $this->initPageRenderer();
+        $this->initCaches();
+    }
+
+    /**
+     * Various initialize methods used for fallback, which can be simplified in TYPO3 v11.0
+     */
+
+    /**
+     * Used to set $this->context. The first argument was $GLOBALS[TYPO3_CONF_VARS] (array) until TYPO3 v8,
+     * so no type hint possible.
+     *
+     * @param Context|array|null $context
+     */
+    private function initializeContextWithGlobalFallback($context): void
+    {
         if ($context instanceof Context) {
             $this->context = $context;
         } else {
@@ -682,7 +721,18 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             trigger_error('TypoScriptFrontendController requires a context object as first constructor argument in TYPO3 v11.0, now falling back to the global Context. This fallback layer will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
             $this->context = GeneralUtility::makeInstance(Context::class);
         }
-        $request = $GLOBALS['TYPO3_REQUEST'] ?? ServerRequestFactory::fromGlobals();
+    }
+
+    /**
+     * Second argument of the constructor. Until TYPO3 v10, this was the Page ID (int/string) but since TYPO3 v10.0
+     * this can also be a SiteInterface object, which will be mandatory in TYPO3 v11.0. If no Site object is given,
+     * this is fetched from the given request object.
+     *
+     * @param SiteInterface|int|string $siteOrId
+     * @param ServerRequestInterface $request
+     */
+    private function initializeSiteWithCompatibility($siteOrId, ServerRequestInterface $request): void
+    {
         if ($siteOrId instanceof SiteInterface) {
             $this->site = $siteOrId;
         } else {
@@ -694,9 +744,22 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 throw new \InvalidArgumentException('TypoScriptFrontendController must be constructed with a valid Site object or a resolved site in the current request as fallback. None given.', 1561583122);
             }
         }
+    }
+
+    /**
+     * Until TYPO3 v10.0, the third argument of the constructor was given from GET/POST "type" to define the page type
+     * Since TYPO3 v10.0, this argument is requested to be of type SiteLanguage, which will be mandatory in TYPO3 v11.0.
+     * If no SiteLanguage object is given, this is fetched from the given request object.
+     *
+     * @param SiteLanguage|int|string $siteLanguageOrType
+     * @param ServerRequestInterface $request
+     */
+    private function initializeSiteLanguageWithCompatibility($siteLanguageOrType, ServerRequestInterface $request): void
+    {
         if ($siteLanguageOrType instanceof SiteLanguage) {
             $this->language = $siteLanguageOrType;
         } else {
+            trigger_error('TypoScriptFrontendController should evaluate the parameter "type" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
             $this->type = $siteLanguageOrType;
             if ($request->getAttribute('language') instanceof SiteLanguage) {
                 $this->language = $request->getAttribute('language');
@@ -704,38 +767,86 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 throw new \InvalidArgumentException('TypoScriptFrontendController must be constructed with a valid SiteLanguage object or a resolved site in the current request as fallback. None given.', 1561583127);
             }
         }
+    }
 
-        if (!$pageArguments instanceof PageArguments) {
-            $pageArguments = $request->getAttribute('routing');
-            if (!$pageArguments instanceof PageArguments) {
-                trigger_error('TypoScriptFrontendController must be constructed with a valid PageArguments object or a resolved page argument in the current request as fallback. None given.', E_USER_DEPRECATED);
-                $queryParams = $request->getQueryParams();
-                $pageId = $queryParams['id'] ?? $request->getParsedBody()['id'] ?? 0;
-                $pageType = $queryParams['type'] ?? $request->getParsedBody()['type'] ?? 0;
-                $pageArguments = new PageArguments((int)$pageId, (string)$pageType, [], $queryParams);
-            }
+    /**
+     * Since TYPO3 v10.0, the fourth constructor argument should be of type PageArguments. However, until TYPO3 v8,
+     * this was the GET/POST parameter "no_cache". If no PageArguments object is given, the given request is checked
+     * for the PageArguments.
+     *
+     * @param bool|string|PageArguments|null $pageArguments
+     * @param ServerRequestInterface $request
+     * @return PageArguments
+     */
+    private function buildPageArgumentsWithFallback($pageArguments, ServerRequestInterface $request): PageArguments
+    {
+        if ($pageArguments instanceof PageArguments) {
+            return $pageArguments;
         }
-        $this->setPageArguments($pageArguments);
+        if ($request->getAttribute('routing') instanceof PageArguments) {
+            return $request->getAttribute('routing');
+        }
+        trigger_error('TypoScriptFrontendController must be constructed with a valid PageArguments object or a resolved page argument in the current request as fallback. None given.', E_USER_DEPRECATED);
+        $queryParams = $request->getQueryParams();
+        $pageId = $this->id ?: ($queryParams['id'] ?? $request->getParsedBody()['id'] ?? 0);
+        $pageType = $this->type ?: ($queryParams['type'] ?? $request->getParsedBody()['type'] ?? 0);
+        return new PageArguments((int)$pageId, (string)$pageType, [], $queryParams);
+    }
 
-        if ($cHashOrFrontendUser !== null) {
-            if ($cHashOrFrontendUser instanceof FrontendUserAuthentication) {
-                $this->fe_user = $cHashOrFrontendUser;
-            } else {
-                trigger_error('TypoScriptFrontendController should evaluate the parameter "cHash" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
-                $this->cHash = $cHashOrFrontendUser;
-            }
+    /**
+     * Since TYPO3 v10.0, the fifth constructor argument is expected to to be of Type FrontendUserAuthentication.
+     * However, up until TYPO3 v9.5 this argument was used to define the "cHash" GET/POST parameter. In order to
+     * ensure maximum compatibility, a deprecation is triggered if an old argument is still used, and PageArguments
+     * are updated accordingly, and returned.
+     *
+     * @param string|FrontendUserAuthentication|null $cHashOrFrontendUser
+     * @param PageArguments $pageArguments
+     * @return PageArguments
+     */
+    private function initializeFrontendUserOrUpdateCHashArgument($cHashOrFrontendUser, PageArguments $pageArguments): PageArguments
+    {
+        if ($cHashOrFrontendUser === null) {
+            return $pageArguments;
         }
-        if ($MP !== null) {
-            trigger_error('TypoScriptFrontendController should evaluate the MountPoint Parameter "MP" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
-            if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
-                $this->MP = (string)$MP;
-            }
+        if ($cHashOrFrontendUser instanceof FrontendUserAuthentication) {
+            $this->fe_user = $cHashOrFrontendUser;
+            return $pageArguments;
         }
+        trigger_error('TypoScriptFrontendController should evaluate the parameter "cHash" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
+        return new PageArguments(
+            $pageArguments->getPageId(),
+            $pageArguments->getPageType(),
+            $pageArguments->getRouteArguments(),
+            array_replace_recursive($pageArguments->getStaticArguments(), ['cHash' => $cHashOrFrontendUser]),
+            $pageArguments->getDynamicArguments()
+        );
+    }
 
-        $this->domainStartPage = $this->site->getRootPageId();
-        $this->uniqueString = md5(microtime());
-        $this->initPageRenderer();
-        $this->initCaches();
+    /**
+     * Since TYPO3 v10.0 the seventh constructor argument is not needed anymore, as all data is already provided by
+     * the given PageArguments object. However, if a specific MP parameter is given anyways, the PageArguments object
+     * is updated and returned.
+     *
+     * @param string|null $MP
+     * @param PageArguments $pageArguments
+     * @return PageArguments
+     */
+    private function initializeLegacyMountPointArgument(?string $MP, PageArguments $pageArguments): PageArguments
+    {
+        if ($MP === null) {
+            return $pageArguments;
+        }
+        trigger_error('TypoScriptFrontendController should evaluate the MountPoint Parameter "MP" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
+        if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
+            return $pageArguments;
+        }
+        return new PageArguments(
+            $pageArguments->getPageId(),
+            $pageArguments->getPageType(),
+            $pageArguments->getRouteArguments(),
+            array_replace_recursive($pageArguments->getStaticArguments(), ['MP' => $MP]),
+            $pageArguments->getDynamicArguments()
+        );
     }
 
     /**