Pmiazga has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/382609 )

Change subject: [POC] Separation of concerns in BookRenderer
......................................................................

[POC] Separation of concerns in BookRenderer

Changes:
 - introduced BookData class to store $pages, $collection
 and $metadata all together. Those three params are usually
 passed around together so we can use a wrapper
 - allow BookData class to handle bulding/retrieving pages,
 collection data and metadata
 - simplified $collection array handling as we use only 'title',
 'subtitle' and 'items' keys. Rendering classes don't have to know
 how $collection array is built
 - introduce BookMetadata class to store $metadata variable
 - only BookMetadata know how to build itself so it minimizes
 scenarios when different parts of the code modify $metadata
 variable in strange way
 - simplified return statements so it doesn't use complex array
 functions

Change-Id: I49228dbba768b8190ee2efb833abd87d18fe7318
---
M Collection.php
A includes/BookData.php
A includes/BookMetadata.php
M includes/BookRenderer.php
M includes/BookRenderingMediator.php
M includes/DataProvider.php
6 files changed, 265 insertions(+), 91 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Collection 
refs/changes/09/382609/1

diff --git a/Collection.php b/Collection.php
index b3751e2..2c07123 100644
--- a/Collection.php
+++ b/Collection.php
@@ -198,6 +198,8 @@
        = __DIR__ . '/includes/BookRenderingMediator.php';
 $wgAutoloadClasses[\MediaWiki\Extensions\Collection\MessageBoxHelper::class ]
        = __DIR__ . '/includes/MessageBoxHelper.php';
+$wgAutoloadClasses[\MediaWiki\Extensions\Collection\BookData::class ]
+       = __DIR__ . '/includes/BookData.php';
 
 $wgAutoloadClasses['CollectionPageTemplate'] = __DIR__ . 
'/templates/CollectionPageTemplate.php';
 $wgAutoloadClasses['CollectionListTemplate'] = __DIR__ . 
