[BUGFIX] Reduce expensive calls in AbstractMenuContentObject 76/50776/5
authorClaus Due <claus@namelesscoder.net>
Mon, 14 Nov 2016 14:21:43 +0000 (15:21 +0100)
committerJan Helke <typo3@helke.de>
Sat, 3 Dec 2016 08:46:38 +0000 (09:46 +0100)
This patch reduces the number of SQL queries and PHP calls
which get performed when rendering menus. The patch has
two parts:

* Runtime cache is used to remember a generated link and
  is given a cache identifier which includes a hash of all
  parameters which may affect the link.
* Runtime cache is used to remember the decision if a page
  is a submenu.

The item rendering function is called every time the same page
is rendered in any menu in the same request, and the decision
function to check if page is a sub-menu is called at least three
times with the same UID when generating a menu. Both of these
implements together reduce the necessary re-calling of methods
which generate the same output given the same arguments.

Change-Id: Idd6225081e8fb3f8160270af3d865b48208b756e
Releases: master, 7.6
Resolves: #78693
Reviewed-on: https://review.typo3.org/50776
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Jan Helke <typo3@helke.de>
Tested-by: Jan Helke <typo3@helke.de>
typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php
typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php

index 8935a0a..8733e44 100644 (file)
@@ -1548,6 +1548,13 @@ abstract class AbstractMenuContentObject
      */
     public function link($key, $altTarget = '', $typeOverride = '')
     {
+        $runtimeCache = $this->getRuntimeCache();
+        $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . serialize($this->menuArr[$key]));
+        $runtimeCachedLink = $runtimeCache->get($cacheId);
+        if ($runtimeCachedLink !== false) {
+            return $runtimeCachedLink;
+        }
+
         // Mount points:
         $MP_var = $this->getMPvar($key);
         $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
@@ -1609,6 +1616,7 @@ abstract class AbstractMenuContentObject
             } catch (\Exception $ex) {
             }
             if (!is_array($shortcut)) {
+                $runtimeCache->set($cacheId, []);
                 return [];
             }
             // Only setting url, not target
@@ -1676,6 +1684,7 @@ abstract class AbstractMenuContentObject
         $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
         $list['TARGET'] = $LD['target'];
         $list['onClick'] = $onClick;
+        $runtimeCache->set($cacheId, $list);
         return $list;
     }
 
@@ -1865,6 +1874,12 @@ abstract class AbstractMenuContentObject
      */
     public function isSubMenu($uid)
     {
+        $cacheId = 'menucontentobject-is-submenu-decision-' . $uid;
+        $runtimeCache = $this->getRuntimeCache();
+        $cachedDecision = $runtimeCache->get($cacheId);
+        if (isset($cachedDecision['result'])) {
+            return $cachedDecision['result'];
+        }
         // Looking for a mount-pid for this UID since if that
         // exists we should look for a subpages THERE and not in the input $uid;
         $mount_info = $this->sys_page->getMountPointInfo($uid);
@@ -1902,6 +1917,7 @@ abstract class AbstractMenuContentObject
             $hasSubPages = true;
             break;
         }
+        $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
         return $hasSubPages;
     }
 
@@ -2245,6 +2261,14 @@ abstract class AbstractMenuContentObject
     }
 
     /**
+     * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
+     */
+    protected function getRuntimeCache()
+    {
+        return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
+    }
+
+    /**
      * Set the parentMenuArr and key to provide the parentMenu informations to the
      * subMenu, special fur IProcFunc and itemArrayProcFunc user functions.
      *
index 5d8f550..c014164 100644 (file)
@@ -13,6 +13,8 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject\Menu;
  *
  * The TYPO3 project - inspiring people to share!
  */
+use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
+use TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject;
 
 /**
  * Test case
@@ -261,7 +263,11 @@ class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         foreach ($menuItems as $page) {
             $menu[] = ['uid' => $page];
         }
-
+        $runtimeCacheMock = $this->getMockBuilder(VariableFrontend::class)->setMethods(['get', 'set'])->disableOriginalConstructor()->getMock();
+        $runtimeCacheMock->expects($this->once())->method('get')->with($this->anything())->willReturn(false);
+        $runtimeCacheMock->expects($this->once())->method('set')->with($this->anything(), ['result' => $expectedResult]);
+        $this->subject = $this->getMockBuilder(AbstractMenuContentObject::class)->setMethods(['getRuntimeCache'])->getMockForAbstractClass();
+        $this->subject->expects($this->once())->method('getRuntimeCache')->willReturn($runtimeCacheMock);
         $this->prepareSectionIndexTest();
         $this->subject->parent_cObj = $this->getMock(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class, []);