Design patterns

Developers have been creating code since way before the appearance of with Internet, and they have been working on a number of different areas, not just web applications. Because of that, a lot of people have already had to confront similar scenarios, carrying the experience of previous attempts for fixing the same thing. In short, it means that almost surely, someone has already designed a good way of solving the problem that you are facing now.

A lot of books have been written trying to group solutions to common problems, also known as design patterns. Design patterns are not algorithms that you copy and paste into your program, showing how to fix something step-by-step, but rather recipes that show you, in a heuristic way, how to look for the answer.

Studying them is essential if you want to become a professional developer, not only for solving problems, but also for communicating with other developers. It is very common to get an answer like "You could use a factory here", when discussing your program design. It saves a lot of time knowing what a factory is, rather than explaining the pattern each time someone mentions it.

As we said, there are entire books that talk about design patterns, and we highly recommend you to have a look at some of them. The goal of this section is to show you what a design pattern is and how you can use it. Additionally, we will show you some of the most common design patterns used with PHP when writing web applications, excluding the MVC pattern, which we will study in Chapter 6, Adapting to MVC.

Other than books, you could also visit the open source project DesignPatternsPHP at http://designpatternsphp.readthedocs.org/en/latest/README.html. There is a good collection of them, and they are implemented in PHP, so it would be easier for you to adapt.

Factory

A factory is a design pattern of the creational group, which means that it allows you to create objects. You might think that we do not need such a thing, as creating an object is as easy as using the new keyword, the class, and its arguments. But letting the user do that is dangerous for different reasons. Apart from the increased difficulty caused by using new when unit testing our code (you will learn about unit testing in Chapter 7, Testing Web Applications), a lot of coupling too gets added into our code.

When we discussed encapsulation, you learned that it is better to hide the internal implementation of a class, and you could consider the constructor as part of it. The reason is that the user needs to know at all times how to create objects, including what the arguments of the constructor are. And what if we want to change our constructor to accept different arguments? We need to go one by one to all the places where we have created objects and update them.

Another reason for using factories is to manage different classes that inherit a super class or implement the same interface. As you know, thanks to polymorphism, you can use one object without knowing the specific class that it instantiates, as long as you know the interface being implemented. It might so happen that your code needs to instantiate an object that implements an interface and use it, but the concrete class of the object may not be important at all.

Think about our bookstore example. We have two types of customers: basic and premium. But for most of the code, we do not really care what type of customer a specific instance is. In fact, we should implement our code to use objects that implement the Customer interface, being unaware of the specific type. So, if we decide in the future to add a new type, as long as it implements the correct interface, our code will work without an issue. But, if that is the case, what do we do when we need to create a new customer? We cannot instantiate an interface, so let's use the factory pattern. Add the following code into src/Domain/Customer/CustomerFactory.php:

<?php

namespace BookstoreDomainCustomer;

use BookstoreDomainCustomer;

class CustomerFactory {
    public static function factory(
        string $type,
        int $id,
        string $firstname,
        string $surname,
        string $email
    ): Customer {
        switch ($type) {
            case 'basic':
                return new Basic($id, $firstname, $surname, $email);
            case 'premium':
                return new Premium($id, $firstname, $surname, $email);
        }
    }
}

The factory in the preceding code is less than ideal for different reasons. In the first one, we use a switch, and add a case for all the existing customer types. Two types do not make much difference, but what if we have 19? Let's try to make this factory method a bit more dynamic.

public static function factory(
        string $type,
        int $id,
        string $firstname,
        string $surname,
        string $email
    ): Customer {
    $classname = __NAMESPACE__ . '' . ucfirst($type);
    if (!class_exists($classname)) {
        throw new InvalidArgumentException('Wrong type.');
    }
    return new $classname($id, $firstname, $surname, $email);
}

Yes, you can do what we did in the preceding code in PHP. Instantiating classes dynamically, that is, using the content of a variable as the name of the class, is one of the things that makes PHP so flexible… and dangerous. Used wrongly, it will make your code horribly difficult to read and maintain, so be careful about it. Note too the constant __NAMESPACE__, which contains the namespace of the current file.

Now this factory looks cleaner, and it is also very dynamic. You could add more customer types and, as long as they are inside the correct namespace and implement the interface, there is nothing to change on the factory side, nor in the usage of the factory.

In order to use it, let's change our init.php file. You can remove all our tests, and just leave the autoloader code. Then, add the following:

CustomerFactory::factory('basic', 2, 'mary', 'poppins', '[email protected]');
CustomerFactory::factory('premium', null, 'james', 'bond', '[email protected]');

The factory design pattern can be as complex as you need. There are different variants of it, and each one has its own place and time, but the general idea is always the same.

Singleton

If someone with a bit of experience with design patterns, or web development in general, reads the title of this section, they will probably start tearing their hair out and claiming that singleton is the worst example of a design pattern. But just bear with me.

