Issues (2)

src/FactoryInternalContainer.php (2 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory;
6
7
use LogicException;
8
use Psr\Container\ContainerInterface;
9
use Yiisoft\Definitions\ArrayDefinition;
10
use Yiisoft\Definitions\Contract\DefinitionInterface;
11
use Yiisoft\Definitions\Contract\ReferenceInterface;
12
use Yiisoft\Definitions\Exception\CircularReferenceException;
13
use Yiisoft\Definitions\Exception\InvalidConfigException;
14
use Yiisoft\Definitions\Exception\NotInstantiableClassException;
15
use Yiisoft\Definitions\Exception\NotInstantiableException;
16
use Yiisoft\Definitions\Helpers\Normalizer;
17
18
use function array_key_exists;
19
use function is_object;
20
use function is_string;
21
22
/**
23
 * Factory's primary container.
24
 *
25
 * @internal
26
 */
27
final class FactoryInternalContainer implements ContainerInterface
28
{
29
    /**
30
     * @var array<string, DefinitionInterface> Object created from definitions indexed by their types.
31
     */
32
    private array $definitionInstances = [];
33
34
    /**
35
     * @var array<string,1> Used to collect IDs instantiated during build to detect circular references.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,1> at position 4 could not be parsed: Unknown type name '1' at position 4 in array<string,1>.
Loading history...
36
     */
37
    private array $creatingIds = [];
38
39
    /**
40
     * @param ContainerInterface|null $container Container to use for resolving dependencies.
41
     * @param array<string, mixed> $definitions Definitions to create objects with.
42
     */
43 112
    public function __construct(
44
        private ?ContainerInterface $container,
45
        private array $definitions
46
    ) {
47 112
    }
48
49
    /**
50
     * @param array<string, mixed> $definitions Definitions to create objects with.
51
     */
52 1
    public function withDefinitions(array $definitions): self
53
    {
54 1
        $new = clone $this;
55 1
        $new->definitions = $definitions;
56 1
        $new->definitionInstances = [];
57 1
        $new->creatingIds = [];
58 1
        return $new;
59
    }
60
61
    /**
62
     * @inheritDoc
63
     *
64
     * @param string $id
65
     */
66 53
    public function get($id): mixed
67
    {
68 53
        if ($this->hasDefinition($id)) {
69 33
            return $this->build($id);
70
        }
71
72 23
        if ($this->container?->has($id)) {
73 16
            return $this->container->get($id);
0 ignored issues
show
The method get() does not exist on null. ( Ignorable by Annotation )

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

73
            return $this->container->/** @scrutinizer ignore-call */ get($id);

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...
74
        }
75
76 8
        throw new NotInstantiableClassException($id);
77
    }
78
79 7
    public function has($id): bool
80
    {
81 7
        return $this->hasDefinition($id) || $this->container?->has($id);
82
    }
83
84 99
    public function create(DefinitionInterface $definition): mixed
85
    {
86 99
        if ($definition instanceof ArrayDefinition) {
87 80
            $this->creatingIds[$definition->getClass()] = 1;
88
        }
89
90
        try {
91 99
            $result = $definition->resolve($this);
92 86
            return is_object($result) ? clone $result : $result;
93
        } finally {
94 99
            if ($definition instanceof ArrayDefinition) {
95 99
                unset($this->creatingIds[$definition->getClass()]);
96
            }
97
        }
98
    }
99
100
    /**
101
     * Get definition by identifier provided.
102
     *
103
     * @throws InvalidConfigException
104
     */
105 71
    public function getDefinition(string $id): DefinitionInterface
106
    {
107 71
        if (!isset($this->definitionInstances[$id])) {
108 71
            if (isset($this->definitions[$id])) {
109 70
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
110 13
                    return Normalizer::normalize($this->definitions[$id], $id);
111
                }
112
113
                if (
114 65
                    is_string($this->definitions[$id])
115 65
                    && $this->hasDefinition($this->definitions[$id])
116 65
                    && $this->definitions[$id] !== $this->definitions[$this->definitions[$id]]
117
                ) {
118 1
                    $this->definitionInstances[$id] = $this->getDefinition($this->definitions[$id]);
119
                } else {
120 65
                    $this->definitionInstances[$id] =
121 65
                        (is_string($this->definitions[$id]) && class_exists($this->definitions[$id]))
122 29
                            ? ArrayDefinition::fromPreparedData($this->definitions[$id])
123 65
                            : Normalizer::normalize($this->definitions[$id], $id);
124
                }
125
            } else {
126 1
                throw new LogicException(
127 1
                    sprintf('No definition found for "%s".', $id)
128 1
                );
129
            }
130
        }
131
132 64
        return $this->definitionInstances[$id];
133
    }
134
135
    /**
136
     * Check if there is a definition with a given identifier.
137
     *
138
     * @param string $id Identifier to look for.
139
     *
140
     * @return bool If there is a definition with a given identifier.
141
     */
142 105
    public function hasDefinition(string $id): bool
143
    {
144 105
        return array_key_exists($id, $this->definitions);
145
    }
146
147
    /**
148
     * @throws CircularReferenceException
149
     * @throws InvalidConfigException
150
     * @throws NotFoundException
151
     * @throws NotInstantiableException
152
     */
153 33
    private function build(string $id): mixed
154
    {
155 33
        if (isset($this->creatingIds[$id])) {
156 5
            throw new CircularReferenceException(
157 5
                sprintf(
158 5
                    'Circular reference to "%s" detected while creating: %s.',
159 5
                    $id,
160 5
                    implode(', ', array_keys($this->creatingIds))
161 5
                )
162 5
            );
163
        }
164
165 32
        $definition = $this->getDefinition($id);
166
167 32
        $this->creatingIds[$id] = 1;
168
        try {
169 32
            return $definition->resolve($this);
170
        } finally {
171 32
            unset($this->creatingIds[$id]);
172
        }
173
    }
174
}
175