[!!!][FEATURE] Allow individual content caching 15/35915/15
authorPhilipp Gampe <philipp.gampe@typo3.org>
Fri, 9 Jan 2015 03:55:56 +0000 (04:55 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Tue, 30 Jun 2015 09:00:40 +0000 (11:00 +0200)
Since TYPO3 4.7 'stdWrap.cache' is available. This stores the rendered
string into the cache_hash via the caching framework and retrieves it
on successive renderings.
However stdWrap kicks in too late for many cObjects, e.g. COA.

Moving the 'cache.' logic into the central render method avoids the
rendering of the cObject, thus gaining a major speed improvement if
a cache entry already exists for a cObject.

Custom cObjects and USER objects utilizing the 'cache.' property need
adoption.

This change allows to make effective use of page section caching to
share the same generated HTML across many pages without regeneration.
It provides the same power as Magento block caching.

Additionally the properties key, lifetime and tags get stdWrap support.

Resolves: #64200
Releases: master
Change-Id: Ic0776082e09c70e03a9b432b41207031b4f2efa6
Reviewed-on: http://review.typo3.org/35915
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Benjamin Mack <benni@typo3.org>
Tested-by: Benjamin Mack <benni@typo3.org>
Reviewed-by: Stephan GroƟberndt <stephan@grossberndt.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
typo3/sysext/core/Documentation/Changelog/master/Breaking-64200-CustomCObject.cache.TypoScriptEvaluation.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-64200-AllowIndividualContentCaching.rst [new file with mode: 0644]
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-64200-CustomCObject.cache.TypoScriptEvaluation.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-64200-CustomCObject.cache.TypoScriptEvaluation.rst
new file mode 100644 (file)
index 0000000..ba3d9e0
--- /dev/null
@@ -0,0 +1,56 @@
+=================================================================
+Breaking: #64200 - Custom [cObject].cache.* TypoScript evaluation
+=================================================================
+
+Description
+===========
+
+The ``stdWrap.cache.`` property is now available as first-class function to all
+content objects. This skips the rendering even for content objects that evaluate
+``stdWrap`` after rendering (e.g. ``COA``).
+
+Additionally, stdWrap support is added to key, lifetime and tags.
+
+
+Impact
+======
+
+If you've previously used the ``cache.`` property in your custom cObject,
+this will now fail, because ``cache.`` is unset to avoid double caching.
+
+``stdWrap.cache`` continues to exist and can be used as before. However
+the top level ``stdWrap`` of certain cObjects (e.g. ``TEXT`` cObject)
+will not evaluate ``cache.`` as part of ``stdWrap``, but before starting
+the rendering of the cObject. In conjunction the storing will happen
+after the ``stdWrap`` processing right before the content is returned.
+
+Top level ``cache.`` will not evaluate the hook
+``$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore']``
+any more.
+
+
+Affected Installations
+======================
+
+-      All installations with custom ``cObject`` implementations which make use of the ``cache.`` property.
+
+-      Installations that purposely rely on the content object being evaluated before the cache is tried.
+
+-      Installations that rely on the order of the ``cache.`` evaluation.
+
+-      Installations that make use of the hook
+       ``$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore']``
+       on top level ``cache.``.
+
+
+Migration
+=========
+
+Rename your property or rely on the Core implementation.
+
+If you need ``cache.`` being evaluated as part of ``stdWrap``, please move it down one level
+by writing ``stdWrap.cache`` instead.
+
+If you used the hook
+``$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore']``,
+please use ``stdWrap`` and the available hooks inside ``stdWrap`` to achieve your goal.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-64200-AllowIndividualContentCaching.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-64200-AllowIndividualContentCaching.rst
new file mode 100644 (file)
index 0000000..77715bc
--- /dev/null
@@ -0,0 +1,61 @@
+==================================================
+Feature: #64200 - Allow individual content caching
+==================================================
+
+Description
+===========
+
+The ``stdWrap.cache.`` property is now available as first-class function to all
+content objects. This skips the rendering even for content objects that evaluate
+``stdWrap`` after rendering (e.g. ``COA``).
+
+Usage:
+
+.. code-block:: typoscript
+
+       page = PAGE
+       page.10 = COA
+       page.10 {
+               cache.key = coaout
+               cache.lifetime = 60
+               #stdWrap.cache.key = coastdWrap
+               #stdWrap.cache.lifetime = 60
+               10 = TEXT
+               10 {
+                       cache.key = mycurrenttimestamp
+                       cache.lifetime = 60
+                       data = date : U
+                       strftime = %H:%M:%S
+                       noTrimWrap = |10: | |
+               }
+               20 = TEXT
+               20 {
+                       data = date : U
+                       strftime = %H:%M:%S
+                       noTrimWrap = |20: | |
+               }
+       }
+
+The commented part is ``stdWrap.cache.`` property available since 4.7,
+that does not stop the rendering of ``COA`` including all sub-cObjects.
+
+Additionally, stdWrap support is added to key, lifetime and tags.
+
+
+Impact
+======
+
+If you've previously used the ``cache.`` property in your custom cObject,
+this will now fail, because ``cache.`` is unset to avoid double caching.
+You are encouraged to rely on the core methods for caching cObjects or
+rename your property.
+
+``stdWrap.cache`` continues to exists and can be used as before. However
+the top level ``stdWrap`` of certain cObjects (e.g. ``TEXT`` cObject)
+will not evaluate ``cache.`` as part of ``stdWrap``, but before starting
+the rendering of the cObject. In conjunction the storing will happen
+after the ``stdWrap`` processing right before the content is returned.
+
+Top level ``cache.`` will not evaluate the hook
+``$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore']``
+any more.
index 0926f8d..b2ec17c 100644 (file)
@@ -892,7 +892,8 @@ class ContentObjectRenderer {
         ********************************************/
 
        /**
-        * Renders a content object by taking exception handling into consideration
+        * Renders a content object by taking exception and cache handling
+        * into consideration
         *
         * @param AbstractContentObject $contentObject Content object instance
         * @param array $configuration Array of TypoScript properties
@@ -903,6 +904,18 @@ class ContentObjectRenderer {
         */
        public function render(AbstractContentObject $contentObject, $configuration = array()) {
                $content = '';
+
+               // Evaluate possible cache and return
+               $cacheConfiguration = isset($configuration['cache.']) ? $configuration['cache.'] : NULL;
+               if ($cacheConfiguration !== NULL) {
+                       unset($configuration['cache.']);
+                       $cache = $this->getFromCache($cacheConfiguration);
+                       if ($cache !== FALSE) {
+                               return $cache;
+                       }
+               }
+
+               // Render content
                try {
                        $content .= $contentObject->render($configuration);
                } catch (ContentRenderingException $exception) {
@@ -917,6 +930,19 @@ class ContentObjectRenderer {
                                $content = $exceptionHandler->handle($exception, $contentObject, $configuration);
                        }
                }
+
+               // Store cache
+               if ($cacheConfiguration !== NULL) {
+                       $key = $this->calculateCacheKey($cacheConfiguration);
+                       if (!empty($key)) {
+                               /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
+                               $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
+                               $tags = $this->calculateCacheTags($cacheConfiguration);
+                               $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
+                               $cacheFrontend->set($key, $content, $tags, $lifetime);
+                       }
+               }
+
                return $content;
        }
 
@@ -2357,15 +2383,11 @@ class ContentObjectRenderer {
         * @return string The processed input value
         */
        public function stdWrap_cacheRead($content = '', $conf = array()) {
-               if (!empty($conf['cache.']['key'])) {
-                       /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
-                       $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
-                       if ($cacheFrontend && $cacheFrontend->has($conf['cache.']['key'])) {
-                               $content = $cacheFrontend->get($conf['cache.']['key']);
-                               $this->stopRendering[$this->stdWrapRecursionLevel] = TRUE;
-                       }
+               if (!isset($conf['cache.'])) {
+                       return $content;
                }
-               return $content;
+               $result = $this->getFromCache($conf['cache.']);
+               return $result === FALSE ? $content : $result;
        }
 
        /**
@@ -3624,38 +3646,29 @@ class ContentObjectRenderer {
         * @return string The processed input value
         */
        public function stdWrap_cacheStore($content = '', $conf = array()) {
-               if (!empty($conf['cache.']['key'])) {
-                       /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
-                       $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
-                       if ($cacheFrontend) {
-                               $tags = !empty($conf['cache.']['tags']) ? GeneralUtility::trimExplode(',', $conf['cache.']['tags']) : array();
-                               if (strtolower($conf['cache.']['lifetime']) == 'unlimited') {
-                                       // unlimited
-                                       $lifetime = 0;
-                               } elseif (strtolower($conf['cache.']['lifetime']) == 'default') {
-                                       // default lifetime
-                                       $lifetime = NULL;
-                               } elseif ((int)$conf['cache.']['lifetime'] > 0) {
-                                       // lifetime in seconds
-                                       $lifetime = (int)$conf['cache.']['lifetime'];
-                               } else {
-                                       // default lifetime
-                                       $lifetime = NULL;
-                               }
-                               if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'])) {
-                                       foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] as $_funcRef) {
-                                               $params = array(
-                                                       'key' => $conf['cache.']['key'],
-                                                       'content' => $content,
-                                                       'lifetime' => $lifetime,
-                                                       'tags' => $tags
-                                               );
-                                               GeneralUtility::callUserFunction($_funcRef, $params, $this);
-                                       }
-                               }
-                               $cacheFrontend->set($conf['cache.']['key'], $content, $tags, $lifetime);
+               if (!isset($conf['cache.'])) {
+                       return $content;
+               }
+               $key = $this->calculateCacheKey($conf['cache.']);
+               if (empty($key)) {
+                       return $content;
+               }
+               /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
+               $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
+               $tags = $this->calculateCacheTags($conf['cache.']);
+               $lifetime = $this->calculateCacheLifetime($conf['cache.']);
+               if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'])) {
+                       foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] as $_funcRef) {
+                               $params = array(
+                                       'key' => $key,
+                                       'content' => $content,
+                                       'lifetime' => $lifetime,
+                                       'tags' => $tags
+                               );
+                               GeneralUtility::callUserFunction($_funcRef, $params, $this);
                        }
                }
+               $cacheFrontend->set($key, $content, $tags, $lifetime);
                return $content;
        }
 
