PHP Logograham-campbell/manager

The `graham-campbell/manager` package is a robust and flexible PHP library designed to simplify the management of various "drivers" for a particular service or component within an application. It provides an abstract base class, `GrahamCampbell\Manager\AbstractManager`, which encapsulates the logic for creating, retrieving, and extending different implementations (drivers) of a common interface or abstract class.

Purpose and Problem Solved:
In many applications, a single service might have multiple ways of performing its task. For example, a caching service could use Memcached, Redis, or local file storage. A notification service might send emails, SMS messages, or Slack notifications. Manually managing the instantiation and configuration of these different "drivers" can become cumbersome, especially when dealing with default configurations, runtime switching, and adding new drivers. The `graham-campbell/manager` package provides a structured and convention-based solution to this problem.

How it Works:
1. Abstract Manager: You extend the `AbstractManager` class to create your specific manager (e.g., `CacheManager`, `NotificationManager`).
2. Configuration Name (`getConfigName`): Your concrete manager class must implement `getConfigName()`, which returns a string key used to access the relevant configuration array (e.g., 'cache', 'notification'). This configuration typically defines the default driver and specific settings for each driver.
3. Driver Creation Methods: For each driver your manager supports, you implement a public method named `create[DriverName]Driver()`. For instance, `createEmailDriver()`, `createSmsDriver()`, `createRedisDriver()`, etc. These methods are responsible for instantiating and returning the specific driver object.
4. Default Driver (`createDefaultDriver`): The manager automatically determines the default driver based on your configuration (e.g., `config('notification.default')`). It then calls the corresponding `create[DriverName]Driver()` method. If a specific driver is requested via `manager->driver('sms')`, it calls `createSmsDriver()`.
5. Extensibility (`extend`): The package allows you to dynamically add new driver creation logic at runtime using the `extend()` method. This is particularly useful in frameworks like Laravel where you might want to allow third-party packages or application code to add new drivers to an existing manager without modifying the manager itself.

Key Benefits:
* Centralized Driver Management: All driver creation and configuration logic is consolidated in one place.
* Decoupling: It promotes loose coupling between your application logic and the specific driver implementations.
* Configuration-Driven: Easily switch between drivers by changing a configuration value.
* Extensibility: New drivers can be added without modifying the core manager class.
* Readability and Maintainability: Provides a clear, consistent pattern for managing multiple implementations of a service.
* Integration: While framework-agnostic, it's particularly well-suited for integration with IoC containers like Laravel's Service Container, making driver resolution and dependency injection straightforward.

Example Code

<?php

require 'vendor/autoload.php'; // Assuming composer autoloader is available (e.g., via `composer install`)

use GrahamCampbell\Manager\AbstractManager;
use InvalidArgumentException;

// --- 1. Define an Interface for our drivers ---
interface NotifierInterface
{
    public function send(string $message, string $recipient): void;
    public function getName(): string;
}

// --- 2. Implement Concrete Drivers ---
class EmailNotifier implements NotifierInterface
{
    protected string $senderEmail;

    public function __construct(string $senderEmail)
    {
        $this->senderEmail = $senderEmail;
    }

    public function send(string $message, string $recipient): void
    {
        echo "Sending Email from {$this->senderEmail} to {$recipient}: {$message}\n";
    }

    public function getName(): string
    {
        return 'email';
    }
}

class SmsNotifier implements NotifierInterface
{
    protected string $senderNumber;

    public function __construct(string $senderNumber)
    {
        $this->senderNumber = $senderNumber;
    }

    public function send(string $message, string $recipient): void
    {
        echo "Sending SMS from {$this->senderNumber} to {$recipient}: {$message}\n";
    }

    public function getName(): string
    {
        return 'sms';
    }
}

// --- 3. Create the Manager ---
class NotificationManager extends AbstractManager
{
    /
     * The config instance. (Simulated for this example)
     * In a real application, this would typically come from a framework's config service.
     *
     * @var array
     */
    protected array $config;

    /
     * Create a new notification manager instance.
     *
     * @param array $config The configuration array.
     * @return void
     */
    public function __construct(array $config)
    {
        $this->config = $config;
    }

    /
     * Get the configuration name.
     * This name corresponds to the top-level key in the config array (e.g., 'notification').
     *
     * @return string
     */
    protected function getConfigName(): string
    {
        return 'notification';
    }

    /
     * Get the default driver name from the configuration.
     *
     * @throws \InvalidArgumentException If the default driver is not configured or invalid.
     *
     * @return string
     */
    protected function getDefaultDriver(): string
    {
        // In a framework like Laravel, you'd typically use `config($this->getConfigName().'.default')`
        return $this->config[$this->getConfigName()]['default'] ?? parent::getDefaultDriver();
    }

