Design Patterns that you may actually use

Design Patterns that you may actually use

In software development, there are many design patterns; they are general concepts that will help you with common problems in your system, kind of a list of ideas that you look over and choose once that work best for your project. Here I will talk only about a couple of them that I think are the most useful, less abstract ones you may actually use.

Singleton

Singleton is a class that can only have one instance of itself. Imagine you have a Config class that you need to initialize and give all initial settings to

$config = new Config();
$config->language = "ENG";
$config->currency = "EUR";
$config->maxBulkSize = 1000;

And you want to be able to have access to this exact instance all over your project, or you have a config class whose settings depends on other classes, so you want to pass this config into different classes and then end up with a complete config instance that you want to use. Here's how you can make your config class a hardcoded singleton.

class Config
{
    // property that will store our only instance
    private static $instance;
    private function __constuct() {}
    public static function instance()
    {
        // Check if Config already was initialized
        if (is_null(static::$instance)) {
            //If no, initialize it and store
            static::$instance = new static();
        }
        // Return container instance
        return static::$instance;
    }
}
// Getting the instance
$config = Config::instance();

Or you can just use your framework's built-in resolver/container.

// normal use case new instance every time
$config = $container->get('Config'); 
// initializes class if no instance exist and returns same instance everytime
$config = $container->getSingleton('Config');

The same can be used with dependency injection if you define this class as a singleton in your framework's containers configuration.

Repository

Repositories are classes that you can use to encapsulate your logic for interaction with a database.

<?php
class ProductRepository
{
    public function getAll()
    {
        $products = $this->db->execute("SELECT * FROM products");
        return $products;
    }
}

Most frameworks provide Models to interact with a database. The same code as above may look like

$products = Products::all();

In such cases, you can use a repository pattern to store your more complicated queries.

<?php
class ProductRepository
{
    public function getTopProducts()
    {
        $result = Products::join(Statistics::$table, 'products.id', '=', 'statistics.product_id')
            ->select('products.name', 'statistics.views')
            ->orderBy('statistics.views', 'desc');
        return $result;
    }
}

Service

Service classes are used to store "business" logic, e.g., if you have multiple API endpoints that share the same login verification logic, or if your controllers' methods grew too big and you want to store the main logic in a different place.

<?php
class UserController
{
    public function login(Request $request)
    {
        // basic request validation
        $request->validate([
            'email' => 'required|email|max:255',
            'password' => 'required|min:8',
        ]);

        $result = $this->userService->login($request->email, $request->password);
        if (!$result) {
            return redirect('/login');
        }
        return redirect("/dashboard");
    }
}
class UserService
{
    public function login($email, $password)
    {
        // your complicated login logic
    }
}

Strategy

Strategies are classes that you can use to have different solution implementations for the same problem.

<?php
class Controller
{
    public function validator($request)
    {
        switch ($request->source) {
            case 'GET':
                return $this->getValidationStategy->validate($request);
                break;
            case 'POST':
                return $this->postValidationStategy->validate($request);
                break;
            case 'PUT':
                return $this->putValidationStategy->validate($request);
                break;
            case 'DELETE':
                return $this->deleteValidationStategy->validate($request);
                break;
            case 'PATCH':
                return $this->patchValidationStategy->validate($request);
                break;
            default:
                return $this->getValidationStategy->validate($request);
                break;
        }
    }
}

Facade

Facades are a way of obscuring internal complicated logic and providing users with an easy "interface" to work with

Auth::login($username, $password);
Auth::register($data);
Auth::remove($username);
class AuthFacade {
    public static function login($username, $password) {
        // Password and username verification logic
        // Calls to services that call repositories that call models...
        return $result;
    }
}

Observer

Observer's pattern is used to create listeners that will react to some actions in your classes.

<?php
class ProductsService
{
    private $observers = [];
    private function notify()
    {
        foreach ($this->observers as $observer) {
            $observer->update();
        }
    }
    public function remove($product)
    {
        $this->product->remove();
        $this->notify();
    }
}
class ObserverA
{
    public function update()
    {
        // Do stuff
    }
}
class ObserverB
{
    public function update()
    {
        // Do stuff
    }
}

Personally don't like observers, because they are side effects and make your code unpredictable and harder to debug. Imagine having a chain of the method calls and some of them having observables. Results of such execution become really hard to imagine/predict.

Factories, Dependency Injection, and Container

Factories are classes that let you create objects directly from concrete classes or interfaces. However, most frameworks use dependancty injection and container that replaces factory pattern.

I dropped Interface to save space, but all the patterns should use interfaces.

For more in-depth research and more examples will recommend
refactoring.guru/design-patterns/php designpatternsphp.readthedocs.io/en/latest/.. Book: "Design Patterns: Elements of Reusable"