'/templates/CollectionListTemplate.php';
diff --git a/includes/BookData.php b/includes/BookData.php
new file mode 100644
index 0000000..3ade08f
--- /dev/null
+++ b/includes/BookData.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace MediaWiki\Extensions\Collection;
+
+class BookData {
+
+       /**
+        * @var array
+        */
+       private $collection;
+       /**
+        * @var array
+        */
+       private $pages;
+       /**
+        * @var BookMetadata
+        */
+       private $metadata;
+       /**
+        * @var bool
+        */
+       private $hasChapters;
+       /**
+        * @var int
+        */
+       private $articlesCount;
+       /**
+        * @var array
+        */
+       private $outline = [];
+
+       public function __construct( $collection, $pages, BookMetadata 
$metadata ) {
+               $this->collection = $collection;
+               $this->pages = $pages;
+               $this->metadata = $metadata;
+       }
+
+       public function getItems() {
+               return $this->collection['items'];
+       }
+
+       public function getTtile() {
+               return $this->collection['title'];
+       }
+
+       public function getSubtitle() {
+               return $this->collection['subtitle'];
+       }
+
+       public function getPages() {
+               return $this->pages;
+       }
+
+       public function getMetadata() {
+               return $this->metadata;
+       }
+
+       public function hasChapters() {
+               if ( $this->hasChapters === null ) {
+                       $this->hasChapters = (bool)array_filter( 
$this->collection['items'], function ( $item ) {
+                               return $item['type'] === 'chapter';
+                       } );
+               }
+               return $this->hasChapters;
+       }
+
+       public function getArticlesCount() {
+               if ( $this->articlesCount === null ) {
+                       $this->articlesCount = count(
+                               array_filter( $this->collection['items'], 
function ( $item ) {
+                                       return $item['type'] === 'article';
+                               } ) );
+               }
+               return $this->articlesCount;
+       }
+
+       public function overrideSections( $dbkey, $newSections ) {
+               $this->metadata->setSections( $dbkey, $newSections );
+       }
+
+       public function addOutline( $outline ) {
+               $this->outline[] = $outline;
+       }
+
+       public function getOutline() {
+               return $this->outline;
+       }
+
+       public function getDisplayTitleFor( $dbkey ) {
+               return $this->metadata->getDisplayTitle( $dbkey );
+       }
+
+       public function getSectionsFor( $dbkey ) {
+               return $this->metadata->getSections( $dbkey );
+       }
+
+       public function getContributors() {
+               return $this->metadata->getContributors();
+       }
+
+       public function getPage( $dbkey ) {
+               return $this->pages[$dbkey];
+       }
+
+}
diff --git a/includes/BookMetadata.php b/includes/BookMetadata.php
new file mode 100644
index 0000000..b59c7bb
--- /dev/null
+++ b/includes/BookMetadata.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace MediaWiki\Extensions\Collection;
+
+class BookMetadata {
+
+       private $metadata = [
+               'displaytitle' => [],
+               'sections' => [],
+               'contributors' => [],
+               'modules' => [],
+               'modulescripts' => [],
+               'modulestyles' => [],
+               'jsconfigvars' => [],
+       ];
+
+       public function addContributor( $name, $id ) {
+               $this->metadata['contributors'][$name] = $id;
+       }
+
+       public function getContributors() {
+               return $this->metadata['contributors'];
+       }
+
+       public function setDisplayTitle( $dbkey, $displayTitle ) {
+               $this->metadata['displaytitle'][$dbkey] = $displayTitle;
+       }
+
+       public function getDisplayTitle( $dbkey ) {
+               return $this->metadata['displaytitle'][$dbkey];
+       }
+
+       public function setSections( $dbkey, $sections ) {
+               $this->metadata['sections'][$dbkey] = $sections;
+       }
+
+       public function getSections( $dbkey ) {
+               return $this->metadata['sections'][$dbkey];
+       }
+
+       public function mergeModules( $modules ) {
+               $this->mergeData( 'modules', $modules );
+       }
+
+       public function mergeModuleScripts( $modules ) {
+               $this->mergeData( 'modulescripts', $modules );
+       }
+
+       public function mergeModuleStyles( $modules ) {
+               $this->mergeData( 'modulestyles', $modules );
+       }
+
+       public function mergeModuleJSConfigVars( $modules ) {
+               $this->mergeData( 'jsconfigvars', $modules );
+       }
+
+       public function getModules() {
+               return $this->metadata['modules'];
+       }
+
+       public function getModuleScripts() {
+               return $this->metadata['modulescripts'];
+       }
+
+       public function getModuleStyles() {
+               return $this->metadata['modulestyles'];
+       }
+
+       public function getJSConfigVars() {
+               return $this->metadata['jsconfigvars'];
+       }
+
+       private function mergeData( $field, $data ) {
+               $this->metadata[$field] = array_merge( $this->metadata[$field], 
$data );
+       }
+
+}
diff --git a/includes/BookRenderer.php b/includes/BookRenderer.php
index 1825ca7..d3b4531 100644
--- a/includes/BookRenderer.php
+++ b/includes/BookRenderer.php
@@ -26,21 +26,11 @@
 
        /**
         * Generate the concatenated page.
-        * @param array[] $collection Collection, as returned by 
CollectionSession::getCollection().
-        * @param string[] $pages Map of prefixed DB key => Parsoid HTML.
-        * @param array[] &$metadata Map of prefixed DB key => metadata, as 
returned by fetchMetadata().
-        *   Section data will be updated to account for heading level and id 
changes.
-        *   Also, an outline will be added (see renderCoverAndToc() for 
format).
+        * @param BookData $bookData
         * @return string HTML of the rendered book (without body/head).
         */
