Skip to content

Commit 0efb82b

Browse files
committed
Add way to handle protected constructors on reflection safely
1 parent 3a15637 commit 0efb82b

7 files changed

Lines changed: 87 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All Notable changes to `League\Container` will be documented in this file
44

5+
## 4.1.0
6+
7+
### Added
8+
- Way to handle non-public controllers safely (@beryllium)
9+
510
## 4.0.0
611

712
### Added
@@ -16,6 +21,12 @@ All Notable changes to `League\Container` will be documented in this file
1621
- `ServiceProviderInterface` now defines return types
1722
- Service providers now require implementation of a `provides` method rather than relying on a class property.
1823

24+
## 3.4.1
25+
26+
### Added
27+
- Way to handle non-public controllers safely (@beryllium)
28+
- PHPUnit ^7.0 for PHP versions that support it (@beryllium)
29+
1930
## 3.4.0
2031

2132
### Removed

docs/_data/releases.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
name: League\Container 4.x
2323
type: Current
2424
requires: PHP >= 7.2
25-
release: 4.0.0 - 2021-07
25+
release: 4.1.0 - 2021-07
2626
support: Ongoing
2727
url: /4.x/
2828
menu:
@@ -40,7 +40,7 @@
4040
name: League\Container 3.x
4141
type: Old
4242
requires: PHP >= 7.0
43-
release: 3.4.0 - 2021-07
43+
release: 3.4.1 - 2021-07
4444
support: 2022-02
4545
url: /3.x/
4646
menu:

src/Argument/ArgumentResolverTrait.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,22 @@ public function resolveArguments(array $arguments): array
4141
// resolve the argument from the container, if it happens to be another
4242
// argument wrapper, use that value
4343
if ($container instanceof ContainerInterface && $container->has($argValue)) {
44-
$arg = $container->get($argValue);
44+
try {
45+
$arg = $container->get($argValue);
4546

46-
if ($arg instanceof ArgumentInterface) {
47-
$arg = $arg->getValue();
48-
}
47+
if ($arg instanceof ArgumentInterface) {
48+
$arg = $arg->getValue();
49+
}
4950

50-
continue;
51+
continue;
52+
} catch (NotFoundException $e) {
53+
}
5154
}
5255

5356
// if we have a default value, we use that, no more resolution as
5457
// we expect a default/optional argument value to be literal
5558
if ($arg instanceof DefaultValueInterface) {
5659
$arg = $arg->getDefaultValue();
57-
continue;
5860
}
5961
}
6062

src/ReflectionContainer.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function get($id, array $args = [])
3838
return $this->cache[$id];
3939
}
4040

41-
if (! $this->has($id)) {
41+
if (!$this->has($id)) {
4242
throw new NotFoundException(
4343
sprintf('Alias (%s) is not an existing class and therefore cannot be resolved', $id)
4444
);
@@ -47,9 +47,15 @@ public function get($id, array $args = [])
4747
$reflector = new ReflectionClass($id);
4848
$construct = $reflector->getConstructor();
4949

50+
if ($construct && !$construct->isPublic()) {
51+
throw new NotFoundException(
52+
sprintf('Alias (%s) has a non-public constructor and therefore cannot be instantiated', $id)
53+
);
54+
}
55+
5056
$resolution = $construct === null
5157
? new $id()
52-
: $resolution = $reflector->newInstanceArgs($this->reflectArguments($construct, $args))
58+
: $reflector->newInstanceArgs($this->reflectArguments($construct, $args))
5359
;
5460

5561
if ($this->cacheResolutions === true) {

tests/Asset/ProBar.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace League\Container\Test\Asset;
4+
5+
class ProBar implements BarInterface
6+
{
7+
protected function __construct()
8+
{
9+
}
10+
11+
public static function factory(): ProBar
12+
{
13+
return new self();
14+
}
15+
}

tests/Asset/ProFoo.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace League\Container\Test\Asset;
4+
5+
class ProFoo
6+
{
7+
public $bar;
8+
9+
public function __construct(?ProBar $bar = null)
10+
{
11+
$this->bar = $bar;
12+
}
13+
}

tests/ReflectionContainerTest.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use League\Container\Container;
88
use League\Container\Exception\NotFoundException;
99
use League\Container\ReflectionContainer;
10-
use League\Container\Test\Asset\{Foo, FooCallable, Bar};
10+
use League\Container\Test\Asset\{Foo, FooCallable, Bar, ProFoo, ProBar};
1111
use PHPUnit\Framework\TestCase;
1212

1313
class ReflectionContainerTest extends TestCase
@@ -191,4 +191,33 @@ public function testCallResolvesFunction(): void
191191
self::assertInstanceOf(Foo::class, $foo);
192192
self::assertInstanceOf(Bar::class, $foo->bar);
193193
}
194+
195+
public function testGetInstantiatesClassWithConstructorAndSkipsProtectedConstructor(): void
196+
{
197+
$classWithConstructor = ProFoo::class;
198+
199+
$container = new Container();
200+
$container->delegate(new ReflectionContainer());
201+
202+
$item = $container->get($classWithConstructor);
203+
204+
$this->assertInstanceOf($classWithConstructor, $item);
205+
$this->assertNull($item->bar);
206+
}
207+
208+
public function testGetInstantiatesClassWithConstructorAndUsesFactory(): void
209+
{
210+
$classWithConstructor = ProFoo::class;
211+
$dependencyClass = ProBar::class;
212+
213+
$container = new Container();
214+
$container->delegate(new ReflectionContainer());
215+
216+
$container->add($dependencyClass, [$dependencyClass, 'factory']);
217+
218+
$item = $container->get($classWithConstructor);
219+
220+
$this->assertInstanceOf($classWithConstructor, $item);
221+
$this->assertInstanceOf($dependencyClass, $item->bar);
222+
}
194223
}

0 commit comments

Comments
 (0)