Lexeme   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 418
Duplicated Lines 0 %

Test Coverage

Coverage 90.44%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 43
eloc 128
c 2
b 0
f 0
dl 0
loc 418
ccs 123
cts 136
cp 0.9044
rs 8.96

17 Methods

Rating   Name   Duplication   Size   Complexity  
A fragment() 0 7 1
A syntax2Array() 0 10 1
A syntax() 0 38 5
A closed() 0 5 1
A __construct() 0 5 1
A properties() 0 8 2
A get() 0 25 5
A property() 0 23 3
A addFolder() 0 5 1
A types() 0 9 2
A lexer() 0 8 2
A loader() 0 18 4
A apply() 0 24 4
A data() 0 10 2
A getLexemes() 0 11 2
A lexerApply() 0 21 3
A tryLoad() 0 19 4

How to fix   Complexity   

Complex Class

Complex classes like Lexeme 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.

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 Lexeme, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bavix\Flow;
4
5
use Bavix\Helpers\Arr;
6
use Bavix\Lexer\Lexer;
7
use Bavix\SDK\FileLoader;
8
9
/**
10
 * Class Lexeme
11
 *
12
 * @package Bavix\Flow
13
 */
14
class Lexeme
15
{
16
17
    /**
18
     * @var array
19
     */
20
    protected $types;
21
22
    /**
23
     * @var array
24
     */
25
    protected $default = [
26
        'types' => [
27
            'variable'
28
        ]
29
    ];
30
31
    /**
32
     * @var array
33
     */
34
    protected $props = [];
35
36
    /**
37
     * @var array
38
     */
39
    protected $data = [];
40
41
    /**
42
     * @var array
43
     */
44
    protected $closed = [];
45
46
    /**
47
     * @var array
48
     */
49
    protected $items = [];
50
51
    /**
52
     * @var string[]
53
     */
54
    protected $folders = [];
55
56
    /**
57
     * @var Lexer
58
     */
59
    protected $lexer;
60
61
    /**
62
     * @var Flow
63
     */
64
    protected $flow;
65
66
    /**
67
     * Lexeme constructor.
68
     *
69
     * @param Flow $flow
70
     */
71 13
    public function __construct(Flow $flow)
72
    {
73 13
        $this->types = Property::get('types');
74 13
        $this->addFolder(\dirname(__DIR__, 2) . '/lexemes');
75 13
        $this->flow = $flow;
76 13
    }
77
78
    /**
79
     * @param string $path
80
     *
81
     * @return self
82
     */
83 13
    public function addFolder(string $path): self
84
    {
85 13
        Arr::unShift($this->folders, $path);
86
87 13
        return $this;
88
    }
89
90
    /**
91
     * @return Lexer
92
     */
93 13
    protected function lexer(): Lexer
94
    {
95 13
        if (!$this->lexer)
96
        {
97 13
            $this->lexer = new Lexer();
98
        }
99
100 13
        return $this->lexer;
101
    }
102
103
    /**
104
     * @param $file
105
     *
106
     * @return FileLoader\DataInterface|null
107
     */
108 13
    protected function loader($file)
109
    {
110 13
        foreach ($this->folders as $folder)
111
        {
112 13
            foreach (FileLoader::extensions() as $ext)
113
            {
114
                try
115
                {
116 13
                    return FileLoader::load($folder . '/' . $file . '.' . $ext);
117
                }
118 8
                catch (\Throwable $throwable)
119 8
                {
120
                    // skip...
121
                }
122
            }
123
        }
124
125 8
        return null;
126
    }
127
128
    /**
129
     * @param array  $props
130
     * @param string $key
131
     *
132
     * @return array
133
     */
134 11
    public function property(array $props, string $key): array
135
    {
136 11
        $self = $this;
137 11
        $prop = &$props[$key];
138
139 11
        if (empty($this->props[$key]))
140
        {
141 11
            if (isset($prop['extends']))
142
            {
143
                $extends = Arr::map((array)$prop['extends'], function ($extend) use ($self, &$props) {
144
                    return $self->property($props, $extend);
145
                });
146
147
                $prop = \array_merge_recursive(
148
                    $prop,
149
                    ...$extends
150
                );
151
            }
152
153 11
            $this->props[$key] = $prop;
154
        }
155
156 11
        return $this->props[$key];
157
    }
158
159
    /**
160
     * @param array $props
161
     *
162
     * @return array
163
     */
164 13
    protected function properties(array $props): array
165
    {
166 13
        foreach ($props as $key => $prop)
167
        {
168 11
            $this->property($props, $key);
169
        }
170
171 13
        return $this->props;
172
    }
173
174
    /**
175
     * @param array $types
176
     *
177
     * @return array
178
     */
179 13
    protected function types(array $types): array
180
    {
181 13
        $results = [];
182 13
        foreach ($types as $type)
183
        {
184 13
            $results[] = $this->types[$type];
185
        }
186
187 13
        return $results;
188
    }
189
190
    /**
191
     * @param string $key
192
     *
193
     * @return string
194
     */
195 13
    protected function fragment(string $key): string
196
    {
197 13
        $property = $this->props[$key] ?? $this->default;
198
199 13
        return '(?<' . $key . '>(' .
200 13
            implode('|', $this->types($property['types'])) .
201 13
            '))';
202
    }
203
204
    /**
205
     * @param string $key
206
     * @param array  $syntax
207
     *
208
     * @return array
209
     */
210 13
    public function syntax2Array(string $key, array $syntax): array
211
    {
212 13
        $props  = $this->props[$key] ?? null;
213 13
        $closed = $this->closed[$key] ?? false;
214
215 13
        return Cache::get(__CLASS__ . $key, function () use ($syntax, $props, $closed) {
216
            return [
217 13
                'syntax' => $syntax,
218 13
                'props'  => $props,
219 13
                'closed' => $closed,
220
            ];
221 13
        });
222
    }
223
224 13
    protected function tryLoad(string $key)
225
    {
226 13
        if (empty($this->data[$key]))
227
        {
228 13
            $item = Cache::getItem(__CLASS__ . $key);
229
230 13
            if ($item && $item->isHit())
231
            {
232
                $_cache = $item->get();
233
234
                $this->data[$key]   = $_cache['syntax'];
235
                $this->props[$key]  = $_cache['props'];
236
                $this->closed[$key] = $_cache['closed'];
237
238
                return $this->data[$key];
239
            }
240
        }
241
242 13
        return null;
243
    }
244
245
    /**
246
     * @param string $key
247
     * @param array  $data
248
     *
249
     * @return array|mixed
250
     */
251 13
    protected function getLexemes(string $key, array $data)
252
    {
253 13
        $syntax = $this->tryLoad($key);
254
255 13
        if (empty($this->data[$key]))
256
        {
257 13
            $syntax = $this->syntax($key, $data);
258 13
            $this->syntax2Array($key, $syntax);
259
        }
260
261 13
        return $syntax;
262
    }
263
264
    /**
265
     * @param string $key
266
     * @param array  $data
267
     *
268
     * @return array
269
     */
270 13
    protected function syntax(string $key, array $data)
271
    {
272 13
        foreach ($data['directives'] ?? [] as $_key => $directive)
273
        {
274 6
            $this->data($_key, $directive ?: []);
275
        }
276
277 13
        $this->closed[$key] = $data['closed'] ?? false;
278 13
        $this->properties($data['properties'] ?? []);
279
280 13
        $syntax = [];
281
282 13
        foreach ($data['syntax'] ?? [] as $text)
283
        {
284 13
            $tokens = $this->lexer()->tokens($text);
0 ignored issues
show
Deprecated Code introduced by Babichev Maxim
The function Bavix\Lexer\Lexer::tokens() has been deprecated: use fragments ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

284
            $tokens = /** @scrutinizer ignore-deprecated */ $this->lexer()->tokens($text);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
285 13
            $vars   = $tokens[Lexer::PRINTER] ?? [];
286 13
            $code   = \str_replace(
287 13
                ['\\(', '\\)', ','],
288 13
                ['\\( ', ' \\)', ' ,'],
289 13
                $text
290
            );
291
292 13
            foreach ($vars as $var)
293
            {
294 13
                $code = \str_replace(
295 13
                    $var['code'],
296 13
                    $this->fragment($var['fragment']),
297 13
                    $code
298
                );
299
            }
300
301 13
            $syntax[] = [
302 13
                'vars'   => $vars,
303 13
                'regexp' => '~^' . $key . ' ' . $code . '$~ui'
304
            ];
305
        }
306
307 13
        return $syntax;
308
    }
309
310
    /**
311
     * @param string     $key
312
     * @param array|null $data
313
     *
314
     * @return array|bool|mixed
315
     */
316 13
    protected function get(string $key, array $data = null)
317
    {
318
        /**
319
         * @var $loader mixed
320
         */
321 13
        $loader = $this->loader($key) ?: $data;
322
323 13
        if (null === $loader)
324
        {
325 7
            return true;
326
        }
327
328 13
        $mixed = [];
329
330 13
        if ($loader)
331
        {
332 13
            $mixed = $loader;
333
334 13
            if (\is_object($mixed))
335
            {
336 13
                $mixed = $loader->asArray();
337
            }
338
        }
339
340 13
        return $this->getLexemes($key, $mixed);
341
    }
342
343
    /**
344
     * @param string $key
345
     *
346
     * @return bool
347
     */
348 13
    public function closed(string $key): bool
349
    {
350 13
        $this->data($key);
351
352 13
        return $this->closed[$key] ?? false;
353
    }
354
355
    /**
356
     * @param string $key
357
     * @param array  $data
358
     *
359
     * @return array|bool
360
     */
361 13
    public function data(string $key, array $data = null)
362
    {
363 13
        $this->tryLoad($key);
364
365 13
        if (!\array_key_exists($key, $this->data))
366
        {
367 13
            $this->data[$key] = $this->get($key, $data);
368
        }
369
370 13
        return $this->data[$key];
371
    }
372
373
    /**
374
     * @param string $value
375
     *
376
     * @return array
377
     */
378 13
    public function lexerApply(string $value): array
379
    {
380 13
        $name = __FUNCTION__ . $value;
381 13
        $item = Cache::getItem($name);
382
383 13
        if ($item && $item->isHit())
384
        {
385
            return $item->get();
386
        }
387
388 13
        $value  = '{{ ' . $value . ' }}';
389 13
        $tokens = $this->flow->lexer()->tokens($value);
0 ignored issues
show
Deprecated Code introduced by Babichev Maxim
The function Bavix\Lexer\Lexer::tokens() has been deprecated: use fragments ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

389
        $tokens = /** @scrutinizer ignore-deprecated */ $this->flow->lexer()->tokens($value);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
390 13
        $_lexer = \current($tokens[Lexer::PRINTER]);
391
392
        $store = [
393 13
            'lexer' => $_lexer,
394 13
            'code'  => $this->flow->build($_lexer)
395
        ];
396
397 13
        return Cache::get($name, function () use ($store) {
398 13
            return $store;
399 13
        });
400
    }
401
402
    /**
403
     * @param string $key
404
     * @param string $tpl
405
     *
406
     * @return array|null
407
     */
408 13
    public function apply(string $key, string $tpl)
409
    {
410 13
        $lexData = $this->data($key);
411 13
        $data    = null;
412
413 13
        if (true === $lexData)
414
        {
415
            return $data;
416
        }
417
418 13
        foreach ($lexData as $datum)
419
        {
420 13
            if (\preg_match($datum['regexp'], $tpl, $outs))
421
            {
422 13
                $data = Arr::filter($outs, function (...$args) {
423 13
                    return \is_string(\end($args));
424 13
                });
425
426 13
                $data = Arr::map($data, [$this, 'lexerApply']);
427 13
                break;
428
            }
429
        }
430
431 13
        return $data;
432
    }
433
434
}
435