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