Browse Source

Merge pull request #3216 from quickapps/3.0

View Cells for #3052
Mark Story 12 years ago
parent
commit
602f085395

+ 2 - 19
src/Controller/Controller.php

@@ -27,6 +27,7 @@ use Cake\Network\Response;
 use Cake\ORM\TableRegistry;
 use Cake\Routing\RequestActionTrait;
 use Cake\Routing\Router;
+use Cake\Utility\CellTrait;
 use Cake\Utility\Inflector;
 use Cake\Utility\MergeVariablesTrait;
 use Cake\Utility\ModelAwareTrait;
@@ -78,6 +79,7 @@ use Cake\View\View;
  */
 class Controller extends Object implements EventListener {
 
+	use CellTrait;
 	use MergeVariablesTrait;
 	use ModelAwareTrait;
 	use RequestActionTrait;
@@ -731,23 +733,4 @@ class Controller extends Object implements EventListener {
 	public function afterFilter(Event $event) {
 	}
 
-/**
- * Constructs the view class instance based on controller properties.
- * Controller stashes view variables and view configuration options in the absence of an
- * instantiated view to set them on, and passes them to the View constructor here.
- *
- * @param string $viewClass Optional namespaced class name of the View class to instantiate.
- * @return View
- */
-	public function createView($viewClass = null) {
-		if ($viewClass === null) {
-			$viewClass = $this->viewClass;
-			if ($this->viewClass !== 'View') {
-				list($plugin, $viewClass) = pluginSplit($viewClass, true);
-				$viewClass = App::classname($viewClass, 'View', 'View');
-			}
-		}
-		$viewOptions = array_intersect_key(get_object_vars($this), array_flip($this->_validViewOptions));
-		return new $viewClass($this->request, $this->response, $this->getEventManager(), $viewOptions);
-	}
 }

+ 84 - 0
src/Utility/CellTrait.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Utility;
+
+use Cake\Core\App;
+use Cake\Utility\Inflector;
+use Cake\View\Error;
+
+/**
+ * Provides cell() method for usage in Controller and View classes.
+ *
+ */
+trait CellTrait {
+
+/**
+ * Renders the given cell.
+ *
+ * Example:
+ *
+ *     // Taxonomy\View\Cell\TagCloudCell::smallList()
+ *     $cell = $this->cell('Taxonomy.TagCloud::smallList', ['limit' => 10]);
+ *
+ *     // App\View\Cell\TagCloudCell::smallList()
+ *     $cell = $this->cell('TagCloud::smallList', ['limit' => 10]);
+ *
+ * The `display` action will be used by default when no action is provided:
+ *
+ *     // Taxonomy\View\Cell\TagCloudCell::display()
+ *     $cell = $this->cell('Taxonomy.TagCloud');
+ *
+ * Cells are not rendered until they are echoed.
+ *
+ * @param string $cell You must indicate both cell name, and optionally a cell action. e.g.: `TagCloud::smallList`
+ * will invoke `View\Cell\TagCloudCell::smallList()`, `display` action will be invoked by default when none is provided.
+ * @param array $data Additional arguments for cell method. e.g.:
+ *    `cell('TagCloud::smallList', ['a1' => 'v1', 'a2' => 'v2'])` maps to `View\Cell\TagCloud::smallList(v1, v2)`
+ * @param array $options Options for Cell's constructor
+ * @return \Cake\View\Cell The cell instance
+ * @throws \Cake\View\Error\MissingCellException If Cell class was not found
+ */
+	public function cell($cell, $data = [], $options = []) {
+		$parts = explode('::', $cell);
+
+		if (count($parts) == 2) {
+			list($pluginAndCell, $action) = [$parts[0], $parts[1]];
+		} else {
+			list($pluginAndCell, $action) = [$parts[0], 'display'];
+		}
+
+		list($plugin, $cellName) = pluginSplit($pluginAndCell);
+
+		$className = App::classname($pluginAndCell, 'View/Cell', 'Cell');
+
+		if (!$className) {
+			throw new Error\MissingCellException(array('className' => $pluginAndCell . 'Cell'));
+		}
+
+		$cellInstance = new $className($this->request, $this->response, $this->getEventManager(), $options);
+		$cellInstance->action = Inflector::underscore($action);
+		$cellInstance->plugin = !empty($plugin) ? $plugin : null;
+		$length = count($data);
+
+		if ($length) {
+			$data = array_values($data);
+		}
+
+		call_user_func_array([$cellInstance, $action], $data);
+
+		return $cellInstance;
+	}
+
+}

+ 21 - 0
src/Utility/ViewVarsTrait.php

@@ -13,6 +13,8 @@
  */
 namespace Cake\Utility;
 
+use Cake\Core\App;
+
 /**
  * Provides the set() method for collecting template context.
  *
@@ -30,6 +32,24 @@ trait ViewVarsTrait {
 	public $viewVars = [];
 
 /**
+ * Constructs the view class instance based on object properties.
+ *
+ * @param string $viewClass Optional namespaced class name of the View class to instantiate.
+ * @return View
+ */
+	public function createView($viewClass = null) {
+		if ($viewClass === null) {
+			$viewClass = $this->viewClass;
+			if ($this->viewClass !== 'View') {
+				list($plugin, $viewClass) = pluginSplit($viewClass, true);
+				$viewClass = App::classname($viewClass, 'View', 'View');
+			}
+		}
+		$viewOptions = array_intersect_key(get_object_vars($this), array_flip($this->_validViewOptions));
+		return new $viewClass($this->request, $this->response, $this->getEventManager(), $viewOptions);
+	}
+
+/**
  * Saves a variable for use inside a template.
  *
  * @param string|array $name A string or an array of data.
@@ -49,4 +69,5 @@ trait ViewVarsTrait {
 		}
 		$this->viewVars = $data + $this->viewVars;
 	}
+
 }

+ 191 - 0
src/View/Cell.php

@@ -0,0 +1,191 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\View;
+
+use Cake\Core\App;
+use Cake\Event\EventManager;
+use Cake\Network\Request;
+use Cake\Network\Response;
+use Cake\Utility\Inflector;
+use Cake\Utility\ModelAwareTrait;
+use Cake\Utility\ViewVarsTrait;
+
+/**
+ * Cell base.
+ *
+ */
+abstract class Cell {
+
+	use ModelAwareTrait;
+	use ViewVarsTrait;
+
+/**
+ * Instance of the View created during rendering. Won't be set until after
+ * Cell::__toString() is called.
+ *
+ * @var \Cake\View\View
+ */
+	public $View;
+
+/**
+ * Name of the action that was invoked.
+ *
+ * Action name will be inflected to get the template name when rendering.
+ *
+ * @var string
+ */
+	public $action;
+
+/**
+ * Automatically set to the name of a plugin.
+ *
+ * @var string
+ */
+	public $plugin = null;
+
+/**
+ * An instance of a Cake\Network\Request object that contains information about the current request.
+ * This object contains all the information about a request and several methods for reading
+ * additional information about the request.
+ *
+ * @var \Cake\Network\Request
+ */
+	public $request;
+
+/**
+ * An instance of a Response object that contains information about the impending response
+ *
+ * @var \Cake\Network\Response
+ */
+	public $response;
+
+/**
+ * The name of the View class this cell sends output to.
+ *
+ * @var string
+ */
+	public $viewClass = 'Cake\View\View';
+
+/**
+ * Instance of the Cake\Event\EventManager this cell is using
+ * to dispatch inner events.
+ *
+ * @var \Cake\Event\EventManager
+ */
+	protected $_eventManager = null;
+
+/**
+ * These properties are settable directly on Cell and passed to the View as options.
+ *
+ * @var array
+ * @see \Cake\View\View
+ */
+	protected $_validViewOptions = [
+		'viewVars', 'helpers', 'viewPath', 'plugin',
+	];
+
+/**
+ * List of valid options (constructor's fourth arguments)
+ *
+ * @var array
+ */
+	protected $_validCellOptions = [];
+
+/**
+ * Constructor.
+ *
+ * @param \Cake\Network\Request $request
+ * @param \Cake\Network\Response $response
+ * @param \Cake\Event\EventManager $eventManager
+ * @param array $cellOptions
+ */
+	public function __construct(Request $request = null, Response $response = null,
+		EventManager $eventManager = null, array $cellOptions = []) {
+		$this->_eventManager = $eventManager;
+		$this->request = $request;
+		$this->response = $response;
+		$this->modelFactory('Table', ['Cake\ORM\TableRegistry', 'get']);
+
+		foreach ($this->_validCellOptions as $var) {
+			if (isset($cellOptions[$var])) {
+				$this->{$var} = $cellOptions[$var];
+			}
+		}
+	}
+
+/**
+ * Rendering method.
+ *
+ * @param string $action Custom template name to render. If not provided (null), the last
+ * value will be used. This value is automatically set by `CellTrait::cell()`.
+ * @return void
+ */
+	public function render($action = null) {
+		if ($action !== null) {
+			$this->action = $action;
+		}
+
+		return $this->__toString();
+	}
+
+/**
+ * Magic method.
+ *
+ * Starts the rendering process when Cell is echoed.
+ *
+ * @return string Rendered cell
+ */
+	public function __toString() {
+		$this->View = $this->createView();
+
+		$this->View->layout = false;
+		$className = explode('\\', get_class($this));
+		$className = array_pop($className);
+		$this->View->subDir = 'Cell' . DS . substr($className, 0, strpos($className, 'Cell'));
+
+		return $this->View->render(Inflector::underscore($this->action));
+	}
+
+/**
+ * Debug info.
+ *
+ * @return void
+ */
+	public function __debugInfo() {
+		return [
+			'plugin' => $this->plugin,
+			'action' => $this->action,
+			'viewClass' => $this->viewClass,
+			'request' => $this->request,
+			'response' => $this->response,
+		];
+	}
+
+/**
+ * Returns the Cake\Event\EventManager manager instance for this cell.
+ *
+ * You can use this instance to register any new listeners or callbacks to the
+ * cell events, or create your own events and trigger them at will.
+ *
+ * @return \Cake\Event\EventManager
+ */
+	public function getEventManager() {
+		if (empty($this->_eventManager)) {
+			$this->_eventManager = new EventManager();
+		}
+		return $this->_eventManager;
+	}
+
+}

+ 27 - 0
src/View/Error/MissingCellException.php

@@ -0,0 +1,27 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\View\Error;
+
+use Cake\Error\Exception;
+
+/**
+ * Used when a cell class file cannot be found.
+ *
+ */
+class MissingCellException extends Exception {
+
+	protected $_messageTemplate = 'Cell class "%s" is missing.';
+
+}

+ 2 - 0
src/View/View.php

@@ -27,6 +27,7 @@ use Cake\Network\Request;
 use Cake\Network\Response;
 use Cake\Routing\RequestActionTrait;
 use Cake\Routing\Router;
+use Cake\Utility\CellTrait;
 use Cake\Utility\Inflector;
 use Cake\Utility\ViewVarsTrait;
 
@@ -59,6 +60,7 @@ use Cake\Utility\ViewVarsTrait;
  */
 class View extends Object {
 
+	use CellTrait;
 	use RequestActionTrait;
 	use ViewVarsTrait;
 

+ 133 - 0
tests/TestCase/View/CellTest.php

@@ -0,0 +1,133 @@
+<?php
+/**
+ * CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\View;
+
+use Cake\Controller\Controller;
+use Cake\Core\Configure;
+use Cake\Core\Plugin;
+use Cake\Event\EventManager;
+use Cake\TestSuite\TestCase;
+use Cake\Utility\CellTrait;
+use Cake\View\Cell;
+
+/**
+ * CellTest class.
+ *
+ * For testing both View\Cell & Utility\CellTrait
+ */
+class CellTest extends TestCase {
+
+/**
+ * setUp method
+ *
+ * @return void
+ */
+	public function setUp() {
+		parent::setUp();
+		Configure::write('App.namespace', 'TestApp');
+		Configure::write('debug', 2);
+		Plugin::load('TestPlugin');
+		$request = $this->getMock('Cake\Network\Request');
+		$response = $this->getMock('Cake\Network\Response');
+		$this->View = new \Cake\View\View($request, $response);
+	}
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+	public function tearDown() {
+		parent::tearDown();
+		Plugin::unload('TestPlugin');
+		unset($this->View);
+	}
+
+/**
+ * Tests basic cell rendering.
+ *
+ * @return void
+ */
+	public function testCellRender() {
+		$cell = $this->View->cell('Articles::teaserList');
+		$render = "{$cell}";
+
+		$this->assertTrue(
+			strpos($render, '<h2>Lorem ipsum</h2>') !== false &&
+			strpos($render, '<h2>Usectetur adipiscing eli</h2>') !== false &&
+			strpos($render, '<h2>Topis semper blandit eu non</h2>') !== false &&
+			strpos($render, '<h2>Suspendisse gravida neque</h2>') !== false
+		);
+	}
+
+/**
+ * Tests that we are able pass multiple arguments to cell methods.
+ *
+ * @return void
+ */
+	public function testCellWithArguments() {
+		$cell = $this->View->cell('Articles::doEcho', ['msg1' => 'dummy', 'msg2' => ' message']);
+		$render = "{$cell}";
+		$this->assertTrue(strpos($render, 'dummy message') !== false);
+	}
+
+/**
+ * Tests that cell runs default action when none is provided.
+ *
+ * @return void
+ */
+	public function testDefaultCellAction() {
+		$appCell = $this->View->cell('Articles');
+		$this->assertTrue(strpos("{$appCell}", 'dummy') !== false);
+
+		$pluginCell = $this->View->cell('TestPlugin.Dummy');
+		$this->assertTrue(strpos("{$pluginCell}", 'dummy') !== false);
+	}
+
+/**
+ * Tests manual render() invocation.
+ *
+ * @return void
+ */
+	public function testCellManualRender() {
+		$cell = $this->View->cell('Articles::doEcho', ['msg1' => 'dummy', 'msg2' => ' message']);
+		$this->assertTrue(strpos($cell->render(), 'dummy message') !== false);
+
+		$cell->teaserList();
+		$this->assertTrue(strpos($cell->render('teaser_list'), '<h2>Lorem ipsum</h2>') !== false);
+	}
+
+/**
+ * Tests that using plugin's cells works.
+ *
+ * @return void
+ */
+	public function testPluginCell() {
+		$cell = $this->View->cell('TestPlugin.Dummy::echoThis', ['msg' => 'hello world!']);
+		$this->assertTrue(strpos("{$cell}", 'hello world!') !== false);
+	}
+
+/**
+ * Tests that using an unexisting cell throws an exception.
+ *
+ * @expectedException \Cake\View\Error\MissingCellException
+ * @return void
+ */
+	public function testUnexistingCell() {
+		$cell = $this->View->cell('TestPlugin.Void::echoThis', ['arg1' => 'v1']);
+		$cell = $this->View->cell('Void::echoThis', ['arg1' => 'v1', 'arg2' => 'v2']);
+	}
+
+}

+ 1 - 0
tests/test_app/Plugin/TestPlugin/Template/Cell/Dummy/display.ctp

@@ -0,0 +1 @@
+dummy

+ 1 - 0
tests/test_app/Plugin/TestPlugin/Template/Cell/Dummy/echo_this.ctp

@@ -0,0 +1 @@
+<?php echo $msg; ?>

+ 40 - 0
tests/test_app/Plugin/TestPlugin/View/Cell/DummyCell.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace TestPlugin\View\Cell;
+
+/**
+ * DummyCell class
+ *
+ */
+class DummyCell extends \Cake\View\Cell {
+
+/**
+ * Default cell action.
+ *
+ * @return void
+ */
+	public function display() {
+	}
+
+/**
+ * Simple echo.
+ *
+ * @param string $msg
+ * @return void
+ */
+	public function echoThis($msg) {
+		$this->set('msg', $msg);
+	}
+
+}

+ 1 - 0
tests/test_app/TestApp/Template/Cell/Articles/display.ctp

@@ -0,0 +1 @@
+dummy

+ 1 - 0
tests/test_app/TestApp/Template/Cell/Articles/do_echo.ctp

@@ -0,0 +1 @@
+<?php echo $msg; ?>

+ 4 - 0
tests/test_app/TestApp/Template/Cell/Articles/teaser_list.ctp

@@ -0,0 +1,4 @@
+<?php foreach ($articles as $article): ?>
+	<h2><?php echo $article['title']; ?></h2>
+	<p><?php echo $article['body']; ?></p>
+<?php endforeach; ?>

+ 55 - 0
tests/test_app/TestApp/View/Cell/ArticlesCell.php

@@ -0,0 +1,55 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace TestApp\View\Cell;
+
+/**
+ * TagCloudCell class
+ *
+ */
+class ArticlesCell extends \Cake\View\Cell {
+
+/**
+ * Default cell action.
+ *
+ * @return void
+ */
+	public function display() {
+	}
+
+/**
+ * Renders articles in teaser view mode.
+ *
+ * @return void
+ */
+	public function teaserList() {
+		$this->set('articles', [
+			['title' => 'Lorem ipsum', 'body' => 'dolorem sit amet'],
+			['title' => 'Usectetur adipiscing eli', 'body' => 'tortor, in tincidunt sem dictum vel'],
+			['title' => 'Topis semper blandit eu non', 'body' => 'alvinar diam convallis non. Nullam pu'],
+			['title' => 'Suspendisse gravida neque', 'body' => 'pellentesque sed scelerisque libero'],
+		]);
+	}
+
+/**
+ * Simple echo.
+ *
+ * @param string $msg1
+ * @param string $msg2
+ * @return void
+ */
+	public function doEcho($msg1, $msg2) {
+		$this->set('msg', $msg1 . $msg2);
+	}
+
+}