[FEATURE] Backport ApplicationContext from Flow 69/22269/7
authorSteffen Müller <typo3@t3node.com>
Fri, 12 Jul 2013 18:56:44 +0000 (20:56 +0200)
committerStefan Neufeind <typo3.neufeind@speedpartner.de>
Wed, 17 Jul 2013 09:19:06 +0000 (11:19 +0200)
Flow has the notion of ApplicationContext which provides a unique
API for handling contexts.

This API allow us to provide default configuration sets for
particular contexts. For example having decent logging in
production context vs. full reports in development context.

The context is set using the TYPO3_CONTEXT environment variable.
If not set the context defaults to "Production".

The context can be queried using:
\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->getContext();

Resolves: #49988
Releases: 6.2
Change-Id: Id953052f2846c740f27a83931adfb64b0d8d9169
Reviewed-on: https://review.typo3.org/22269
Reviewed-by: Thomas Maroschik
Reviewed-by: Stefan Neufeind
Tested-by: Stefan Neufeind
_.htaccess
typo3/sysext/core/Classes/Core/ApplicationContext.php [new file with mode: 0644]
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Tests/Unit/Core/ApplicationContextTest.php [new file with mode: 0644]

index 8b8b81a..79d8b07 100644 (file)
@@ -83,6 +83,14 @@ RewriteEngine On
 # Change this path, if your TYPO3 installation is located in a subdirectory of the website root.
 #RewriteBase /
 
+# Rules to set ApplicationContext based on hostname
+#RewriteCond %{HTTP_HOST} ^dev\.example\.com$
+#RewriteRule (.*) $1 [E=TYPO3_CONTEXT:Development]
+#RewriteCond %{HTTP_HOST} ^staging\.example\.com$
+#RewriteRule (.*) $1 [E=TYPO3_CONTEXT:Production/Staging]
+#RewriteCond %{HTTP_HOST} ^www\.example\.com$
+#RewriteRule (.*) $1 [E=TYPO3_CONTEXT:Production]
+
 # Rule for versioned static files, configured through:
 # - $TYPO3_CONF_VARS['BE']['versionNumberInFilename']
 # - $TYPO3_CONF_VARS['FE']['versionNumberInFilename']
