Binding Attributes
Occasionally you’ll want multiple bindings for a same type. For example, you might want both a PayPal credit card processor and a Google Checkout processor. To enable this, bindings support an optional binding attribute. The attribute and type together uniquely identify a binding. This pair is called a key.
Defining binding attributes
Define qualifier attribute first. It needs to be annotated with Qualifier
attribute.
use Ray\Di\Di\Qualifier;
#[Attribute, Qualifier]
final class PayPal
{
}
To depend on the annotated binding, apply the attribute to the injected parameter:
public function __construct(
#[Paypal] private readonly CreditCardProcessorInterface $processor
){}
You can specify parameter name with qualifier. Qualifier applied all parameters without it.
public function __construct(
#[Paypal('processor')] private readonly CreditCardProcessorInterface $processor
){}
Lastly we create a binding that uses the attribute. This uses the optional annotatedWith
clause in the bind() statement:
$this->bind(CreditCardProcessorInterface::class)
->annotatedWith(PayPal::class)
->to(PayPalCreditCardProcessor::class);
Binding Attributes in Setters
In order to make your custom Qualifier
attribute inject dependencies by default in any method the
attribute is added, you need to implement the Ray\Di\Di\InjectInterface
:
use Ray\Di\Di\InjectInterface;
use Ray\Di\Di\Qualifier;
#[Attribute, Qualifier]
final class PaymentProcessorInject implements InjectInterface
{
public function isOptional()
{
return $this->optional;
}
public function __construct(
public readonly bool $optional = true
public readonly string $type;
){}
}
The interface requires that you implement the isOptional()
method. It will be used to determine whether
or not the injection should be performed based on whether there is a known binding for it.
Now that you have created your custom injector attribute, you can use it on any method.
#[PaymentProcessorInject(type: 'paypal')]
public setPaymentProcessor(CreditCardProcessorInterface $processor)
{
....
}
Finally, you can bind the interface to an implementation by using your new annotated information:
$this->bind(CreditCardProcessorInterface::class)
->annotatedWith(PaymentProcessorInject::class)
->toProvider(PaymentProcessorProvider::class);
The provider can now use the information supplied in the qualifier attribute in order to instantiate the most appropriate class.
#[Named]
The most common use of a Qualifier attribute is tagging arguments in a function with a certain label,
the label can be used in the bindings in order to select the right class to be instantiated. For those
cases, Ray.Di comes with a built-in binding attribute #[Named]
that takes a string.
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
public function __construct(
#[Named('checkout')] private CreditCardProcessorInterface $processor
){}
To bind a specific name, pass that string using the annotatedWith()
method.
$this->bind(CreditCardProcessorInterface::class)
->annotatedWith('checkout')
->to(CheckoutCreditCardProcessor::class);
You need to put the #[Named]
attribuet in order to specify the parameter.
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
public function __construct(
#[Named('checkout')] private CreditCardProcessorInterface $processor,
#[Named('backup')] private CreditCardProcessorInterface $subProcessor
){}
Binding Annotation
Ray.Di can be used with doctrine/annotation for PHP 7.x. See the old README(v2.10) for annotation code examples. To create forward-compatible annotations for attributes, see custom annotation classes.
Since annotations cannot be applied to arguments, the first argument of a custom annotation should be the name of the variable. This is not necessary if the method has only one argument.
/**
* @Paypal('processor')
*/
public function setCreditCardProcessor(
CreditCardProcessorInterface $processor
OtherDepedeciyInterface $depedency
){