WidgetRegistry.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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\View\StringTemplate;
  18. use Cake\View\View;
  19. use Cake\View\Widget\WidgetInterface;
  20. use Cake\Core\Configure\Engine\PhpConfig;
  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\Button'],
  45. 'checkbox' => ['Cake\View\Widget\Checkbox'],
  46. 'file' => ['Cake\View\Widget\File'],
  47. 'label' => ['Cake\View\Widget\Label'],
  48. 'multicheckbox' => ['Cake\View\Widget\MultiCheckbox', 'label'],
  49. 'radio' => ['Cake\View\Widget\Radio', 'label'],
  50. 'select' => ['Cake\View\Widget\SelectBox'],
  51. 'textarea' => ['Cake\View\Widget\Textarea'],
  52. 'datetime' => ['Cake\View\Widget\DateTime', 'select'],
  53. '_default' => ['Cake\View\Widget\Basic'],
  54. ];
  55. /**
  56. * Templates to use.
  57. *
  58. * @var \Cake\View\StringTemplate
  59. */
  60. protected $_templates;
  61. /**
  62. * Constructor
  63. *
  64. * @param \Cake\View\StringTemplate $templates Templates instance to use.
  65. * @param \Cake\View\View $view The view instance to set as a widget.
  66. * @param mixed $widgets See add() method for more information.
  67. */
  68. public function __construct(StringTemplate $templates, View $view, $widgets = []) {
  69. $this->_templates = $templates;
  70. if (!empty($widgets)) {
  71. if (is_string($widgets)) {
  72. $loader = new PhpConfig();
  73. $widgets = $loader->read($widgets);
  74. }
  75. $this->add($widgets);
  76. }
  77. $this->add(['_view' => $view]);
  78. }
  79. /**
  80. * Adds or replaces existing widget instances/configuration with new ones.
  81. *
  82. * Widget arrays can either be descriptions or instances. For example:
  83. *
  84. * {{{
  85. * $registry->add([
  86. * 'label' => new MyLabel($templates),
  87. * 'checkbox' => ['Fancy.MyCheckbox', 'label']
  88. * ]);
  89. * }}}
  90. *
  91. * The above shows how to define widgets as instances or as
  92. * descriptions including dependencies. Classes can be defined
  93. * with plugin notation, or fully namespaced class names.
  94. *
  95. * @param array $widgets Array of widgets to use.
  96. * @return void
  97. */
  98. public function add(array $widgets) {
  99. $this->_widgets = $widgets + $this->_widgets;
  100. }
  101. /**
  102. * Get a widget.
  103. *
  104. * Will either fetch an already created widget, or create a new instance
  105. * if the widget has been defined. If the widget is undefined an instance of
  106. * the `_default` widget will be returned. An exception will be thrown if
  107. * the `_default` widget is undefined.
  108. *
  109. * @param string $name The widget name to get.
  110. * @return WidgetInterface widget interface class.
  111. * @throws \RuntimeException when widget is undefined.
  112. */
  113. public function get($name) {
  114. if (!isset($this->_widgets[$name]) && empty($this->_widgets['_default'])) {
  115. throw new \RuntimeException(sprintf('Unknown widget "%s"', $name));
  116. }
  117. if (!isset($this->_widgets[$name])) {
  118. $name = '_default';
  119. }
  120. $this->_widgets[$name] = $this->_resolveWidget($this->_widgets[$name]);
  121. return $this->_widgets[$name];
  122. }
  123. /**
  124. * Clear the registry and reset the widgets.
  125. *
  126. * @return void
  127. */
  128. public function clear() {
  129. $this->_widgets = [];
  130. }
  131. /**
  132. * Resolves a widget spec into an instance.
  133. *
  134. * @param mixed $widget The widget to get
  135. * @return WidgetInterface
  136. * @throws \RuntimeException when class cannot be loaded or does not
  137. * implement WidgetInterface.
  138. */
  139. protected function _resolveWidget($widget) {
  140. $type = gettype($widget);
  141. if ($type === 'object' && $widget instanceof WidgetInterface) {
  142. return $widget;
  143. }
  144. if ($type === 'object') {
  145. throw new \RuntimeException(
  146. 'Input objects must implement Cake\View\Widget\WidgetInterface.'
  147. );
  148. }
  149. if ($type === 'string') {
  150. $widget = [$widget];
  151. }
  152. $class = array_shift($widget);
  153. $className = App::className($class, 'View/Input');
  154. if ($className === false || !class_exists($className)) {
  155. throw new \RuntimeException(sprintf('Unable to locate widget class "%s"', $class));
  156. }
  157. if ($type === 'array' && count($widget)) {
  158. $reflection = new ReflectionClass($className);
  159. $arguments = [$this->_templates];
  160. foreach ($widget as $requirement) {
  161. $arguments[] = $this->get($requirement);
  162. }
  163. $instance = $reflection->newInstanceArgs($arguments);
  164. } else {
  165. $instance = new $className($this->_templates);
  166. }
  167. if (!($instance instanceof WidgetInterface)) {
  168. throw new \RuntimeException(sprintf('"%s" does not implement the WidgetInterface', $className));
  169. }
  170. return $instance;
  171. }
  172. }