Skip to content

Commit 1009341

Browse files
committed
Validate Container configuration references valid class instances
1 parent 26e9a7a commit 1009341

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

docs/best-practices/controllers.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,14 @@ This can be useful in these cases:
262262

263263
The configured container instance can be passed into the application like any
264264
other middleware request handler. In most cases this means you create a single
265-
`Container` instance with a number of factory methods and pass this instance as
265+
`Container` instance with a number of factory functions and pass this instance as
266266
the first argument to the `App`.
267267

268+
In its most common form, each entry in the container configuration maps a class
269+
name to a factory function that will be invoked when this class is first
270+
requested. The factory function is responsible for returning an instance that
271+
implements the given class name.
272+
268273
### PSR-11 compatibility
269274

270275
> ⚠️ **Feature preview**

src/Container.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ class Container
1515
/** @var array<class-string,callable():object | object> */
1616
public function __construct(array $map = [])
1717
{
18+
foreach ($map as $name => $value) {
19+
if (!$value instanceof \Closure && !$value instanceof $name) {
20+
throw new \BadMethodCallException('Map for ' . $name . ' contains unexpected ' . (is_object($value) ? get_class($value) : gettype($value)));
21+
}
22+
}
1823
$this->container = $map;
1924
}
2025

@@ -81,7 +86,13 @@ private function load(string $name, int $depth = 64)
8186
{
8287
if (isset($this->container[$name])) {
8388
if ($this->container[$name] instanceof \Closure) {
84-
$this->container[$name] = ($this->container[$name])();
89+
$value = ($this->container[$name])();
90+
91+
if (!$value instanceof $name) {
92+
throw new \BadMethodCallException('Factory for ' . $name . ' returned unexpected ' . (is_object($value) ? get_class($value) : gettype($value)));
93+
}
94+
95+
$this->container[$name] = $value;
8596
}
8697

8798
return $this->container[$name];

tests/ContainerTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,31 @@ public function __invoke(ServerRequestInterface $request)
132132
$this->assertEquals('{"num":1}', (string) $response->getBody());
133133
}
134134

135+
public function testCtorThrowsWhenMapContainsInvalidInteger()
136+
{
137+
$this->expectException(\BadMethodCallException::class);
138+
$this->expectExceptionMessage('Map for stdClass contains unexpected integer');
139+
140+
new Container([
141+
\stdClass::class => 42
142+
]);
143+
}
144+
145+
public function testCallableReturnsCallableThatThrowsWhenFactoryReturnsInvalidInteger()
146+
{
147+
$request = new ServerRequest('GET', 'http://example.com/');
148+
149+
$container = new Container([
150+
\stdClass::class => function () { return 42; }
151+
]);
152+
153+
$callable = $container->callable(\stdClass::class);
154+
155+
$this->expectException(\BadMethodCallException::class);
156+
$this->expectExceptionMessage('Factory for stdClass returned unexpected integer');
157+
$callable($request);
158+
}
159+
135160
public function testInvokeContainerAsMiddlewareReturnsFromNextRequestHandler()
136161
{
137162
$request = new ServerRequest('GET', 'http://example.com/');

0 commit comments

Comments
 (0)