{"id":40464082,"url":"https://github.com/neuron-php/patterns","last_synced_at":"2026-01-20T18:01:12.224Z","repository":{"id":39885221,"uuid":"258238779","full_name":"Neuron-PHP/patterns","owner":"Neuron-PHP","description":"Criteria, Observer, Registry and Singleton.","archived":false,"fork":false,"pushed_at":"2026-01-13T18:11:18.000Z","size":109,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-01-13T19:23:43.503Z","etag":null,"topics":["design-patterns","php8"],"latest_commit_sha":null,"homepage":"https://neuronphp.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Neuron-PHP.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-04-23T14:52:53.000Z","updated_at":"2026-01-13T18:11:22.000Z","dependencies_parsed_at":"2023-01-21T08:05:35.037Z","dependency_job_id":null,"html_url":"https://github.com/Neuron-PHP/patterns","commit_stats":null,"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/Neuron-PHP/patterns","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neuron-PHP%2Fpatterns","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neuron-PHP%2Fpatterns/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neuron-PHP%2Fpatterns/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neuron-PHP%2Fpatterns/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Neuron-PHP","download_url":"https://codeload.github.com/Neuron-PHP/patterns/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neuron-PHP%2Fpatterns/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607962,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["design-patterns","php8"],"created_at":"2026-01-20T18:00:51.552Z","updated_at":"2026-01-20T18:01:12.082Z","avatar_url":"https://github.com/Neuron-PHP.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/Neuron-PHP/patterns/actions/workflows/ci.yml/badge.svg)](https://github.com/Neuron-PHP/patterns/actions)\n[![codecov](https://codecov.io/gh/Neuron-PHP/patterns/branch/develop/graph/badge.svg)](https://codecov.io/gh/Neuron-PHP/patterns)\n# Neuron-PHP Patterns\n\nA comprehensive design patterns library for PHP 8.0+ that provides robust implementations of common software design patterns including Singleton, Registry, Observer, Command, and Criteria patterns.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Patterns Overview](#patterns-overview)\n- [Registry Pattern](#registry-pattern)\n- [Singleton Pattern](#singleton-pattern)\n- [Observer Pattern](#observer-pattern)\n- [Command Pattern](#command-pattern)\n- [Criteria Pattern](#criteria-pattern)\n- [IRunnable Interface](#irunnable-interface)\n- [Usage Examples](#usage-examples)\n- [Testing](#testing)\n- [Best Practices](#best-practices)\n- [More Information](#more-information)\n\n## Installation\n\n### Requirements\n\n- PHP 8.0 or higher\n- Extensions: curl, json\n- Composer\n\n### Install via Composer\n\n```bash\ncomposer require neuron-php/patterns\n```\n\n## Patterns Overview\n\nThe Patterns component provides production-ready implementations of:\n\n- **Registry**: Global object storage and service locator\n- **Singleton**: Single instance management with multiple storage backends\n- **Observer**: Event notification between objects\n- **Command**: Encapsulation of operations as objects\n- **Criteria**: Flexible filtering and selection of entities\n\n## Registry Pattern\n\nThe Registry pattern provides centralized storage for objects and acts as a service locator throughout your application.\n\n### Basic Usage\n\n```php\nuse Neuron\\Patterns\\Registry;\n\n// Get the registry instance (singleton)\n$registry = Registry::getInstance();\n\n// Store objects\n$registry-\u003eset('database', $dbConnection);\n$registry-\u003eset('cache', $cacheManager);\n$registry-\u003eset('config.app', $appConfig);\n\n// Retrieve objects\n$db = $registry-\u003eget('database');\n$cache = $registry-\u003eget('cache');\n\n// Check existence\nif ($registry-\u003ehas('cache')) {\n    // Cache is available\n}\n\n// Remove objects\n$registry-\u003eremove('temp.data');\n\n// Clear all objects\n$registry-\u003ereset();\n```\n\n### Nested Keys\n\n```php\n// Support for dot notation\n$registry-\u003eset('services.email.smtp', $smtpService);\n$registry-\u003eset('services.email.templates', $templateEngine);\n\n// Retrieve nested values\n$smtp = $registry-\u003eget('services.email.smtp');\n```\n\n### Service Locator Pattern\n\n```php\nclass ServiceContainer\n{\n    private Registry $registry;\n\n    public function __construct()\n    {\n        $this-\u003eregistry = Registry::getInstance();\n        $this-\u003eregisterServices();\n    }\n\n    private function registerServices(): void\n    {\n        // Register core services\n        $this-\u003eregistry-\u003eset('logger', new Logger());\n        $this-\u003eregistry-\u003eset('mailer', new Mailer());\n        $this-\u003eregistry-\u003eset('cache', new CacheManager());\n\n        // Register factories\n        $this-\u003eregistry-\u003eset('db.factory', function($config) {\n            return new DatabaseConnection($config);\n        });\n    }\n\n    public function get(string $service)\n    {\n        $service = $this-\u003eregistry-\u003eget($service);\n\n        // Resolve factories\n        if (is_callable($service)) {\n            return $service($this-\u003eregistry-\u003eget('config'));\n        }\n\n        return $service;\n    }\n}\n```\n\n## Singleton Pattern\n\nThe Singleton pattern ensures a class has only one instance and provides global access to it. The component includes multiple storage backends.\n\n### Available Storage Backends\n\n- **Memory**: In-process memory storage (default)\n- **Session**: PHP session-based storage\n- **Memcache**: Memcache server storage\n- **Redis**: Redis server storage\n\n### Memory Singleton\n\n```php\nuse Neuron\\Patterns\\Singleton\\Memory;\n\nclass Configuration extends Memory\n{\n    private array $settings = [];\n\n    public function set(string $key, $value): void\n    {\n        $this-\u003esettings[$key] = $value;\n    }\n\n    public function get(string $key)\n    {\n        return $this-\u003esettings[$key] ?? null;\n    }\n}\n\n// Usage\n$config = Configuration::getInstance();\n$config-\u003eset('app.name', 'My Application');\n\n// Same instance everywhere\n$config2 = Configuration::getInstance();\necho $config2-\u003eget('app.name'); // \"My Application\"\n```\n\n### Session Singleton\n\n```php\nuse Neuron\\Patterns\\Singleton\\Session;\n\nclass UserSession extends Session\n{\n    private ?User $user = null;\n\n    public function login(User $user): void\n    {\n        $this-\u003euser = $user;\n        $this-\u003eserialize(); // Persist to session\n    }\n\n    public function getUser(): ?User\n    {\n        return $this-\u003euser;\n    }\n\n    public function logout(): void\n    {\n        $this-\u003euser = null;\n        $this-\u003einvalidate(); // Clear from session\n    }\n}\n\n// Usage\nsession_start();\n$session = UserSession::getInstance();\n$session-\u003elogin($user);\n\n// Available across requests\n$session = UserSession::getInstance();\n$currentUser = $session-\u003egetUser();\n```\n\n### Redis Singleton\n\n```php\nuse Neuron\\Patterns\\Singleton\\Redis;\n\nclass GlobalCache extends Redis\n{\n    private array $cache = [];\n\n    protected function getRedisKey(): string\n    {\n        return 'app:global:cache';\n    }\n\n    public function set(string $key, $value, int $ttl = 3600): void\n    {\n        $this-\u003ecache[$key] = [\n            'value' =\u003e $value,\n            'expires' =\u003e time() + $ttl\n        ];\n        $this-\u003eserialize(); // Persist to Redis\n    }\n\n    public function get(string $key)\n    {\n        if (!isset($this-\u003ecache[$key])) {\n            return null;\n        }\n\n        if ($this-\u003ecache[$key]['expires'] \u003c time()) {\n            unset($this-\u003ecache[$key]);\n            return null;\n        }\n\n        return $this-\u003ecache[$key]['value'];\n    }\n}\n\n// Shared across application instances\n$cache = GlobalCache::getInstance();\n$cache-\u003eset('api.token', $token, 7200);\n```\n\n### Memcache Singleton\n\n```php\nuse Neuron\\Patterns\\Singleton\\Memcache;\n\nclass SharedState extends Memcache\n{\n    private array $state = [];\n\n    protected function getMemcacheKey(): string\n    {\n        return 'app:shared:state';\n    }\n\n    public function setState(string $key, $value): void\n    {\n        $this-\u003estate[$key] = $value;\n        $this-\u003eserialize(); // Persist to Memcache\n    }\n\n    public function getState(string $key)\n    {\n        return $this-\u003estate[$key] ?? null;\n    }\n}\n\n// Shared across servers\n$state = SharedState::getInstance();\n$state-\u003esetState('maintenance.mode', true);\n```\n\n## Observer Pattern\n\nThe Observer pattern defines a one-to-many dependency between objects, allowing multiple observers to be notified of state changes.\n\n### Basic Implementation\n\n```php\nuse Neuron\\Patterns\\Observer\\ObservableTrait;\nuse Neuron\\Patterns\\Observer\\IObserver;\n\n// Observable class\nclass Product\n{\n    use ObservableTrait;\n\n    private string $name;\n    private float $price;\n\n    public function setPrice(float $price): void\n    {\n        $oldPrice = $this-\u003eprice;\n        $this-\u003eprice = $price;\n\n        // Notify observers of price change\n        $this-\u003enotifyObservers($this, $oldPrice, $price);\n    }\n\n    public function getPrice(): float\n    {\n        return $this-\u003eprice;\n    }\n}\n\n// Observer implementation\nclass PriceWatcher implements IObserver\n{\n    public function observableUpdate($observable, ...$params): void\n    {\n        [$oldPrice, $newPrice] = $params;\n\n        if ($newPrice \u003c $oldPrice) {\n            $discount = (($oldPrice - $newPrice) / $oldPrice) * 100;\n            echo \"Price dropped by {$discount}%!\\n\";\n        }\n    }\n}\n\n// Usage\n$product = new Product();\n$watcher = new PriceWatcher();\n\n$product-\u003eaddObserver($watcher);\n$product-\u003esetPrice(99.99);  // Initial price\n$product-\u003esetPrice(79.99);  // Triggers: \"Price dropped by 20%!\"\n\n// Clean up\n$product-\u003eremoveObserver($watcher);\n```\n\n### Multiple Observers\n\n```php\nclass Stock\n{\n    use ObservableTrait;\n\n    private int $quantity = 0;\n\n    public function updateQuantity(int $quantity): void\n    {\n        $this-\u003equantity = $quantity;\n        $this-\u003enotifyObservers($this, $quantity);\n    }\n}\n\nclass LowStockAlert implements IObserver\n{\n    private int $threshold;\n\n    public function __construct(int $threshold = 10)\n    {\n        $this-\u003ethreshold = $threshold;\n    }\n\n    public function observableUpdate($observable, ...$params): void\n    {\n        $quantity = $params[0];\n\n        if ($quantity \u003c $this-\u003ethreshold) {\n            $this-\u003esendAlert(\"Low stock warning: {$quantity} items remaining\");\n        }\n    }\n\n    private function sendAlert(string $message): void\n    {\n        // Send email, SMS, etc.\n        echo \"ALERT: {$message}\\n\";\n    }\n}\n\nclass StockLogger implements IObserver\n{\n    public function observableUpdate($observable, ...$params): void\n    {\n        $quantity = $params[0];\n        error_log(\"Stock updated: {$quantity} items\");\n    }\n}\n\n// Usage\n$stock = new Stock();\n$stock-\u003eaddObserver(new LowStockAlert(5));\n$stock-\u003eaddObserver(new StockLogger());\n\n$stock-\u003eupdateQuantity(3); // Triggers both observers\n```\n\n## Command Pattern\n\nThe Command pattern encapsulates operations as objects, allowing you to parameterize clients with different requests, queue operations, and support undo operations.\n\n### Command Interface\n\n```php\nuse Neuron\\Patterns\\Command\\ICommand;\n\nclass CreateUserCommand implements ICommand\n{\n    private UserRepository $repository;\n\n    public function __construct(UserRepository $repository)\n    {\n        $this-\u003erepository = $repository;\n    }\n\n    public function execute(?array $params = null): mixed\n    {\n        $user = new User(\n            $params['name'] ?? throw new \\InvalidArgumentException('Name required'),\n            $params['email'] ?? throw new \\InvalidArgumentException('Email required'),\n            $params['password'] ?? throw new \\InvalidArgumentException('Password required')\n        );\n\n        return $this-\u003erepository-\u003esave($user);\n    }\n}\n```\n\n### Command Invoker\n\n```php\nuse Neuron\\Patterns\\Command\\Invoker;\n\n$invoker = new Invoker();\n\n// Set and execute command\n$invoker-\u003esetCommand(new CreateUserCommand($userRepo));\n$user = $invoker-\u003eexecute([\n    'name' =\u003e 'John Doe',\n    'email' =\u003e 'john@example.com',\n    'password' =\u003e 'secret123'\n]);\n```\n\n### Command Factory\n\n```php\nuse Neuron\\Patterns\\Command\\Factory;\n\nclass CommandFactory extends Factory\n{\n    protected array $commands = [\n        'user.create' =\u003e CreateUserCommand::class,\n        'user.delete' =\u003e DeleteUserCommand::class,\n        'user.update' =\u003e UpdateUserCommand::class,\n        'email.send' =\u003e SendEmailCommand::class,\n    ];\n\n    public function createCommand(string $name): ICommand\n    {\n        $class = $this-\u003ecommands[$name] ?? throw new \\InvalidArgumentException(\"Unknown command: {$name}\");\n\n        return $this-\u003econtainer-\u003eget($class);\n    }\n}\n\n// Usage\n$factory = new CommandFactory();\n$command = $factory-\u003ecreateCommand('user.create');\n$result = $command-\u003eexecute($params);\n```\n\n### Macro Commands\n\n```php\nclass MacroCommand implements ICommand\n{\n    private array $commands = [];\n\n    public function addCommand(ICommand $command): void\n    {\n        $this-\u003ecommands[] = $command;\n    }\n\n    public function execute(?array $params = null): mixed\n    {\n        $results = [];\n\n        foreach ($this-\u003ecommands as $command) {\n            $results[] = $command-\u003eexecute($params);\n        }\n\n        return $results;\n    }\n}\n\n// Usage\n$macro = new MacroCommand();\n$macro-\u003eaddCommand(new ValidateUserCommand());\n$macro-\u003eaddCommand(new CreateUserCommand());\n$macro-\u003eaddCommand(new SendWelcomeEmailCommand());\n$macro-\u003eaddCommand(new LogRegistrationCommand());\n\n$results = $macro-\u003eexecute($userData);\n```\n\n### Undoable Commands\n\n```php\ninterface IUndoableCommand extends ICommand\n{\n    public function undo(): void;\n}\n\nclass DeleteFileCommand implements IUndoableCommand\n{\n    private string $filepath;\n    private ?string $backupContent = null;\n\n    public function __construct(string $filepath)\n    {\n        $this-\u003efilepath = $filepath;\n    }\n\n    public function execute(?array $params = null): mixed\n    {\n        if (file_exists($this-\u003efilepath)) {\n            $this-\u003ebackupContent = file_get_contents($this-\u003efilepath);\n            unlink($this-\u003efilepath);\n            return true;\n        }\n        return false;\n    }\n\n    public function undo(): void\n    {\n        if ($this-\u003ebackupContent !== null) {\n            file_put_contents($this-\u003efilepath, $this-\u003ebackupContent);\n        }\n    }\n}\n\n// Command history for undo support\nclass CommandHistory\n{\n    private array $history = [];\n\n    public function execute(ICommand $command, ?array $params = null): mixed\n    {\n        $result = $command-\u003eexecute($params);\n\n        if ($command instanceof IUndoableCommand) {\n            $this-\u003ehistory[] = $command;\n        }\n\n        return $result;\n    }\n\n    public function undo(): void\n    {\n        $command = array_pop($this-\u003ehistory);\n\n        if ($command instanceof IUndoableCommand) {\n            $command-\u003eundo();\n        }\n    }\n}\n```\n\n## Criteria Pattern\n\nThe Criteria pattern provides a way to filter collections of objects using composable criteria.\n\n### Basic Criteria\n\n```php\nuse Neuron\\Patterns\\Criteria\\ICriteria;\nuse Neuron\\Patterns\\Criteria\\Base;\n\nclass ActiveCriteria extends Base\n{\n    public function meetCriteria(array $entities): array\n    {\n        return array_filter($entities, function($entity) {\n            return $entity-\u003eisActive();\n        });\n    }\n}\n\nclass PremiumCriteria extends Base\n{\n    public function meetCriteria(array $entities): array\n    {\n        return array_filter($entities, function($entity) {\n            return $entity-\u003eisPremium();\n        });\n    }\n}\n\n// Usage\n$users = User::all();\n$activeCriteria = new ActiveCriteria();\n$activeUsers = $activeCriteria-\u003emeetCriteria($users);\n```\n\n### KeyValue Criteria\n\n```php\nuse Neuron\\Patterns\\Criteria\\KeyValue;\n\n// Filter by exact key-value match\n$adminCriteria = new KeyValue('role', 'admin');\n$admins = $adminCriteria-\u003emeetCriteria($users);\n\n// Filter by status\n$publishedCriteria = new KeyValue('status', 'published');\n$publishedPosts = $publishedCriteria-\u003emeetCriteria($posts);\n```\n\n### Logical Criteria Combinations\n\n```php\nuse Neuron\\Patterns\\Criteria\\AndCriteria;\nuse Neuron\\Patterns\\Criteria\\OrCriteria;\nuse Neuron\\Patterns\\Criteria\\NotCriteria;\n\n// AND combination\n$activeCriteria = new KeyValue('status', 'active');\n$premiumCriteria = new KeyValue('type', 'premium');\n$activePremium = new AndCriteria($activeCriteria, $premiumCriteria);\n$result = $activePremium-\u003emeetCriteria($users);\n\n// OR combination\n$adminCriteria = new KeyValue('role', 'admin');\n$moderatorCriteria = new KeyValue('role', 'moderator');\n$staffCriteria = new OrCriteria($adminCriteria, $moderatorCriteria);\n$staff = $staffCriteria-\u003emeetCriteria($users);\n\n// NOT criteria\n$notBanned = new NotCriteria(new KeyValue('status', 'banned'));\n$activeUsers = $notBanned-\u003emeetCriteria($users);\n```\n\n### Complex Criteria Composition\n\n```php\n// Find active premium users who are not admins\n$active = new KeyValue('status', 'active');\n$premium = new KeyValue('subscription', 'premium');\n$notAdmin = new NotCriteria(new KeyValue('role', 'admin'));\n\n$criteria = new AndCriteria(\n    $active,\n    new AndCriteria($premium, $notAdmin)\n);\n\n$targetUsers = $criteria-\u003emeetCriteria($allUsers);\n```\n\n### Custom Criteria\n\n```php\nclass DateRangeCriteria extends Base\n{\n    private \\DateTime $start;\n    private \\DateTime $end;\n    private string $field;\n\n    public function __construct(string $field, \\DateTime $start, \\DateTime $end)\n    {\n        $this-\u003efield = $field;\n        $this-\u003estart = $start;\n        $this-\u003eend = $end;\n    }\n\n    public function meetCriteria(array $entities): array\n    {\n        return array_filter($entities, function($entity) {\n            $date = $entity-\u003e{$this-\u003efield};\n            return $date \u003e= $this-\u003estart \u0026\u0026 $date \u003c= $this-\u003eend;\n        });\n    }\n}\n\n// Filter orders by date range\n$lastWeek = new DateRangeCriteria(\n    'createdAt',\n    new \\DateTime('-7 days'),\n    new \\DateTime('now')\n);\n$recentOrders = $lastWeek-\u003emeetCriteria($orders);\n```\n\n## IRunnable Interface\n\nThe IRunnable interface provides a contract for executable objects.\n\n```php\nuse Neuron\\Patterns\\IRunnable;\n\nclass DataProcessor implements IRunnable\n{\n    private array $data;\n\n    public function __construct(array $data)\n    {\n        $this-\u003edata = $data;\n    }\n\n    public function run(): void\n    {\n        foreach ($this-\u003edata as $item) {\n            $this-\u003eprocess($item);\n        }\n    }\n\n    private function process($item): void\n    {\n        // Processing logic\n    }\n}\n\n// Usage with task runner\nclass TaskRunner\n{\n    private array $tasks = [];\n\n    public function addTask(IRunnable $task): void\n    {\n        $this-\u003etasks[] = $task;\n    }\n\n    public function runAll(): void\n    {\n        foreach ($this-\u003etasks as $task) {\n            $task-\u003erun();\n        }\n    }\n}\n\n$runner = new TaskRunner();\n$runner-\u003eaddTask(new DataProcessor($data));\n$runner-\u003eaddTask(new CacheWarmer());\n$runner-\u003eaddTask(new EmailQueue());\n$runner-\u003erunAll();\n```\n\n## Usage Examples\n\n### Service Locator with Registry\n\n```php\nclass Application\n{\n    public function bootstrap(): void\n    {\n        $registry = Registry::getInstance();\n\n        // Register core services\n        $registry-\u003eset('config', new Configuration());\n        $registry-\u003eset('logger', new Logger());\n        $registry-\u003eset('db', new DatabaseConnection($registry-\u003eget('config')));\n        $registry-\u003eset('cache', new CacheManager());\n\n        // Register factories\n        $registry-\u003eset('user.repository', function() use ($registry) {\n            return new UserRepository($registry-\u003eget('db'));\n        });\n    }\n\n    public function getService(string $name)\n    {\n        $service = Registry::getInstance()-\u003eget($name);\n\n        // Resolve factories\n        if (is_callable($service)) {\n            return $service();\n        }\n\n        return $service;\n    }\n}\n```\n\n### Event-Driven Architecture with Observer\n\n```php\nclass EventDrivenSystem\n{\n    use ObservableTrait;\n\n    public function processOrder(Order $order): void\n    {\n        // Process the order\n        $order-\u003eprocess();\n\n        // Notify all observers\n        $this-\u003enotifyObservers('order.processed', $order);\n    }\n}\n\nclass InventoryManager implements IObserver\n{\n    public function observableUpdate($observable, ...$params): void\n    {\n        [$event, $order] = $params;\n\n        if ($event === 'order.processed') {\n            foreach ($order-\u003egetItems() as $item) {\n                $this-\u003edecrementStock($item-\u003egetSku(), $item-\u003egetQuantity());\n            }\n        }\n    }\n}\n\nclass EmailNotifier implements IObserver\n{\n    public function observableUpdate($observable, ...$params): void\n    {\n        [$event, $order] = $params;\n\n        if ($event === 'order.processed') {\n            $this-\u003esendOrderConfirmation($order);\n        }\n    }\n}\n\n// Setup\n$system = new EventDrivenSystem();\n$system-\u003eaddObserver(new InventoryManager());\n$system-\u003eaddObserver(new EmailNotifier());\n$system-\u003eaddObserver(new ShippingNotifier());\n\n// Process order triggers all observers\n$system-\u003eprocessOrder($order);\n```\n\n## Testing\n\n### Testing Singletons\n\n```php\nuse PHPUnit\\Framework\\TestCase;\n\nclass SingletonTest extends TestCase\n{\n    protected function tearDown(): void\n    {\n        // Clean up singleton instances between tests\n        MyAppConfig::getInstance()-\u003einvalidate();\n    }\n\n    public function testSingleInstance(): void\n    {\n        $instance1 = MyAppConfig::getInstance();\n        $instance2 = MyAppConfig::getInstance();\n\n        $this-\u003eassertSame($instance1, $instance2);\n    }\n\n    public function testPersistence(): void\n    {\n        $config = MyAppConfig::getInstance();\n        $config-\u003eset('test.key', 'test.value');\n\n        $config2 = MyAppConfig::getInstance();\n        $this-\u003eassertEquals('test.value', $config2-\u003eget('test.key'));\n    }\n}\n```\n\n### Testing Observers\n\n```php\nclass ObserverTest extends TestCase\n{\n    public function testObserverNotification(): void\n    {\n        $observable = new TestObservable();\n\n        $observer = $this-\u003ecreateMock(IObserver::class);\n        $observer-\u003eexpects($this-\u003eonce())\n                 -\u003emethod('observableUpdate')\n                 -\u003ewith($observable, 'test', 'data');\n\n        $observable-\u003eaddObserver($observer);\n        $observable-\u003etriggerEvent('test', 'data');\n    }\n}\n```\n\n### Testing Commands\n\n```php\nclass CommandTest extends TestCase\n{\n    public function testCommandExecution(): void\n    {\n        $repository = $this-\u003ecreateMock(UserRepository::class);\n        $repository-\u003eexpects($this-\u003eonce())\n                   -\u003emethod('save')\n                   -\u003ewillReturn(true);\n\n        $command = new CreateUserCommand($repository);\n        $result = $command-\u003eexecute([\n            'name' =\u003e 'Test User',\n            'email' =\u003e 'test@example.com',\n            'password' =\u003e 'password123'\n        ]);\n\n        $this-\u003eassertTrue($result);\n    }\n}\n```\n\n## Best Practices\n\n### Registry Usage\n\n```php\n// Good: Clear service names\n$registry-\u003eset('database.connection', $db);\n$registry-\u003eset('cache.manager', $cache);\n\n// Avoid: Unclear or conflicting names\n$registry-\u003eset('db', $db);  // Too generic\n$registry-\u003eset('temp', $data);  // Unclear purpose\n```\n\n### Singleton Design\n\n```php\n// Good: Stateless or minimal state\nclass Logger extends Memory\n{\n    private string $logFile;\n\n    public function log(string $message): void\n    {\n        // Stateless operation\n        file_put_contents($this-\u003elogFile, $message, FILE_APPEND);\n    }\n}\n\n// Avoid: Heavy state in singletons\nclass BadSingleton extends Memory\n{\n    private array $heavyData = [];  // Can grow unbounded\n    private array $connections = []; // Resource management issues\n}\n```\n\n### Observer Pattern\n\n```php\n// Good: Specific observer interfaces\ninterface OrderObserver\n{\n    public function onOrderCreated(Order $order): void;\n    public function onOrderShipped(Order $order): void;\n    public function onOrderCancelled(Order $order): void;\n}\n\n// Good: Clear event data\n$this-\u003enotifyObservers('order.status.changed', $order, $oldStatus, $newStatus);\n\n// Avoid: Generic updates without context\n$this-\u003enotifyObservers($someData);  // What changed?\n```\n\n### Command Pattern\n\n```php\n// Good: Self-contained commands\nclass SendEmailCommand implements ICommand\n{\n    private Mailer $mailer;\n\n    public function __construct(Mailer $mailer)\n    {\n        $this-\u003emailer = $mailer;\n    }\n\n    public function execute(?array $params = null): mixed\n    {\n        // Validate params\n        $this-\u003evalidate($params);\n\n        // Execute with error handling\n        try {\n            return $this-\u003emailer-\u003esend(\n                $params['to'],\n                $params['subject'],\n                $params['body']\n            );\n        } catch (\\Exception $e) {\n            // Handle appropriately\n            throw new CommandException('Email send failed', 0, $e);\n        }\n    }\n}\n```\n\n## More Information\n\n- **Neuron Framework**: [neuronphp.com](http://neuronphp.com)\n- **GitHub**: [github.com/neuron-php/patterns](https://github.com/neuron-php/patterns)\n- **Packagist**: [packagist.org/packages/neuron-php/patterns](https://packagist.org/packages/neuron-php/patterns)\n\n## License\n\nMIT License - see LICENSE file for details","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuron-php%2Fpatterns","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneuron-php%2Fpatterns","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuron-php%2Fpatterns/lists"}