Completed
Push — master ( b4d6a5...352f6d )
by Basil
02:42
created

Element::getElements()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Yii::getAlias($this->viewsFolder) can also be of type boolean. However, the property $_folder is declared as type string. Maybe add an additional type check?

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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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