phpdot / package
Auto-discovery and lifecycle management for PHPdot packages.
Requires
- php: >=8.3
- phpdot/container: ^1.1
- psr/container: ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.0
README
Auto-discovery and lifecycle management for PHPdot packages. Reads Composer's installed.json, topologically sorts by dependencies, runs register then boot.
Install
composer require phpdot/package
| Requirement | Version |
|---|---|
| PHP | >= 8.3 |
| phpdot/container | >= 1.1 |
| psr/container | >= 2.0 |
How It Works
composer.json (per package)
extra.phpdot.package = "Vendor\\Pkg\\PkgPackage"
↓
PackageDiscovery → reads installed.json, filters phpdot packages, caches result
↓
DependencyResolver → topological sort via DFS, detects cycles
↓
PackageKernel
Phase 1: register() → each package feeds definitions into ContainerBuilder
Phase 2: boot() → each package runs post-build logic with live container
PackageInterface
Every package implements this. Two methods, zero magic.
use PHPdot\Container\ContainerBuilder; use PHPdot\Package\PackageInterface; use Psr\Container\ContainerInterface; final class MongoDBPackage implements PackageInterface { public function register(ContainerBuilder $builder): void { $builder->addDefinitions([ Connection::class => scoped(static function (ContainerInterface $c): Connection { return new Connection($c->get(ConnectionConfig::class)); }), ]); } public function boot(ContainerInterface $container): void { } }
Register in composer.json:
{
"extra": {
"phpdot": {
"package": "PHPdot\\MongoDB\\MongoDBPackage"
}
}
}
Install via Composer. Next boot picks it up automatically. No code changes.
PackageKernel
use PHPdot\Container\ContainerBuilder; use PHPdot\Package\DependencyResolver; use PHPdot\Package\PackageDiscovery; use PHPdot\Package\PackageKernel; $builder = new ContainerBuilder(); $kernel = new PackageKernel(new PackageDiscovery(), new DependencyResolver()); $kernel->register($vendorPath, $builder); $container = $builder->build(); $kernel->boot($container);
Dependency Order
Dependencies come from Composer's require keys. Only phpdot-enabled packages are considered.
phpdot/config → deps: []
phpdot/i18n → deps: [phpdot/config]
phpdot/mongodb → deps: [phpdot/config]
company/billing → deps: [phpdot/mongodb]
Sorted: [config, i18n, mongodb, billing]
When any package's register() or boot() runs, every package it depends on has already completed that phase.
Cyclic dependencies throw CyclicDependencyException.
Caching
Discovery caches the sorted result as a PHP file in the vendor directory. Cache is invalidated when installed.json is newer than the cache file.
PackageDiscovery::clearCache($vendorPath);
Works as a Composer script:
{
"scripts": {
"post-autoload-dump": [
"PHPdot\\Package\\PackageDiscovery::clearCache"
]
}
}
API Reference
PackageInterface
interface PackageInterface
register(ContainerBuilder $builder): void
boot(ContainerInterface $container): void
PackageKernel
final class PackageKernel
__construct(PackageDiscovery $discovery, DependencyResolver $resolver)
register(string $vendorPath, ContainerBuilder $builder): void
boot(ContainerInterface $container): void
packages(): list<DiscoveredPackage>
instances(): list<PackageInterface>
PackageDiscovery
final class PackageDiscovery
discover(string $vendorPath): list<DiscoveredPackage>
static clearCache(mixed $eventOrVendorPath): void
DependencyResolver
final class DependencyResolver
resolve(list<DiscoveredPackage> $packages): list<DiscoveredPackage>
DiscoveredPackage
final readonly class DiscoveredPackage
public string $name
public string $class (class-string<PackageInterface>)
public array $dependencies (list<string>)
Exceptions
PackageDiscoveryException — class not found, interface not implemented
CyclicDependencyException — circular dependency detected
License
MIT