Category: OOP

  • Factory Method

    Factory Method

    Factory method provides an interface for creating objects in a superclass,
    allows subclasses to decide wich class to instantiate.

    //Concrete or direct instantiation. Client code with no factory.
    $myEmailNotification = new emailNotification;
    $mySmsNotification = new smsNotification;
    
    //Abstracted object creation. Client code with a factory-based instantiation.
    $myEmailNotification = NotificationFactory::create('email');
    $mySmsNotification = NotificationFactory::create('sms');

    Factories allow client code to operate on generalizations, this is called coding to an interface, not an implementation and is a fundamental principle in object-oriented programming that promotes flexibility, maintainability, and testability in software design. 

    It means that client code should interact with a system through an abstract interface (or abstract class) rather than create the object directly, without worrying about the details of object creation. 

    <?php
    // interface defines a contract — all notification classes must have a send() method.
    interface Notification
    {
        public function send(string $to, string $message): void;
    }
    
    // Concrete implementations of different notification types
    
    class EmailNotification implements Notification
    {
        public function send(string $to, string $message): void
        {
            echo "Sending EMAIL to {$to}: {$message}" . PHP_EOL;
        }
    }
    
    class SmsNotification implements Notification
    {
        public function send(string $to, string $message): void
        {
            echo "Sending SMS to {$to}: {$message}" . PHP_EOL;
        }
    }
    
    class PushNotification implements Notification
    {
        public function send(string $to, string $message): void
        {
            echo "Sending PUSH notification to {$to}: {$message}" . PHP_EOL;
        }
    }
    
    // The Factory class — decides which notification to create
    class NotificationFactory
    {
        public static function create(string $type): Notification
        {
            return match (strtolower($type)) {
                'email' => new EmailNotification(),
                'sms'   => new SmsNotification(),
                'push'  => new PushNotification(),
                default => throw new InvalidArgumentException("Unknown notification type: {$type}")
            };
        }
    }
    
    // Usage exemple
    try {
        $notification = NotificationFactory::create('email');
        $notification->send('user@example.com', 'Your order has been shipped.');
    
        $notification = NotificationFactory::create('sms');
        $notification->send('+5547999999999', 'Your code is 123456.');
    
        $notification = NotificationFactory::create('push');
        $notification->send('User123', 'You have a new message.');
    } catch (Exception $e) {
        echo "Error: " . $e->getMessage();
    }

  • Singleton

    Singleton

    Singleton – ensure that a class has only one instance,
    while providing a global access point to this instance

    <?php
    
    class Logger
    {
        private static ?Logger $uniqueInstance = null;
        private array $logs = [];
    
        // prevents direct instantiation
        private function __construct() {}
    
        // no one outside the class can duplicate the instance.
        private function __clone() {}
    
        /* __wakeup must be public (by PHP’s design), 
        since it’s automatically called during unserialize().*/
        public function __wakeup()
        {
            throw new \Exception("Cannot unserialize a singleton.");
        }
    
        /* Lazy construction / initialization - returns the single instance 
        of this class*/
        public static function getInstance(): Logger
        {
            if (self::$uniqueInstance === null) {
                self::$uniqueInstance = new Logger();
            }
            return self::$uniqueInstance;
        }
    
        // Methods
        public function addLog(string $message): void
        {
            $this->logs[] = $message;
        }
    
        public function showLogs(): void
        {
            foreach ($this->logs as $log) {
                echo $log . PHP_EOL;
            }
        }
    }
    
    // Usage exemple
    $logger1 = Logger::getInstance();
    $logger2 = Logger::getInstance();
    
    $logger1->addLog("First log entry.");
    $logger2->addLog("Second log entry.");
    
    $logger1->showLogs();
    
    //Checking the solid proof...
    
    if ($logger1 === $logger2) {
        echo "Same instance" . PHP_EOL;
    } else {
        echo "Different instances" . PHP_EOL;
    }
    
    echo "Logger1 ID: " . spl_object_id($logger1) . PHP_EOL;
    echo "Logger2 ID: " . spl_object_id($logger2) . PHP_EOL;
    
    /*
    Output:
    
    First log entry.
    Second log entry.
    Same instance
    Logger1 ID: 1
    Logger2 ID: 1
    */

    The code above works perfectly for our purpose, but it’s not inheritance-safe, meaning it always creates a Logger instance, even if called from a subclass. Therefore:

    class FileLogger extends Logger {}
    FileLogger::getInstance(); //still returns the instance of Logger, not a instance of FileLogger

    If we want that each subclass has its own singleton, we must change this behavior by modifying the getInstance() method, like this:

    public static function getInstance(): Logger 
    {
        $className = static::class;
        if (!isset(self::$uniqueInstance[$className])) {
            self::$uniqueInstance[$className] = new static();
        }
        return self::$uniqueInstance[$className];
    }

    Comparison:

    FeatureVersion
    self
    Version
    static::class
    Supports inheritance❌ No✅ Yes
    Each subclass has its own singleton❌ No✅ Yes
    Refers to who?Refer to the class where the code was originally defined.Refer to the class that was actually called at runtime.
    When use?You want a single, strict singleton for only the original class.You expect inheritance or extensions of your original class.

    When Singleton pattern is useful?

    • Logger – Keep a single instance that writes all logs from different parts of the app.
    • Configuration Manager – Store settings (like API keys, database credentials, etc.) in one place.
    • Database Connection – Maintain one connection to the database.
    • Cache Manager – Keep a single cache handler (like Redis, Memcached, or file cache)
    • Session Manager – Handle user session data globally.
    • Event Dispatcher or Queue Manager – Centralize how events or background tasks are managed.