Translation   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 402
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 63
lcom 1
cbo 0
dl 0
loc 402
rs 3.36
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
B fetch() 0 43 10
A merge() 0 5 2
A unsetKeys() 0 9 3
A getVarTranslation() 0 4 1
A makeExtraVarKey() 0 4 1
A makeVar() 0 4 1
A makeVarKeys() 0 12 3
A getVarPregPattern() 0 8 2
A containsVar() 0 4 1
A removeVars() 0 7 3
B translateString() 0 36 10
B translate() 0 26 6
A mergeValues() 0 8 2
A setDefaults() 0 16 5
A setDefault() 0 6 1
A fetchVars() 0 7 2
B pushVars() 0 38 9
A resolveBasePathParts() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Translation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Translation, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Webino (http://webino.sk)
4
 *
5
 * @link        https://github.com/webino/WebinoDraw for the canonical source repository
6
 * @copyright   Copyright (c) 2012-2017 Webino, s. r. o. (http://webino.sk)
7
 * @author      Peter Bačinský <[email protected]>
8
 * @license     BSD-3-Clause
9
 */
10
11
namespace WebinoDraw\VarTranslator;
12
13
use ArrayAccess;
14
use ArrayObject;
15
use WebinoDraw\Stdlib\ArrayFetchInterface;
16
use WebinoDraw\Stdlib\ArrayMergeInterface;
17
18
/**
19
 * Class Translation
20
 */
21
class Translation extends ArrayObject implements
22
    ArrayFetchInterface,
23
    ArrayMergeInterface
24
{
25
    /**
26
     * Pattern of a variable
27
     */
28
    const VAR_PATTERN = '{$%s}';
29
30
    /**
31
     * Prefix of the extra variables to avoid conflicts
32
     */
33
    const EXTRA_VAR_PREFIX = '_';
34
35
    /**
36
     * Pattern to match variables
37
     *
38
     * @var string
39
     */
40
    protected $varPregPattern;
41
42
    /**
43
     * Return value in depth from multidimensional array
44
     *
45
     * @param string $basePath Something like: value.in.the.depth
46
     * @return mixed Result value
47
     */
48
    public function fetch($basePath)
49
    {
50
        $value = $this->getArrayCopy();
51
        foreach ($this->resolveBasePathParts($basePath) as $key) {
52
            // magic keys
53
            if ('_first' === $key) {
54
                reset($value);
55
                $key = key($value);
56
57
            } elseif ('_last' === $key) {
58
                end($value);
59
                $key = key($value);
60
            }
61
62
            // unescape
63
            $key = str_replace('\.', '.', $key);
64
65
            // undefined
66
            if (!array_key_exists($key, $value)) {
67
                $value = null;
68
                break;
69
            }
70
71
            $value = &$value[$key];
72
73
            // array only
74
            if (!is_array($value) && !($value instanceof ArrayObject)) {
75
                if (is_object($value)) {
76
                    if (method_exists($value, 'toArray')) {
77
                        $value = $value->toArray();
78
                    } elseif (method_exists($value, 'getArrayCopy')) {
79
                        $value = $value->getArrayCopy();
80
                    } else {
81
                        break;
82
                    }
83
                } else {
84
                    break;
85
                }
86
            }
87
        }
88
89
        return $value;
90
    }
91
92
    /**
93
     * @param array $array
94
     * @return $this
95
     */
96
    public function merge(array $array)
97
    {
98
        empty($array) or $this->exchangeArray(array_replace_recursive($this->getArrayCopy(), $array));
99
        return $this;
100
    }
101
102
    /**
103
     * @param array $keys
104
     * @return $this
105
     */
106
    public function unsetKeys(array $keys)
107
    {
108
        foreach ($keys as $key) {
109
            $this->offsetExists($key)
110
                and $this->offsetUnset($key);
111
        }
112
113
        return $this;
114
    }
115
116
    /**
117
     * @return $this
118
     */
119
    public function getVarTranslation()
120
    {
121
        return $this->makeVarKeys($this);
122
    }
123
124
    /**
125
     * @param string $varKey
126
     * @return string
127
     */
128
    public function makeExtraVarKey($varKey)
129
    {
130
        return self::EXTRA_VAR_PREFIX . $varKey;
131
    }
132
133
    /**
134
     * Transform varName into {$varName}.
135
     *
136
     * @param string $key
137
     * @return string
138
     */
139
    public function makeVar($key)
140
    {
141
        return sprintf(self::VAR_PATTERN, $key);
142
    }
143
144
    /**
145
     * Transform subject keys to {$var} like
146
     *
147
     * @param ArrayAccess $subject
148
     * @return ArrayAccess|self
149
     */
150
    public function makeVarKeys(ArrayAccess $subject = null)
151
    {
152
        $_subject     = (null !== $subject) ? $_subject = $subject : $_subject = $this;
153
        $subjectClone = clone $_subject;
154
155
        foreach ($_subject as $key => $value) {
156
            $subjectClone->offsetSet($this->makeVar($key), $value);
157
            $subjectClone->offsetUnset($key);
158
        }
159
160
        return $subjectClone;
161
    }
162
163
    /**
164
     * Match {$var} regular pattern
165
     *
166
     * @return string
167
     */
168
    public function getVarPregPattern()
169
    {
170
        if (null === $this->varPregPattern) {
171
            $pattern = str_replace('%s', '[^\}]+', preg_quote(self::VAR_PATTERN));
172
            $this->varPregPattern = '~' . $pattern . '~';
173
        }
174
        return $this->varPregPattern;
175
    }
176
177
    /**
178
     * Return true if {$var} is in the string
179
     *
180
     * @param string $string
181
     * @return bool
182
     */
183
    public function containsVar($string)
184
    {
185
        return (bool) preg_match($this->getVarPregPattern(), $string);
186
    }
187
188
    /**
189
     * Remove vars from string
190
     *
191
     * @param string $string
192
     * @return bool
193
     */
194
    public function removeVars($string)
195
    {
196
        if (!is_string($string) || !$this->containsVar($string)) {
197
            return $string;
198
        }
199
        return trim(preg_replace($this->getVarPregPattern(), '', $string));
200
    }
201
202
    /**
203
     * Replace {$var} in string with data from translation
204
     *
205
     * If $str = {$var} and translation has item with key {$var} = array,
206
     * immediately return this array.
207
     *
208
     * @param string $str
209
     * @return mixed
210
     */
211
    public function translateString($str)
212
    {
213
        if (!is_string($str)) {
214
            return $str;
215
        }
216
217
        $match = [];
218
        preg_match_all($this->getVarPregPattern(), $str, $match);
219
220
        if (empty($match[0])) {
221
            return $str;
222
        }
223
224
        foreach ($match[0] as $key) {
225
            if (!$this->offsetExists($key)) {
226
                continue;
227
            }
228
229
            $value = $this->offsetGet($key);
230
            if ($key === $str
231
                && (is_object($value)
232
                    || is_array($value)
233
                    || is_int($value)
234
                    || is_float($value))
235
            ) {
236
                // return early for non-strings
237
                // this is useful to pass subjects
238
                // to functions, helpers and filters
239
                return $value;
240
            }
241
242
            $str = str_replace($key, $value, $str);
243
        }
244
245
        return $str;
246
    }
247
248
    /**
249
     * Replace {$var} in $subject with data from $translation
250
     *
251
     * @param string|array $subject
252
     * @return $this
253
     */
254
    public function translate(&$subject)
255
    {
256
        if (empty($subject)) {
257
            return $this;
258
        }
259
260
        if (is_string($subject)) {
261
            $subject = $this->translateString($subject);
262
            return $this;
263
        }
264
265
        if (!is_array($subject)) {
266
            return $this;
267
        }
268
269
        foreach ($subject as &$param) {
270
            if (is_array($param)) {
271
                $this->translate($param);
272
                continue;
273
            }
274
275
            $param = $this->translateString($param);
276
        }
277
278
        return $this;
279
    }
280
281
    /**
282
     * @param array $values
283
     * @return $this
284
     */
285
    public function mergeValues(array $values)
286
    {
287
        foreach ($values as $key => $value) {
288
            $this->getVarTranslation()->translate($value);
289
            $this->offsetSet($key, $value);
290
        }
291
        return $this;
292
    }
293
294
    /**
295
     * Set translated defaults into translation
296
     *
297
     * @param array $defaults
298
     * @return array
299
     */
300
    public function setDefaults(array $defaults)
301
    {
302
        foreach ($defaults as $key => $defaultValue) {
303
            if (!$this->offsetExists($key)) {
304
                $this->setDefault($key, $defaultValue);
305
                continue;
306
            }
307
308
            $value = $this->offsetGet($key);
309
            if (empty($value) && !is_numeric($value)) {
310
                $this->setDefault($key, $defaultValue);
311
            }
312
        }
313
314
        return $this;
315
    }
316
317
    /**
318
     * @param mixed $key
319
     * @param mixed $value
320
     * @return $this
321
     */
322
    protected function setDefault($key, $value)
323
    {
324
        $this->getVarTranslation()->translate($value);
325
        $this->offsetSet($key, $value);
326
        return $this;
327
    }
328
329
    /**
330
     * Fetch custom variables into translation
331
     *
332
     * example of properties:
333
     * <pre>
334
     * $translation = [
335
     *     'value' => [
336
     *         'in' => [
337
     *             'the' => [
338
     *                 'depth' => 'valueInTheDepth',
339
     *             ],
340
     *         ],
341
     *     ],
342
     * ];
343
     * </pre>
344
     *
345
     * example of options:
346
     *
347
     * <pre>
348
     * $options = [
349
     *     'customVar' => 'value.in.the.depth',
350
     * ];
351
     * </pre>
352
     *
353
     * @param array $options
354
     * @return $this
355
     */
356
    public function fetchVars(array $options)
357
    {
358
        foreach ($options as $key => $basePath) {
359
            $this->offsetSet($key, $this->fetch($this->getVarTranslation()->translateString($basePath)));
360
        }
361
        return $this;
362
    }
363
364
    /**
365
     * Push variables into translation
366
     *
367
     * @param array $options
368
     * @param self $translation
369
     * @return $this
370
     */
371
    public function pushVars(array $options, self $translation = null)
372
    {
373
        $translation and $translation->getVarTranslation()->translate($options);
374
375
        foreach ($options as $basePath => $value) {
376
            $key      = 0;
377
            $index    = null;
378
            $create   = false;
379
            $subValue = $this;
380
381
            foreach ($this->resolveBasePathParts($basePath) as $key) {
382
                if (null === $index) {
383
                    $index = $key;
384
                }
385
386
                if ($create) {
387
                    $create   = false;
388
                    $subValue = new ArrayObject;
389
                }
390
391
                // undefined
392
                if (!array_key_exists($key, $subValue) || is_scalar($value)) {
393
                    // create or override scalar
394
                    $create = true;
395
                    continue;
396
                }
397
398
                $subValue = &$subValue[$key];
399
            }
400
401
            $subValue[$key] = $value;
402
            if ($this !== $subValue) {
403
                $this->offsetSet($index, $subValue);
404
            }
405
        }
406
407
        return $this;
408
    }
409
410
    /**
411
     * Explode path by dots and return those parts
412
     *
413
     * @param string $basePath
414
     * @return array
415
     */
416
    private function resolveBasePathParts($basePath)
417
    {
418
        $parts = [];
419
        preg_match_all('~[^\.]+\\\.[^\.]+|[^\.]+~', $basePath, $parts);
420
        return $parts[0];
421
    }
422
}
423