diff --git a/typo3/sysext/core/Classes/Core/ApplicationContext.php b/typo3/sysext/core/Classes/Core/ApplicationContext.php
new file mode 100644 (file)
index 0000000..9643c10
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+namespace TYPO3\CMS\Core\Core;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2013 The respective TYPO3 Flow framework authors
+ *  (c) 2013 Steffen Müller <typo3@t3node.com> (Backport to TYPO3 CMS)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * The TYPO3 Context object.
+ *
+ * A TYPO3 Application context is something like "Production", "Development",
+ * "Production/StagingSystem", and is set using the TYPO3_CONTEXT environment variable.
+ *
+ * A context can contain arbitrary sub-contexts, which are delimited with slash
+ * ("Production/StagingSystem", "Production/Staging/Server1"). The top-level
+ * contexts, however, must be one of "Testing", "Development" and "Production".
+ *
+ * Mainly, you will use $context->isProduction(), $context->isTesting() and
+ * $context->isDevelopment() inside your custom code.
+ *
+ * This class is derived from the TYPO3 Flow framework.
+ * Credits go to the respective authors.
+ *
+ * @author Steffen Müller <typo3@t3node.com> (Backport to TYPO3 CMS)
+ */
+class ApplicationContext {
+
+       /**
+        * The (internal) context string; could be something like "Development" or "Development/MyLocalMacBook"
+        *
+        * @var string
+        */
+       protected $contextString;
+
+       /**
+        * The root context; must be one of "Development", "Testing" or "Production"
+        *
+        * @var string
+        */
+       protected $rootContextString;
+
+       /**
+        * The parent context, or NULL if there is no parent context
+        *
+        * @var \TYPO3\CMS\Core\Core\ApplicationContext
+        */
+       protected $parentContext;
+
+       /**
+        * Initialize the context object.
+        *
+        * @param string $contextString
+        * @throws \Exception if the parent context is none of "Development", "Production" or "Testing"
+        */
+       public function __construct($contextString) {
+               if (strstr($contextString, '/') === FALSE) {
+                       $this->rootContextString = $contextString;
+                       $this->parentContext = NULL;
+               } else {
+                       $contextStringParts = explode('/', $contextString);
+                       $this->rootContextString = $contextStringParts[0];
+                       array_pop($contextStringParts);
+                       $this->parentContext = new ApplicationContext(implode('/', $contextStringParts));
+               }
+
+               if (!in_array($this->rootContextString, array('Development', 'Production', 'Testing'))) {
+                       throw new \TYPO3\CMS\Core\Exception('The given context "' . $contextString . '" was not valid. Only allowed are Development, Production and Testing, including their sub-contexts', 1335436551);
+               }
+
+               $this->contextString = $contextString;
+       }
+
+       /**
+        * Returns the full context string, for example "Development", or "Production/LiveSystem"
+        *
+        * @return string
+        * @api
+        */
+       public function __toString() {
+               return $this->contextString;
+       }
+
+       /**
+        * Returns TRUE if this context is the Development context or a sub-context of it
+        *
+        * @return boolean
+        * @api
+        */
+       public function isDevelopment() {
+               return ($this->rootContextString === 'Development');
+       }
+
+       /**
+        * Returns TRUE if this context is the Production context or a sub-context of it
+        *
+        * @return boolean
+        * @api
+        */
+
+       public function isProduction() {
+               return ($this->rootContextString === 'Production');
+       }
+
+       /**
+        * Returns TRUE if this context is the Testing context or a sub-context of it
+        *
+        * @return boolean
+        * @api
+        */
+       public function isTesting() {
+               return ($this->rootContextString === 'Testing');
+       }
+
+       /**
+        * Returns the parent context object, if any
+        *
+        * @return \TYPO3\CMS\Core\Core\ApplicationContext the parent context or NULL, if there is none
+        * @api
+        */
+       public function getParent() {
+               return $this->parentContext;
+       }
+}
+?>
\ No newline at end of file
index 54114ae..81e060d 100644 (file)
@@ -59,10 +59,21 @@ class Bootstrap {
        protected $requestId;
 
        /**
+        * The application context
+        *
+        * @var \TYPO3\CMS\Core\Core\ApplicationContext
+        */
+       protected $context;
+
+       /**
         * Disable direct creation of this object.
+        * Set unique requestId and the application context
+        *
+        * @var string Application context
         */
-       protected function __construct() {
+       protected function __construct($context) {
                $this->requestId = uniqid();
+               $this->context = new ApplicationContext($context);
        }
 
        /**
@@ -80,7 +91,9 @@ class Bootstrap {
         */
        static public function getInstance() {
                if (is_null(self::$instance)) {
-                       self::$instance = new \TYPO3\CMS\Core\Core\Bootstrap();
+                       require_once(__DIR__ . '/ApplicationContext.php');
+                       $context = trim(getenv('TYPO3_CONTEXT'), '"\' ') ? : 'Production';
+                       self::$instance = new \TYPO3\CMS\Core\Core\Bootstrap($context);
                }
                return self::$instance;
        }
@@ -96,6 +109,15 @@ class Bootstrap {
        }
 
        /**
+        * Returns the context this bootstrap was started in.
+        *
+        * @return \TYPO3\CMS\Core\Core\ApplicationContext The context encapsulated in an object
+        */
+       public function getContext() {
+               return $this->context;
+       }
+
+       /**
         * Prevent any unwanted output that may corrupt AJAX/compression.
         * This does not interfere with "die()" or "echo"+"exit()" messages!
         *
diff --git a/typo3/sysext/core/Tests/Unit/Core/ApplicationContextTest.php b/typo3/sysext/core/Tests/Unit/Core/ApplicationContextTest.php
new file mode 100644 (file)
index 0000000..312e8af
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Core;
+
+/*                                                                        *
+ * This script belongs to the TYPO3 Flow framework.                       *
+ *                                                                        *
+ * It is free software; you can redistribute it and/or modify it under    *
+ * the terms of the GNU Lesser General Public License, either version 3   *
+ * of the License, or (at your option) any later version.                 *
+ *                                                                        *
+ * The TYPO3 project - inspiring people to share!                         *
+ *                                                                        */
+
+use TYPO3\CMS\Core\Core\ApplicationContext;
+
+/**
+ * Testcase for the ApplicationContext class
+ */
+class ApplicationContextTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * Data provider with allowed contexts.
+        *
+        * @return array
+        */
+       public function allowedContexts() {
+               return array(
+                       array('Production'),
+                       array('Testing'),
+                       array('Development'),
+
+                       array('Development/MyLocalComputer'),
+                       array('Development/MyLocalComputer/Foo'),
+                       array('Production/SpecialDeployment/LiveSystem'),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider allowedContexts
+        */
+       public function contextStringCanBeSetInConstructorAndReadByCallingToString($allowedContext) {
+               $context = new ApplicationContext($allowedContext);
+               $this->assertSame($allowedContext, (string)$context);
+       }
+
+       /**
+        * Data provider with forbidden contexts.
+        *
+        * @return array
+        */
+       public function forbiddenContexts() {
+               return array(
+                       array('MySpecialContexz'),
+                       array('Testing123'),
+                       array('DevelopmentStuff'),
+                       array('DevelopmentStuff/FooBar'),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider forbiddenContexts
+        * @expectedException \TYPO3\CMS\Core\Exception
+        */
+       public function constructorThrowsExceptionIfMainContextIsForbidden($forbiddenContext) {
+               new ApplicationContext($forbiddenContext);
+       }
+
+       /**
+        * Data provider with expected is*() values for various contexts.
+        *
+        * @return array
+        */
+       public function isMethods() {
+               return array(
+                       'Development' => array(
+                               'contextName' => 'Development',
+                               'isDevelopment' => TRUE,
+                               'isProduction' => FALSE,
+                               'isTesting' => FALSE,
+                               'parentContext' => NULL
+                       ),
+                       'Development/YourSpecialContext' => array(
+                               'contextName' => 'Development/YourSpecialContext',
+                               'isDevelopment' => TRUE,
+                               'isProduction' => FALSE,
+                               'isTesting' => FALSE,
+                               'parentContext' => 'Development'
+                       ),
+
+                       'Production' => array(
+                               'contextName' => 'Production',
+                               'isDevelopment' => FALSE,
+                               'isProduction' => TRUE,
+                               'isTesting' => FALSE,
+                               'parentContext' => NULL
+                       ),
+                       'Production/MySpecialContext' => array(
+                               'contextName' => 'Production/MySpecialContext',
+                               'isDevelopment' => FALSE,
+                               'isProduction' => TRUE,
+                               'isTesting' => FALSE,
+                               'parentContext' => 'Production'
+                       ),
+
+                       'Testing' => array(
+                               'contextName' => 'Testing',
+                               'isDevelopment' => FALSE,
+                               'isProduction' => FALSE,
+                               'isTesting' => TRUE,
+                               'parentContext' => NULL
+                       ),
+                       'Testing/MySpecialContext' => array(
+                               'contextName' => 'Testing/MySpecialContext',
+                               'isDevelopment' => FALSE,
+                               'isProduction' => FALSE,
+                               'isTesting' => TRUE,
+                               'parentContext' => 'Testing'
+                       )
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider isMethods
+        */
+       public function contextMethodsReturnTheCorrectValues($contextName, $isDevelopment, $isProduction, $isTesting, $parentContext) {
+               $context = new ApplicationContext($contextName);
+               $this->assertSame($isDevelopment, $context->isDevelopment());
+               $this->assertSame($isProduction, $context->isProduction());
+               $this->assertSame($isTesting, $context->isTesting());
+               $this->assertSame((string)$parentContext, (string)$context->getParent());
+       }
+
+       /**
+        * @test
+        */
+       public function parentContextIsConnectedRecursively() {
+               $context = new ApplicationContext('Production/Foo/Bar');
+               $parentContext = $context->getParent();
+               $this->assertSame('Production/Foo', (string) $parentContext);
+
+               $rootContext = $parentContext->getParent();
+               $this->assertSame('Production', (string) $rootContext);
+       }
+}
+?>
\ No newline at end of file