English | 中文文档
Package: super-kernel/attribute · Packagist · Documentation index
super-kernel/attribute holds the native PHP Attributes that SuperKernel packages, third-party packages, and other framework implementations share. Each one fixes a declaration's shape and the meaning it carries: where it can be placed, whether it repeats, its parameters, and which values are valid. Classes declare what they provide with these Attributes, and the consuming packages act on them.
The package contains declarations only and depends on nothing but PHP. skernel build collects the Attributes, super-kernel/annotator stores and restores them, and the consuming packages, such as super-kernel/di, super-kernel/config, and super-kernel/event-dispatcher, interpret them. Every Attribute lives in the SuperKernel\Attribute namespace.
#[Provider(Target::class)]on a class maps the identifierTargetto that class, whichsuper-kernel/dithen constructs. A class may map itself,#[Provider(OwnClass::class)], to be constructed as a shared service. The Attribute is not repeatable.#[FactoryProvider(Target::class)]on a class declares that the class's public, non-static__invoke()createsTarget. The class may have no constructor or a public one; the container fills the parameters of both. The Attribute is repeatable: each declaration maps one more identifier to the same class, in declaration order, and the container keeps a separate product for each identifier.- The identifier of both Attributes must be a non-empty string without NUL bytes. Whether the class is compatible with the identifier, and whether an identifier has two providers, the consuming container checks.
The interface, saved as src/Clock/Clock.php:
<?php
declare(strict_types=1);
namespace Src\Clock;
interface Clock
{
public function zone(): string;
}The implementation, saved as src/Clock/ZonedClock.php:
<?php
declare(strict_types=1);
namespace Src\Clock;
final readonly class ZonedClock implements Clock
{
public function __construct(private string $zone)
{
}
public function zone(): string
{
return $this->zone;
}
}A factory for both identifiers, saved as src/Clock/ClockFactory.php:
<?php
declare(strict_types=1);
namespace Src\Clock;
use SuperKernel\Attribute\FactoryProvider;
use function getenv;
#[FactoryProvider(Clock::class)]
#[FactoryProvider(ZonedClock::class)]
final class ClockFactory
{
public function __invoke(): ZonedClock
{
return new ZonedClock(getenv('APP_TIMEZONE') ?: 'UTC');
}
}A script that looks both up, saved as bin/clock.php:
<?php
declare(strict_types=1);
use Src\Clock\Clock;
use Src\Clock\ZonedClock;
use SuperKernel\Context\ApplicationContext;
require dirname(__DIR__) . '/vendor/autoload.php';
$container = ApplicationContext::getContainer();
echo $container->get(Clock::class)->zone(), PHP_EOL;
var_dump($container->get(Clock::class) === $container->get(ZonedClock::class));Build the project and run the script:
skernel build
APP_TIMEZONE=Asia/Shanghai php bin/clock.phpIt prints the following; the last line shows that each identifier has its own product:
Asia/Shanghai
bool(false)
The rules of super-kernel/di for both Attributes are on its page: Declare services.
#[Autowired] on a property asks the container to set it after the constructor returns.
- Without an argument, the property's type selects the entry.
#[Autowired(Target::class)]names the class or interface to inject instead; it must exist, or constructing the Attribute fails withInjectable identifier <identifier> does not exist. #[Autowired]takes no other parameter. Whether the property receives a lazy proxy is the container's choice;super-kernel/diinjects lazy proxies by default.- The Attribute may also be placed on a method, where
super-kernel/ditreats it as metadata only and never calls the method.
A class with an injected property, saved as src/Clock/Schedule.php:
<?php
declare(strict_types=1);
namespace Src\Clock;
use SuperKernel\Attribute\Autowired;
final class Schedule
{
#[Autowired(ZonedClock::class)]
public Clock $clock;
}A script that uses it, saved as bin/schedule.php:
<?php
declare(strict_types=1);
use Src\Clock\Schedule;
use SuperKernel\Context\ApplicationContext;
require dirname(__DIR__) . '/vendor/autoload.php';
echo ApplicationContext::getContainer()->get(Schedule::class)->clock->zone(), PHP_EOL;After skernel build, php bin/schedule.php prints UTC.
The injection order and the lazy proxies are described on the DI page: Property injection.
#[Definer(priority: N)]registers a class that turns identifiers into definitions;#[Resolver(priority: N)]registers a class that turns definitions into values. Both take one integerpriority,0by default, and are not repeatable. A higher priority is asked first; equal priorities keep the order of discovery.#[PropertyHandler(TheAttribute::class)]registers the annotated class as the handler of a custom property Attribute. It is repeatable, so one handler may serve several Attributes. The mapped Attribute declares its own target with PHP's#[Attribute(Attribute::TARGET_PROPERTY)]; the name must be a non-empty string without NUL bytes.
The interfaces these classes implement and complete examples are on the DI page: Custom property Attributes and Definers and resolvers.
#[Configuration] serves super-kernel/config. It is not repeatable, and takes ?string $className = null and bool $default = false:
- On a class, it declares a configuration source.
classNamenames the class or interface the source provides; when omitted,super-kernel/configinfers it from the interfaces the class implements. A givenclassNamemust be a non-empty string without NUL bytes. - On a method of a source that implements
SuperKernel\Contract\MultipleConfigInterface, it declares a named item; the method name is the item's name.classNamemay be omitted or repeat the source's item type. Passdefault: trueby name to choose the default among several items.
#[Provision] on a method declares that the method creates a container service. It takes any number of names, #[Provision('a', 'b')], kept in order in its public name list; each must be a non-empty string without NUL bytes. Without names, the service's identifier is the method's return type. The class needs no #[Configuration], and super-kernel/config calls the method through the container.
The configuration interface, saved as src/Mail/MailConfig.php:
<?php
declare(strict_types=1);
namespace Src\Mail;
use SuperKernel\Contract\SingleConfigInterface;
interface MailConfig extends SingleConfigInterface
{
public function host(): string;
}The application's source, saved as src/Mail/AppMailConfig.php:
<?php
declare(strict_types=1);
namespace Src\Mail;
use SuperKernel\Attribute\Configuration;
#[Configuration(MailConfig::class)]
final readonly class AppMailConfig implements MailConfig
{
public function host(): string
{
return 'smtp.example.com';
}
}A factory method for a named service, saved as src/Mail/MailServices.php:
<?php
declare(strict_types=1);
namespace Src\Mail;
use SuperKernel\Attribute\Provision;
use SuperKernel\Contract\ConfigInterface;
final readonly class MailServices
{
public function __construct(private ConfigInterface $config)
{
}
#[Provision('mail.dsn')]
public function dsn(): string
{
$mail = $this->config->get(MailConfig::class);
assert($mail instanceof MailConfig);
return 'smtp://' . $mail->host();
}
}A script that reads the service, saved as bin/mail.php:
<?php
declare(strict_types=1);
use SuperKernel\Context\ApplicationContext;
require dirname(__DIR__) . '/vendor/autoload.php';
echo ApplicationContext::getContainer()->get('mail.dsn'), PHP_EOL;After skernel build, php bin/mail.php prints smtp://smtp.example.com.
Sources, items, defaults, and provisions are described on the Config page: SuperKernel Config.
#[Listener] on a class that implements SuperKernel\Contract\ListenerInterface registers it with super-kernel/event-dispatcher. It takes one integer priority, 0 by default, and is not repeatable. A higher priority runs first; equal priorities keep the order of discovery. The events a listener receives are returned by its listen() method, not declared by the Attribute.
The event, saved as src/Signup/UserRegistered.php:
<?php
declare(strict_types=1);
namespace Src\Signup;
final readonly class UserRegistered
{
public function __construct(public string $email)
{
}
}A listener with a high priority, saved as src/Signup/RecordAuditEntry.php:
<?php
declare(strict_types=1);
namespace Src\Signup;
use SuperKernel\Attribute\Listener;
use SuperKernel\Contract\ListenerInterface;
#[Listener(priority: 100)]
final class RecordAuditEntry implements ListenerInterface
{
public function listen(): array
{
return [
UserRegistered::class,
];
}
public function process(object $event): void
{
assert($event instanceof UserRegistered);
echo "Audit: {$event->email} registered", PHP_EOL;
}
}A listener with the default priority, saved as src/Signup/AddToNewsletter.php:
<?php
declare(strict_types=1);
namespace Src\Signup;
use SuperKernel\Attribute\Listener;
use SuperKernel\Contract\ListenerInterface;
#[Listener]
final class AddToNewsletter implements ListenerInterface
{
public function listen(): array
{
return [
UserRegistered::class,
];
}
public function process(object $event): void
{
assert($event instanceof UserRegistered);
echo "Newsletter: {$event->email} added", PHP_EOL;
}
}A script that publishes the event, saved as bin/signup.php:
<?php
declare(strict_types=1);
use Psr\EventDispatcher\EventDispatcherInterface;
use Src\Signup\UserRegistered;
use SuperKernel\Context\ApplicationContext;
require dirname(__DIR__) . '/vendor/autoload.php';
ApplicationContext::getContainer()
->get(EventDispatcherInterface::class)
->dispatch(new UserRegistered('ada@example.com'));After skernel build, php bin/signup.php prints:
Audit: ada@example.com registered
Newsletter: ada@example.com added
A package whose own code uses these Attributes requires super-kernel/attribute directly. Receiving it through super-kernel/di would tie the package to one container, while the Attributes are meant to be interpreted by any implementation; a package that uses them only in tests may require it for development only.
super-kernel/di interprets Provider, FactoryProvider, Autowired, Definer, Resolver, and PropertyHandler; super-kernel/config interprets Configuration and Provision; super-kernel/event-dispatcher interprets Listener. What another container does with them is up to that container.
The Attributes raise InvalidArgumentException when they are constructed, which happens during skernel build. skernel build then skips the class, prints it with the message, and records it in the skipped-class diagnostics of super-kernel/annotator.
| Situation | Message |
|---|---|
Provider with an empty identifier or one with a NUL byte |
Provider className must be non-empty and contain no NUL bytes. |
FactoryProvider with an empty identifier or one with a NUL byte |
FactoryProvider className must be non-empty and contain no NUL bytes. |
PropertyHandler with an empty Attribute name or one with a NUL byte |
PropertyHandler attribute must be non-empty and contain no NUL bytes. |
Autowired naming a class or interface that does not exist |
Injectable identifier <identifier> does not exist. |
Configuration with an empty className or one with a NUL byte |
Configuration className must be non-empty and contain no NUL bytes when provided. |
Provision with an empty name or one with a NUL byte |
Provision name must be non-empty and contain no NUL bytes. |
super-kernel/diinterprets the service, injection, and extension Attributes.super-kernel/configinterpretsConfigurationandProvision.super-kernel/event-dispatcherinterpretsListener.super-kernel/annotatorstores the Attributes thatskernel buildcollects and answers queries about them.super-kernel/contractdeclares the interfaces that annotated classes implement, such asListenerInterfaceandMultipleConfigInterface.- Exception handlers are declared with an Attribute of
super-kernel/exception-dispatcher, not of this package.
Do not rely on super-kernel/di to install the Attributes. A package whose code uses them then breaks in an application that installs another container instead. Require super-kernel/attribute directly.
Do not ignore the classes that skernel build skips. An invalid Attribute value, such as #[Provider('')], fails with Provider className must be non-empty and contain no NUL bytes., and the whole class is left out: its other Attributes are lost too, and the application behaves as if the class had none. Read what skernel build prints and fix the declaration.
Do not expect the Attribute to check what its consumer checks. An Attribute only checks its own values. Whether a Provider's class is compatible with its identifier, or whether two classes provide one identifier, is reported by super-kernel/di at the first lookup that needs it.