crodas/service-provider
A lightweight PHP service-provider library for registering, configuring, and bootstrapping application services through a simple, container-friendly API. It keeps dependency wiring modular and maintainable, making service initialization easier to organize in PHP projects.
crodas/service-provider is a tiny, single-author DI/configuration container. It is not a Laravel package — there is no service provider, no facade, no config file. You pull it in directly and use it as a standalone container, or wrap it yourself.
composer require crodas/service-provider
First steps, in order:
src/ before anything else. With 2 stars and zero dependents, the README is the spec. Confirm the exact class name of the container base class — the package exposes a provider class you extend, and the FQCN matters for extends and for any type-hints you inject. Don't trust blog posts; trust vendor/crodas/service-provider/src.init-style method using closures.First real use case: a standalone CLI/bootstrap — a script or queue worker that needs a handful of wired-together services (a DB wrapper, a mailer, a repository) without booting the whole framework. That is where this package fits naturally.
// Confirm the base class name against the repo before relying on this.
use Crodas\ServiceProvider\Container;
class AppContainer extends Container
{
public function __construct(array $config = [])
{
parent::__construct($config);
$this->register('db', fn ($c) => new PDO($c['config']['dsn']));
$this->register('userRepo', fn ($c) => new UserRepository($c->get('db')));
}
}
$c = new AppContainer(require __DIR__.'/config.php');
$repo = $c->get('userRepo');
Closure-per-service registration. The bread-and-butter pattern: every service is a closure that receives the container and returns the built object. Keep closures pure and free of side effects — they may be re-invoked if the container isn't caching singletons.
Singleton vs. factory discipline. Small containers often re-instantiate on every get(). If the package distinguishes them (some crodas releases use a flag on the registration call), use singletons for stateful things (DB connections, loggers) and factories for value objects. Verify this in src/ — it's the single most important behavioral detail for day-to-day use.
Annotation-based wiring (crodas' house style). crodas packages lean on docblock annotations via his Notoj parser. If this package does the same, you'll declare dependencies with @Inject-style tags on properties or constructor params. That is convenient for demos and painful in production — always pair it with an annotation cache, or pre-warm the container at deploy time.
Bridging into Laravel. If you must use it inside an app, do it in a register() method of a real Laravel service provider and bind the instance as a singleton:
public function register(): void
{
$this->app->singleton(AppContainer::class, function ($app) {
return new AppContainer(config('services.legacy'));
});
$this->app->alias(AppContainer::class, 'legacy.container');
}
Then inject AppContainer into controllers/commands. Do not try to make Laravel's container delegate to it globally — you'll fight app(), facades, and Octane.
Config merge pattern. Treat the constructor array as the only config source. Read it once, validate required keys eagerly (fail fast at bootstrap), and pass plain values into closures rather than having closures re-read $c['config'] at resolution time.
Testing integration. Because services are closures, you can swap any binding with a fake in a setUp() — that's the main advantage over a full framework in a CLI context.
composer.json and vendor-review the source before shipping. Have a migration plan: every binding you write should be a plain closure that could move to Laravel's $this->app->bind() in an afternoon.ServiceProvider is the most overloaded class name in Laravel (Illuminate\Support\ServiceProvider). You will get import conflicts and IDE confusion. Always alias on import: use Crodas\ServiceProvider\Container as LegacyContainer;.Psr\Container\ContainerInterface. If it's absent, none of the standard get()/has() interop tooling works, and you cannot type-hint ContainerInterface in shared code.get() fails, the exception is often a bare "service not found" with no key list. Add your own has() check or a small dumpServiceKeys() helper during development — it saves hours.Illuminate\Container already does everything here, is battle-tested, PSR-11 compliant, and has autowiring. Reserve this package for isolated CLI tooling or code that predates the framework.How can I help you explore Laravel packages today?