When explaining interfaces, I added a note about how developers tend to complicate their code too much just so they can use all the tools they know. Using design patterns is one of the cases where this happens. They have been so famous, and people claimed that good use of them is directly linked to great developers, that everybody that learns them tries to use them absolutely everywhere.

The singleton pattern is probably the most infamous of the design patterns used in PHP for web development. This pattern has a very specific purpose, and when that is the case, the pattern proves to be very useful. But this pattern is so easy to implement that developers continuously try to add singletons everywhere, turning their code into something unmaintainable. It is for this reason that people call this an anti-pattern, something that should be avoided rather than used.

I do agree with this point of view, but I still think that you should be very familiar with this design pattern. Even though you should avoid its overuse, people still use it everywhere, and they refer to it countless times, so you should be in a position to either agree with them or rather have enough reasons to discourage them to use it. Having said that, let's see what the aim of the singleton pattern is.

The idea is simple: singletons are used when you want one class to always have one unique instance. Every time, and everywhere you use that class, it has to be through the same instance. The reason is to avoid having too many instances of some heavy resource, or to keep always the same state everywhere—to be global. Examples of this are database connections or configuration handlers.

Imagine that in order to run, our application needs some configuration, such as credentials for the database, URLs of special endpoints, directory paths for finding libraries or important files, and so on. When you receive a request, the first thing you do is to load this configuration from the filesystem, and then you store it as an array or some other data structure. Save the following code as your src/Utils/Config.php file:

<?php

namespace BookstoreUtils;

use BookstoreExceptionsNotFoundException;

class Config {
    private $data;

    public function __construct() {
    $json = file_get_contents(__DIR__ . '/../../config/app.json');
        $this->data = json_decode($json, true);
    }

    public function get($key) {
        if (!isset($this->data[$key])) {
            throw new NotFoundException("Key $key not in config.");
        }
        return $this->data[$key];
    }
}

As you can see, this class uses a new exception. Create it under src/Utils/NotFoundException.php:

<?php

namespace BookstoreExceptions;

use Exception;

class NotFoundException extends Exception {
}

Also, the class reads a file, config/app.json. You could add the following JSON map inside it:

{
  "db": {
    "user": "Luke",
    "password": "Skywalker"
  }
}

In order to use this configuration, let's add the following code into your init.php file.

$config = new Config();
$dbConfig = $config->get('db');
var_dump($dbConfig);

That seems a very good way to read configuration, right? But pay attention to the highlighted line. We instantiate the Config object, hence, we read a file, transform its contents from JSON to array, and store it. What if the file contains hundreds of lines instead of just six? You should notice then that instantiating this class is very expensive.

You do not want to read the files and transform them into arrays each time you ask for some data from your configuration. That is way too expensive! But, for sure, you will need the configuration array in very different places of your code, and you cannot carry this array everywhere you go. If you understood static properties and methods, you could argue that implementing a static array inside the object should fix the problem. You instantiate it once, and then just call a static method that will access an already populated static property. Theoretically, we skip the instantiation, right?

<?php

namespace BookstoreUtils;

use BookstoreExceptionsNotFoundException;

class Config {
    private static $data;

    public function __construct() {
        $json = file_get_contents(__DIR__ . '/../config/app.json');
        self::$data = json_decode($json, true);
    }

    public static function get($key) {
        if (!isset(self::$data[$key])) {
            throw new NotFoundException("Key $key not in config.");
        }
        return self::$data[$key];
    }
}

This seems to be a good idea, but it is highly dangerous. How can you be absolutely sure that the array has already been populated? And how can you be sure that, even using a static context, the user will not keep instantiating this class again and again? That is where singletons come in handy.

Implementing a singleton implies the following points:

  1. Make the constructor of the class private, so absolutely no one from outside the class can ever instantiate that class.
  2. Create a static property named $instance, which will contain an instance of itself—that is, in our Config class, the $instance property will contain an instance of the class Config.
  3. Create a static method, getInstance, which will check if $instance is null, and if it is, it will create a new instance using the private constructor. Either way, it will return the $instance property.

Let's see what the singleton class would look like:

<?php

namespace BookstoreUtils;

use BookstoreExceptionsNotFoundException;

class Config {
    private $data;
    private static $instance;

    private function __construct() {
        $json = file_get_contents(__DIR__ . '/../config/app.json');
        $this->data = json_decode($json, true);
    }

    public static function getInstance(){
        if (self::$instance == null) {
            self::$instance = new Config();
        }
        return self::$instance;
    }

    public function get($key) {
        if (!isset($this->data[$key])) {
            throw new NotFoundException("Key $key not in config.");
        }
        return $this->data[$key];
    }
}

If you run this code right now, it will throw you an error, as the constructor of this class is private. First achievement unlocked! Let's use this class properly:

$config = Config::getInstance();
$dbConfig = $config->get('db');
var_dump($dbConfig);

Does it convince you? It proves to be very handy indeed. But I cannot emphasize this enough: be careful when you use this design pattern, as it has very, very, specific use cases. Avoid falling into the trap of implementing it everywhere!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.73.207