Completed
Pull Request — master (#52)
by Wilmer
01:35
created

Widget::setView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 64
    public function __construct(EventDispatcherInterface $eventDispatcher)
44
    {
45 64
        self::$eventDispatcher = $eventDispatcher;
46
    }
47
48
    /**
49
     * Begins a widget.
50
     *
51
     * This method creates an instance of the calling class. It will apply the configuration to the created instance.
52
     * A matching {@see end()} call should be called later. As some widgets may use output buffering, the {@see end()}
53
     * call should be made in the same view to avoid breaking the nesting of output buffers.
54
     *
55
     * @return Widget the newly created widget instance.
56
     *
57
     * {@see end()}
58
     */
59 5
    public static function begin(): Widget
60
    {
61 5
        $widget = new static(self::$eventDispatcher);
62
63 5
        self::$stack[] = $widget;
64
65 5
        return $widget;
66
    }
67
68
    /**
69
     * Ends a widget
70
     *
71
     * Note that the rendering result of the widget is directly echoed out
72
     *
73
     * @return Widget the widget instance that is ended
74
     *
75
     * @throws \BadFunctionCallException if {@see begin()]} and {@see end()} calls are not properly nested.
76
     *
77
     * {@see begin()}
78
     */
79 6
    public static function end(): Widget
80
    {
81 6
        if (!empty(self::$stack)) {
82 5
            $widget = array_pop(self::$stack);
0 ignored issues
show
Bug introduced by
self::stack of type Yiisoft\Widget\Widget is incompatible with the type array expected by parameter $array of array_pop(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

82
            $widget = array_pop(/** @scrutinizer ignore-type */ self::$stack);
Loading history...
83
84 5
            if (get_class($widget) === static::class) {
85
                /* @var $widget Widget */
86 4
                if ($widget->beforeRun()) {
87 4
                    $result = $widget->run();
88 4
                    $result = $widget->afterRun($result);
89 4
                    echo $result;
90
                }
91
92 4
                return $widget;
93
            }
94 1
            throw new \BadFunctionCallException('Expecting end() of ' . get_class($widget) . ', found ' . static::class);
95
        }
96 1
        throw new \BadFunctionCallException(
97 1
            'Unexpected ' . static::class . '::end() call. A matching begin() is not found.'
98
        );
99
    }
100
101
    /**
102
     * Creates a widget instance.
103
     *
104
     * @return Widget $widget.
105
     */
106 7
    public static function widget(): Widget
107
    {
108 7
        $widget = new static(self::$eventDispatcher);
109
110 7
        static::$widget = $widget;
111
112 7
        return $widget;
113
    }
114
115
    /**
116
     * Returns the view object that can be used to render views or view files.
117
     *
118
     * The {@see render()} and {@see renderFile()} methods will use this view object to implement the actual view
119
     * rendering. If not set, it will default to the "view" application component.
120
     */
121 1
    public function getView(): WebView
122
    {
123 1
        return self::$webView;
124
    }
125
126
    /**
127
     * Sets the view object to be used by this widget.
128
     *
129
     * @param WebView $view the view object that can be used to render views or view files.
130
     */
131 64
    public function setView(WebView $webView): void
132
    {
133 64
        self::$webView = $webView;
134
    }
135
136
    public function init(): void
137
    {
138
    }
139
140
    public function show(): string
141
    {
142
    }
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...
143
144
    /**
145
     * Executes the widget.
146
     *
147
     * @return string the result of widget execution to be outputted.
148
     */
149 1
    public function run(): string
150
    {
151 1
        $out = '';
152 1
        $widget = static::$widget;
153
154 1
        if ($widget->beforeRun()) {
155 1
            $result = $widget->show();
156 1
            $out = $widget->afterRun($result);
157
        }
158
159 1
        return $out;
160
    }
161
162
    /**
163
     * Renders a view.
164
     *
165
     * The view to be rendered can be specified in one of the following formats:
166
     *
167
     * - [path alias](guide:concept-aliases) (e.g. "@app/views/site/index");
168
     * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
169
     * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
170
     * - relative path (e.g. "index"): the actual view file will be looked for under {@see viewPath}.
171
     *
172
     * If the view name does not contain a file extension, it will use the default one `.php`.
173
     *
174
     * @param string $view the view name.
175
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
176
     *
177
     * @return string the rendering result.
178
     */
179
    public function render(string $view, array $params = []): string
180
    {
181
        return $this->getView()->render($view, $params, $this);
182
    }
183
184
    /**
185
     * Renders a view file.
186
     *
187
     * @param string $file the view file to be rendered. This can be either a file path or a [path alias](guide:concept-aliases).
188
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
189
     *
190
     * @return string the rendering result.
191
     */
192
    public function renderFile(string $file, array $params = []): string
193
    {
194
        return $this->getView()->renderFile($file, $params, $this);
195
    }
196
197
    /**
198
     * Returns the directory containing the view files for this widget.
199
     * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
200
     *
201
     * @return string the directory containing the view files for this widget.
202
     *
203
     * @throws \InvalidArgumentException
204
     */
205
    public function getViewPath(): string
206
    {
207
        $class = new ReflectionClass($this);
208
209
        if (!is_string($class->getFileName())) {
0 ignored issues
show
introduced by
The condition is_string($class->getFileName()) is always true.
Loading history...
210
            throw new \InvalidArgumentException('Pathmap should contain strings');
211
        }
212
213
        return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
214
    }
215
216
    /**
217
     * This method is invoked right before the widget is executed.
218
     *
219
     * The method will trigger the {@see BeforeRun()} event. The return value of the method will determine whether the
220
     * widget should continue to run.
221
     *
222
     * When overriding this method, make sure you call the parent implementation like the following:
223
     *
224
     * ```php
225
     * public function beforeRun()
226
     * {
227
     *     if (!parent::beforeRun()) {
228
     *         return false;
229
     *     }
230
     *
231
     *     // your custom code here
232
     *
233
     *     return true; // or false to not run the widget
234
     * }
235
     * ```
236
     *
237
     * @return bool whether the widget should continue to be executed.
238
     */
239 5
    public function beforeRun(): bool
240
    {
241 5
        $event = new BeforeRun();
242 5
        self::$eventDispatcher->dispatch($event);
243
244 5
        return !$event->isPropagationStopped();
245
    }
246
247
    /**
248
     * This method is invoked right after a widget is executed.
249
     *
250
     * The method will trigger the {@see {AfterRun()} event. The return value of the method will be used as the widget
251
     * return value.
252
     *
253
     * If you override this method, your code should look like the following:
254
     *
255
     * ```php
256
     * public function afterRun($result)
257
     * {
258
     *     $result = parent::afterRun($result);
259
     *     // your custom code here
260
     *     return $result;
261
     * }
262
     * ```
263
     *
264
     * @param mixed $result the widget return result.
265
     *
266
     * @return mixed the processed widget result.
267
     */
268 5
    public function afterRun($result)
269
    {
270 5
        $event = new AfterRun($result);
271 5
        self::$eventDispatcher->dispatch($event);
272
273 5
        return $event->getResult();
274
    }
275
}
276