graham-campbell/manager
Adds a reusable “manager” layer for Laravel packages and apps, helping you build driver-based services (create, cache, and resolve drivers) with a consistent API. Supports Laravel 8–13 and PHP 7.4–8.5.
Install the package:
composer require graham-campbell/manager:^5.3
No service provider registration is required—just include the package in your project.
First use case: Create a basic manager
Extend AbstractManager to implement your connection logic. Example for a LoggerManager:
use GrahamCampbell\Manager\AbstractManager;
class LoggerManager extends AbstractManager
{
protected function createConnection(array $config)
{
return new CustomLogger($config);
}
protected function getConfigName()
{
return 'logging';
}
}
Register the manager in config/logging.php:
'drivers' => [
'loggly' => [
'token' => env('LOGGLY_TOKEN'),
],
'sentry' => [
'dsn' => env('SENTRY_DSN'),
],
],
Use the manager in a controller/service:
$logger = app(LoggerManager::class);
$logger->connection('loggly')->log('Test message');
// Or dynamically (thanks to __call):
$logger->loggly()->log('Test message');
Connection Lifecycle Management
connection('name') to get an instance.reconnect('name') to bypass the cache (e.g., after config changes).disconnect('name') to remove a connection from the pool (e.g., for security-sensitive operations).Dynamic Method Dispatch
Leverage __call to avoid repetitive connection()->method() calls:
// Instead of:
$manager->connection('s3')->upload('file.jpg');
// Use:
$manager->s3()->upload('file.jpg');
Runtime Extensibility Add custom drivers at runtime without code changes:
$manager->extend('datadog', function () {
return new DatadogLogger(config('logging.datadog'));
});
Now app(LoggerManager::class)->datadog()->log(...) works.
Config Resolution
getConnectionConfig('name') to fetch config for a specific connection.getDefaultConnection()/setDefaultConnection('name') to manage defaults.Facades\Manager::connection('s3')->putObject(...);
$this->app->singleton(LoggerManager::class, function ($app) {
return new LoggerManager($app['config']);
});
AbstractManager and overriding createConnection():
$manager = new TestLoggerManager();
$manager->extend('mock', fn() => new MockLogger());
Connection Leaks
disconnect() can lead to memory leaks if connections hold resources (e.g., database connections, HTTP clients).finally block or use Laravel’s withConnection() for scoped connections:
$manager->connection('mysql')->query(...);
// Connection is automatically disconnected when out of scope.
Config Name Mismatches
getConfigName() must return the exact config key (e.g., 'logging'). Typos here will cause ConfigException.boot():
if (!config()->has($this->getConfigName())) {
throw new \RuntimeException("Config [{$this->getConfigName()}] not found.");
}
Dynamic Method Ambiguity
__call can conflict with existing methods. For example, if your manager has a log() method, $manager->log() will call it instead of dispatching to a connection.connection()->method() explicitly.Thread Safety
sync flag or implement a lock mechanism:
\Illuminate\Support\Facades\Cache::lock('manager-'.$name, 5, function () use ($manager) {
$manager->reconnect($name);
});
dd(app(LoggerManager::class)->getConnections());
dd(app(LoggerManager::class)->getConnectionConfig('s3'));
createConnection() to trace initialization:
protected function createConnection(array $config)
{
\Log::debug('Creating connection with config:', $config);
return new S3Client($config);
}
Custom Connection Factories
Override createConnection() to support complex initialization:
protected function createConnection(array $config)
{
return tap(new S3Client($config), function ($client) {
$client->configure(['region' => $config['region']]);
});
}
Connection Events Extend the manager to dispatch events on connect/disconnect:
use Illuminate\Support\Facades\Event;
protected function createConnection(array $config)
{
$connection = new S3Client($config);
Event::dispatch('manager.connected', [$this, $connection]);
return $connection;
}
Fluent Interface Add chainable methods to connections:
$manager->extend('s3', function () {
return new S3Manager(config('filesystems.disks.s3'));
});
// Usage:
$manager->s3()->disk()->put('file.jpg', $content);
Config Validation
Validate connection configs in createConnection():
if (empty($config['token'])) {
throw new \InvalidArgumentException('Token is required.');
}
How can I help you explore Laravel packages today?