Completed
Pull Request — master (#52)
by Alexander
03:31
created

Widget::setView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
declare(strict_types = 1);
3
4
namespace Yiisoft\Widget;
5
6
use ReflectionClass;
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use Yiisoft\View\ViewContextInterface;
9
use Yiisoft\View\WebView;
10
use Yiisoft\Widget\Event\AfterRun;
11
use Yiisoft\Widget\Event\BeforeRun;
12
13
/**
14
 * Widget is the base class for widgets.
15
 *
16
 * For more details and usage information on Widget, see the [guide article on widgets](guide:structure-widgets).
17
 */
18
class Widget implements ViewContextInterface
19
{
20
    /**
21
     * @var EventDispatcherInterface event handler.
22
     */
23
    protected static $eventDispatcher;
24
25
    /**
26
     * The widgets that are currently being rendered (not ended). This property is maintained by {@see static::begin()}
27
     * and {@see static::end()} methods.
28
     *
29
     * @var Widget[] $stack
30
     */
31
    protected static $stack;
32
33
    /**
34
     * @var WebView $view
35
     */
36
    protected static $webView;
37
38
    /**
39
     * @var Widget $widget
40
     */
41
    protected static $widget;
42
43 78
    public function __construct(EventDispatcherInterface $eventDispatcher, WebView $webView)
44
    {
45 78
        self::$eventDispatcher = $eventDispatcher;
46 78
        self::$webView = $webView;
47
    }
48
49
    /**
50
     * Begins a widget.
51
     *
52
     * This method creates an instance of the calling class. It will apply the configuration to the created instance.
53
     * A matching {@see end()} call should be called later. As some widgets may use output buffering, the {@see end()}
54
     * call should be made in the same view to avoid breaking the nesting of output buffers.
55
     *
56
     * @return Widget the newly created widget instance.
57
     *
58
     * {@see end()}
59
     */
60 8
    public static function begin(): Widget
61
    {
62 8
        $widget = new static(self::$eventDispatcher, self::$webView);
63
64 8
        self::$stack[] = $widget;
65
66 8
        return $widget;
67
    }
68
69
    /**
70
     * Ends a widget
71
     *
72
     * Note that the rendering result of the widget is directly echoed out
73
     *
74
     * @return Widget the widget instance that is ended
75
     *
76
     * @throws \BadFunctionCallException if {@see begin()]} and {@see end()} calls are not properly nested.
77
     *
78
     * {@see begin()}
79
     */
80 9
    public static function end(): Widget
81
    {
82 9
        if (!empty(self::$stack)) {
83 8
            $widget = array_pop(self::$stack);
84
85 8
            if (get_class($widget) === static::class) {
86
                /* @var $widget Widget */
87 7
                if ($widget->beforeRun()) {
88 7
                    $result = $widget->run();
89 7
                    $result = $widget->afterRun($result);
90 7
                    echo $result;
91
                }
92
93 7
                return $widget;
94
            }
95 1
            throw new \BadFunctionCallException('Expecting end() of ' . get_class($widget) . ', found ' . static::class);
96
        }
97 1
        throw new \BadFunctionCallException(
98 1
            'Unexpected ' . static::class . '::end() call. A matching begin() is not found.'
99
        );
100
    }
101
102
    /**
103
     * Creates a widget instance.
104
     *
105
     * @return Widget $widget.
106
     */
107 17
    public static function widget(): Widget
108
    {
109 17
        $widget = new static(self::$eventDispatcher, self::$webView);
110
111 17
        static::$widget = $widget;
112
113 17
        return $widget;
114
    }
115
116
    /**
117
     * Returns the view object that can be used to render views or view files.
118
     *
119
     * The {@see render()} and {@see renderFile()} methods will use this view object to implement the actual view
120
     * rendering. If not set, it will default to the "view" application component.
121
     */
122 2
    public function getView(): WebView
123
    {
124 2
        return self::$webView;
125
    }
126
127
    public function init(): void
128
    {
129
    }
130
131
    public function getContent(): string
132
    {
133
    }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
134
135
    /**
136
     * Executes the widget.
137
     *
138
     * @return string the result of widget execution to be outputted.
139
     */
140 16
    public function run(): string
141
    {
142 16
        $out = '';
143 16
        $widget = static::$widget;
144
145 16
        if ($widget->beforeRun()) {
146 16
            $result = $widget->getContent();
147 16
            $out = $widget->afterRun($result);
148
        }
149
150 16
        return $out;
151
    }
152
153
    /**
154
     * Renders a view.
155
     *
156
     * The view to be rendered can be specified in one of the following formats:
157
     *
158
     * - [path alias](guide:concept-aliases) (e.g. "@app/views/site/index");
159
     * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
160
     * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
161
     * - relative path (e.g. "index"): the actual view file will be looked for under {@see viewPath}.
162
     *
163
     * If the view name does not contain a file extension, it will use the default one `.php`.
164
     *
165
     * @param string $view the view name.
166
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
167
     *
168
     * @return string the rendering result.
169
     */
170
    public function render(string $view, array $params = []): string
171
    {
172
        return $this->getView()->render($view, $params, $this);
173
    }
174
175
    /**
176
     * Renders a view file.
177
     *
178
     * @param string $file the view file to be rendered. This can be either a file path or a [path alias](guide:concept-aliases).
179
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
180
     *
181
     * @return string the rendering result.
182
     * @throws \Throwable
183
     */
184
    public function renderFile(string $file, array $params = []): string
185
    {
186
        return $this->getView()->renderFile($file, $params, $this);
187
    }
188
189
    /**
190
     * Returns the directory containing the view files for this widget.
191
     * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
192
     *
193
     * @return string the directory containing the view files for this widget.
194
     *
195
     * @throws \InvalidArgumentException
196
     * @throws \ReflectionException
197
     */
198
    public function getViewPath(): string
199
    {
200
        $class = new ReflectionClass($this);
201
202
        return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
203
    }
204
205
    /**
206
     * This method is invoked right before the widget is executed.
207
     *
208
     * The method will trigger the {@see BeforeRun()} event. The return value of the method will determine whether the
209
     * widget should continue to run.
210
     *
211
     * When overriding this method, make sure you call the parent implementation like the following:
212
     *
213
     * ```php
214
     * public function beforeRun()
215
     * {
216
     *     if (!parent::beforeRun()) {
217
     *         return false;
218
     *     }
219
     *
220
     *     // your custom code here
221
     *
222
     *     return true; // or false to not run the widget
223
     * }
224
     * ```
225
     *
226
     * @return bool whether the widget should continue to be executed.
227
     */
228 23
    public function beforeRun(): bool
229
    {
230 23
        $event = new BeforeRun();
231 23
        $event = self::$eventDispatcher->dispatch($event);
232
233 23
        return !$event->isPropagationStopped();
234
    }
235
236
    /**
237
     * This method is invoked right after a widget is executed.
238
     *
239
     * The method will trigger the {@see {AfterRun()} event. The return value of the method will be used as the widget
240
     * return value.
241
     *
242
     * If you override this method, your code should look like the following:
243
     *
244
     * ```php
245
     * public function afterRun($result)
246
     * {
247
     *     $result = parent::afterRun($result);
248
     *     // your custom code here
249
     *     return $result;
250
     * }
251
     * ```
252
     *
253
     * @param mixed $result the widget return result.
254
     *
255
     * @return mixed the processed widget result.
256
     */
257 23
    public function afterRun($result)
258
    {
259 23
        $event = new AfterRun($result);
260 23
        $event = self::$eventDispatcher->dispatch($event);
261
262 23
        return $event->getResult();
263
    }
264
}
265