resourceloader: Move packaging to a new getModuleContent() method
authorTimo Tijhof <krinklemail@gmail.com>
Thu, 14 May 2015 19:05:47 +0000 (20:05 +0100)
committerTimo Tijhof <krinklemail@gmail.com>
Tue, 9 Jun 2015 01:04:58 +0000 (02:04 +0100)
Centralise the building of module packages into this method so
that it can be easily re-used.

This is in preparation for providing the option for modules to use
content-based hashing (instead of based on meta-data) in the version
string of modules. Having a getModuleContent method allows that to
be implemented in a way that is well-cached (as we'll be calling it
from multiple multiple code paths) and without duplicating this logic.

Bug: T98087
Change-Id: I376233caaabe44b6101565b70a50904abdf8ab4f

includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderModule.php

index d5b17d8..bbe55ab 100644 (file)
@@ -298,8 +298,16 @@ class ResourceLoader implements LoggerAwareInterface {
        }
 
        /**
-        * @param MessageBlobStore $blobStore
+        * @since 1.26
+        * @return MessageBlobStore
+        */
+       public function getMessageBlobStore() {
+               return $this->blobStore;
+       }
+
+       /**
         * @since 1.25
+        * @param MessageBlobStore $blobStore
         */
        public function setMessageBlobStore( MessageBlobStore $blobStore ) {
                $this->blobStore = $blobStore;
@@ -962,7 +970,7 @@ MESSAGE;
                // Pre-fetch blobs
                if ( $context->shouldIncludeMessages() ) {
                        try {
-                               $blobs = $this->blobStore->get( $this, $modules, $context->getLanguage() );
+                               $this->blobStore->get( $this, $modules, $context->getLanguage() );
                        } catch ( Exception $e ) {
                                MWExceptionHandler::logException( $e );
                                $this->logger->warning( 'Prefetching MessageBlobStore failed: {exception}', array(
@@ -970,8 +978,6 @@ MESSAGE;
                                ) );
                                $this->errors[] = self::formatExceptionNoComment( $e );
                        }
-               } else {
-                       $blobs = array();
                }
 
                foreach ( $missing as $name ) {
@@ -981,79 +987,13 @@ MESSAGE;
                // Generate output
                $isRaw = false;
                foreach ( $modules as $name => $module ) {
-                       /**
-                        * @var $module ResourceLoaderModule
-                        */
-
                        try {
-                               $scripts = '';
-                               if ( $context->shouldIncludeScripts() ) {
-                                       // If we are in debug mode, we'll want to return an array of URLs if possible
-                                       // However, we can't do this if the module doesn't support it
-                                       // We also can't do this if there is an only= parameter, because we have to give
-                                       // the module a way to return a load.php URL without causing an infinite loop
-                                       if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
-                                               $scripts = $module->getScriptURLsForDebug( $context );
-                                       } else {
-                                               $scripts = $module->getScript( $context );
-                                               // rtrim() because there are usually a few line breaks
-                                               // after the last ';'. A new line at EOF, a new line
-                                               // added by ResourceLoaderFileModule::readScriptFiles, etc.
-                                               if ( is_string( $scripts )
-                                                       && strlen( $scripts )
-                                                       && substr( rtrim( $scripts ), -1 ) !== ';'
-                                               ) {
-                                                       // Append semicolon to prevent weird bugs caused by files not
-                                                       // terminating their statements right (bug 27054)
-                                                       $scripts .= ";\n";
-                                               }
-                                       }
-                               }
-                               // Styles
-                               $styles = array();
-                               if ( $context->shouldIncludeStyles() ) {
-                                       // Don't create empty stylesheets like array( '' => '' ) for modules
-                                       // that don't *have* any stylesheets (bug 38024).
-                                       $stylePairs = $module->getStyles( $context );
-                                       if ( count( $stylePairs ) ) {
-                                               // If we are in debug mode without &only= set, we'll want to return an array of URLs
-                                               // See comment near shouldIncludeScripts() for more details
-                                               if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
-                                                       $styles = array(
-                                                               'url' => $module->getStyleURLsForDebug( $context )
-                                                       );
-                                               } else {
-                                                       // Minify CSS before embedding in mw.loader.implement call
-                                                       // (unless in debug mode)
-                                                       if ( !$context->getDebug() ) {
-                                                               foreach ( $stylePairs as $media => $style ) {
-                                                                       // Can be either a string or an array of strings.
-                                                                       if ( is_array( $style ) ) {
-                                                                               $stylePairs[$media] = array();
-                                                                               foreach ( $style as $cssText ) {
-                                                                                       if ( is_string( $cssText ) ) {
-                                                                                               $stylePairs[$media][] = $this->filter( 'minify-css', $cssText );
-                                                                                       }
-                                                                               }
-                                                                       } elseif ( is_string( $style ) ) {
-                                                                               $stylePairs[$media] = $this->filter( 'minify-css', $style );
-                                                                       }
-                                                               }
-                                                       }
-                                                       // Wrap styles into @media groups as needed and flatten into a numerical array
-                                                       $styles = array(
-                                                               'css' => self::makeCombinedStyles( $stylePairs )
-                                                       );
-                                               }
-                                       }
-                               }
-
-                               // Messages
-                               $messagesBlob = isset( $blobs[$name] ) ? $blobs[$name] : '{}';
+                               $content = $module->getModuleContent( $context );
 
                                // Append output
                                switch ( $context->getOnly() ) {
                                        case 'scripts':
+                                               $scripts = $content['scripts'];
                                                if ( is_string( $scripts ) ) {
                                                        // Load scripts raw...
                                                        $out .= $scripts;
@@ -1063,6 +1003,7 @@ MESSAGE;
                                                }
                                                break;
                                        case 'styles':
+                                               $styles = $content['styles'];
                                                // We no longer seperate into media, they are all combined now with
                                                // custom media type groups into @media .. {} sections as part of the css string.
                                                // Module returns either an empty array or a numerical array with css strings.
@@ -1071,10 +1012,10 @@ MESSAGE;
                                        default:
                                                $out .= self::makeLoaderImplementScript(
                                                        $name,
-                                                       $scripts,
-                                                       $styles,
-                                                       new XmlJsCode( $messagesBlob ),
-                                                       $module->getTemplates()
+                                                       isset( $content['scripts'] ) ? $content['scripts'] : '',
+                                                       isset( $content['styles'] ) ? $content['styles'] : array(),
+                                                       isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : array(),
+                                                       isset( $content['templates'] ) ? $content['templates'] : array()
                                                );
                                                break;
                                }
index 958990c..8d4804c 100644 (file)
@@ -64,6 +64,8 @@ abstract class ResourceLoaderModule {
        protected $msgBlobMtime = array();
        // In-object cache for version hash
        protected $versionHash = array();
+       // In-object cache for module content
+       protected $contents = array();
 
        // Whether the position returned by getPosition() is defined in the module configuration
        // and not a default value
@@ -439,6 +441,122 @@ abstract class ResourceLoaderModule {
                $this->msgBlobMtime[$lang] = $mtime;
        }
 
+       /**
+        * Get an array of this module's resources. Ready for serving to the web.
+        *
+        * @since 1.26
+        * @param ResourceLoaderContext $context
+        * @return array
+        */
+       public function getModuleContent( ResourceLoaderContext $context ) {
+               $contextHash = $context->getHash();
+               // Cache this expensive operation. This calls builds the scripts, styles, and messages
+               // content which typically involves filesystem and/or database access.
+               if ( !array_key_exists( $contextHash, $this->contents ) ) {
+                       $this->contents[ $contextHash ] = $this->buildContent( $context );
+               }
+               return $this->contents[ $contextHash ];
+       }
+
+       /**
+        * Bundle all resources attached to this module into an array.
+        *
+        * @since 1.26
+        * @param ResourceLoaderContext $context
+        * @return array
+        */
+       final protected function buildContent( ResourceLoaderContext $context ) {
+               $rl = $context->getResourceLoader();
+
+               // Only include properties that are relevant to this context (e.g. only=scripts)
+               // and that are non-empty (e.g. don't include "templates" for modules without
+               // templates). This helps prevent invalidating cache for all modules when new
+               // optional properties are introduced.
+               $content = array();
+
+               // Scripts
+               if ( $context->shouldIncludeScripts() ) {
+                       // If we are in debug mode, we'll want to return an array of URLs if possible
+                       // However, we can't do this if the module doesn't support it
+                       // We also can't do this if there is an only= parameter, because we have to give
+                       // the module a way to return a load.php URL without causing an infinite loop
+                       if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
+                               $scripts = $this->getScriptURLsForDebug( $context );
+                       } else {
+                               $scripts = $this->getScript( $context );
+                               // rtrim() because there are usually a few line breaks
+                               // after the last ';'. A new line at EOF, a new line
+                               // added by ResourceLoaderFileModule::readScriptFiles, etc.
+                               if ( is_string( $scripts )
+                                       && strlen( $scripts )
+                                       && substr( rtrim( $scripts ), -1 ) !== ';'
+                               ) {
+                                       // Append semicolon to prevent weird bugs caused by files not
+                                       // terminating their statements right (bug 27054)
+                                       $scripts .= ";\n";
+                               }
+                       }
+                       $content['scripts'] = $scripts;
+               }
+
+               // Styles
+               if ( $context->shouldIncludeStyles() ) {
+                       $styles = array();
+                       // Don't create empty stylesheets like array( '' => '' ) for modules
+                       // that don't *have* any stylesheets (bug 38024).
+                       $stylePairs = $this->getStyles( $context );
+                       if ( count( $stylePairs ) ) {
+                               // If we are in debug mode without &only= set, we'll want to return an array of URLs
+                               // See comment near shouldIncludeScripts() for more details
+                               if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
+                                       $styles = array(
+                                               'url' => $this->getStyleURLsForDebug( $context )
+                                       );
+                               } else {
+                                       // Minify CSS before embedding in mw.loader.implement call
+                                       // (unless in debug mode)
+                                       if ( !$context->getDebug() ) {
+                                               foreach ( $stylePairs as $media => $style ) {
+                                                       // Can be either a string or an array of strings.
+                                                       if ( is_array( $style ) ) {
+                                                               $stylePairs[$media] = array();
+                                                               foreach ( $style as $cssText ) {
+                                                                       if ( is_string( $cssText ) ) {
+                                                                               $stylePairs[$media][] = $rl->filter( 'minify-css', $cssText );
+                                                                       }
+                                                               }
+                                                       } elseif ( is_string( $style ) ) {
+                                                               $stylePairs[$media] = $rl->filter( 'minify-css', $style );
+                                                       }
+                                               }
+                                       }
+                                       // Wrap styles into @media groups as needed and flatten into a numerical array
+                                       $styles = array(
+                                               'css' => $rl->makeCombinedStyles( $stylePairs )
+                                       );
+                               }
+                       }
+                       $content['styles'] = $styles;
+               }
+
+               // Messages
+               $blobs = $rl->getMessageBlobStore()->get(
+                       $rl,
+                       array( $this->getName() => $this ),
+                       $context->getLanguage()
+               );
+               if ( isset( $blobs[$this->getName()] ) ) {
+                       $content['messagesBlob'] = $blobs[$this->getName()];
+               }
+
+               $templates = $this->getTemplates();
+               if ( $templates ) {
+                       $content['templates'] = $templates;
+               }
+
+               return $content;
+       }
+
        /**
         * Get a string identifying the current version of this module in a given context.
         *