spiral/core
Spiral Core provides the framework’s dependency injection container with IoC scopes and injectable configuration support. Use it to manage bindings, resolve services, and handle scoped lifecycles in Spiral apps.
spiral/core is a lightweight PSR-11 compatible IoC container with advanced dependency resolution, scoping, and configuration management. It’s not a framework itself, but the foundational IoC layer used by the Spiral Framework. Start by installing:
composer require spiral/core
The first use case is typically bootstrapping a container and resolving services:
use Spiral\Core\Container;
$container = new Container();
// Bind a simple service
$container->bind('logger', fn() => new Monolog\Logger('app'));
// Resolve
$logger = $container->get('logger');
To get started quickly, read the Framework Container Docs — despite the name, it covers core container concepts like bind, singleton, autowiring, and scopes.
The container automatically resolves dependencies via reflection unless explicitly bound:
class Service {
public function __construct(Repository $repo) { ... }
}
$container->get(Service::class); // Automatically instantiates with Repository
Use Autowire::wire() (added in v1.4.4) to customize injection for ambiguous or complex cases:
use Spiral\Core\Autowire;
$container->bind(Service::class, Autowire::wire(fn(ContainerInterface $c) => new Service(
$c->get(Repository::class),
'custom-logger-name'
)));
⚠️ Breaking Change Note: As of v1.4.4, the container no longer supports PHP 7.2–7.3. The minimum PHP version is now 7.4 (per Travis config update). Verify compatibility when upgrading from older versions.
Use scopes to control object lifetime (e.g., request-level or command-level reuse):
use Spiral\Core\ScopeInterface;
// Enter a new scope
$scope = $container->resolveScope(ScopeInterface::RUN);
// Resolve service scoped to RUN
$scope->get(Service::class);
// Scope destroys on GC or explicit $scope = null;
The package defines ConfigInterface and ConfiguratorInterface, allowing config to be injected as dependencies (common in Spiral):
interface ConfigInterface {
public function get(string $key, mixed $default = null);
}
class MyService {
public function __construct(private ConfigInterface $config) {}
public function doSomething() {
$apiUrl = $this->config->get('api.url');
}
}
Configuration is usually registered by frameworks (e.g., Spiral Framework’s Bootloaders), but can be mocked for tests.
PHP 7.4+ Required: Since v1.4.4, PHP 7.4 is the minimum version (PHP 8.0+ recommended). Versions targeting PHP 7.2/7.3 (≤v1.4.3) are no longer maintained.
No Direct Factory Injection: The container doesn’t auto-bind Psr\Container\ContainerInterface — bind it manually if needed:
$container->set(ContainerInterface::class, $container);
Circular Dependencies: Use constructor injection cautiously — cycles throw CyclicDependencyException. Prefer setter/property injection or lazy resolution (e.g., fn(ContainerInterface $c) => $c->get(ExpensiveService::class)).
Scope Nesting: Nested scopes inherit bindings but don’t share resolutions unless explicitly wrapped. Avoid deep nesting — prefer flat scopes (e.g., one RUN, one THREAD).
Extensibility: Extend Container and override resolveArguments() for custom injection logic (e.g., adding synthetic parameters like current user).
Testing Tip: Mock or replace the container in tests using Container::withScope() and ScopeInterface::RUN to control state:
$container->withScope(ScopeInterface::RUN, function(Container $c) {
// isolated container state
});
Debugging: Enable verbose exceptions (added in v1.3.1) — they now include full resolution chains in DependencyResolutionException.
💡 Pro Tip: Though only 18 stars, this package powers large production systems (e.g., Spiral Framework). Use it for lightweight DI where Symfony/PHP-DI feel too heavy — especially when you need fine-grained scoping and config integration.
How can I help you explore Laravel packages today?