Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions Tests/ContainerResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,68 @@ public function testResetWithInstance()

$this->assertNotSame($one, $two);
}

/**
* @testdox If the resource is created with a proxy class, the instance is created on first use
*
* @covers Joomla\DI\ContainerResource
* @uses Joomla\DI\Container
*/
public function testGetInstanceAsProxy()
{
if (PHP_VERSION_ID < 80400) {
$this->markTestSkipped('Lazy objects are only supported in PHP 8.4 or newer.');
}

$container = new Container();

$factoryCalled = false;

$resource = new ContainerResource(
$container,
static function() use (&$factoryCalled) {
$factoryCalled = true;
return new Stub6('test');
},
0,
Stub6::class
);

$stub = $resource->getInstance();

$this->assertTrue((new \ReflectionClass(Stub6::class))->isUninitializedLazyObject($stub) );
$this->assertFalse($factoryCalled);
}

/**
* @testdox If the resource is created with an invalid proxy class, the instance is created on first use
*
* @covers Joomla\DI\ContainerResource
* @uses Joomla\DI\Container
*/
public function testGetInstanceWithInvalidProxyClass()
{
if (PHP_VERSION_ID < 80400) {
$this->markTestSkipped('Lazy objects are only supported in PHP 8.4 or newer.');
}

$container = new Container();

$factoryCalled = false;

$resource = new ContainerResource(
$container,
static function() use (&$factoryCalled) {
$factoryCalled = true;
return new Stub6('test');
},
0,
'invalid'
);

$stub = $resource->getInstance();

$this->assertTrue($stub instanceof Stub6);
$this->assertTrue($factoryCalled);
}
}
24 changes: 14 additions & 10 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -603,13 +603,14 @@ private function getMethodArgs(\ReflectionMethod $method): array
* @param mixed $value Callable function to run or string to retrieve when requesting the specified $key.
* @param boolean $shared True to create and store a shared instance.
* @param boolean $protected True to protect this item from being overwritten. Useful for services.
* @param string $proxyClass The class to create the proxy for.
*
* @return $this
*
* @since 1.0
* @throws ProtectedKeyException Thrown if the provided key is already set and is protected.
*/
public function set($key, $value, $shared = false, $protected = false)
public function set($key, $value, $shared = false, $protected = false, ?string $proxyClass = '')
{
$key = $this->resolveAlias($key);

Expand All @@ -628,7 +629,7 @@ public function set($key, $value, $shared = false, $protected = false)
$mode = $shared ? ContainerResource::SHARE : ContainerResource::NO_SHARE;
$mode |= $protected ? ContainerResource::PROTECT : ContainerResource::NO_PROTECT;

$this->resources[$key] = new ContainerResource($this, $value, $mode);
$this->resources[$key] = new ContainerResource($this, $value, $mode, $proxyClass);

return $this;
}
Expand All @@ -639,14 +640,15 @@ public function set($key, $value, $shared = false, $protected = false)
* @param string $key Name of dataStore key to set.
* @param mixed $value Callable function to run or string to retrieve when requesting the specified $key.
* @param boolean $shared True to create and store a shared instance.
* @param string $proxyClass The class to create the proxy for.
*
* @return $this
*
* @since 1.0
*/
public function protect($key, $value, $shared = false)
public function protect($key, $value, $shared = false, ?string $proxyClass = '')
{
return $this->set($key, $value, $shared, true);
return $this->set($key, $value, $shared, true, $proxyClass);
}

/**
Expand All @@ -655,28 +657,30 @@ public function protect($key, $value, $shared = false)
* @param string $key Name of dataStore key to set.
* @param mixed $value Callable function to run or string to retrieve when requesting the specified $key.
* @param boolean $protected True to protect this item from being overwritten. Useful for services.
* @param string $proxyClass The class to create the proxy for.
*
* @return $this
*
* @since 1.0
*/
public function share($key, $value, $protected = false)
public function share($key, $value, $protected = false, ?string $proxyClass = '')
{
return $this->set($key, $value, true, $protected);
return $this->set($key, $value, true, $protected, $proxyClass);
}

/**
* Get the raw data assigned to a key.
*
* @param string $key The key for which to get the stored item.
* @param boolean $bail Throw an exception, if the key is not found
* @param string $key The key for which to get the stored item.
* @param boolean $bail Throw an exception, if the key is not found
* @param string $proxyClass The class to create the proxy for.
*
* @return ContainerResource|null The resource if present, or null if instructed to not bail
*
* @since 2.0.0
* @throws KeyNotFoundException
*/
public function getResource(string $key, bool $bail = false): ?ContainerResource
public function getResource(string $key, bool $bail = false, ?string $proxyClass = ''): ?ContainerResource
{
if (isset($this->resources[$key])) {
return $this->resources[$key];
Expand All @@ -687,7 +691,7 @@ public function getResource(string $key, bool $bail = false): ?ContainerResource
}

if ($this->parent instanceof ContainerInterface && $this->parent->has($key)) {
return new ContainerResource($this, $this->parent->get($key), ContainerResource::SHARE | ContainerResource::PROTECT);
return new ContainerResource($this, $this->parent->get($key), ContainerResource::SHARE | ContainerResource::PROTECT, $proxyClass);
}

if ($bail) {
Expand Down
6 changes: 5 additions & 1 deletion src/ContainerResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,21 @@ final class ContainerResource
* @param Container $container The container
* @param mixed $value The resource or its factory closure
* @param integer $mode Resource mode, defaults to Resource::NO_SHARE | Resource::NO_PROTECT
* @param string $proxyClass The class to create the proxy for, is only used when $value is a callable
*
* @since 2.0.0
*/
public function __construct(Container $container, $value, int $mode = 0)
public function __construct(Container $container, $value, int $mode = 0, ?string $proxyClass = '')
{
$this->container = $container;
$this->shared = ($mode & self::SHARE) === self::SHARE;
$this->protected = ($mode & self::PROTECT) === self::PROTECT;

if (\is_callable($value)) {
$this->factory = $value;
if ($proxyClass && class_exists($proxyClass, false) && PHP_VERSION_ID >= 80400) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if class doesn't exist we should throw an exception.

$this->factory = fn () => (new \ReflectionClass($proxyClass))->newLazyProxy(fn () => $value($this->container));
}
} else {
if ($this->shared) {
$this->instance = $value;
Expand Down