Why Learn Advanced PHP and PHP Design Patterns?

Oct 21, 2024 |
Twitter

You've learned all about OOP in PHP, but now what? When you make an object, you have no idea what to do with it. This is why Design Patterns matter.

I have been working as developer and tech lead for 19 years. I’ve written code for over 200 projects. Some of it was cool, most of it was really boring. The cooler the code, the harder it is to maintain. The best code is simple and easy to explain, and the way it works even has a name. 

The name that your senior colleagues will recognize. It's name is one of the design patterns, like these:

  1. Problem: I need to use one class inside another, and my code’s becomes a mess quick
    Solution: Dependency Injection
    by injecting dependencies, you pass configured objects into a class - instead of creating them inside another class (thus developing dependency)

    class UserController {
        private $userService;
        public function __construct(UserService $userService) { 
            $this->userService = $userService; 
        }
        public function getUser($id) {
            return $this->userService->findUser($id);
        }
    }
    
    // Usage
    $controller = new UserController(new UserService());
  2. Problem: I need to create complex objects, but it’s a lot of copy-paste. That can't be good, right?
    Solution: Factory Pattern
    Instead of creating objects everywhere, let a Factory handle it.

    class UserFactory {
        public function create($userId, $userType) {
            switch ($userType) {
                case 'admin':
                    return new AdminUser($userId);
                case 'visitor':
                    return new VisitorUser($userId);
                default:
                    throw new Exception("Invalid user type");
            }
        }
    }
    
    // Usage example
    $userFactory = new UserFactory();
    $adminUser = $userFactory->create(1, 'admin');
    $visitorUser = $userFactory->create(2, 'visitor');

  3. Problem: I need to add new features to an object - but really I cannot change the whole thing
    Solution: Decorator Pattern
    "Wrap" an object to add new features, without messing with its original structure.

    interface Product { 
        public function getPrice(); 
        public function getDescription(); 
    }
    
    class BasicProduct implements Product {
        public function getPrice() { return 100; }
        public function getDescription() { return "Basic product"; }
    }
    
    class DiscountDecorator implements Product {
        public function __construct(private Product $product) {}
        public function getPrice() { return $this->product->getPrice() - 20; }
        public function getDescription() { return $this->product->getDescription() . ", with discount"; }
    }
    
    class GiftWrapDecorator implements Product {
        public function __construct(private Product $product) {}
        public function getPrice() { return $this->product->getPrice() + 5; }
        public function getDescription() { return $this->product->getDescription() . ", gift wrapped"; }
    }
    
    // Usage
    $product = new GiftWrapDecorator(new DiscountDecorator(new BasicProduct()));
    echo $product->getDescription() . " costs $" . $product->getPrice();
    
  4. Problem: This process is too complicated — there are too many moving parts to do a simple thing
    Solution: Facade Pattern
    The Facade pattern gives you a simple interface to manage complex systems. It hides the complexity and makes your code easier to work with. 

    
    class OrderProcessor {
        public function processOrder($orderId) {
            $inventory = new Inventory();
            $payment = new Payment();
            $shipping = new Shipping();
    
            if ($inventory->checkStock($orderId) && $payment->processPayment($orderId)) {
                $shipping->shipOrder($orderId);
                return "Order processed successfully.";
            }
            return "Order processing failed.";
        }
    }
    
    class Inventory {
        public function checkStock($orderId) { return true; }
    }
    
  5. Problem: I want something to happen automatically when something else happens, but I need to change too much unpredictable code
    Solution: Observer Pattern
    The Observer pattern lets you set up automatic reactions to certain events. When something happens (like an order being placed), all the "observers" are notified and can respond. This keeps your code flexible and allows you to easily add or remove actions (like sending emails or updating stock) without touching the core logic.

    interface Observer {
        public function update($message);
    }
    
    class ConcreteObserver implements Observer {
        public function __construct(private $name) {}
        public function update($message) {
            echo "$this->name received: $message\n";
        }
    }
    
    class Subject {
        private $observers = [];
        public function attach(Observer $observer) { $this->observers[] = $observer; }
        public function notify($message) {
            foreach ($this->observers as $observer) $observer->update($message);
        }
    }
    
    // Usage
    $subject = new Subject();
    $subject->attach(new ConcreteObserver("Observer 1"));
    $subject->attach(new ConcreteObserver("Observer 2"));
    
    $subject->notify("Hello Observers!");

I've made few videos on how to apply these patterns on real life code

First one is a real life code a student has sent me for review

and second one is a sneap peek inside internals of popular framework like Laravel

Categories: : Clean code