PropertySpecBuilder::build()   B
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 12
cts 12
cp 1
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 13
nc 5
nop 1
crap 4
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of the pinepain/js-sandbox PHP library.
5
 *
6
 * Copyright (c) 2016-2017 Bogdan Padalko <[email protected]>
7
 *
8
 * Licensed under the MIT license: http://opensource.org/licenses/MIT
9
 *
10
 * For the full copyright and license information, please view the
11
 * LICENSE file that was distributed with this source or visit
12
 * http://opensource.org/licenses/MIT
13
 */
14
15
16
namespace Pinepain\JsSandbox\Specs\Builder;
17
18
19
use Pinepain\JsSandbox\Extractors\ExtractorDefinitionBuilderException;
20
use Pinepain\JsSandbox\Extractors\ExtractorDefinitionBuilderInterface;
21
use Pinepain\JsSandbox\Specs\Builder\Exceptions\PropertySpecBuilderException;
22
use Pinepain\JsSandbox\Specs\PropertySpec;
23
use Pinepain\JsSandbox\Specs\PropertySpecInterface;
24
use function strlen;
25
26
27
class PropertySpecBuilder implements PropertySpecBuilderInterface
28
{
29
    /**
30
     * @var ExtractorDefinitionBuilderInterface
31
     */
32
    private $builder;
33
34 9
    public function __construct(ExtractorDefinitionBuilderInterface $builder)
35
    {
36 9
        $this->builder = $builder;
37 9
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42 9
    public function build(string $definition): PropertySpecInterface
43
    {
44 9
        $definition = trim($definition);
45
46 9
        if (!$definition) {
47 1
            throw new PropertySpecBuilderException('Definition must be non-empty string');
48
        }
49
50 8
        $proto = new PropertySpecPrototype();
51
52 8
        if ($this->isSpecReadonly($definition)) {
53 1
            $definition = trim(substr($definition, strlen('readonly')));
54
55 1
            $proto->readonly = true;
56
        }
57
58
        try {
59 8
            $this->getSpecMethodsOrType($proto, $definition);
60 4
        } catch (ExtractorDefinitionBuilderException $e) {
61 2
            throw new PropertySpecBuilderException("Failed to build definition from '{$definition}': " . $e->getMessage());
62
        }
63
64 4
        return new PropertySpec($proto->readonly, $proto->definition, $proto->getter, $proto->setter);
65
    }
66
67
    /**
68
     * @param string $definition
69
     *
70
     * @return bool
71
     */
72 8
    protected function isSpecReadonly(string $definition): bool
73
    {
74 8
        return preg_match('/^readonly\b\s*/i', $definition) > 0;
75
    }
76
77
    /**
78
     * @param PropertySpecPrototype $proto
79
     * @param string                $definition
80
     *
81
     * @throws PropertySpecBuilderException
82
     */
83 8
    protected function getSpecMethodsOrType(PropertySpecPrototype $proto, string $definition)
84
    {
85 8
        if (preg_match("/^(?:get\:\s*(?<getter>\w+)\(\))(?:\s+set\:\s*(?<setter>\w+)\(\s*(?<type>.*)\s*\))?$/", $definition, $matches)) {
86
87 4
            $proto->getter = $matches['getter'];
88
89 4
            if (isset($matches['setter'])) {
90 3
                $proto->setter = $matches['setter'];
91
92 3
                if (!$matches['type']) {
93 1
                    throw new PropertySpecBuilderException("Setter type is missed from definition: '{$definition}'");
94
                }
95 2
                $proto->definition = $this->builder->build($matches['type']);
96
            } else {
97 1
                $proto->readonly = true;
98
            }
99
100 2
            return;
101
        }
102
103 4
        if (preg_match('/^(?<type>([\w\-]*(?:\(.*\))?(?:\[\s*\])?)(?:\s*\|\s*(?-1))*)$/', $definition, $matches)) {
104 3
            $proto->definition = $this->builder->build($matches['type']);
105
106 2
            return;
107
        }
108
109 1
        throw new PropertySpecBuilderException("Unable to parse definition: '{$definition}'");
110
    }
111
}
112