@@ -8340,4 +8353,74 @@ class ContentObjectRenderer {
                return GeneralUtility::getIndpEnv($key);
        }
 
+       /**
+        * Fetches content from cache
+        *
+        * @param array $configuration Array
+        * @return string|bool FALSE on cache miss
+        * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+        */
+       protected function getFromCache(array $configuration) {
+               $content = FALSE;
+
+               $cacheKey = $this->calculateCacheKey($configuration);
+               if (!empty($cacheKey)) {
+                       /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend */
+                       $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)
+                               ->getCache('cache_hash');
+                       $content = $cacheFrontend->get($cacheKey);
+               }
+               return $content;
+       }
+
+       /**
+        * Calculates the lifetime of a cache entry based on the given configuration
+        *
+        * @param array $configuration
+        * @return int|null
+        */
+       protected function calculateCacheLifetime(array $configuration) {
+               $lifetimeConfiguration = isset($configuration['lifetime'])
+                       ? $configuration['lifetime']
+                       : '';
+               $lifetimeConfiguration = isset($configuration['lifetime.'])
+                       ? $this->stdWrap($lifetimeConfiguration, $configuration['lifetime.'])
+                       : $lifetimeConfiguration;
+
+               $lifetime = NULL; // default lifetime
+               if (strtolower($lifetimeConfiguration) === 'unlimited') {
+                       $lifetime = 0; // unlimited
+               } elseif ($lifetimeConfiguration > 0) {
+                       $lifetime = (int)$lifetimeConfiguration; // lifetime in seconds
+               }
+               return $lifetime;
+       }
+
+       /**
+        * Calculates the tags for a cache entry bases on the given configuration
+        *
+        * @param array $configuration
+        * @return array
+        */
+       protected function calculateCacheTags(array $configuration) {
+               $tags = isset($configuration['tags']) ? $configuration['tags'] : '';
+               $tags = isset($configuration['tags.'])
+                       ? $this->stdWrap($tags, $configuration['tags.'])
+                       : $tags;
+               return empty($tags) ? [] : GeneralUtility::trimExplode(',', $tags);
+       }
+
+       /**
+        * Applies stdWrap to the cache key
+        *
+        * @param array $configuration
+        * @return string
+        */
+       protected function calculateCacheKey(array $configuration) {
+               $key = isset($configuration['key']) ? $configuration['key'] : '';
+               return isset($configuration['key.'])
+                       ? $this->stdWrap($key, $configuration['key.'])
+                       : $key;
+       }
+
 }