Passed
Push — master ( 5c453d...74aac2 )
by Alexander
04:19 queued 02:04
created

Widget   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 19
c 5
b 0
f 0
dl 0
loc 147
ccs 25
cts 25
cp 1
rs 10
wmc 12

7 Methods

Rating   Name   Duplication   Size   Complexity  
A widget() 0 7 3
A begin() 0 4 1
A end() 0 15 3
A __toString() 0 3 1
A beforeRun() 0 3 1
A afterRun() 0 3 1
A render() 0 7 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Widget;
6
7
use RuntimeException;
8
use Yiisoft\Factory\Exception\InvalidConfigException;
9
use Yiisoft\Html\NoEncodeStringableInterface;
10
11
use function array_key_exists;
12
use function get_class;
13
use function is_array;
14
15
/**
16
 * Widget generates a string content based on some logic and input data.
17
 * These are typically used in templates to conceal complex HTML rendering logic.
18
 *
19
 * This is the base class that is meant to be inherited when implementing your own widgets.
20
 */
21
abstract class Widget implements NoEncodeStringableInterface
22
{
23
    /**
24
     * The widgets that are currently opened and not yet closed.
25
     * This property is maintained by {@see begin()} and {@see end()} methods.
26
     *
27
     * @var static[]
28
     */
29
    private static array $stack;
30
31
    /**
32
     * Used to open a wrapping widget (the one with begin/end).
33
     *
34
     * When implementing this method, don't forget to call parent::begin().
35
     *
36
     * @return string|null Opening part of widget markup.
37
     */
38 5
    public function begin(): ?string
39
    {
40 5
        self::$stack[] = $this;
41 5
        return null;
42
    }
43
44
    /**
45
     * Renders widget content.
46
     *
47
     * This method is used by {@see render()} and is meant to be overridden
48
     * when implementing concrete widget.
49
     */
50
    abstract protected function run(): string;
51
52
    /**
53
     * Checks that the widget was opened with {@see begin()}. If so, runs it and returns content generated.
54
     *
55
     * @throws RuntimeException
56
     */
57 8
    final public static function end(): string
58
    {
59 8
        if (empty(self::$stack)) {
60 3
            throw new RuntimeException(
61 3
                'Unexpected ' . static::class . '::end() call. A matching begin() is not found.'
62
            );
63
        }
64
65 5
        $widget = array_pop(self::$stack);
66
67 5
        if (get_class($widget) !== static::class) {
68 1
            throw new RuntimeException('Expecting end() of ' . get_class($widget) . ', found ' . static::class . '.');
69
        }
70
71 4
        return $widget->render();
72
    }
73
74
    /**
75
     * Creates a widget instance.
76
     *
77
     * @param array|callable|string $config The parameters for creating a widget.
78
     *
79
     * @throws InvalidConfigException
80
     *
81
     * @return static The widget instance.
82
     */
83 14
    final public static function widget($config = []): self
84
    {
85 14
        if (is_array($config) && !array_key_exists('class', $config)) {
86 14
            $config['class'] = static::class;
87
        }
88
89 14
        return WidgetFactory::createWidget($config);
90
    }
91
92
    /**
93
     * Executes the widget.
94
     *
95
     * @return string The result of widget execution to be outputted.
96
     */
97 8
    final public function render(): string
98
    {
99 8
        if (!$this->beforeRun()) {
100 1
            return '';
101
        }
102
103 7
        return $this->afterRun($this->run());
104
    }
105
106
    /**
107
     * This method is invoked right before the widget is executed.
108
     *
109
     * The return value of the method will determine whether the widget should continue to run.
110
     *
111
     * When overriding this method, make sure you call the parent implementation like the following:
112
     *
113
     * ```php
114
     * public function beforeRun(): bool
115
     * {
116
     *     if (!parent::beforeRun()) {
117
     *         return false;
118
     *     }
119
     *
120
     *     // your custom code here
121
     *
122
     *     return true; // or false to not run the widget
123
     * }
124
     * ```
125
     *
126
     * @return bool Whether the widget should continue to be executed.
127
     */
128 7
    protected function beforeRun(): bool
129
    {
130 7
        return true;
131
    }
132
133
    /**
134
     * This method is invoked right after a widget is executed.
135
     *
136
     * The return value of the method will be used as the widget return value.
137
     *
138
     * If you override this method, your code should look like the following:
139
     *
140
     * ```php
141
     * public function afterRun(string $result): string
142
     * {
143
     *     $result = parent::afterRun($result);
144
     *     // your custom code here
145
     *     return $result;
146
     * }
147
     * ```
148
     *
149
     * @param string $result The widget return result.
150
     *
151
     * @return string The processed widget result.
152
     */
153 7
    protected function afterRun(string $result): string
154
    {
155 7
        return $result;
156
    }
157
158
    /**
159
     * Allows not to call `->render()` explicitly:
160
     *
161
     * ```php
162
     * <?= MyWidget::widget(); ?>
163
     * ```
164
     */
165 1
    final public function __toString(): string
166
    {
167 1
        return $this->render();
168
    }
169
}
170