-
Notifications
You must be signed in to change notification settings - Fork 11k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[11.x] Add support for acting on attributes through container (#51934)
* feat: support contextual attribute binding * feat: add after resolving attribute callback * test: update first class resolution test * style: apply changes from style-ci * test: fix second assertion * formatting * fix(contextual-attributes): support primitives * feat(contextual-binding): support `resolve` method in contextual attributes * style: apply fixes from style-ci * formatting * formatting * add after support * add config attribute by default --------- Co-authored-by: Taylor Otwell <[email protected]>
- Loading branch information
1 parent
0de031f
commit cb64466
Showing
5 changed files
with
477 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
namespace Illuminate\Container\Attributes; | ||
|
||
use Attribute; | ||
use Illuminate\Contracts\Container\Container; | ||
use Illuminate\Contracts\Container\ContextualAttribute; | ||
|
||
#[Attribute(Attribute::TARGET_PARAMETER)] | ||
class Config implements ContextualAttribute | ||
{ | ||
/** | ||
* Create a new class instance. | ||
*/ | ||
public function __construct(public string $key, public mixed $default = null) | ||
{ | ||
} | ||
|
||
/** | ||
* Resolve the configuration value. | ||
* | ||
* @param self $attribute | ||
* @param \Illuminate\Contracts\Container\Container $container | ||
* @return mixed | ||
*/ | ||
public static function resolve(self $attribute, Container $container) | ||
{ | ||
return $container->make('config')->get($attribute->key, $attribute->default); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Illuminate\Contracts\Container; | ||
|
||
interface ContextualAttribute | ||
{ | ||
// | ||
} |
137 changes: 137 additions & 0 deletions
137
tests/Container/AfterResolvingAttributeCallbackTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
<?php | ||
|
||
namespace Illuminate\Tests\Container; | ||
|
||
use Attribute; | ||
use Illuminate\Container\Container; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class AfterResolvingAttributeCallbackTest extends TestCase | ||
{ | ||
public function testCallbackIsCalledAfterDependencyResolutionWithAttribute() | ||
{ | ||
$container = new Container(); | ||
|
||
$container->afterResolvingAttribute(ContainerTestOnTenant::class, function (ContainerTestOnTenant $attribute, HasTenantImpl $hasTenantImpl, Container $container) { | ||
$hasTenantImpl->onTenant($attribute->tenant); | ||
}); | ||
|
||
$hasTenantA = $container->make(ContainerTestHasTenantImplPropertyWithTenantA::class); | ||
$this->assertInstanceOf(HasTenantImpl::class, $hasTenantA->property); | ||
$this->assertEquals(Tenant::TenantA, $hasTenantA->property->tenant); | ||
|
||
$hasTenantB = $container->make(ContainerTestHasTenantImplPropertyWithTenantB::class); | ||
$this->assertInstanceOf(HasTenantImpl::class, $hasTenantB->property); | ||
$this->assertEquals(Tenant::TenantB, $hasTenantB->property->tenant); | ||
} | ||
|
||
public function testCallbackIsCalledAfterClassWithAttributeIsResolved() | ||
{ | ||
$container = new Container(); | ||
|
||
$container->afterResolvingAttribute( | ||
ContainerTestBootable::class, | ||
fn ($_, $instance, Container $container) => method_exists($instance, 'booting') && $container->call([$instance, 'booting']) | ||
); | ||
|
||
$instance = $container->make(ContainerTestHasBootable::class); | ||
|
||
$this->assertInstanceOf(ContainerTestHasBootable::class, $instance); | ||
$this->assertTrue($instance->hasBooted); | ||
} | ||
|
||
public function testCallbackIsCalledAfterClassWithConstructorAndAttributeIsResolved() | ||
{ | ||
$container = new Container(); | ||
|
||
$container->afterResolvingAttribute(ContainerTestConfiguresClass::class, function (ContainerTestConfiguresClass $attribute, $class) { | ||
$class->value = $attribute->value; | ||
}); | ||
|
||
$container->when(ContainerTestHasSelfConfiguringAttributeAndConstructor::class) | ||
->needs('$value') | ||
->give('no-the-right-value'); | ||
|
||
$instance = $container->make(ContainerTestHasSelfConfiguringAttributeAndConstructor::class); | ||
|
||
$this->assertInstanceOf(ContainerTestHasSelfConfiguringAttributeAndConstructor::class, $instance); | ||
$this->assertEquals('the-right-value', $instance->value); | ||
} | ||
} | ||
|
||
#[Attribute(Attribute::TARGET_PARAMETER)] | ||
final class ContainerTestOnTenant | ||
{ | ||
public function __construct( | ||
public readonly Tenant $tenant | ||
) { | ||
} | ||
} | ||
|
||
enum Tenant | ||
{ | ||
case TenantA; | ||
case TenantB; | ||
} | ||
|
||
final class HasTenantImpl | ||
{ | ||
public ?Tenant $tenant = null; | ||
|
||
public function onTenant(Tenant $tenant): void | ||
{ | ||
$this->tenant = $tenant; | ||
} | ||
} | ||
|
||
final class ContainerTestHasTenantImplPropertyWithTenantA | ||
{ | ||
public function __construct( | ||
#[ContainerTestOnTenant(Tenant::TenantA)] | ||
public readonly HasTenantImpl $property | ||
) { | ||
} | ||
} | ||
|
||
final class ContainerTestHasTenantImplPropertyWithTenantB | ||
{ | ||
public function __construct( | ||
#[ContainerTestOnTenant(Tenant::TenantB)] | ||
public readonly HasTenantImpl $property | ||
) { | ||
} | ||
} | ||
|
||
#[Attribute(Attribute::TARGET_CLASS)] | ||
final class ContainerTestConfiguresClass | ||
{ | ||
public function __construct( | ||
public readonly string $value | ||
) { | ||
} | ||
} | ||
|
||
#[ContainerTestConfiguresClass(value: 'the-right-value')] | ||
final class ContainerTestHasSelfConfiguringAttributeAndConstructor | ||
{ | ||
public function __construct( | ||
public string $value | ||
) { | ||
} | ||
} | ||
|
||
#[Attribute(Attribute::TARGET_CLASS)] | ||
final class ContainerTestBootable | ||
{ | ||
} | ||
|
||
#[ContainerTestBootable] | ||
final class ContainerTestHasBootable | ||
{ | ||
public bool $hasBooted = false; | ||
|
||
public function booting(): void | ||
{ | ||
$this->hasBooted = true; | ||
} | ||
} |
Oops, something went wrong.