WidgetRegistry.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\View\Widget;
  16. use Cake\Core\App;
  17. use Cake\Core\Configure\Engine\PhpConfig;
  18. use Cake\View\StringTemplate;
  19. use Cake\View\View;
  20. use Cake\View\Widget\WidgetInterface;
  21. use \ReflectionClass;
  22. /**
  23. * A registry/factory for input widgets.
  24. *
  25. * Can be used by helpers/view logic to build form widgets
  26. * and other HTML widgets.
  27. *
  28. * This class handles the mapping between names and concrete classes.
  29. * It also has a basic name based dependency resolver that allows
  30. * widgets to depend on each other.
  31. *
  32. * Each widget should expect a StringTemplate instance as their first
  33. * argument. All other dependencies will be included after.
  34. *
  35. * Widgets can ask for the current view by using the `_view` widget.
  36. */
  37. class WidgetRegistry {
  38. /**
  39. * Array of widgets + widget configuration.
  40. *
  41. * @var array
  42. */
  43. protected $_widgets = [
  44. 'button' => ['Cake\View\Widget\ButtonWidget'],
  45. 'checkbox' => ['Cake\View\Widget\CheckboxWidget'],
  46. 'file' => ['Cake\View\Widget\FileWidget'],
  47. 'label' => ['Cake\View\Widget\LabelWidget'],
  48. 'nestingLabel' => ['Cake\View\Widget\NestingLabelWidget'],
  49. 'multicheckbox' => ['Cake\View\Widget\MultiCheckboxWidget', 'nestingLabel'],
  50. 'radio' => ['Cake\View\Widget\RadioWidget', 'nestingLabel'],
  51. 'select' => ['Cake\View\Widget\SelectBoxWidget'],
  52. 'textarea' => ['Cake\View\Widget\TextareaWidget'],
  53. 'datetime' => ['Cake\View\Widget\DateTimeWidget', 'select'],
  54. '_default' => ['Cake\View\Widget\BasicWidget'],
  55. ];
  56. /**
  57. * Templates to use.
  58. *
  59. * @var \Cake\View\StringTemplate
  60. */
  61. protected $_templates;
  62. /**
  63. * Constructor
  64. *
  65. * @param \Cake\View\StringTemplate $templates Templates instance to use.
  66. * @param \Cake\View\View $view The view instance to set as a widget.
  67. * @param string|array $widgets See add() method for more information.
  68. */
  69. public function __construct(StringTemplate $templates, View $view, $widgets = []) {
  70. $this->_templates = $templates;
  71. if (!empty($widgets)) {
  72. if (is_string($widgets)) {
  73. $this->load($widgets);
  74. } else {
  75. $this->add($widgets);
  76. }
  77. }
  78. $this->_widgets['_view'] = $view;
  79. }
  80. /**
  81. * Load a config file containing widgets.
  82. *
  83. * Widget files should define a `$config` variable containing
  84. * all the widgets to load. Loaded widgets will be merged with existing
  85. * widgets.
  86. *
  87. * @param string $file The file to load
  88. * @return void
  89. */
  90. public function load($file) {
  91. $loader = new PhpConfig();
  92. $widgets = $loader->read($file);
  93. $this->add($widgets);
  94. }
  95. /**
  96. * Adds or replaces existing widget instances/configuration with new ones.
  97. *
  98. * Widget arrays can either be descriptions or instances. For example:
  99. *
  100. * {{{
  101. * $registry->add([
  102. * 'label' => new MyLabelWidget($templates),
  103. * 'checkbox' => ['Fancy.MyCheckbox', 'label']
  104. * ]);
  105. * }}}
  106. *
  107. * The above shows how to define widgets as instances or as
  108. * descriptions including dependencies. Classes can be defined
  109. * with plugin notation, or fully namespaced class names.
  110. *
  111. * @param array $widgets Array of widgets to use.
  112. * @return void
  113. * @throws \RuntimeException When class does not implement WidgetInterface.
  114. */
  115. public function add(array $widgets) {
  116. foreach ($widgets as $object) {
  117. if (gettype($object) === 'object' &&
  118. !($object instanceof WidgetInterface)
  119. ) {
  120. throw new \RuntimeException(
  121. 'Widget objects must implement Cake\View\Widget\WidgetInterface.'
  122. );
  123. }
  124. }
  125. $this->_widgets = $widgets + $this->_widgets;
  126. }
  127. /**
  128. * Get a widget.
  129. *
  130. * Will either fetch an already created widget, or create a new instance
  131. * if the widget has been defined. If the widget is undefined an instance of
  132. * the `_default` widget will be returned. An exception will be thrown if
  133. * the `_default` widget is undefined.
  134. *
  135. * @param string $name The widget name to get.
  136. * @return WidgetInterface widget interface class.
  137. * @throws \RuntimeException when widget is undefined.
  138. */
  139. public function get($name) {
  140. if (!isset($this->_widgets[$name]) && empty($this->_widgets['_default'])) {
  141. throw new \RuntimeException(sprintf('Unknown widget "%s"', $name));
  142. }
  143. if (!isset($this->_widgets[$name])) {
  144. $name = '_default';
  145. }
  146. $this->_widgets[$name] = $this->_resolveWidget($this->_widgets[$name]);
  147. return $this->_widgets[$name];
  148. }
  149. /**
  150. * Clear the registry and reset the widgets.
  151. *
  152. * @return void
  153. */
  154. public function clear() {
  155. $this->_widgets = [];
  156. }
  157. /**
  158. * Resolves a widget spec into an instance.
  159. *
  160. * @param mixed $widget The widget to get
  161. * @return WidgetInterface
  162. * @throws \RuntimeException when class cannot be loaded or does not
  163. * implement WidgetInterface.
  164. */
  165. protected function _resolveWidget($widget) {
  166. $type = gettype($widget);
  167. if ($type === 'object') {
  168. return $widget;
  169. }
  170. if ($type === 'string') {
  171. $widget = [$widget];
  172. }
  173. $class = array_shift($widget);
  174. $className = App::className($class, 'View/Widget', 'Widget');
  175. if ($className === false || !class_exists($className)) {
  176. throw new \RuntimeException(sprintf('Unable to locate widget class "%s"', $class));
  177. }
  178. if ($type === 'array' && count($widget)) {
  179. $reflection = new ReflectionClass($className);
  180. $arguments = [$this->_templates];
  181. foreach ($widget as $requirement) {
  182. $arguments[] = $this->get($requirement);
  183. }
  184. $instance = $reflection->newInstanceArgs($arguments);
  185. } else {
  186. $instance = new $className($this->_templates);
  187. }
  188. if (!($instance instanceof WidgetInterface)) {
  189. throw new \RuntimeException(sprintf('"%s" does not implement the WidgetInterface', $className));
  190. }
  191. return $instance;
  192. }
  193. }