-       public function renderBook( $collection, $pages, &$metadata ) {
-               $hasChapters = (bool)array_filter( $collection['items'], 
function ( $item ) {
-                       return $item['type'] === 'chapter';
-               } );
-               $articleCount = count( array_filter( $collection['items'], 
function ( $item ) {
-                       return $item['type'] === 'article';
-               } ) );
-
+       public function renderBook( BookData $bookData ) {
+               $items = $bookData->getItems();
                $final = '';
                $headingCounter = new HeadingCounter();
 
@@ -50,9 +40,9 @@
                $formatter = new \RemexHtml\Serializer\HtmlFormatter();
                $serializer = new \RemexHtml\Serializer\Serializer( $formatter 
);
                $munger = new RemexCollectionMunger( $serializer, [
-                       'topHeadingLevel' => $hasChapters ? 3 : 2,
+                       'topHeadingLevel' => $bookData->hasChapters() ? 3 : 2,
                ] );
-               foreach ( $collection['items'] as $item ) {
+               foreach ( $items as $item ) {
                        if ( $item['type'] === 'chapter' ) {
                                $final .= Html::element( 'h1', [
                                                'id' => 'mw-book-chapter-' . 
Sanitizer::escapeIdForAttribute( $item['title'] ),
@@ -62,21 +52,22 @@
                        } elseif ( $item['type'] === 'article' ) {
                                $title = Title::newFromText( $item['title'] );
                                $dbkey = $title->getPrefixedDBkey();
-                               $html = $this->getBodyContents( $pages[$dbkey] 
);
+                               $html = $this->getBodyContents( 
$bookData->getPage( $dbkey ) );
 
                                $headingAttribs = [
                                        'id' => 'mw-book-article-' . $dbkey,
                                        'class' => 'mw-book-article',
                                ];
                                $mungerOptions = [];
-                               if ( $articleCount > 1 ) {
+                               if ( $bookData->getArticlesCount() > 1 ) {
                                        $mungerOptions['sectionNumberPrefix'] = 
$headingAttribs['data-mw-sectionnumber']
                                                = 
$headingCounter->incrementAndGet( -1 );
                                }
-                               $final .= Html::rawElement( 'h2', 
$headingAttribs, $metadata['displaytitle'][$dbkey] ) . "\n";
+                               $final .= Html::rawElement( 'h2', 
$headingAttribs,
+                                               $bookData->getDisplayTitleFor( 
$dbkey ) ). "\n";
+                               $sections = $bookData->getSectionsFor( $dbkey );
 
-                               $munger->startCollectionSection( './' . $dbkey, 
$metadata['sections'][$dbkey],
-                                       $headingCounter );
+                               $munger->startCollectionSection( './' . $dbkey, 
$sections, $headingCounter );
                                $treeBuilder = new 
\RemexHtml\TreeBuilder\TreeBuilder( $munger, [] );
                                $dispatcher = new 
\RemexHtml\TreeBuilder\Dispatcher( $treeBuilder );
                                $tokenizer = new 
\RemexHtml\Tokenizer\Tokenizer( $dispatcher, $html, [
@@ -93,105 +84,101 @@
                                $final .= Html::openElement( 'article' )
                                        . substr( $serializer->getResult(), 15 
) // strip "<!DOCTYPE html>"
                                        . Html::closeElement( 'article' );
+
+                               // munger is modifing $sections array, we have 
to update it
+                               $bookData->overrideSections( $dbkey, $sections 
);
                        }
                }
+               $this->buildOutline( $bookData );
 
-               $final = $this->renderCoverAndToc( $collection, $metadata )
+               $final = $this->renderCoverAndToc( $bookData )
                                 . $final
-                                . $this->renderContributors( $metadata, 
$headingCounter->incrementAndGetTopLevel() );
+                                . $this->renderContributors( $bookData, 
$headingCounter->incrementAndGetTopLevel() );
                return $final;
        }
 
-       /**
-        * Generate HTML for book cover page and table of contents.
-        * @param array $collection Collection, as returned by 
CollectionSession::getCollection().
-        * @param array[] $metadata Map of prefixed DB key => metadata, as 
returned by fetchMetadata().
-        *   An outline will be added which is similar to sections but flat and 
each item has the fields
-        *     - text: text of the outline item (article title, section title 
etc)
-        *     - type: 'chapter', 'article', 'section' or 'contributors'
-        *     - level: heading level or -2 for chapter, -1 for article
-        *     - anchor: id of the document node which the outline item refers 
to
-        *     - number: a hierarchical section number (something like "1.2.3")
-        * @return string HTML to prepend to the book.
-        */
-       private function renderCoverAndToc( $collection, &$metadata ) {
-               $hasChapters = (bool)array_filter( $collection['items'], 
function ( $item ) {
-                       return $item['type'] === 'chapter';
-               } );
-               $articleCount = count( array_filter( $collection['items'], 
function ( $item ) {
-                       return $item['type'] === 'article';
-               } ) );
+       private function buildOutline( BookData $bookData ) {
                $headingCounter = new HeadingCounter();
-               $outline = [];
-               foreach ( $collection['items'] as $item ) {
+               $items = $bookData->getItems();
+
+               foreach ( $items as $item ) {
                        if ( $item['type'] === 'chapter' ) {
-                               $outline[] = [
+                               $bookData->addOutline( [
                                        'text' => htmlspecialchars( 
$item['title'], ENT_QUOTES ),
                                        'type' => 'chapter',
                                        'level' => -2,
                                        'anchor' => 'mw-book-chapter-' . 
Sanitizer::escapeIdForAttribute( $item['title'] ),
                                        'number' => 
$headingCounter->incrementAndGet( -2 ),
-                               ];
+                               ] );
                        } elseif ( $item['type'] === 'article' ) {
                                $title = Title::newFromText( $item['title'] );
                                $dbkey = $title->getPrefixedDBkey();
-                               if ( $articleCount > 1 ) {
-                                       $outline[] = [
-                                               'text' => 
$metadata['displaytitle'][$dbkey],
+                               if ( $bookData->getArticlesCount() > 1 ) {
+                                       $bookData->addOutline( [
+                                               'text' => 
$bookData->getDisplayTitleFor( $dbkey ),
                                                'type' => 'article',
                                                'level' => -1,
                                                'anchor' => 'mw-book-article-' 
. $dbkey,
                                                'number' => 
$headingCounter->incrementAndGet( -1 ),
-                                       ];
+                                       ] );
                                }
-                               foreach ( $metadata['sections'][$dbkey] as 
$section ) {
-                                       $outline[] = [
+                               $sections = $bookData->getSectionsFor( $dbkey );
+                               foreach ( $sections as $section ) {
+                                       $bookData->addOutline( [
                                                'text' => $section['title'],
                                                'type' => 'section',
                                                'level' => $section['level'],
                                                'anchor' => $section['id'],
                                                'number' => 
$headingCounter->incrementAndGet( $section['level'] ),
-                                       ];
+                                       ] );
                                }
                        } else {
                                throw new LogicException( 'Unknown collection 
item type: ' . $item['type'] );
                        }
                }
 
-               if ( $hasChapters ) {
+               if ( $bookData->hasChapters() ) {
                        $contributorsLevel = -2;
-               } elseif ( $articleCount > 1 ) {
+               } elseif ( $bookData->getArticlesCount() > 1 ) {
                        $contributorsLevel = -1;
                } else {
                        $contributorsLevel = 0;
                }
-               $outline[] = [
+               $bookData->addOutline( [
                        'text' => wfMessage( 'coll-contributors-title' 
)->text(),
                        'type' => 'contributors',
                        'level' => $contributorsLevel,
                        'anchor' => 'mw-book-contributors',
                        'number' => $headingCounter->incrementAndGetTopLevel(),
-               ];
-               $metadata['outline'] = $outline;
+               ] );
+       }
 
+       /**
+        * Generate HTML for book cover page and table of contents.
+        * @param BookData $bookData
+        * @return string HTML to prepend to the book.
+        */
+       private function renderCoverAndToc( BookData $bookData ) {
                return $this->templateParser->processTemplate( 'toc', 
$this->fixTemplateData( [
-                       'title' => $collection['title'],
-                       'subtitle' => $collection['subtitle'],
+                       'title' => $bookData->getTtile(),
+                       'subtitle' => $bookData->getSubtitle(),
                        'toctitle' => wfMessage( 'coll-toc-title' )->text(),
-                       'tocitems' => $this->getNestedOutline( $outline ),
+                       'tocitems' => $this->getNestedOutline( 
$bookData->getOutline() ),
                ] ) );
        }
 
        /**
         * Generate HTML for the list of contributors.
-        * @param array[] $metadata Map of prefixed DB key => metadata, as 
returned by fetchMetadata().
+        * @param BookData $bookData
         * @param string $sectionNumber The section number for the contributors 
section, if any.
         * @return string HTML to append to the book.
         */
-       private function renderContributors( $metadata, $sectionNumber = null ) 
{
+       private function renderContributors( BookData $bookData, $sectionNumber 
= null ) {
+               $contributors = $bookData->getContributors();
+
                $list = array_map( function ( $name ) {
                        return Html::element( 'li', [], $name );
-               }, array_keys( $metadata['contributors'] ) );
+               }, array_keys( $contributors ) );
 
                $attribs = [ 'id' => 'mw-book-contributors' ];
                if ( $sectionNumber ) {
diff --git a/includes/BookRenderingMediator.php 
b/includes/BookRenderingMediator.php
index 9d30550..6b4325c 100644
--- a/includes/BookRenderingMediator.php
+++ b/includes/BookRenderingMediator.php
@@ -58,25 +58,35 @@
        public function getBook( array $collection ) {
                $dataProvider = new DataProvider( $this->restServiceClient );
                $dataProvider->setLogger( $this->logger );
-               $bookRenderer = new BookRenderer( $this->templateParser );
 
                $status = $dataProvider->fetchPages( $collection );
-               if ( $status->isOK() ) {
-                       $pages = $status->getValue();
-               } else {
+               if ( !$status->isOK() ) {
                        $message = Status::wrap( $status )->getMessage( false, 
'coll-rendererror-fetch-wrapper' );
                        throw new ErrorPageError( 'coll-rendererror-title', 
$message );
                }
+
+               $pages = $status->getValue();
                $status = $dataProvider->fetchMetadata( array_keys( $pages ) );
-               if ( $status->isOK() ) {
-                       $metadata = $status->getValue();
-               } else {
+               if ( !$status->isOK() ) {
                        throw new ErrorPageError( 'coll-rendererror-title', 
Status::wrap( $status )->getMessage() );
                }
-               $html = $bookRenderer->renderBook( $collection, $pages, 
$metadata );
+               /** @var BookMetadata $bookMetadata */
+               $bookMetadata = $status->getValue();
+               $bookData = new BookData( $collection, $pages, $bookMetadata );
+               return $this->getBookContent( $bookData );
+       }
 
-               $fields = [ 'html', 'modules', 'modulescripts', 'modulestyles', 
'jsconfigvars' ];
-               return array_intersect_key( [ 'html' => $html ] + $metadata, 
array_fill_keys( $fields, null ) );
+       private function getBookContent( BookData $bookData ) {
+               $bookRenderer = new BookRenderer( $this->templateParser );
+               $html = $bookRenderer->renderBook( $bookData );
+               $metadata = $bookData->getMetadata();
+               return [
+                       'html' => $html,
+                       'modules' => $metadata->getModules(),
+                       'modulescripts' => $metadata->getModuleScripts(),
+                       'modulestyles' => $metadata->getModuleStyles(),
+                       'jsconfigvars' => $metadata->getJSConfigVars()
+               ];
        }
 
        /**
diff --git a/includes/DataProvider.php b/includes/DataProvider.php
index dacef53..ee7df61 100644
--- a/includes/DataProvider.php
+++ b/includes/DataProvider.php
@@ -123,15 +123,7 @@
         *   - jsconfigvars: [ var, ... ]
         */
        public function fetchMetadata( $dbkeys ) {
-               $metadata = [
-                       'displaytitle' => [],
-                       'sections' => [],
-                       'contributors' => [],
-                       'modules' => [],
-                       'modulescripts' => [],
-                       'modulestyles' => [],
-                       'jsconfigvars' => [],
-               ];
+               $metadata = new BookMetadata();
 
                // get contributors
                $params = [
@@ -148,7 +140,7 @@
                        $params = $continue + $params;
                        foreach ( $data['query']['pages'] as $page ) {
                                foreach ( $page['contributors'] as $key => 
$contrib ) {
-                                       
$metadata['contributors'][$contrib['name']] = $contrib['userid'];
+                                       $metadata->addContributor( 
$contrib['name'], $contrib['userid'] );
                                }
                        }
                } while ( $continue );
@@ -161,18 +153,19 @@
                                'prop' => 
'sections|displaytitle|modules|jsconfigvars',
                                'page' => $dbkey,
                        ] );
-                       $metadata['displaytitle'][$dbkey] = 
$data['parse']['displaytitle'];
-                       $metadata['sections'][$dbkey] = array_map( function ( 
$sectionData ) {
+                       $metadata->setDisplayTitle( $dbkey, 
$data['parse']['displaytitle'] );
+                       $metadata->setSections( $dbkey, array_map( function ( 
$sectionData ) {
                                return [
                                        'title' => $sectionData['line'],
                                        'id' => $sectionData['anchor'],
-                                       'level' => intval( 
$sectionData['level'] ),
+                                       'level' => (int)$sectionData['level'],
                                ];
-                       }, $data['parse']['sections'] );
-                       foreach ( [ 'modules', 'modulescripts', 'modulestyles', 
'jsconfigvars' ] as $field ) {
-                               // let's hope there is no conflict in 
jsconfigvars...
-                               $metadata[$field] = array_merge( 
$metadata[$field], $data['parse'][$field] );
-                       }
+                       }, $data['parse']['sections'] ) );
+
+                       $metadata->mergeModules( $data['parse']['modules'] );
+                       $metadata->mergeModuleScripts( 
$data['parse']['modules'] );
+                       $metadata->mergeModuleStyles( $data['parse']['modules'] 
);
+                       $metadata->mergeModuleJSConfigVars( 
$data['parse']['modules'] );
                }
 
                return StatusValue::newGood( $metadata );

-- 
To view, visit https://gerrit.wikimedia.org/r/382609
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I49228dbba768b8190ee2efb833abd87d18fe7318
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Collection
Gerrit-Branch: master
Gerrit-Owner: Pmiazga <pmia...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to