• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / cache-warmup / 13524687764

25 Feb 2025 03:21PM UTC coverage: 88.771% (-1.1%) from 89.845%
13524687764

Pull #470

github

web-flow
Merge 76e3bf262 into 06ad72b10
Pull Request #470: [FEATURE] Use custom slim container implementation for DI

54 of 81 new or added lines in 9 files covered. (66.67%)

1763 of 1986 relevant lines covered (88.77%)

10.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.87
/src/DependencyInjection/Container.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/cache-warmup".
7
 *
8
 * Copyright (C) 2020-2025 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\CacheWarmup\DependencyInjection;
25

26
use EliasHaeussler\CacheWarmup\Exception;
27
use ReflectionClass;
28
use ReflectionException;
29
use ReflectionNamedType;
30
use ReflectionParameter;
31

32
use function class_exists;
33
use function in_array;
34

35
/**
36
 * Container.
37
 *
38
 * @author Elias Häußler <elias@haeussler.dev>
39
 * @license GPL-3.0-or-later
40
 *
41
 * @internal
42
 */
43
final class Container
44
{
45
    /**
46
     * @var array<class-string, object>
47
     */
48
    private array $serviceBag = [];
49

50
    /**
51
     * @var array<class-string, true>
52
     */
53
    private array $currentlyBuilding = [];
54

55
    /**
56
     * @template T of object
57
     *
58
     * @param class-string<T> $className
59
     *
60
     * @return T
61
     *
62
     * @throws Exception\ClassCannotBeReflected
63
     * @throws Exception\ClassConstructorIsInaccessible
64
     * @throws Exception\ClassDoesNotExist
65
     * @throws Exception\ParameterCannotBeAutowired
66
     * @throws Exception\RecursionInServiceCreation
67
     */
68
    public function get(string $className): object
13✔
69
    {
70
        if (isset($this->serviceBag[$className])) {
13✔
71
            /* @phpstan-ignore return.type */
72
            return $this->serviceBag[$className];
5✔
73
        }
74

75
        return $this->serviceBag[$className] = $this->constructNewService($className);
11✔
76
    }
77

78
    /**
79
     * @param class-string $className
80
     */
81
    public function has(string $className): bool
5✔
82
    {
83
        try {
84
            $this->get($className);
5✔
85
        } catch (Exception\ClassCannotBeReflected|Exception\ClassConstructorIsInaccessible|Exception\ClassDoesNotExist|Exception\ParameterCannotBeAutowired) {
4✔
86
            return false;
3✔
87
        }
88

89
        return true;
2✔
90
    }
91

92
    /**
93
     * @template T of object
94
     *
95
     * @param class-string<T> $className
96
     * @param T               $service
97
     */
98
    public function set(string $className, object $service): self
4✔
99
    {
100
        if (!isset($this->serviceBag[$className])) {
4✔
101
            $this->serviceBag[$className] = $service;
4✔
102
        }
103

104
        if ($className !== $service::class) {
4✔
105
            $this->serviceBag[$service::class] = $service;
2✔
106
        }
107

108
        return $this;
4✔
109
    }
110

111
    /**
112
     * @template T of object
113
     *
114
     * @param class-string<T> $className
115
     *
116
     * @return T
117
     *
118
     * @throws Exception\ClassCannotBeReflected
119
     * @throws Exception\ClassConstructorIsInaccessible
120
     * @throws Exception\ClassDoesNotExist
121
     * @throws Exception\ParameterCannotBeAutowired
122
     * @throws Exception\RecursionInServiceCreation
123
     */
124
    private function constructNewService(string $className): object
11✔
125
    {
126
        if (isset($this->currentlyBuilding[$className])) {
11✔
127
            throw new Exception\RecursionInServiceCreation($className);
1✔
128
        }
129

130
        if (!class_exists($className)) {
11✔
131
            throw new Exception\ClassDoesNotExist($className);
3✔
132
        }
133

134
        // Flag to avoid recursion
135
        $this->currentlyBuilding[$className] = true;
10✔
136

137
        try {
138
            $reflection = new ReflectionClass($className);
10✔
139
            $constructor = $reflection->getConstructor();
10✔
140

141
            // Return empty instance if no constructor exists
142
            if (null === $constructor) {
10✔
143
                return $reflection->newInstance();
2✔
144
            }
145

146
            // Throw exception on private constructor
147
            if (!$constructor->isPublic()) {
8✔
148
                throw new Exception\ClassConstructorIsInaccessible($className);
1✔
149
            }
150

151
            // Autowire constructor parameters
152
            $parameters = [];
7✔
153
            foreach ($constructor->getParameters() as $parameter) {
7✔
154
                $parameters[] = $this->autowireParameter($parameter, $className);
7✔
155
            }
156

157
            return $reflection->newInstanceArgs($parameters);
4✔
158
        } catch (ReflectionException $exception) {
4✔
NEW
159
            throw new Exception\ClassCannotBeReflected($className, $exception);
×
160
        } finally {
161
            // Free from recursion check
162
            unset($this->currentlyBuilding[$className]);
10✔
163
        }
164
    }
165

166
    /**
167
     * @param class-string $className
168
     *
169
     * @throws Exception\ClassCannotBeReflected
170
     * @throws Exception\ClassConstructorIsInaccessible
171
     * @throws Exception\ClassDoesNotExist
172
     * @throws Exception\ParameterCannotBeAutowired
173
     * @throws Exception\RecursionInServiceCreation
174
     * @throws ReflectionException
175
     */
176
    private function autowireParameter(ReflectionParameter $parameter, string $className): mixed
7✔
177
    {
178
        $type = $parameter->getType();
7✔
179

180
        if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
7✔
181
            $name = $type->getName();
4✔
182

183
            // Resolve self-references
184
            if (in_array($name, ['self', 'static'], true)) {
4✔
185
                $name = $className;
1✔
186
            }
187

188
            /* @phpstan-ignore argument.type */
189
            if ($this->has($name)) {
4✔
190
                /* @phpstan-ignore argument.type, argument.templateType */
191
                return $this->get($type->getName());
1✔
192
            }
193
        }
194

195
        if ($parameter->isOptional()) {
5✔
196
            return $parameter->getDefaultValue();
2✔
197
        }
198

199
        if ($parameter->allowsNull()) {
3✔
200
            return null;
1✔
201
        }
202

203
        throw new Exception\ParameterCannotBeAutowired($className, $parameter->getName());
2✔
204
    }
205
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc