Passed
Push — master ( 961a81...e155a4 )
by Andreas
02:50
created

Engine::renderField()   C

Complexity

Conditions 11
Paths 2

Size

Total Lines 37
Code Lines 27

Duplication

Lines 5
Ratio 13.51 %

Importance

Changes 0
Metric Value
dl 5
loc 37
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 27
nc 2
nop 5

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of AirTemplate.
4
 *
5
 * (c) 2016 Andreas Blaser
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @package AirTemplate
11
 * @author  Andreas Blaser <[email protected]>
12
 * @license http://www.spdx.org/licenses/MIT MIT License
13
 */
14
15
namespace AirTemplate;
16
17
/**
18
 * Class Engine
19
 *
20
 * The extended render engine class.
21
 * Supports:
22
 * - Everything from the BaseEngine class
23
 * - Nested templates: {{field|render("sub-template")}}, {{field|each("sub-template")}}
24
 * - Datapath: {{field=rel-path/to/value}}, {{field=/abs-path/to/value}}
25
 */
26
class Engine extends BaseEngine
27
{
28
29
    /**
30
     * Complexity level (0=low, 1=high)
31
     *
32
     * @var int
33
     */
34
    protected $complexity = [];
35
36
    /**
37
     * Array of 'path' arrays pointing to other locations in data.
38
     *
39
     * @var array
40
     */
41
    protected $datapath = [];
42
43
    /**
44
     * Constructor.
45
     *
46
     * @param array $templates    Array of parsed templates
47
     * @param array $fieldOptions Array of field options
48
     */
49
    public function __construct(array $templates, array $fieldOptions)
50
    {
51
        parent::__construct($templates, $fieldOptions);
52
        foreach ($templates as $name => $template) {
53
            $this->datapath[$name] = $template['datapath'];
54
            $this->complexity[$name] = $template['complexity'];
55
        }
56
    }
57
58
    /**
59
     * Merge template and values and return the rendered string. Replacement
60
     * values may be specified as an assoc array or an object.
61
     *
62
     * @param string       $name     Template name
63
     * @param array|object $data     Data object or array
64
     * @param bool         $isObject Template name
65
     *
66
     * @return string The rendered result
67
     */
68
    protected function merge($name, $data, $isObject = false)
69
    {
70
        $result = '';
71
        if ($this->complexity[$name] < 1) {
72
            return parent::merge($name, $data, $isObject);
73
        }
74
        foreach ($this->templates[$name] as $index => $fragment) {
75
            if (!isset($this->fields[$name][$index])) {
76
                $result .= $fragment;
77
                continue;
78
            }
79
            $field = $this->fields[$name][$index];
80
            $value = self::getFieldValue(
81
                $field,
82
                $data,
83
                $this->datapath[$name][$field],
84
                $isObject
85
            );
86 View Code Duplication
            if ($this->fieldOptions[$name][$field] !== false) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
                $value = $this->renderField(
88
                    $field,
89
                    $value,
90
                    $data,
91
                    $this->fieldOptions[$name][$field],
92
                    $isObject
93
                );
94
            }
95
            $result .= $value;
96
        }
97
        return $result;
98
    }
99
100
    /**
101
     * Renders the output according to the specified option.
102
     * Options:
103
     *
104
     * @param string       $field   Field name
105
     * @param mixed        $value   Replacement value
106
     * @param array|object $data    Data array or object
107
     * @param mixed        $options Fields options
108
     * @param bool         $isObject true if $data is an object
109
     *
110
     * @return string The formatted value
111
     */
112
    protected function renderField(
113
        $field,
114
        $value,
115
        $data,
116
        $options,
117
        $isObject
118
    ) {
119
        foreach ($options as $option) {
120
            if ($option[0] == 'default:' && empty($value)) {
121
                $value = $option[1];
122
                break;
123
            }
124
            if ($option[0] == 'php:') {
125
                $value = $this->phpFunction($value, $option[1]);
126
                continue;
127
            }
128
            if ($option[0] == 'self:') {
129
                $value = $this->renderSubTemplate($value, $data, $option[1]);
130
                continue;
131
            }
132
            if ($isObject) {
133 View Code Duplication
                if ($option[0] == 'data::') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134
                    $option = [get_class($data), $option[1]];
135
                } elseif ($option[0] == 'data:') {
136
                    $option = [$data, $option[1]];
137
                }
138
                $option = $this->dataMethod($option, $data);
139
            }
140
            if ($option[0] == 'user:') {
141
                $option = $option[1];
142
            }
143
            if (is_callable($option)) {
144
                $value = $option($value, $field, $data);
145
            }
146
        }
