Test Failed
Push — master ( 08713d...9fa0b4 )
by Kirill
03:13
created

ValueTypeResolver::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\SDL\Compiler\Builder\Common;
11
12
use Railt\Reflection\Contracts\Definition\ScalarDefinition;
13
use Railt\Reflection\Contracts\Definition\TypeDefinition;
14
use Railt\Reflection\Contracts\Dictionary;
15
use Railt\SDL\Exception\TypeConflictException;
16
17
/**
18
 * The class serves to dynamically output a type from values
19
 */
20
class ValueTypeResolver
21
{
22
    /**
23
     * @var callable[]
24
     */
25
    private const DEFAULT_BREAKPOINTS = [
26
        'String'  => '\\is_string',
27
        'Boolean' => '\\is_bool',
28
        'Float'   => '\\is_float',
29
        'Int'     => '\\is_int',
30
    ];
31
32
    /**
33
     * @var Dictionary
34
     */
35
    private $dictionary;
36
37
    /**
38
     * @var callable[]
39
     */
40
    private $breakpoints = self::DEFAULT_BREAKPOINTS;
41
42
    /**
43
     * ValueTypeResolver constructor.
44
     * @param Dictionary $dictionary
45
     */
46
    public function __construct(Dictionary $dictionary)
47
    {
48
        $this->dictionary = $dictionary;
49
    }
50
51
    /**
52
     * @param string $type
53
     * @param callable $filter
54
     * @return ValueTypeResolver
55
     */
56
    public function breakpoint(string $type, callable $filter): self
57
    {
58
        $this->breakpoints[$type] = $filter;
59
60
        return $this;
61
    }
62
63
    /**
64
     * @param string $type
65
     * @return TypeDefinition
66
     * @throws \Railt\Reflection\Exception\TypeNotFoundException
67
     */
68
    private function load(string $type): TypeDefinition
69
    {
70
        return $this->dictionary->get($type);
71
    }
72
73
    /**
74
     * @param TypeDefinition $type
75
     * @param mixed $value
76
     * @param string $renderedValue
77
     * @return mixed
78
     * @throws TypeConflictException
79
     * @throws \Railt\Reflection\Exception\TypeNotFoundException
80
     */
81
    public function castTo(TypeDefinition $type, $value, string $renderedValue = null)
82
    {
83
        foreach ($this->resolveType($value) as $haystack) {
84
            if ($haystack->instanceOf($type)) {
85
                if ($type instanceof ScalarDefinition) {
86
                    return $type->parse($value);
0 ignored issues
show
Bug introduced by
The method parse() does not seem to exist on object<Railt\Reflection\...ition\ScalarDefinition>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
87
                }
88
89
                return $value;
90
            }
91
        }
92
93
        $error = 'Could not cast %s to %s';
94
        $error = \sprintf($error, $renderedValue, $type);
95
96
        throw new TypeConflictException($error);
97
    }
98
99
    /**
100
     * @param mixed $value
101
     * @return iterable|TypeDefinition[]
102
     * @throws \Railt\Reflection\Exception\TypeNotFoundException
103
     */
104
    public function resolveType($value): iterable
105
    {
106
        foreach ($this->breakpoints as $name => $filter) {
107
            if ($filter($value)) {
108
                $inheritance = $this->inheritedBy($this->load($name), $this->getFilter());
109
110
                foreach ($inheritance as $child) {
111
                    yield $child;
112
                }
113
            }
114
        }
115
116
        yield $this->load('Any');
117
    }
118
119
    /**
120
     * @return \Closure
121
     */
122
    private function getFilter(): \Closure
123
    {
124
        return function (TypeDefinition $resolved): bool {
125
            return $this->shouldBreak($resolved);
126
        };
127
    }
128
129
    /**
130
     * @param TypeDefinition $resolved
131
     * @return bool
132
     */
133
    private function shouldBreak(TypeDefinition $resolved): bool
134
    {
135
        return ! isset($this->breakpoints[$resolved->getName()]);
136
    }
137
138
    /**
139
     * @param TypeDefinition $type
140
     * @param \Closure $filter
141
     * @return \Generator|TypeDefinition[]
142
     */
143
    private function inheritedBy(TypeDefinition $type, \Closure $filter): \Traversable
144
    {
145
        yield $type;
146
147
        foreach ($type->inheritedBy() as $child) {
0 ignored issues
show
Bug introduced by
The method inheritedBy() does not seem to exist on object<Railt\Reflection\...inition\TypeDefinition>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
148
            if (! $filter($child)) {
149
                continue;
150
            }
151
152
            yield from $this->inheritedBy($child, $filter);
153
        }
154
    }
155
}
156