degraciamathieu/manager
Manager is a Laravel package that provides a simple, configurable CRUD interface for your Eloquent models. Browse, create, edit, and delete records with minimal setup, reducing repetitive admin boilerplate and keeping model management inside your Laravel app.
The package vendors Laravel's internal Illuminate\Support\Manager (the engine behind CacheManager, FilesystemManager, AuthManager, etc.) into a standalone, version-stable class so you can build your own driver-style managers without depending on an @internal component of the framework.
composer require degraciamathieu/manager
Look first at the base Manager class in the package source — its surface is intentionally small: driver(), extend(), getDefaultDriver(), create{Studly}Driver() methods, and __call pass-through to the default driver.
Minimal setup: create a manager that knows how to build two or three concrete "drivers" from config.
namespace App\Support;
use Degraciamathieu\Manager\Manager;
class SmsManager extends Manager
{
public function getDefaultDriver(): string
{
return $this->container['config']->get('sms.default');
}
protected function createVonageDriver(): VonageClient
{
return new VonageClient($this->container['config']->get('sms.drivers.vonage'));
}
protected function createTwilioDriver(): TwilioClient
{
return new TwilioClient($this->container['config']->get('sms.drivers.twilio'));
}
}
Bind it as a singleton in a service provider:
$this->app->singleton(SmsManager::class, fn ($app) => new SmsManager($app));
First use case: you have one interface (send an SMS, store a file, charge a card) and several interchangeable backends selected by config. Call app(SmsManager::class)->driver('twilio')->send(...), or rely on the default driver via app(SmsManager::class)->send(...).
Driver-per-provider with shared contract. Every createXDriver() returns something that satisfies the same PHP interface or abstract class. The manager is a factory + registry; the contract is what you type-hint. Keep the manager thin — construction, not behavior.
Custom drivers via extend(). The killer feature for package/plugin authors and for testing:
$manager->extend('fake', fn ($container) => new FakeSmsClient());
Consumers can register a driver in their own service provider without touching your createXDriver() methods. This is also how you avoid Mockery gymnastics in tests — swap in a deterministic fake driver instead of mocking the manager.
Config-driven selection. Read getDefaultDriver() from config so switching backends is an env/config change, not a code change. Mirror the config shape Laravel uses for config/filesystems.php (default + disks) — anyone who has edited filesystems.php or logging.php already understands your config.
Ergonomic proxy via __call. Because the base class forwards unknown calls to driver(), app(SmsManager::class)->send($to, $msg) is shorthand for ->driver()->send(...). Use this to keep call sites clean, but don't rely on it inside the manager itself — be explicit with $this->driver()->... when you call from the manager.
Integration with Laravel containers. Pass $this->container (the app) to custom creators so they can resolve dependencies:
protected function createRedisDriver()
{
return new RedisClient($this->container->make(RedisConnector::class));
}
Layered managers. A package manager (e.g., PaymentManager) can be constructed inside your app manager as a driver, or you can compose managers per concern. Prefer one manager per axis of variation; don't build a EverythingManager.
Testing pattern. In setUp(), resolve the manager, call forgetDrivers(), then extend('test', fn () => $this->fake) so each test starts from a clean slate against a known driver.
Driver name → method name is StudlyCase. driver('sms-provider') looks for createSmsProviderDriver(). Hyphens and underscores get PascalCased. A typo throws InvalidArgumentException mentioning "Driver [...] not supported" — grep your create*Driver methods first when a driver "doesn't exist."
Drivers are cached in memory for the manager's lifetime. If bound as a singleton (which you want for extend() registrations to persist), driver('x') only calls createXDriver() once per request. Call forgetDrivers() after changing config at runtime (e.g., between feature tests, or after multi-tenant boot).
extend() must run before driver() for that name. The custom creator wins over the createXDriver() convention. Register extensions in a service provider's register()/boot(), not lazily at the call site. Order matters if you also override the default via config.
getDefaultDriver() returning null throws. driver() with no argument and no default raises InvalidArgumentException — if a package config isn't published, users hit this on their first call. Guard with a sensible default or fail loudly with a message that names the config key.
Namespace and framework drift. The package tracks Laravel's Manager source. If you're also using Illuminate\Support\Manager directly elsewhere, decide on one. Mixing both is fine but means two slightly different Manager base classes coexist in autoload — confusing in stack traces.
Don't use a Manager for a single implementation. If you have one driver, a plain service class in the container is simpler and clearer than a Manager that always resolves to one thing. A Manager buys you runtime swap + per-tenant override + test fakes; if none of those apply, skip it.
Debugging: $manager->getDrivers() shows what's been instantiated so far — empty array means nothing resolved yet. Step into createDriver() (or use Xdebug) if a custom creator isn't firing; usually the name in extend() doesn't match the name passed to driver().
Extension points worth documenting for your own users: the extend() hook for custom drivers, the expected return type from createXDriver() (interface, not concrete), and where default driver selection lives in config. Those three things cover 90% of "how do I plug in my own X" questions.
How can I help you explore Laravel packages today?