Completed
Pull Request — master (#56)
by
unknown
02:00
created

Widget   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Test Coverage

Coverage 7.14%

Importance

Changes 0
Metric Value
wmc 16
eloc 37
dl 0
loc 231
ccs 3
cts 42
cp 0.0714
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A renderFile() 0 3 1
A end() 0 19 4
A run() 0 10 2
A getView() 0 3 1
A init() 0 2 1
A begin() 0 7 1
A getContent() 0 2 1
A beforeRun() 0 6 1
A getViewPath() 0 5 1
A render() 0 3 1
A afterRun() 0 6 1
1
<?php
2
declare(strict_types = 1);
3
4
namespace Yiisoft\Widget;
5
6
use Psr\EventDispatcher\EventDispatcherInterface;
7
use ReflectionClass;
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 55
    public function __construct(EventDispatcherInterface $eventDispatcher, WebView $webView)
44
    {
45 55
        self::$eventDispatcher = $eventDispatcher;
46 55
        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
     * @param WebView $view
57
     * @return Widget the newly created widget instance.
58
     *
59
     * {@see end()}
60
     */
61
    public static function begin(WebView $view): Widget
62
    {
63
        $widget = $view->widget(static::class);
64
65
        self::$stack[] = $widget;
66
67
        return $widget;
68
    }
69
70
    /**
71
     * Ends a widget
72
     *
73
     * Note that the rendering result of the widget is directly echoed out
74
     *
75
     * @return Widget the widget instance that is ended
76
     *
77
     * @throws \BadFunctionCallException if {@see begin()]} and {@see end()} calls are not properly nested.
78
     *
79
     * {@see begin()}
80
     */
81
    public static function end(): Widget
82
    {
83
        if (!empty(self::$stack)) {
84
            $widget = array_pop(self::$stack);
85
86
            if (get_class($widget) === static::class) {
87
                /* @var $widget Widget */
88
                if ($widget->beforeRun()) {
89
                    $result = $widget->run();
90
                    $result = $widget->afterRun($result);
91
                    echo $result;
92
                }
93
94
                return $widget;
95
            }
96
            throw new \BadFunctionCallException('Expecting end() of ' . get_class($widget) . ', found ' . static::class);
97
        }
98
        throw new \BadFunctionCallException(
99
            'Unexpected ' . static::class . '::end() call. A matching begin() is not found.'
100
        );
101
    }
102
103
    /**
104
     * Returns the view object that can be used to render views or view files.
105
     *
106
     * The {@see render()} and {@see renderFile()} methods will use this view object to implement the actual view
107
     * rendering. If not set, it will default to the "view" application component.
108
     */
109
    public function getView(): WebView
110
    {
111
        return self::$webView;
112
    }
113
114
    public function init(): void
115
    {
116
    }
117
118
    public function getContent(): string
119
    {
120
    }
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...
121
122
    /**
123
     * Executes the widget.
124
     *
125
     * @return string the result of widget execution to be outputted.
126
     */
127
    public function run(): string
128
    {
129
        $out = '';
130
131
        if ($this->beforeRun()) {
132
            $result = $this->getContent();
133
            $out = $this->afterRun($result);
134
        }
135
136
        return $out;
137
    }
138
139
    /**
140
     * Renders a view.
141
     *
142
     * The view to be rendered can be specified in one of the following formats:
143
     *
144
     * - [path alias](guide:concept-aliases) (e.g. "@app/views/site/index");
145
     * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
146
     * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
147
     * - relative path (e.g. "index"): the actual view file will be looked for under {@see viewPath}.
148
     *
149
     * If the view name does not contain a file extension, it will use the default one `.php`.
150
     *
151
     * @param string $view the view name.
152
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
153
     *
154
     * @return string the rendering result.
155
     */
156
    public function render(string $view, array $params = []): string
157
    {
158
        return $this->getView()->render($view, $params, $this);
159
    }
160
161
    /**
162
     * Renders a view file.
163
     *
164
     * @param string $file the view file to be rendered. This can be either a file path or a [path alias](guide:concept-aliases).
165
     * @param array $params the parameters (name-value pairs) that should be made available in the view.
166
     *
167
     * @return string the rendering result.
168
     * @throws \Throwable
169
     */
170
    public function renderFile(string $file, array $params = []): string
171
    {
172
        return $this->getView()->renderFile($file, $params, $this);
173
    }
174
175
    /**
176
     * Returns the directory containing the view files for this widget.
177
     * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
178
     *
179
     * @return string the directory containing the view files for this widget.
180
     *
181
     * @throws \InvalidArgumentException
182
     * @throws \ReflectionException
183
     */
184
    public function getViewPath(): string
185
    {
186
        $class = new ReflectionClass($this);
187
188
        return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
189
    }
190
191
    /**
192
     * This method is invoked right before the widget is executed.
193
     *
194
     * The method will trigger the {@see BeforeRun()} event. The return value of the method will determine whether the
195
     * widget should continue to run.
196
     *
197
     * When overriding this method, make sure you call the parent implementation like the following:
198
     *
199
     * ```php
200
     * public function beforeRun()
201
     * {
202
     *     if (!parent::beforeRun()) {
203
     *         return false;
204
     *     }
205
     *
206
     *     // your custom code here
207
     *
208
     *     return true; // or false to not run the widget
209
     * }
210
     * ```
211
     *
212
     * @return bool whether the widget should continue to be executed.
213
     */
214
    public function beforeRun(): bool
215
    {
216
        $event = new BeforeRun();
217
        $event = self::$eventDispatcher->dispatch($event);
218
219
        return !$event->isPropagationStopped();
220
    }
221
222
    /**
223
     * This method is invoked right after a widget is executed.
224
     *
225
     * The method will trigger the {@see {AfterRun()} event. The return value of the method will be used as the widget
226
     * return value.
227
     *
228
     * If you override this method, your code should look like the following:
229
     *
230
     * ```php
231
     * public function afterRun($result)
232
     * {
233
     *     $result = parent::afterRun($result);
234
     *     // your custom code here
235
     *     return $result;
236
     * }
237
     * ```
238
     *
239
     * @param mixed $result the widget return result.
240
     *
241
     * @return mixed the processed widget result.
242
     */
243
    public function afterRun($result)
244
    {
245
        $event = new AfterRun($result);
246
        $event = self::$eventDispatcher->dispatch($event);
247
248
        return $event->getResult();
249
    }
250
}
251