[TASK] Instantiate Context through dependency injection 74/61274/9
authorBenjamin Franzke <bfr@qbus.de>
Thu, 11 Jul 2019 22:43:52 +0000 (00:43 +0200)
committerAndreas Fernandez <a.fernandez@scripting-base.de>
Sat, 20 Jul 2019 13:05:22 +0000 (15:05 +0200)
Context is stateful as Aspects dependent on the (currently dispatched)
request type (frontend/backend/installtool/CLI).
Reqest-dependent arguments can not be injected during service
creation; therefore the Context class is now created without default
aspects and enhanced by the application classes (on demand).

Note: The UserAspect constructor is adapted to use an explicit stdClass
allocation instead of an immutable array casted to an object for the
(fallback) pseudo user. This is to avoid php segmentation faults in
functional tests (and also random unit test runs). The segmentation
faults would be triggered due to the constructor change in the Context
class, which now uses ondemand instead of preemptive Aspect creation.
Background: immutable arrays are stored on stack. The cast to an object
probably didn't relocate this memory to the heap which then causes
segmentations faults when the static memory area (on stack) is exceeded.

Releases: master
Resolves: #88793
Change-Id: Ib165f85b66b34e8025e28ef483260463f1e2c826
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61274
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog <look@susi.dev>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Susanne Moog <look@susi.dev>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
typo3/sysext/backend/Classes/Http/Application.php
typo3/sysext/backend/Classes/ServiceProvider.php
typo3/sysext/core/Classes/Console/CommandApplication.php
typo3/sysext/core/Classes/Context/Context.php
typo3/sysext/core/Classes/Context/UserAspect.php
typo3/sysext/core/Classes/ServiceProvider.php
typo3/sysext/frontend/Classes/Http/Application.php
typo3/sysext/frontend/Classes/ServiceProvider.php
typo3/sysext/install/Classes/Http/Application.php
typo3/sysext/install/Classes/ServiceProvider.php

index e13a28e..ac42267 100644 (file)
@@ -25,7 +25,6 @@ use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Http\AbstractApplication;
 use TYPO3\CMS\Core\Http\RedirectResponse;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Backend (HTTP requests)
@@ -38,19 +37,20 @@ class Application extends AbstractApplication
     protected $configurationManager;
 
     /**
-     * @param RequestHandlerInterface $requestHandler
-     * @param ConfigurationManager $configurationManager
+     * @var Context
      */
-    public function __construct(RequestHandlerInterface $requestHandler, ConfigurationManager $configurationManager)
-    {
+    protected $context;
+
+    public function __construct(
+        RequestHandlerInterface $requestHandler,
+        ConfigurationManager $configurationManager,
+        Context $context
+    ) {
         $this->requestHandler = $requestHandler;
         $this->configurationManager = $configurationManager;
+        $this->context = $context;
     }
 
-    /**
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
     protected function handle(ServerRequestInterface $request): ResponseInterface
     {
         if (!$this->checkIfEssentialConfigurationExists()) {
@@ -84,13 +84,10 @@ class Application extends AbstractApplication
 
     /**
      * Initializes the Context used for accessing data and finding out the current state of the application
-     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
      */
-    protected function initializeContext()
+    protected function initializeContext(): void
     {
-        GeneralUtility::makeInstance(Context::class, [
-            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
-            'visibility' => new VisibilityAspect(true, true)
-        ]);
+        $this->context->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
+        $this->context->setAspect('visibility', new VisibilityAspect(true, true));
     }
 }
index f22ae77..d1a41e6 100644 (file)
@@ -18,6 +18,7 @@ namespace TYPO3\CMS\Backend;
 use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Exception as CoreException;
 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
 use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
@@ -50,7 +51,11 @@ class ServiceProvider extends AbstractServiceProvider
             $container->get('backend.middlewares'),
             $container
         );
-        return new Http\Application($requestHandler, $container->get(ConfigurationManager::class));
+        return new Http\Application(
+            $requestHandler,
+            $container->get(ConfigurationManager::class),
+            $container->get(Context::class)
+        );
     }
 
     public static function getRequestHandler(ContainerInterface $container): Http\RequestHandler
index 356fa20..bd44edb 100644 (file)
@@ -29,8 +29,14 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  */
 class CommandApplication implements ApplicationInterface
 {
-    public function __construct()
+    /**
+     * @var Context
+     */
+    protected $context;
+
+    public function __construct(Context $context)
     {
+        $this->context = $context;
         $this->checkEnvironmentOrDie();
     }
 
@@ -62,15 +68,12 @@ class CommandApplication implements ApplicationInterface
 
     /**
      * Initializes the Context used for accessing data and finding out the current state of the application
-     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
      */
-    protected function initializeContext()
+    protected function initializeContext(): void
     {
-        GeneralUtility::makeInstance(Context::class, [
-            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
-            'visibility' => new VisibilityAspect(true, true),
-            'workspace' => new WorkspaceAspect(0),
-            'backend.user' => new UserAspect(null),
-        ]);
+        $this->context->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
+        $this->context->setAspect('visibility', new VisibilityAspect(true, true));
+        $this->context->setAspect('workspace', new WorkspaceAspect(0));
+        $this->context->setAspect('backend.user', new UserAspect(null));
     }
 }
