DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 966,904 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Design Patterns in PHP 8: Factory method
Max Zhuk
Max Zhuk

Posted on

Design Patterns in PHP 8: Factory method

Hi!
In this article, I'll show you how to use the Factory Method design pattern with an example.

Let's say we have a shopping cart class and that class contains methods for caching the cart and for persisting it for a long time. It is proposed to cache in RedisDB, and save in MySQL.

class CartModel
{
    public array $data;
    // Better take this settings from special file or env variables
    // but this article not about storing settings
    private array $redisSettings = [
        'user' => 'test_user',
        'password' => 'password'
    ];
    private array $mysqlSettings = [
        'hostname' => 'localhost',
        'login' => 'test_user',
        'password' => 'secret',
        'database' => 'test_db'
    ];

    // We need to implement a cache storage method
    // the Redis DB is better suited for this
    public function cache(): void
    {
    }

    public function save(): void
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is such a blank class turned out, now we need to implement these methods. In fact, the meaning of each of them is to connect to the desired database and store data about the basket in it. Thanks to the Factory Method pattern, we will move the common code (saving) for all classes working with databases into an abstract class. And the functionality associated with the connection will be different for each database, so we will take it out separately using the common interface.

abstract class AbstractDataBaseFactory
{
    // A directly factory method that allows subclasses to return
    // any concrete connectors of the desired interface, since it is made abstract
    // We will create the interface a little later
    abstract public function getDataBase(): DataBaseConnector;

    // And this method will be the same for all databases
    public function save(array $data): void
    {
        $database = $this->getDataBase();
        $database->connect();
        $database->save($data);
        $database->disconnect();
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's implement a concrete class for RedisDB.

class RedisFactory extends AbstractDataBaseFactory
{
    // php8 allows you to add private login and password fields using the constructor
    public function __construct(private readonly string $login, private readonly string $password)
    {}

    // Concrete Factory Method Implementation
    // Returns an instance of the connector class that implements the DataBaseConnector interface
    public function getDataBase(): DataBaseConnector
    {
        return new RedisConnector($this->login, $this->password);
    }
}
Enter fullscreen mode Exit fullscreen mode

About the same way we create a class for the Mysql database.

class MysqlFactory extends AbstractDataBaseFactory
{
    // Unlike Redis, we will need an array of data to connect
    public function __construct(private readonly array $settings)
    {}

    // Concrete Factory Method Implementation
    public function getDataBase(): DataBaseConnector
    {
        return new MysqlConnection($this->settings);
    }
}
Enter fullscreen mode Exit fullscreen mode

It is with these database classes that we have just created that our basket will work.

But the interface of connectors as well as they are not written yet. Let's fix this omission. We will need methods for connecting to the database, disconnecting and, of course, saving data. In the future, it will be possible to extend the interface with various methods, but for now this is enough.

interface DataBaseConnector
{
    public function connect();
    public function disconnect();
    public function save(array $data): void;
}
Enter fullscreen mode Exit fullscreen mode

I will not describe the implementations of the RedisDB and Mysql connectors, everything can be implemented there quite standardly.

class RedisConnector implements DataBaseConnector
{
    public function __construct(private $login, private $password)
    {}

    /**
     * @throws Exception
     */
    public function connect(): void
    {
        // connect() method implementation
    }

    public function disconnect()
    {
        // disconnect() method implementation
    }

    public function save(array $data): void
    {
        // save() method implementation
    }
}
Enter fullscreen mode Exit fullscreen mode
class MysqlConnection implements DataBaseConnector
{
    public function __construct(private $settings)
    {}

    public function connect()
    {
        // connect() method implementation
    }

    public function disconnect()
    {
        // disconnect() method implementation
    }

    public function save(array $data): void
    {
        // save() method implementation
    }
}
Enter fullscreen mode Exit fullscreen mode

Everything is ready to be used in the cart methods.

class CartModel
{
    //...

    public function cache(): void
    {
        try {
            $redis = new RedisFactory($this->redisSettings['user'], $this->redisSettings['password']);
            $redis->save($this->data);
        } catch (\Exception $e) {
            //...
        }
    }

    public function save(): void
    {
        try {
            $mysql = new MysqlFactory($this->mysqlSettings);
            $mysql->save($this->data);
        } catch (\Exception $e) {
            //...
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Thanks to the use of the Factory Method pattern, we can access various databases inside models such as a shopping cart without worrying about the details of their work: connection, saving, data format, disconnect, etc. We avoid code duplication, excessive load on methods and classes, creation of divine classes.


Β© Photo by Patrick Hendry on Unsplash

Top comments (6)

Collapse
 
otsch profile image
otsch

I'm not sure if this is actually an example for the factory method pattern.
Wikipedia says:

In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created.

en.wikipedia.org/wiki/Factory_meth...

In the example you just instantiate the classes Redis and Mysql via their constructor. Using the factory method pattern I think you'd rather have something like a DatabaseFactory with methods redis and mysql that'll create and return those objects.

Collapse
 
maymeow profile image
May Meow

Another nice examples of design patterns are here

designpatternsphp.readthedocs.io/e...

For factory method here: designpatternsphp.readthedocs.io/e...

Collapse
 
zhukmax profile image
Max Zhuk Author

Hi, otsch.
I see, It looks like wrong using pattern just because I called abstract and concrete factory classes without "factory" word. If you look closer wikipedia php-example you see that.
But using "factory" word in classes names can make my article clearly. Thank you, I'll add it.

Collapse
 
suckup_de profile image
Lars Moelleken • Edited on

Maybe you can also update the CartModel example code, this still use the old class names Redis and Mysql.

PS: here are also some nice exmaples refactoring.guru/design-patterns/f...

Thread Thread
 
zhukmax profile image
Max Zhuk Author

Thx, did it

Collapse
 
otsch profile image
otsch

I think you renamed the classes Redis and Mysql to RedisFactory and MysqlFactory and forgot to rename it in the CartModel class. But I'd rather revert naming those classes Factories because they as a whole aren't Factory classes.

Besides that I just got what the factory method in the example is: the getDataBase() method, right? It's true that it creates an instance of a class without knowing the concrete implementation from where you call it. As you can guess from my comment I was expecting and looking for the factory method in the wrong place.

After all I think it's a nice code example but I'm not sure if readers get what the factory method pattern is about. Maybe you can make it a bit clearer what the actual factory method is, and maybe tell a little bit about the pattern in general?

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.