147
        return $value;
148
    }
149
150
    /**
151
     * Renders a sub-template.
152
     *
153
     * When the ? parameter is NOT set, we go a level deeper,
154
     * otherwise we stay on the same level and we pass $data
155
     * instead of $value to render or each.
156
     *
157
     * @param string       $value  Field value
158
     * @param array|object $data   Data array or object
159
     * @param array        $option A single option
160
     *
161
     * @return string Field value
162
     */
163
    protected function renderSubTemplate($value, $data, $option)
164
    {
165
        if ($option[0] == 'each') {
166
            $sep = (isset($option[1][1]) ? $option[1][1] : '');
167
            if ($sep != '') {
168
                $sep = str_replace(
169
                    array_keys($this->escapeChars),
170
                    array_values($this->escapeChars),
171
                    $sep
172
                );
173
            }
174
            return $this->each($option[1][0], $value, $sep);
175
        }
176
        $value = (
177
            (isset($option[1][1]) && trim($option[1][1]) == '?')
178
                ? $data
179
                : $value
180
        );
181
        return $this->render($option[1][0], $value);
0 ignored issues
show
Bug introduced by
It seems like $value defined by isset($option[1][1]) && ...== '?' ? $data : $value on line 177 can also be of type string; however, AirTemplate\BaseEngine::render() does only seem to accept array|object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
182
    }
183
184
    /**
185
     * get the value for a field or an empty string.
186
     *
187
     * @param string       $field    Field name
188
     * @param array|object $data     Data array or object
189
     * @param array|bool   A datapath array or false
190
     * @param bool         $isObject true if $data is an object
191
     *
192
     * @return mixed Field value or empty string
193
     */
194
    private static function getFieldValue($field, $data, $datapath, $isObject)
1 ignored issue
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
195
    {
196
        if ($isObject) {
197
            if ($datapath === false && isset($data->$field)) {
198
                return $data->$field;
199
            }
200
            if ($datapath !== false) {
201
                return self::queryObject($datapath, $data);
1 ignored issue
show
Bug introduced by
It seems like $data defined by parameter $data on line 194 can also be of type array; however, AirTemplate\Engine::queryObject() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
202
            }
203
            return '';
204
        }
205
        if ($datapath === false && isset($data[$field])) {
206
            return $data[$field];
207
        }
208
        if ($datapath !== false) {
209
            return self::queryArray($datapath, $data);
1 ignored issue
show
Bug introduced by
It seems like $data defined by parameter $data on line 194 can also be of type object; however, AirTemplate\Engine::queryArray() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
210
        }
211
        return '';
212
    }
213
214
    /**
215
     * Lookup a value in the data array.
216
     *
217
     * @param array $keys An array of access keys
218
     * @param array $data Data array
219
     *
220
     * @return mixed Field value or empty string
221
     */
222
    private static function queryArray(array $keys, array $data)
223
    {
224
        $result = $data;
225
        foreach ($keys as $key) {
226
            if (!isset($result[$key])) {
227
                return '';
228
            }
229
            $result = &$result[$key];
230
        }
231
        return $result;
232
    }
233
234
    /**
235
     * Lookup a value in the data object.
236
     *
237
     * @param array  $keys An array of access keys
238
     * @param object $data Data object
239
     *
240
     * @return mixed Field value or empty string
241
     */
242
    private static function queryObject(array $keys, $data)
243
    {
244
        $result_a = $data;
245
        $result_o = $data;
246
        foreach ($keys as $key) {
247
            if ($key[0] == '@') {
248
                $key = substr($key, 1);
249
                if (isset($key) && is_a($result_o, 'SimpleXMLElement')) {
250
                    return self::queryObjectAttr($key, $result_o);
251
                }
252
            }
253
            $result_a = (array) $result_a;
254
            if (!isset($result_o->$key) && !isset($result_a[$key])) {
255
                return '';
256
            }
257
            $result_a = &$result_a[$key];
258
            $result_o = isset($result_o->$key) ? $result_o->$key : null;
259
        }
260
        return !is_null($result_a)
261
            ? $result_a
262
            : (!is_null($result_o) ? $result_o : '');
263
    }
264
265
    /**
266
     * Lookup an attribute value in the data element.
267
     *
268
     * @param string $key     Property name
269
     * @param object $element Object property
270
     *
271
     * @return mixed Attribute value or empty string
272
     */
273
    private static function queryObjectAttr($key, $element)
274
    {
275
        $attr = $element->attributes();
276
        if (count($attr) > 0 && isset($attr[$key])) {
277
            return (string) $attr[$key];
278
        }
279
        return '';
280
    }
281
}
282