index de52d2b..95be43d 100644 (file)
@@ -66,28 +66,6 @@ class Context implements SingletonInterface
                 $this->aspects[$name] = $defaultAspect;
             }
         }
-        // Ensure the default aspects are set, this is mostly necessary for tests to not set up everything
-        if (!$this->hasAspect('date')) {
-            $this->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
-        }
-        if (!$this->hasAspect('visibility')) {
-            $this->setAspect('visibility', new VisibilityAspect());
-        }
-        if (!$this->hasAspect('backend.user')) {
-            $this->setAspect('backend.user', new UserAspect());
-        }
-        if (!$this->hasAspect('frontend.user')) {
-            $this->setAspect('frontend.user', new UserAspect());
-        }
-        if (!$this->hasAspect('workspace')) {
-            $this->setAspect('workspace', new WorkspaceAspect());
-        }
-        if (!$this->hasAspect('language')) {
-            $this->setAspect('language', new LanguageAspect());
-        }
-        if (!$this->hasAspect('typoscript')) {
-            $this->setAspect('typoscript', new TypoScriptAspect());
-        }
     }
 
     /**
@@ -98,7 +76,19 @@ class Context implements SingletonInterface
      */
     public function hasAspect(string $name): bool
     {
-        return isset($this->aspects[$name]);
+        switch ($name) {
+        // Ensure the default aspects are available, this is mostly necessary for tests to not set up everything
+        case 'date':
+        case 'visibility':
+        case 'backend.user':
+        case 'frontend.user':
+        case 'workspace':
+        case 'language':
+        case 'typoscript':
+            return true;
+        default:
+            return isset($this->aspects[$name]);
+        }
     }
 
     /**
@@ -110,8 +100,33 @@ class Context implements SingletonInterface
      */
     public function getAspect(string $name): AspectInterface
     {
-        if (!$this->hasAspect($name)) {
-            throw new AspectNotFoundException('No aspect named "' . $name . '" found.', 1527777641);
+        if (!isset($this->aspects[$name])) {
+            // Ensure the default aspects are available, this is mostly necessary for tests to not set up everything
+            switch ($name) {
+            case 'date':
+                $this->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
+                break;
+            case 'visibility':
+                $this->setAspect('visibility', new VisibilityAspect());
+                break;
+            case 'backend.user':
+                $this->setAspect('backend.user', new UserAspect());
+                break;
+            case 'frontend.user':
+                $this->setAspect('frontend.user', new UserAspect());
+                break;
+            case 'workspace':
+                $this->setAspect('workspace', new WorkspaceAspect());
+                break;
+            case 'language':
+                $this->setAspect('language', new LanguageAspect());
+                break;
+            case 'typoscript':
+                $this->setAspect('typoscript', new TypoScriptAspect());
+                break;
+            default:
+                throw new AspectNotFoundException('No aspect named "' . $name . '" found.', 1527777641);
+            }
         }
         return $this->aspects[$name];
     }
@@ -131,7 +146,7 @@ class Context implements SingletonInterface
             throw new AspectNotFoundException('No aspect named "' . $name . '" found.', 1527777868);
         }
         try {
-            return $this->aspects[$name]->get($property);
+            return $this->getAspect($name)->get($property);
         } catch (AspectPropertyNotFoundException $e) {
             return $default;
         }
index 07230bf..066fdb3 100644 (file)
@@ -52,11 +52,21 @@ class UserAspect implements AspectInterface
      */
     public function __construct(AbstractUserAuthentication $user = null, array $alternativeGroups = null)
     {
-        $this->user = $user ?? (object)['user' => []];
+        $this->user = $user ?? $this->createPseudoUser();
         $this->groups = $alternativeGroups;
     }
 
     /**
+     * @return object
+     */
+    private function createPseudoUser(): object
+    {
+        $user = new \stdClass;
+        $user->user = [];
+        return $user;
+    }
+
+    /**
      * Fetch common information about the user
      *
      * @param string $name
index 1dccda1..dd39cf6 100644 (file)
@@ -33,6 +33,7 @@ class ServiceProvider extends AbstractServiceProvider
         return [
             Cache\CacheManager::class => [ static::class, 'getCacheManager' ],
             Console\CommandApplication::class => [ static::class, 'getConsoleCommandApplication' ],
+            Context\Context::class => [ static::class, 'getContext' ],
             Http\MiddlewareStackResolver::class => [ static::class, 'getMiddlewareStackResolver' ],
             Service\DependencyOrderingService::class => [ static::class, 'getDependencyOrderingService' ],
             'middlewares' => [ static::class, 'getMiddlewares' ],
@@ -63,7 +64,7 @@ class ServiceProvider extends AbstractServiceProvider
 
     public static function getConsoleCommandApplication(ContainerInterface $container): Console\CommandApplication
     {
-        return new Console\CommandApplication;
+        return new Console\CommandApplication($container->get(Context\Context::class));
     }
 
     public static function getDependencyOrderingService(ContainerInterface $container): Service\DependencyOrderingService
@@ -71,6 +72,11 @@ class ServiceProvider extends AbstractServiceProvider
         return new Service\DependencyOrderingService;
     }
 
+    public static function getContext(ContainerInterface $container): Context\Context
+    {
+        return new Context\Context;
+    }
+
     public static function getMiddlewareStackResolver(ContainerInterface $container): Http\MiddlewareStackResolver
     {
         return new Http\MiddlewareStackResolver(
index 0980b47..ac01059 100644 (file)
@@ -27,7 +27,6 @@ use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Http\AbstractApplication;
 use TYPO3\CMS\Core\Http\RedirectResponse;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Frontend
@@ -40,19 +39,20 @@ class Application extends AbstractApplication
     protected $configurationManager;
 
     /**
-     * @param RequestHandlerInterface $requestHandler
-     * @param ConfigurationManager $configurationManager
+     * @var Context
      */
