Zhu Jinhui 7 жил өмнө
parent
commit
b6382a8947

+ 98 - 0
src/Library/BigPipe/BigPipe/BigPipe.php

@@ -0,0 +1,98 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# BigPipe main class                         [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+#                                                                              #
+# The BigPipe main class is responsible for sorting and rendering the pagelets #
+# and their associated resources. This class also provides methods to turn off #
+# the pipeline mode or turn on the debugging mode.                             #
+#                                                                              #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+namespace BigPipe;
+
+class BigPipe {
+	private static $enabled  = TRUE;
+	private static $pagelets = [];
+
+	#===============================================================================
+	# Enable or disable the pipeline mode
+	#===============================================================================
+	public static function enabled($change = NULL) {
+		if($change !== NULL) {
+			self::$enabled = (bool) $change;
+		}
+
+		return self::$enabled;
+	}
+
+	#===============================================================================
+	# Insert pagelet into queue
+	#===============================================================================
+	public static function enqueue(Pagelet $Pagelet) {
+		self::$pagelets[spl_object_hash($Pagelet)] = $Pagelet;
+	}
+
+	#===============================================================================
+	# Remove pagelet from queue
+	#===============================================================================
+	public static function dequeue(Pagelet $Pagelet) {
+		unset(self::$pagelets[spl_object_hash($Pagelet)]);
+	}
+
+	#===============================================================================
+	# Sends output buffer so far as possible towards user
+	#===============================================================================
+	public static function flushOutputBuffer() {
+		ob_flush(); flush();
+	}
+
+	#===============================================================================
+	# Renders all remaining pagelets from the queue in the appropriate order
+	#===============================================================================
+	public static function completeResponse() {
+		self::flushOutputBuffer();
+
+		$pagelets_ordered = [];
+
+		foreach(self::$pagelets as $Pagelet) {
+			$pagelets_ordered[$Pagelet->getPriority()][] = $Pagelet;
+		}
+
+		krsort($pagelets_ordered);
+
+		if(!empty($pagelets_ordered)) {
+			$pagelets = call_user_func_array('array_merge', $pagelets_ordered);
+
+			if(self::enabled()) {
+				foreach($pagelets as $Pagelet) {
+					$Pagelet->flush();
+				}
+			}
+
+			# NOTE: If BigPipe is disabled, Pagelet::flush() will NOT call BigPipe::dequeue().
+			# This means that (if the pipeline is disabled) $pagelets_ordered contains ALL
+			# Pagelets regardless of whether if Pagelet::flush() was already called. And then
+			# we can iterate over them and echo all requiered CSS and JS resources.
+			else {
+				foreach($pagelets as $Pagelet) {
+					foreach($Pagelet->getResources()[Resource::TYPE_STYLESHEET] as $Resource) {
+						echo "{$Resource->renderHTML()}\n";
+					}
+
+					foreach($Pagelet->getResources()[Resource::TYPE_JAVASCRIPT] as $Resource) {
+						echo "{$Resource->renderHTML()}\n";
+					}
+
+					foreach($Pagelet->getJSCode() as $JSCode) {
+						echo "<script>{$JSCode}</script>\n";
+					}
+				}
+			}
+		}
+
+		if(self::enabled()) {
+			echo "<script>BigPipe.onLastPageletArrived();</script>\n";
+		}
+	}
+}
+?>

+ 43 - 0
src/Library/BigPipe/BigPipe/Item.php

