-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
/
Copy pathWidget.php
337 lines (306 loc) · 11 KB
/
Widget.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
<?php
/**
* @link https://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license https://www.yiiframework.com/license/
*/
namespace yii\base;
use ReflectionClass;
use Yii;
/**
* Widget is the base class for widgets.
*
* For more details and usage information on Widget, see the [guide article on widgets](guide:structure-widgets).
*
* @property string|null $id ID of the widget. Note that the type of this property differs in getter and
* setter. See [[getId()]] and [[setId()]] for details.
* @property \yii\web\View $view The view object that can be used to render views or view files. Note that the
* type of this property differs in getter and setter. See [[getView()]] and [[setView()]] for details.
* @property-read string $viewPath The directory containing the view files for this widget.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Widget extends Component implements ViewContextInterface
{
/**
* @event Event an event that is triggered when the widget is initialized via [[init()]].
* @since 2.0.11
*/
const EVENT_INIT = 'init';
/**
* @event WidgetEvent an event raised right before executing a widget.
* You may set [[WidgetEvent::isValid]] to be false to cancel the widget execution.
* @since 2.0.11
*/
const EVENT_BEFORE_RUN = 'beforeRun';
/**
* @event WidgetEvent an event raised right after executing a widget.
* @since 2.0.11
*/
const EVENT_AFTER_RUN = 'afterRun';
/**
* @var int a counter used to generate [[id]] for widgets.
* @internal
*/
public static $counter = 0;
/**
* @var string the prefix to the automatically generated widget IDs.
* @see getId()
*/
public static $autoIdPrefix = 'w';
/**
* @var Widget[] the widgets that are currently being rendered (not ended). This property
* is maintained by [[begin()]] and [[end()]] methods.
* @internal
*/
public static $stack = [];
/**
* @var string[] used widget classes that have been resolved to their actual class name.
*/
private static $_resolvedClasses = [];
/**
* Initializes the object.
* This method is called at the end of the constructor.
* The default implementation will trigger an [[EVENT_INIT]] event.
*/
public function init()
{
parent::init();
$this->trigger(self::EVENT_INIT);
}
/**
* Begins a widget.
* This method creates an instance of the calling class. It will apply the configuration
* to the created instance. A matching [[end()]] call should be called later.
* As some widgets may use output buffering, the [[end()]] call should be made in the same view
* to avoid breaking the nesting of output buffers.
* @param array $config name-value pairs that will be used to initialize the object properties
* @return static the newly created widget instance
* @see end()
*/
public static function begin($config = [])
{
$config['class'] = get_called_class();
/* @var $widget Widget */
$widget = Yii::createObject($config);
self::$stack[] = $widget;
self::$_resolvedClasses[get_called_class()] = get_class($widget);
return $widget;
}
/**
* Ends a widget.
* Note that the rendering result of the widget is directly echoed out.
* @return static the widget instance that is ended.
* @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested
* @see begin()
*/
public static function end()
{
if (!empty(self::$stack)) {
$widget = array_pop(self::$stack);
$calledClass = self::$_resolvedClasses[get_called_class()] ?? get_called_class();
if (get_class($widget) === $calledClass) {
/* @var $widget Widget */
if ($widget->beforeRun()) {
$result = $widget->run();
$result = $widget->afterRun($result);
echo $result;
}
return $widget;
}
throw new InvalidCallException('Expecting end() of ' . get_class($widget) . ', found ' . get_called_class());
}
throw new InvalidCallException('Unexpected ' . get_called_class() . '::end() call. A matching begin() is not found.');
}
/**
* Creates a widget instance and runs it.
* The widget rendering result is returned by this method.
* @param array $config name-value pairs that will be used to initialize the object properties
* @return string the rendering result of the widget.
* @throws \Throwable
*/
public static function widget($config = [])
{
ob_start();
ob_implicit_flush(false);
try {
/* @var $widget Widget */
$config['class'] = get_called_class();
$widget = Yii::createObject($config);
$out = '';
if ($widget->beforeRun()) {
$result = $widget->run();
$out = $widget->afterRun($result);
}
} catch (\Exception $e) {
// close the output buffer opened above if it has not been closed already
if (ob_get_level() > 0) {
ob_end_clean();
}
throw $e;
} catch (\Throwable $e) {
// close the output buffer opened above if it has not been closed already
if (ob_get_level() > 0) {
ob_end_clean();
}
throw $e;
}
return ob_get_clean() . $out;
}
private $_id;
/**
* Returns the ID of the widget.
* @param bool $autoGenerate whether to generate an ID if it is not set previously
* @return string|null ID of the widget.
*/
public function getId($autoGenerate = true)
{
if ($autoGenerate && $this->_id === null) {
$this->_id = static::$autoIdPrefix . static::$counter++;
}
return $this->_id;
}
/**
* Sets the ID of the widget.
* @param string $value id of the widget.
*/
public function setId($value)
{
$this->_id = $value;
}
private $_view;
/**
* Returns the view object that can be used to render views or view files.
* The [[render()]] and [[renderFile()]] methods will use
* this view object to implement the actual view rendering.
* If not set, it will default to the "view" application component.
* @return \yii\web\View the view object that can be used to render views or view files.
*/
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
}
return $this->_view;
}
/**
* Sets the view object to be used by this widget.
* @param View $view the view object that can be used to render views or view files.
*/
public function setView($view)
{
$this->_view = $view;
}
/**
* Executes the widget.
*
* @return string|void the rendering result may be directly "echoed" or returned as a string
*/
public function run()
{
}
/**
* Renders a view.
*
* The view to be rendered can be specified in one of the following formats:
*
* - [path alias](guide:concept-aliases) (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
* active module.
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* If the view name does not contain a file extension, it will use the default one `.php`.
*
* @param string $view the view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidArgumentException if the view file does not exist.
*/
public function render($view, $params = [])
{
return $this->getView()->render($view, $params, $this);
}
/**
* Renders a view file.
* @param string $file the view file to be rendered. This can be either a file path or a [path alias](guide:concept-aliases).
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidArgumentException if the view file does not exist.
*/
public function renderFile($file, $params = [])
{
return $this->getView()->renderFile($file, $params, $this);
}
/**
* Returns the directory containing the view files for this widget.
* The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
* @return string the directory containing the view files for this widget.
*/
public function getViewPath()
{
$class = new ReflectionClass($this);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
/**
* This method is invoked right before the widget is executed.
*
* The method will trigger the [[EVENT_BEFORE_RUN]] event. The return value of the method
* will determine whether the widget should continue to run.
*
* When overriding this method, make sure you call the parent implementation like the following:
*
* ```php
* public function beforeRun()
* {
* if (!parent::beforeRun()) {
* return false;
* }
*
* // your custom code here
*
* return true; // or false to not run the widget
* }
* ```
*
* @return bool whether the widget should continue to be executed.
* @since 2.0.11
*/
public function beforeRun()
{
$event = new WidgetEvent();
$this->trigger(self::EVENT_BEFORE_RUN, $event);
return $event->isValid;
}
/**
* This method is invoked right after a widget is executed.
*
* The method will trigger the [[EVENT_AFTER_RUN]] event. The return value of the method
* will be used as the widget return value.
*
* If you override this method, your code should look like the following:
*
* ```php
* public function afterRun($result)
* {
* $result = parent::afterRun($result);
* // your custom code here
* return $result;
* }
* ```
*
* @param mixed $result the widget return result.
* @return mixed the processed widget result.
* @since 2.0.11
*/
public function afterRun($result)
{
$event = new WidgetEvent();
$event->result = $result;
$this->trigger(self::EVENT_AFTER_RUN, $event);
return $event->result;
}
}