Passed
Push — main ( c31469...6fabbe )
by Johny
02:46
created

DefinitionContainer::has()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DummyGenerator\Container;
6
7
use DummyGenerator\Definitions\Calculator\EanCalculatorInterface;
8
use DummyGenerator\Definitions\Calculator\IbanCalculatorInterface;
9
use DummyGenerator\Definitions\Calculator\IsbnCalculatorInterface;
10
use DummyGenerator\Definitions\Calculator\LuhnCalculatorInterface;
11
use DummyGenerator\Definitions\DefinitionInterface;
12
use DummyGenerator\Definitions\Extension\Awareness\EanCalculatorAwareExtensionInterface;
13
use DummyGenerator\Definitions\Extension\Awareness\IbanCalculatorAwareExtensionInterface;
14
use DummyGenerator\Definitions\Extension\Awareness\IsbnCalculatorAwareExtensionInterface;
15
use DummyGenerator\Definitions\Extension\Awareness\LuhnCalculatorAwareExtensionInterface;
16
use DummyGenerator\Definitions\Extension\Awareness\RandomizerAwareExtensionInterface;
17
use DummyGenerator\Definitions\Extension\Awareness\ReplacerAwareExtensionInterface;
18
use DummyGenerator\Definitions\Randomizer\RandomizerInterface;
19
use DummyGenerator\Definitions\Replacer\RandomizerAwareReplacerInterface;
20
use DummyGenerator\Definitions\Replacer\ReplacerInterface;
21
use DummyGenerator\Definitions\Transliterator\TransliteratorAwareReplacerInterface;
22
use DummyGenerator\Definitions\Transliterator\TransliteratorInterface;
23
24
final class DefinitionContainer implements DefinitionContainerInterface
25
{
26
    /**
27
     * @var array<callable(): DefinitionInterface|DefinitionInterface|class-string<DefinitionInterface>> $definitions
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<callable(): Defini...g<DefinitionInterface>> at position 2 could not be parsed: Expected '>' at position 2, but found 'callable'.
Loading history...
28
     */
29
    private array $definitions;
30
31
    /**
32
     * @var array<string, DefinitionInterface>
33
     */
34
    private array $services = [];
35
36
    /**
37
     * Create a container object with a set of definitions.
38
     * The array value MUST produce an object that implements DefinitionInterface.
39
     *
40
     * @param array<string, callable(): DefinitionInterface|class-string<DefinitionInterface>> $definitions
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, callable()...g<DefinitionInterface>> at position 4 could not be parsed: Expected '>' at position 4, but found 'callable'.
Loading history...
41
     */
42 162
    public function __construct(array $definitions = [])
43
    {
44 162
        $this->definitions = $definitions;
45
    }
46
47
    /**
48
     * Retrieve a definition from the container.
49
     *
50
     * @throws \InvalidArgumentException
51
     * @throws \RuntimeException
52
     * @throws ContainerException
53
     * @throws NotInContainerException
54
     */
55 147
    public function get(string $id): DefinitionInterface
56
    {
57 147
        if (array_key_exists($id, $this->services)) {
58 65
            return $this->services[$id];
59
        }
60
61 147
        if (!$this->has($id)) {
62 1
            throw new NotInContainerException(sprintf(
63 1
                'There is not service with id "%s" in the container.',
64 1
                $id
65 1
            ));
66
        }
67
68 146
        $definition = $this->definitions[$id];
69
70 146
        return $this->services[$id] = $this->getService($id, $definition);
71
    }
72
73
    /**
74
     * Check if the container contains a given identifier.
75
     */
76 153
    public function has(string $id): bool
77
    {
78 153
        return array_key_exists($id, $this->definitions);
79
    }
80
81
    /**
82
     * Add new definition
83
     *
84
     * @param callable(): DefinitionInterface|DefinitionInterface|class-string<DefinitionInterface> $value
85
     * @param string $name
86
     */
87 149
    public function add(string $name, callable|DefinitionInterface|string $value): void
88
    {
89 149
        $this->definitions[$name] = $value;
90
    }
91
92
    /**
93
     * Find proper Extension for given method
94
     */
95 147
    public function findProcessor(string $name): null|DefinitionInterface
96
    {
97 147
        foreach ($this->definitions as $id => $definition) {
98 147
            $service = $this->getService($id, $definition);
99
100 147
            if (method_exists($service, $name)) {
101 147
                return $service;
102
            }
103
        }
104
105 2
        return null;
106
    }
107
108
    /**
109
     * Get the service from a definition.
110
     *
111
     * @param callable(): DefinitionInterface|DefinitionInterface|class-string<DefinitionInterface> $definition
112
     */
113 153
    private function getService(string $id, callable|object|string $definition): DefinitionInterface
114
    {
115 153
        if (is_callable($definition)) {
116
            try {
117 4
                return $this->handleAwareness($definition());
118 1
            } catch (\Throwable $e) {
119 1
                throw new ContainerException(
120 1
                    sprintf('Error while invoking callable for "%s"', $id),
121 1
                    0,
122 1
                    $e
123 1
                );
124
            }
125 149
        } elseif ($definition instanceof DefinitionInterface) {
126 12
            return $this->handleAwareness($definition);
127 144
        } elseif (is_string($definition)) {
128 144
            if (class_exists($definition)) {
129
                try {
130 143
                    $class = new $definition();
131
132 142
                    if ($class instanceof DefinitionInterface) {
133 141
                        return $this->handleAwareness($class);
134
                    }
135
136
                    // @phpstan-ignore-next-line
137 1
                    throw new ContainerException(sprintf('Class for "%s" is not implementing DefinitionInterface', $id));
138 2
                } catch (\Throwable $e) {
139 2
                    throw new ContainerException(sprintf('Could not instantiate class for "%s"', $id), 0, $e);
140
                }
141
            }
142
143 1
            throw new ContainerException(sprintf(
144 1
                'Could not instantiate class for "%s". Class was not found.',
145 1
                $id
146 1
            ));
147
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return DummyGenerator\Definitions\DefinitionInterface. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
148
    }
149
150 149
    private function handleAwareness(DefinitionInterface $extension): DefinitionInterface
151
    {
152 149
        if ($extension instanceof TransliteratorAwareReplacerInterface) {
153
            /** @var TransliteratorInterface $transliterator */
154 62
            $transliterator = $this->get(TransliteratorInterface::class);
155 62
            $extension = $extension->withTransliterator($transliterator);
156
        }
157
158 149
        if ($extension instanceof RandomizerAwareReplacerInterface) {
159
            /** @var RandomizerInterface $randomizer */
160 62
            $randomizer = $this->get(RandomizerInterface::class);
161 62
            $extension = $extension->withRandomizer($randomizer);
162
        }
163
164 149
        if ($extension instanceof RandomizerAwareExtensionInterface) {
165
            /** @var RandomizerInterface $randomizer */
166 140
            $randomizer = $this->get(RandomizerInterface::class);
167 140
            $extension = $extension->withRandomizer($randomizer);
168
        }
169
170 149
        if ($extension instanceof ReplacerAwareExtensionInterface) {
171
            /** @var ReplacerInterface $replacer */
172 54
            $replacer = $this->get(ReplacerInterface::class);
173 54
            $extension = $extension->withReplacer($replacer);
174
        }
175
176 149
        if ($extension instanceof EanCalculatorAwareExtensionInterface) {
177
            /** @var EanCalculatorInterface $calculator */
178 5
            $calculator = $this->get(EanCalculatorInterface::class);
179 5
            $extension = $extension->withEanCalculator($calculator);
180
        }
181
182 149
        if ($extension instanceof IbanCalculatorAwareExtensionInterface) {
183
            /** @var IbanCalculatorInterface $calculator */
184 9
            $calculator = $this->get(IbanCalculatorInterface::class);
185 9
            $extension = $extension->withIbanCalculator($calculator);
186
        }
187
188 149
        if ($extension instanceof IsbnCalculatorAwareExtensionInterface) {
189
            /** @var IsbnCalculatorInterface $calculator */
190 5
            $calculator = $this->get(IsbnCalculatorInterface::class);
191 5
            $extension = $extension->withIsbnCalculator($calculator);
192
        }
193
194 149
        if ($extension instanceof LuhnCalculatorAwareExtensionInterface) {
195
            /** @var LuhnCalculatorInterface $calculator */
196 12
            $calculator = $this->get(LuhnCalculatorInterface::class);
197 12
            $extension = $extension->withLuhnCalculator($calculator);
198
        }
199
200 149
        return $extension;
201
    }
202
}
203