Passed
Pull Request — master (#58)
by
unknown
01:23
created

Widget::widget()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
rs 10
ccs 4
cts 4
cp 1
cc 1
nc 1
nop 0
crap 1
1
<?php
2
declare(strict_types = 1);
3
4
namespace Yiisoft\Widget;
5
6
use BadFunctionCallException;
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use ReflectionClass;
9
use Yiisoft\View\ViewContextInterface;
10
use Yiisoft\View\WebView;
11
use Yiisoft\Widget\Event\AfterRun;
12
use Yiisoft\Widget\Event\BeforeRun;
13
14
/**
15
 * Widget is the base class for widgets.
16
 *
17
 * For more details and usage information on Widget, see the [guide article on widgets](guide:structure-widgets).
18
 */
19
class Widget implements ViewContextInterface
20
{
21
    /**
22
     * @var EventDispatcherInterface event handler.
23
     */
24
    protected static $eventDispatcher;
25
    /**
26
     * @var Widget $widget
27
     */
28
    protected static $widget;
29
    /**
30
     * The widgets that are currently being rendered (not ended). This property is maintained by {@see static::begin()}
31
     * and {@see static::end()} methods.
32
     *
33
     * @var Widget[] $stack
34
     */
35
    protected static $stack;
36
    /**
37
     * @var WebView $view
38
     */
39
    protected static $webView;
40
41 80
    public function __construct(EventDispatcherInterface $eventDispatcher, WebView $webView)
42
    {
43 80
        self::$eventDispatcher = $eventDispatcher;
44 80
        self::$webView = $webView;
45
    }
46
47
    /**
48
     * Begins a widget.
49
     *
50
     * This method creates an instance of the calling class. It will apply the configuration to the created instance.
51
     * A matching {@see end()} call should be called later. As some widgets may use output buffering, the {@see end()}
52
     * call should be made in the same view to avoid breaking the nesting of output buffers.
53
     *
54
     * @return Widget the newly created widget instance.
55
     *
56
     * {@see end()}
57
     */
58 8
    public static function begin(): Widget
59
    {
60 8
        $widget = new static(self::$eventDispatcher, self::$webView);
61
62 8
        self::$stack[] = $widget;
63
64 8
        return $widget;
65
    }
66
67
    /**
68
     * Ends a widget
69
     *
70
     * Note that the rendering result of the widget is directly echoed out
71
     *
72
     * @return Widget the widget instance that is ended
73
     *
74
     * @throws BadFunctionCallException if {@see begin()]} and {@see end()} calls are not properly nested.
75
     *
76
     * {@see begin()}
77
     */
78 9
    public static function end(): Widget
79
    {
80 9
        if (empty(self::$stack)) {
81 1
            throw new BadFunctionCallException(
82 1
                'Unexpected ' . static::class . '::end() call. A matching begin() is not found.'
83
            );
84
        }
85
86 8
        $widget = array_pop(self::$stack);
87 8
        if (get_class($widget) !== static::class) {
88 1
            throw new BadFunctionCallException('Expecting end() of ' . get_class($widget) . ', found ' . static::class);
89
        }
90 7
        if ($widget->beforeRun()) {
91 7
            $result = $widget->run();
92 7
            $result = $widget->afterRun($result);
93 7
            echo $result;
94
        }
95
96 7
        return $widget;
97
    }
98
99
    /**
100
     * Creates a widget instance.
101
     *
102
     * @return Widget $widget.
103
     */
104 17
    public static function widget(): Widget
105
    {
106 17
        $widget = new static(self::$eventDispatcher, self::$webView);
107
108 17
        static::$widget = $widget;
109
110 17
        return $widget;
111
    }
112
113
    /**
114
     * Returns the view object that can be used to render views or view files.
115
     *
116
     * The {@see render()} and {@see renderFile()} methods will use this view object to implement the actual view
117
     * rendering. If not set, it will default to the "view" application component.
118
     */
119 2
    public function getView(): WebView
120
    {
121 2
        return self::$webView;
122
    }
123
124
    public function init(): void
125
    {
126
    }
127
128
    public function getContent(): string
129
    {
130
        return '';
131
    }
132
133
    /**
134
     * Executes the widget.
135
     *
136
     * @return string the result of widget execution to be outputted.
137
     */
138 16
    public function run(): string
139
    {
140 16
        $out = '';
141 16
        $widget = static::$widget;
142
143 16
        if ($widget->beforeRun()) {
144 16
            $result = $widget->getContent();
145 16
            $out = $widget->afterRun($result);
146
        }
147
148 16
        return $out;
149
    }
150
151
    /**
152
     * Renders a view.
153
     * The view to be rendered can be specified in one of the following formats:
154
     * - [path alias](guide:concept-aliases) (e.g. "@app/views/site/index");
155
     * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
156
     * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
157
     * - relative path (e.g. "index"): the actual view file will be looked for under {@see viewPath}.
158
     * If the view name does not contain a file extension, it will use the default one `.php`.
159
     *
160
     * @param string $view the view name.
161
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
162
     * @return string the rendering result.
163
     * @throws \Throwable
164
     */
165
    public function render(string $view, array $params = []): string
166
    {
167
        return $this->getView()->render($view, $params, $this);
168
    }
169
170
    /**
171
     * Renders a view file.
172
     *
173
     * @param string $file the view file to be rendered. This can be either a file path or a [path alias](guide:concept-aliases).
174
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
175
     *
176
     * @return string the rendering result.
177
     * @throws \Throwable
178
     */
179
    public function renderFile(string $file, array $params = []): string
180
    {
181
        return $this->getView()->renderFile($file, $params, $this);
182
    }
183
184
    /**
185
     * Returns the directory containing the view files for this widget.
186
     * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
187
     *
188
     * @return string the directory containing the view files for this widget.
189
     *
190
     * @throws \InvalidArgumentException
191
     * @throws \ReflectionException
192
     */
193
    public function getViewPath(): string
194
    {
195
        $class = new ReflectionClass($this);
196
197
        return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
198
    }
199
200
    /**
201
     * This method is invoked right before the widget is executed.
202
     *
203
     * The method will trigger the {@see BeforeRun()} event. The return value of the method will determine whether the
204
     * widget should continue to run.
205
     *
206
     * When overriding this method, make sure you call the parent implementation like the following:
207
     *
208
     * ```php
209
     * public function beforeRun()
210
     * {
211
     *     if (!parent::beforeRun()) {
212
     *         return false;
213
     *     }
214
     *
215
     *     // your custom code here
216
     *
217
     *     return true; // or false to not run the widget
218
     * }
219
     * ```
220
     *
221
     * @return bool whether the widget should continue to be executed.
222
     */
223 23
    public function beforeRun(): bool
224
    {
225 23
        $event = new BeforeRun();
226 23
        $event = self::$eventDispatcher->dispatch($event);
227
228 23
        return !$event->isPropagationStopped();
229
    }
230
231
    /**
232
     * This method is invoked right after a widget is executed.
233
     *
234
     * The method will trigger the {@see {AfterRun()} event. The return value of the method will be used as the widget
235
     * return value.
236
     *
237
     * If you override this method, your code should look like the following:
238
     *
239
     * ```php
240
     * public function afterRun($result)
241
     * {
242
     *     $result = parent::afterRun($result);
243
     *     // your custom code here
244
     *     return $result;
245
     * }
246
     * ```
247
     *
248
     * @param string $result the widget return result.
249
     *
250
     * @return string the processed widget result.
251
     */
252 23
    public function afterRun(string $result): string
253
    {
254 23
        $event = new AfterRun($result);
255 23
        $event = self::$eventDispatcher->dispatch($event);
256
257 23
        return $event->getResult();
258
    }
259
}
260