    /
     * Get the configuration for a specific driver.
     *
     * @param string $name The name of the driver (e.g., 'email', 'sms').
     *
     * @throws \InvalidArgumentException If the driver is not configured.
     *
     * @return array
     */
    protected function getConnectionConfig(string $name): array
    {
        // In a framework like Laravel, you'd typically use `config($this->getConfigName().'.drivers.'.$name)`
        if (!isset($this->config[$this->getConfigName()]['drivers'][$name])) {
            throw new InvalidArgumentException(sprintf('The [%s] driver has not been configured.', $name));
        }
        return $this->config[$this->getConfigName()]['drivers'][$name];
    }

    /
     * Create the email driver instance.
     *
     * This method follows the `create[DriverName]Driver` convention.
     *
     * @return \NotifierInterface
     */
    protected function createEmailDriver(): NotifierInterface
    {
        $config = $this->getConnectionConfig('email');
        return new EmailNotifier($config['sender_email']);
    }

    /
     * Create the sms driver instance.
     *
     * This method follows the `create[DriverName]Driver` convention.
     *
     * @return \NotifierInterface
     */
    protected function createSmsDriver(): NotifierInterface
    {
        $config = $this->getConnectionConfig('sms');
        return new SmsNotifier($config['sender_number']);
    }

    // You could add other fixed drivers here, e.g., createSlackDriver()
}

// --- 4. Simulate Application Configuration ---
$appConfig = [
    'notification' => [
        'default' => 'email', // The default notification method
        'drivers' => [
            'email' => [
                'sender_email' => 'noreply@example.com',
                'log_level' => 'info' // Example of other driver-specific settings
            ],
            'sms' => [
                'sender_number' => '+15551234567',
                'api_key' => 'your_sms_api_key' // Example of other driver-specific settings
            ],
        ],
    ],
];

// --- 5. Usage Example ---
echo "--- Using NotificationManager ---\n";

// Instantiate the manager with our simulated config
$notificationManager = new NotificationManager($appConfig);

// Get the default driver (configured as 'email')
$defaultNotifier = $notificationManager->driver();
echo "Default Notifier (" . $defaultNotifier->getName() . "):\n";
$defaultNotifier->send("Hello via default!", "user@example.com");

echo "\n";

// Get a specific driver (SMS)
$smsNotifier = $notificationManager->driver('sms');
echo "SMS Notifier (" . $smsNotifier->getName() . "):\n";
$smsNotifier->send("Hello via SMS!", "+1234567890");

echo "\n";

// Get another specific driver (email)
$emailNotifier = $notificationManager->driver('email');
echo "Email Notifier (" . $emailNotifier->getName() . "):\n";
$emailNotifier->send("Hello via Email!", "another.user@example.com");

echo "\n--- Extending the Manager at Runtime ---\n";

// Let's say we want to add a "Slack" notifier dynamically without modifying NotificationManager class
class SlackNotifier implements NotifierInterface
{
    protected string $webhookUrl;

    public function __construct(string $webhookUrl)
    {
        $this->webhookUrl = $webhookUrl;
    }

    public function send(string $message, string $recipient): void
    {
        // In a real application, this would make an HTTP request to the webhook URL
        echo "Sending Slack message to {$recipient} via webhook {$this->webhookUrl}: {$message}\n";
    }

    public function getName(): string
    {
        return 'slack';
    }
}

// Update the application configuration to include the new 'slack' driver settings.
// In a real framework, this would be managed by the configuration service.
$appConfig['notification']['drivers']['slack'] = [
    'webhook_url' => 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX',
    'channel' => '#general' // Example of Slack-specific setting
];

// We need to re-instantiate the manager or provide a way to update its internal config
// if it were mutable, as the `extend` method receives the configuration for the driver.
// For simplicity and clarity in this example, we'll re-instantiate `NotificationManager`
// with the updated `$appConfig`. In a framework, the manager instance would often be a singleton
// and the config system dynamic.
$notificationManagerExtended = new NotificationManager($appConfig);

// Dynamically add a new driver using the `extend` method.
// The closure receives the specific driver's configuration.
$notificationManagerExtended->extend('slack', function (array $driverConfig) {
    return new SlackNotifier($driverConfig['webhook_url']);
});

// Now get the dynamically added Slack driver
$slackNotifier = $notificationManagerExtended->driver('slack');
echo "Slack Notifier (" . $slackNotifier->getName() . "):\n";
$slackNotifier->send("Urgent alert!", "#dev-channel");

echo "\n--- Testing invalid driver access ---\n";
try {
    // Attempt to get a driver that does not exist and has not been extended
    $notificationManager->driver('nonexistent');
} catch (InvalidArgumentException $e) {
    echo "Caught exception for nonexistent driver: " . $e->getMessage() . "\n";
}

?>