-    public function __construct(RequestHandlerInterface $requestHandler, ConfigurationManager $configurationManager)
-    {
+    protected $context;
+
+    public function __construct(
+        RequestHandlerInterface $requestHandler,
+        ConfigurationManager $configurationManager,
+        Context $context
+    ) {
         $this->requestHandler = $requestHandler;
         $this->configurationManager = $configurationManager;
+        $this->context = $context;
     }
 
-    /**
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
     protected function handle(ServerRequestInterface $request): ResponseInterface
     {
         if (!$this->checkIfEssentialConfigurationExists()) {
@@ -86,16 +86,13 @@ class Application extends AbstractApplication
 
     /**
      * Initializes the Context used for accessing data and finding out the current state of the application
-     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
      */
-    protected function initializeContext()
+    protected function initializeContext(): void
     {
-        GeneralUtility::makeInstance(Context::class, [
-            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
-            'visibility' => new VisibilityAspect(),
-            'workspace' => new WorkspaceAspect(0),
-            'backend.user' => new UserAspect(null),
-            'frontend.user' => new UserAspect(null, [0, -1]),
-        ]);
+        $this->context->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
+        $this->context->setAspect('visibility', new VisibilityAspect());
+        $this->context->setAspect('workspace', new WorkspaceAspect(0));
+        $this->context->setAspect('backend.user', new UserAspect(null));
+        $this->context->setAspect('frontend.user', new UserAspect(null, [0, -1]));
     }
 }
index aad761f..41c7f1b 100644 (file)
@@ -18,6 +18,7 @@ namespace TYPO3\CMS\Frontend;
 use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Exception as CoreException;
 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
 use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
@@ -49,7 +50,11 @@ class ServiceProvider extends AbstractServiceProvider
             $container->get('frontend.middlewares'),
             $container
         );
-        return new Http\Application($requestHandler, $container->get(ConfigurationManager::class));
+        return new Http\Application(
+            $requestHandler,
+            $container->get(ConfigurationManager::class),
+            $container->get(Context::class)
+        );
     }
 
     public static function getRequestHandler(ContainerInterface $container): Http\RequestHandler
index d632390..4aed1cc 100644 (file)
@@ -23,7 +23,6 @@ use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Http\AbstractApplication;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Install Tool
@@ -32,17 +31,18 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class Application extends AbstractApplication
 {
     /**
-     * @param RequestHandlerInterface $requestHandler
+     * @var Context
      */
-    public function __construct(RequestHandlerInterface $requestHandler)
-    {
+    protected $context;
+
+    public function __construct(
+        RequestHandlerInterface $requestHandler,
+        Context $context
+    ) {
         $this->requestHandler = $requestHandler;
+        $this->context = $context;
     }
 
-    /**
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
     protected function handle(ServerRequestInterface $request): ResponseInterface
     {
         $this->initializeContext();
@@ -51,15 +51,12 @@ class Application extends AbstractApplication
 
     /**
      * Initializes the Context used for accessing data and finding out the current state of the application
-     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
      */
-    protected function initializeContext()
+    protected function initializeContext(): void
     {
-        GeneralUtility::makeInstance(Context::class, [
-            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
-            'visibility' => new VisibilityAspect(true, true, true),
-            'workspace' => new WorkspaceAspect(0),
-            'backend.user' => new UserAspect(),
-        ]);
+        $this->context->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
+        $this->context->setAspect('visibility', new VisibilityAspect(true, true, true));
+        $this->context->setAspect('workspace', new WorkspaceAspect(0));
+        $this->context->setAspect('backend.user', new UserAspect());
     }
 }
index 1ad772e..c42e51f 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Install;
 
 use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
 use TYPO3\CMS\Core\Package\AbstractServiceProvider;
@@ -54,7 +55,7 @@ class ServiceProvider extends AbstractServiceProvider
         $dispatcher->lazy(Middleware\Installer::class);
         $dispatcher->add($container->get(Middleware\Maintenance::class));
 
-        return new Http\Application($dispatcher);
+        return new Http\Application($dispatcher, $container->get(Context::class));
     }
 
     public static function getNotFoundRequestHandler(ContainerInterface $container): Http\NotFoundRequestHandler