@@ -0,0 +1,43 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Abstract item class                        [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+#                                                                              #
+# The item class abstracts the properties and methods that are required by the #
+# Pagelet and Resource class both. Each one can have PhaseDoneJS callbacks for #
+# several phases numbers which are defined as constants of the specific class. #
+#                                                                              #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+namespace BigPipe;
+
+abstract class Item {
+	protected $ID = '';
+	protected $phaseDoneJS = [];
+
+	#===============================================================================
+	# Required methods in child classes
+	#===============================================================================
+	abstract public function getStructure(): array;
+
+	#===============================================================================
+	# Return the unique ID
+	#===============================================================================
+	public function getID() {
+		return $this->ID;
+	}
+
+	#===============================================================================
+	# Return all registered PhaseDoneJS callbacks
+	#===============================================================================
+	public function getPhaseDoneJS(): array {
+		return $this->phaseDoneJS;
+	}
+
+	#===============================================================================
+	# Attach a PhaseDoneJS callback
+	#===============================================================================
+	public function addPhaseDoneJS($phase, $callback) {
+		return $this->phaseDoneJS[$phase][] = $callback;
+	}
+}
+?>

+ 178 - 0
src/Library/BigPipe/BigPipe/Pagelet.php

@@ -0,0 +1,178 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Pagelet representation class               [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+#                                                                              #
+# [More information coming soon]                                               #
+#                                                                              #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+namespace BigPipe;
+
+class Pagelet extends Item {
+	private $HTML         = '';
+	private $JSCode       = [];
+	private $priority     = NULL;
+	private $resources    = [];
+	private $dependencies = [];
+	private $tagname      = 'div';
+	private $tagHTML      = '';
+
+	#===============================================================================
+	# Priorities for pagelet sorting
+	#===============================================================================
+	const PRIORITY_HIGHEST = 100;
+	const PRIORITY_HIGH    = 75;
+	const PRIORITY_NORMAL  = 50;
+	const PRIORITY_LOW     = 25;
+	const PRIORITY_LOWEST  = 0;
+
+	#===============================================================================
+	# Phase numbers for PhaseDoneJS
+	#===============================================================================
+	const PHASE_INIT    = 0; # After the pagelet object was initialized
+	const PHASE_LOADCSS = 1; # After all the CSS resources have been loaded
+	const PHASE_HTML    = 2; # After the placeholder HTML was replaced
+	const PHASE_LOADJS  = 3; # After all the JS resources have been loaded
+	const PHASE_DONE    = 4; # After the static JS code has been executed
+
+	public function __construct($customID = NULL, $priority = self::PRIORITY_NORMAL) {
+		$this->ID = $customID ?? spl_object_hash($this);
+
+		$this->priority    = $priority;
+		$this->resources   = array_pad($this->resources,   2, []);
+		$this->phaseDoneJS = array_pad($this->phaseDoneJS, 5, []);
+
+		BigPipe::enqueue($this);
+	}
+
+	#===============================================================================
+	# Return the priority
+	#===============================================================================
+	public function getPriority() {
+		return $this->priority;
+	}
+
+	#===============================================================================
+	# Return the HTML content
+	#===============================================================================
+	public function getHTML() {
+		return $this->HTML;
+	}
+
+	#===============================================================================
+	# Return the main JS code
+	#===============================================================================
+	public function getJSCode(): array {
+		return $this->JSCode;
+	}
+
+	#===============================================================================
+	# Return attached resources
+	#===============================================================================
+	public function getResources(): array {
+		return $this->resources;
+	}
+
+	#===============================================================================
+	# Return all display dependencies
+	#===============================================================================
+	public function getDependencies(): array {
+		return array_unique($this->dependencies);
+	}
+
+	#===============================================================================
+	# Add HTML or attach more
+	#===============================================================================
+	public function addHTML($HTML) {
+		return $this->HTML .= $HTML;
+	}
+
+	#===============================================================================
+	# Add resource
+	#===============================================================================
+	public function addResource(Resource $Resource): Resource {
+		return $this->resources[$Resource->getType()][] = $Resource;
+	}
+
+	#===============================================================================
+	# Attach a main JS code part
+	#===============================================================================
+	public function addJSCode($code) {
+		return $this->JSCode[] = $code;
+	}
+
+	#===============================================================================
+	# Attach a display dependency
+	#===============================================================================
+	public function addDependency(Pagelet $Pagelet) {
+		return $this->dependencies[] = $Pagelet->getID();
+	}
+
+	#===============================================================================
+	# Set custom placeholder tagname
+	#===============================================================================
+	public function setTagname($tagname) {
+		return $this->tagname = $tagname;
+	}
+
+	#===============================================================================
+	# Set custom placeholder HTML
+	#===============================================================================
+	public function setPlaceholderHTML($HTML) {
+		return $this->tagHTML = $HTML;
+	}
+
+	#===============================================================================
+	# Return the pagelet structure
+	#===============================================================================
+	public function getStructure(): array {
+		foreach($this->getResources()[Resource::TYPE_STYLESHEET] as $Resource) {
+			$stylesheets[] = $Resource->getStructure();
+		}
+
+		foreach($this->getResources()[Resource::TYPE_JAVASCRIPT] as $Resource) {
+			$javascripts[] = $Resource->getStructure();
+		}
+
+		return [
+			'ID' => $this->getID(),
+			'NEED' => $this->getDependencies(),
+			'RSRC' => [
+				Resource::TYPE_STYLESHEET => $stylesheets ?? [],
+				Resource::TYPE_JAVASCRIPT => $javascripts ?? []
+			],
+			'CODE' => $this->getJSCode(),
+			'PHASE' => $this->getPhaseDoneJS()
+		];
+	}
+
+	#===============================================================================
+	# Flush pagelet immediately
+	#===============================================================================
+	public function flush() {
+		if(BigPipe::enabled()) {
+			$pageletHTML = str_replace(["\r", "\n", "\t"], '', $this->getHTML());
+			$pageletHTML = str_replace('--', '&#45;&#45;', $pageletHTML);
+
+			$pageletJSON = json_encode($this->getStructure());
+
+			echo "<code hidden id=\"_{$this->getID()}\"><!-- {$pageletHTML} --></code>\n";
+			echo "<script>BigPipe.onPageletArrive({$pageletJSON}, document.getElementById(\"_{$this->getID()}\"));</script>\n\n";
+
+			BigPipe::dequeue($this);
+			BigPipe::flushOutputBuffer();
+		}
+	}
+
+	#===============================================================================
+	# Magic method: __toString()
+	#===============================================================================
+	public function __toString() {
+		$pageletHTML  = "<{$this->tagname} id=\"{$this->getID()}\">";
+		$pageletHTML .= !BigPipe::enabled() ? $this->getHTML() : $this->tagHTML;
+		$pageletHTML .= "</{$this->tagname}>";
+
+		return $pageletHTML;
+	}
+}
+?>

+ 65 - 0
src/Library/BigPipe/BigPipe/Resource.php

@@ -0,0 +1,65 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Abstract Resource representation class     [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+#                                                                              #
+# [More information coming soon]                                               #
+#                                                                              #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+namespace BigPipe;
+
+abstract class Resource extends Item {
+	private $type         = '';
+	private $resourceURL  = '';
+
+	#===============================================================================
+	# Render resource HTML for disabled pipeline
+	#===============================================================================
+	abstract public function renderHTML();
+
+	#===============================================================================
+	# Resource types
+	#===============================================================================
+	const TYPE_STYLESHEET = 0;
+	const TYPE_JAVASCRIPT = 1;
+
+	#===============================================================================
+	# Phase numbers for PhaseDoneJS
+	#===============================================================================
+	const PHASE_INIT = 0; # Resource object has been initialized
+	const PHASE_LOAD = 1; # Loading of resource has been started
+	const PHASE_DONE = 2; # Loading of resource is done.
+
+	#===============================================================================
+	# Build resource
+	#===============================================================================
+	public function __construct($customID = NULL, $type, $resourceURL) {
+		$this->ID = $customID ?? spl_object_hash($this);
+		$this->type = $type;
+		$this->resourceURL = $resourceURL;
+
+		$this->phaseDoneJS = array_pad($this->phaseDoneJS, 3, []);
+	}
+
+	#===============================================================================
+	# Return the resource type
+	#===============================================================================
+	public function getType() {
+		return $this->type;
+	}
+
+	#===============================================================================
+	# Return the resource URL
+	#===============================================================================
+	public function getURL() {
+		return $this->resourceURL;
+	}
+
+	#===============================================================================
+	# Return the resource structure
+	#===============================================================================
+	public function getStructure(): array {
+		return ['ID' => $this->getID(), 'HREF' => $this->getURL(), 'PHASE' => $this->getPhaseDoneJS()];
+	}
+}
+?>

+ 27 - 0
src/Library/BigPipe/BigPipe/Resource/Javascript.php

@@ -0,0 +1,27 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Resource representation [javascript]       [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+#                                                                              #
+# [More information coming soon]                                               #
+#                                                                              #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+namespace BigPipe\Resource;
+
+class Javascript extends \BigPipe\Resource {
+
+	#===============================================================================
+	# Build resource
+	#===============================================================================
+	public function __construct($customID = NULL, $resourceURL) {
+		parent::__construct($customID, parent::TYPE_JAVASCRIPT, $resourceURL);
+	}
+
+	#===============================================================================
+	# Render resource HTML
+	#===============================================================================
+	public function renderHTML() {
+		return sprintf('<script data-id="%s" src="%s"></script>', $this->getID(), $this->getURL());
+	}
+}
+?>

+ 27 - 0
src/Library/BigPipe/BigPipe/Resource/Stylesheet.php

@@ -0,0 +1,27 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Resource representation [stylesheet]       [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+#                                                                              #
+# [More information coming soon]                                               #
+#                                                                              #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+namespace BigPipe\Resource;
+
+class Stylesheet extends \BigPipe\Resource {
+
+	#===============================================================================
+	# Build resource
+	#===============================================================================
+	public function __construct($customID = NULL, $resourceURL) {
+		parent::__construct($customID, parent::TYPE_STYLESHEET, $resourceURL);
+	}
+
+	#===============================================================================
+	# Render resource HTML
+	#===============================================================================
+	public function renderHTML() {
+		return sprintf('<link data-id="%s" href="%s" rel="stylesheet" />', $this->getID(), $this->getURL());
+	}
+}
+?>

+ 14 - 0
src/Library/BigPipe/Debugging/Pagelet.php

@@ -0,0 +1,14 @@
+<?php
+namespace Debugging;
+
+class Pagelet extends \BigPipe\Pagelet {
+	public function __construct($customID = NULL, $priority = self::PRIORITY_NORMAL) {
+		parent::__construct(...func_get_args());
+
+		foreach(['INIT', 'LOADCSS', 'HTML', 'LOADJS', 'DONE'] as $phase) {
+			$code = 'console.log("PhaseDoneJS for Pagelet %%c#%s%%c: %s", "color:#008B45", "color:inherit")';
+			$this->addPhaseDoneJS(constant("self::PHASE_$phase"), sprintf($code, $this->getID(), $phase));
+		}
+	}
+}
+?>

+ 14 - 0
src/Library/BigPipe/Debugging/Resource/Javascript.php

@@ -0,0 +1,14 @@
+<?php
+namespace Debugging\Resource;
+
+class Javascript extends \BigPipe\Resource\Javascript {
+	public function __construct($customID = NULL, $resourceURL) {
+		parent::__construct(...func_get_args());
+
+		foreach(['INIT', 'LOAD', 'DONE'] as $phase) {
+			$code = 'console.log("PhaseDoneJS for Javascript %%c#%s%%c: %s", "color:#B24A4A", "color:inherit")';
+			$this->addPhaseDoneJS(constant("self::PHASE_$phase"), sprintf($code, $this->getID(), $phase));
+		}
+	}
+}
+?>

+ 14 - 0
src/Library/BigPipe/Debugging/Resource/Stylesheet.php

@@ -0,0 +1,14 @@
+<?php
+namespace Debugging\Resource;
+
+class Stylesheet extends \BigPipe\Resource\Stylesheet {
+	public function __construct($customID = NULL, $resourceURL) {
+		parent::__construct(...func_get_args());
+
+		foreach(['INIT', 'LOAD', 'DONE'] as $phase) {
+			$code = 'console.log("PhaseDoneJS for Stylesheet %%c#%s%%c: %s", "color:#4169E1", "color:inherit")';
+			$this->addPhaseDoneJS(constant("self::PHASE_$phase"), sprintf($code, $this->getID(), $phase));
+		}
+	}
+}
+?>

+ 62 - 0
src/Library/BigPipeApp.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * Big pipe
+ */
+namespace Qii\Library;
+
+use BigPipe;
+
+class BigPipeApp {
+	public static $debugging = FALSE;
+
+    /**
+     * Create Pagelet instance
+     *
+     * @param string $ID id标识
+     * @return BigPipe\Pagelet
+     */
+	public static function createPagelet($ID = NULL): BigPipe\Pagelet {
+		$namespace = self::$debugging ? 'Debugging' : 'BigPipe';
+		$classname = "{$namespace}\Pagelet";
+
+		$Pagelet = new $classname(...func_get_args());
+		return $Pagelet;
+	}
+
+    /**
+     * Create Stylesheet instance
+     *
+     * @param string $ID id标识
+     * @param $href
+     * @return BigPipe\Resource\Stylesheet
+     */
+	public static function createStylesheet($ID, $href): BigPipe\Resource\Stylesheet {
+		$namespace = self::$debugging ? 'Debugging' : 'BigPipe';
+		$classname = "{$namespace}\Resource\Stylesheet";
+
+		$Stylesheet = new $classname(...func_get_args());
+		return $Stylesheet;
+	}
+
+    /**
+     * Create Javascript instance
+     *
+     * @param string $ID id标识
+     * @param $href
+     * @return BigPipe\Resource\Javascript
+     */
+	public static function createJavascript($ID, $href): BigPipe\Resource\Javascript {
+		$namespace = self::$debugging ? 'Debugging' : 'BigPipe';
+		$classname = "{$namespace}\Resource\Javascript";
+
+		$Javascript = new $classname(...func_get_args());
+		return $Javascript;
+	}
+
+    /**
+     * 渲染
+     */
+	public static function render() {
+        BigPipe\BigPipe::completeResponse();
+    }
+}

+ 2 - 0
src/Qii.php

@@ -200,6 +200,7 @@ if (!function_exists('catch_fatal_error')) {
     ->setUseNamespace('Qii\Router', true)
     ->setUseNamespace('Qii\View', true)
     ->setUseNamespace('WhichBrowser', true)
+    ->setUseNamespace('BigPipe', true)
     ->setUseNamespace('Smarty\\', false)
     ->setUseNamespace('Smarty\\Internal', false);
 
@@ -224,6 +225,7 @@ if (!function_exists('catch_fatal_error')) {
     ->addNamespace('Smarty', Qii_DIR . DS . 'View' . DS . 'smarty')
     ->addNamespace('Smarty', Qii_DIR . DS . 'View' . DS . 'smarty' . DS . 'sysplugins')
     ->addNamespace('WhichBrowser', Qii_DIR . DS . 'Library'. DS . 'Third'. DS . 'WhichBrowser')
+    ->addNamespace('BigPipe', Qii_DIR . DS . 'Library'. DS .'BigPipe'. DS .'BigPipe')
 ;
 
 //加载默认语言包