|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace luya\web; |
|
4
|
|
|
|
|
5
|
|
|
use Yii; |
|
6
|
|
|
use luya\Exception; |
|
7
|
|
|
use luya\helpers\FileHelper; |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* HTML Element Component. |
|
11
|
|
|
* |
|
12
|
|
|
* Ability to register small html elements via closure function and run those |
|
13
|
|
|
* parts an every part of the page. |
|
14
|
|
|
* |
|
15
|
|
|
* ```php |
|
16
|
|
|
* Yii::$app->element->addElement('foo', function($param) { |
|
17
|
|
|
* return '<button>' . $param . '</button>'; |
|
18
|
|
|
* }); |
|
19
|
|
|
* ``` |
|
20
|
|
|
* |
|
21
|
|
|
* The above example can be execute like this: |
|
22
|
|
|
* |
|
23
|
|
|
* ```php |
|
24
|
|
|
* echo Yii::$app->element->foo('Hello World'); |
|
25
|
|
|
* ``` |
|
26
|
|
|
* |
|
27
|
|
|
* By default the Element-Component will lookup for an `elements.php` file returning an array |
|
28
|
|
|
* where the key is the element name and value the closure to be execute. |
|
29
|
|
|
* |
|
30
|
|
|
* Example elements.php |
|
31
|
|
|
* |
|
32
|
|
|
* ```php |
|
33
|
|
|
* <?php |
|
34
|
|
|
* return [ |
|
35
|
|
|
* 'button' => function($value, $link) { |
|
36
|
|
|
* return '<a class="btn btn-primary" href="'.$link.'">'.$value.'</a>'; |
|
37
|
|
|
* }, |
|
38
|
|
|
* 'teaserbox' => function($title, $text, $buttonValue, $buttonLink) { |
|
39
|
|
|
* return '<div class="teaser-box" style="padding:10px; border:1px solid red;"><h1>'.$title.'</h1><p>'.$text.'</p>'.$this->button($buttonValue, $buttonLink).'</div>'; |
|
40
|
|
|
* }, |
|
41
|
|
|
* ]; |
|
42
|
|
|
* ``` |
|
43
|
|
|
* |
|
44
|
|
|
* Its also possible to directly provided mocked arguments for the styleguide: |
|
45
|
|
|
* |
|
46
|
|
|
* ```php |
|
47
|
|
|
* return [ |
|
48
|
|
|
* 'button' => [function($value, $link) { |
|
49
|
|
|
* return '<a class="btn btn-primary" href="'.$link.'">'.$value.'</a>'; |
|
50
|
|
|
* }, ['value' => 'Value for Button', 'link' => 'Value for Link']], |
|
51
|
|
|
* ]; |
|
52
|
|
|
* ``` |
|
53
|
|
|
* |
|
54
|
|
|
* Or directly from the add Element method: |
|
55
|
|
|
* |
|
56
|
|
|
* ```php |
|
57
|
|
|
* Yii::$app->element->addElement('button', function($value, $link) { |
|
58
|
|
|
* return '<a class="btn btn-primary" href="'.$link.'">'.$value.'</a>'; |
|
59
|
|
|
* }, ['value' => 'Value for Button', 'link' => 'Value for Link']); |
|
60
|
|
|
* ``` |
|
61
|
|
|
* |
|
62
|
|
|
* The styleguide will now insert the mocked values instead of generic values. |
|
63
|
|
|
* |
|
64
|
|
|
* > Important: If you want to pass database values from active records objects or things which are more memory intense, you should add this method into a lambda / callable function |
|
65
|
|
|
* > which is then lazy loaded only when creating the guide. For example: |
|
66
|
|
|
* > ['myModel' => function() { return MyModel::findOne(1); } ] |
|
67
|
|
|
* |
|
68
|
|
|
* @author Basil Suter <[email protected]> |
|
69
|
|
|
* @since 1.0.0 |
|
70
|
|
|
*/ |
|
71
|
|
|
class Element extends \yii\base\Component |
|
72
|
|
|
{ |
|
73
|
|
|
/** |
|
74
|
|
|
* @var string The path to the default element file. Alias names will be parsed by Yii::getAlias. |
|
75
|
|
|
*/ |
|
76
|
|
|
public $configFile = '@app/views/elements.php'; |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* @var string The path to the folder where the view files to render can be found. |
|
80
|
|
|
*/ |
|
81
|
|
|
public $viewsFolder = '@app/views/elements/'; |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* @var array Contains all registered elements. |
|
85
|
|
|
*/ |
|
86
|
|
|
private $_elements = []; |
|
87
|
|
|
|
|
88
|
|
|
/** |
|
89
|
|
|
* @var string Parsed path of the folder where the view files are stored. |
|
90
|
|
|
*/ |
|
91
|
|
|
private $_folder; |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* Yii intializer, is loading the default elements.php if existing. |
|
95
|
|
|
*/ |
|
96
|
|
|
public function init() |
|
97
|
|
|
{ |
|
98
|
|
|
$path = Yii::getAlias($this->configFile); |
|
99
|
|
|
if (file_exists($path)) { |
|
100
|
|
|
$config = (include($path)); |
|
101
|
|
|
foreach ($config as $name => $closure) { |
|
102
|
|
|
if (is_array($closure)) { |
|
103
|
|
|
$this->addElement($name, $closure[0], $closure[1]); |
|
104
|
|
|
} else { |
|
105
|
|
|
$this->addElement($name, $closure); |
|
106
|
|
|
} |
|
107
|
|
|
} |
|
108
|
|
|
} |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* Magic method to run an closre directly from the Element-Object, for convincience. Example use. |
|
113
|
|
|
* |
|
114
|
|
|
* ```php |
|
115
|
|
|
* $element = new Element(); |
|
116
|
|
|
* $element->name($param1); |
|
117
|
|
|
* ``` |
|
118
|
|
|
* |
|
119
|
|
|
* @param string $name access method name |
|
120
|
|
|
* @param array $params access method params |
|
121
|
|
|
* @return mixed |
|
122
|
|
|
*/ |
|
123
|
|
|
public function __call($name, $params) |
|
124
|
|
|
{ |
|
125
|
|
|
return $this->getElement($name, $params); |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
/** |
|
129
|
|
|
* Add an element with a closure to the elements array. |
|
130
|
|
|
* |
|
131
|
|
|
* @param string $name The identifier of the element where the close is binde to. |
|
132
|
|
|
* @param callable $closure The closure function to registered, for example: |
|
133
|
|
|
* |
|
134
|
|
|
* ```php |
|
135
|
|
|
* function() { |
|
136
|
|
|
* return 'foobar'; |
|
137
|
|
|
* } |
|
138
|
|
|
* ``` |
|
139
|
|
|
* |
|
140
|
|
|
* @param array $mockedArgs An array with key value pairing for the argument in order to render them for the styleguide. |
|
141
|
|
|
*/ |
|
142
|
|
|
public function addElement($name, $closure, $mockedArgs = []) |
|
143
|
|
|
{ |
|
144
|
|
|
$this->_elements[$name] = $closure; |
|
145
|
|
|
|
|
146
|
|
|
$this->mockArgs($name, $mockedArgs); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* Checks whether an elemnt exists in the elements list or not |
|
151
|
|
|
* |
|
152
|
|
|
* @param string $name The name of the element |
|
153
|
|
|
* @return boolean |
|
154
|
|
|
*/ |
|
155
|
|
|
public function hasElement($name) |
|
156
|
|
|
{ |
|
157
|
|
|
return array_key_exists($name, $this->_elements); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
/** |
|
161
|
|
|
* Returns an array with all registered Element-Names. |
|
162
|
|
|
* |
|
163
|
|
|
* @return array Value is the name of the element |
|
164
|
|
|
*/ |
|
165
|
|
|
public function getNames() |
|
166
|
|
|
{ |
|
167
|
|
|
return array_keys($this->_elements); |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
/** |
|
171
|
|
|
* Return all elements as an array where the key is the name and the value the closure. |
|
172
|
|
|
* |
|
173
|
|
|
* @return array Key is the Name of the Element, value the Closure. |
|
174
|
|
|
*/ |
|
175
|
|
|
public function getElements() |
|
176
|
|
|
{ |
|
177
|
|
|
return $this->_elements; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** |
|
181
|
|
|
* Renders the closure for the given name and returns the content. |
|
182
|
|
|
* |
|
183
|
|
|
* @param string $name The name of the elemente to execute. |
|
184
|
|
|
* @param array $params The params to pass to the closure methode. |
|
185
|
|
|
* @return mixed The return value of the executed closure function. |
|
186
|
|
|
* @throws Exception |
|
187
|
|
|
*/ |
|
188
|
|
|
public function getElement($name, array $params = []) |
|
189
|
|
|
{ |
|
190
|
|
|
if (!array_key_exists($name, $this->_elements)) { |
|
191
|
|
|
throw new Exception("The requested element '$name' does not exist in the list. You may register the element first with `addElement(name, closure)`."); |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
return call_user_func_array($this->_elements[$name], $params); |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* Returns the path to the view files used for the render() method. Singleton method the return |
|
199
|
|
|
* the evaluated viewFolder path once. |
|
200
|
|
|
* |
|
201
|
|
|
* @return string Evaluated view foler path |
|
202
|
|
|
*/ |
|
203
|
|
|
public function getFolder() |
|
204
|
|
|
{ |
|
205
|
|
|
if ($this->_folder === null) { |
|
206
|
|
|
$this->_folder = Yii::getAlias($this->viewsFolder); |
|
|
|
|
|
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
return $this->_folder; |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
private $_mockedArguments = []; |
|
213
|
|
|
|
|
214
|
|
|
/** |
|
215
|
|
|
* Mock arguments for an element in order to render those inside the styleguide. |
|
216
|
|
|
* |
|
217
|
|
|
* @param string $elementName The element name the arguments are defined for. |
|
218
|
|
|
* @param array $args Arguments where the key is the argument name value and value to mock. |
|
219
|
|
|
*/ |
|
220
|
|
|
public function mockArgs($elementName, array $args) |
|
221
|
|
|
{ |
|
222
|
|
|
$this->_mockedArguments[$elementName] = $args; |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
/** |
|
226
|
|
|
* Find the mocked value for an element argument. |
|
227
|
|
|
* |
|
228
|
|
|
* @param string $elementName The name of the element. |
|
229
|
|
|
* @param string $argName The name of the argument. |
|
230
|
|
|
* @return mixed|boolean Whether the mocked argument value exists returns the value otherwise false. |
|
231
|
|
|
*/ |
|
232
|
|
|
public function getMockedArgValue($elementName, $argName) |
|
233
|
|
|
{ |
|
234
|
|
|
if (isset($this->_mockedArguments[$elementName]) && isset($this->_mockedArguments[$elementName][$argName])) { |
|
235
|
|
|
$response = $this->_mockedArguments[$elementName][$argName]; |
|
236
|
|
|
if (is_callable($response)) { |
|
237
|
|
|
$response = call_user_func($response); |
|
238
|
|
|
} |
|
239
|
|
|
return $response; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
return false; |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
/** |
|
246
|
|
|
* Method to render twig files with theyr specific arguments, can be used inside the element closure depending |
|
247
|
|
|
* on where the closure was registered. Otherwhise the use of the element variable must be provided. |
|
248
|
|
|
* |
|
249
|
|
|
* @param string $file The name of the file to render. |
|
250
|
|
|
* @param array $args The parameters to pass in the render file. |
|
251
|
|
|
* |
|
252
|
|
|
* @return string The render value of the view file. |
|
253
|
|
|
*/ |
|
254
|
|
|
public function render($file, array $args = []) |
|
255
|
|
|
{ |
|
256
|
|
|
$view = new View(); |
|
257
|
|
|
$view->autoRegisterCsrf = false; |
|
258
|
|
|
return $view->renderPhpFile(rtrim($this->getFolder(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . FileHelper::ensureExtension($file, 'php'), $args); |
|
259
|
|
|
} |
|
260
|
|
|
} |
|
261
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.