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 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
ccs 0
cts 2
cp 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
    private $view;
37
38
    /**
39
     * @var Widget $widget
40
     */
41
    protected static $widget;
42
43 63
    public function __construct(EventDispatcherInterface $eventDispatcher)
44
    {
45 63
        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 4
    public static function begin(): Widget
60
    {
61 4
        $widget = new static(self::$eventDispatcher);
62
63 4
        self::$stack[] = $widget;
64
65 4
        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 5
    public static function end(): Widget
80
    {
81 5
        if (!empty(self::$stack)) {
82 4
            $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 4
            if (get_class($widget) === static::class) {
85
                /* @var $widget Widget */
86 3
                if ($widget->beforeRun()) {
87 3
                    $result = $widget->run();
88 3
                    $result = $widget->afterRun($result);
89 3
                    echo $result;
90
                }
91
92 3
                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 [[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
    public function getView(): WebView
122
    {
123
        return $this->view;
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
    public function setView(WebView $view): void
132
    {
133
        $this->view = $view;
134
    }
135
136
    public function init(): void
137
    {
138
    }
139
140
    /**
141
     * Executes the widget.
142
     *
143
     * @return string the result of widget execution to be outputted.
144
     */
145 1
    public function run(): string
146
    {
147 1
        $out = '';
148 1
        $widget = static::$widget;
149
150 1
        if ($widget->beforeRun()) {
151 1
            $result = $widget->show();
0 ignored issues
show
Bug introduced by
The method show() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Widget\Tests\Stubs\TestWidget or Yiisoft\Widget\Menu. ( Ignorable by Annotation )

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

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