Exploring the Factory Pattern in PHP 8.2

Grzegorz Lasak
10 min readSep 26, 2023

Software development is a delicate balance of problem-solving, and programming languages equip us with a variety of tools to craft effective solutions. Among these tools are ‘design patterns’ — tried-and-true solutions to common challenges we encounter. One standout pattern is the Factory Pattern, renowned for its ability to simplify and streamline object creation.

In this article, we’ll explore the Factory Pattern within the context of PHP 8.2. With the latest enhancements in PHP, we can leverage features like Enums and the match syntax, both of which breathe fresh life into the implementation of this pattern.

First we will look into traditional and simple implementation and next we will dive into a bit more advanced example to see how we can make things more complicated… Ekhm… I mean more solid and testable and repeatable and all these good things! ;)

By the article’s conclusion, you’ll not only appreciate the versatility of the Factory Pattern but also witness its practical application. Armed with this knowledge, you’ll be poised to integrate it into your own code.

A Brief Recap of the Factory Pattern

Technical Definition: The Factory Pattern is a creational design pattern that provides an interface for creating instances of classes, with its subclasses deciding which class to instantiate. Instead of calling a class’s constructor directly, a factory method is used to create the object, abstracting the instantiation process and promoting a loose coupling between classes.

Yeah… Let’s try to get it with some day-to-day example.

Imagine you’re in a toy store, and you want a toy. Instead of making the toy yourself from scratch (which would be complicated…), you go to a special counter (let’s call it the “Toy Factory Counter”). You just tell the person behind the counter what toy you want, and they’ll give it to you. You don’t need to know how the toy is made or where it comes from; the “Toy Factory Counter” takes care of that for you.

In this scenario:

  • The “Toy Factory Counter” is like our Factory in programming.
  • The act of you asking for a toy is like a piece of code asking for an object.
  • The person behind the counter deciding which toy to give you (maybe a doll, a robot, or a car) is like the Factory Pattern deciding which object to create based on the request.

Just as the toy store simplifies the process of getting a toy, the Factory Pattern simplifies the process of creating objects in our code.

Factory Pattern: Traditional Implementation

Let’s continue with our toy analogy.
At our “Toy Factory Counter”, there’s a method (or a way) for the person behind the counter to give us toys based on our choice: a doll, a robot, or a car. If we ask for a doll, the toy factory knows it should give us a doll, and so forth. This decision-making process, simplified, is the essence of the Factory Pattern.

Implementation:

Let’s define our classes:

class Doll {
public function getDescription() {
return "This is a doll.";
}
}

class Robot {
public function getDescription() {
return "This is a robot.";
}
}

class Car {
public function getDescription() {
return "This is a car.";
}
}

Now, we’ll create our toy factory:

class ToyFactory {
public function getToy($toyType) {
switch ($toyType) {
case "doll":
return new Doll();
case "robot":
return new Robot();
case "car":
return new Car();
default:
throw new Exception("Invalid toy type");
}
}
}

Using the factory is simple:

$factory = new ToyFactory();

$doll = $factory->getToy("doll");
echo $doll->getDescription(); // Outputs: This is a doll.

In this traditional implementation:

  • We have a ToyFactory class that decides which toy object to create based on the toyType provided.
  • Instead of creating new toy objects directly using the new keyword everywhere in our code, we use the factory. This centralizes the object creation process and makes our code more organized and manageable.

In the next section, we’ll see how PHP 8.2’s Enums,match syntax and Interfaces can enhance this basic structure.

A bit more advanced example of Factory Pattern implementation

Heads-up: code is from Laravel 10, so some of the functions might be coming from that framework but that should not affect overall explanation.
Beside, Classes and Interfaces are bundled into one ‘file’ just for simplicity.
Please, read comments in the code as it contains further explanation of some of the code.

Also, example comes from real application but is heavily modified.

First we will look into the implementation of mentioned pattern with using Enums, match syntax and Interfaces and in further part, we can discuss extending it even more.

Short explanation of example:

We are building API project which will allow us to use one endpoint to fetch data from third-party providers (the same type of data). Let’s say there are 2 of these.

We need 2 Services (1 for each) which will handle data provider-specific logic since these differ due to different authentication method and responses. After all parsing is done, we want to return it to our caller.

Credentials for each of these providers are stored in DB (security measurements taken into consideration), so we need endpoint(s) that will allow us to point which data provider we want data from and which credentials to use (hence dynamic {id} below). In reality it is more complicated case but let’s keep it simple here.

We can implement 2 different endpoints for GET method, f.ex.

  1. https://ourhost.app/api/fetch/firstDataProvider/{id}/testData
  2. https://ourhost.app/api/fetch/secondDataProvider/{id}/testData

Next step is to implement Controller(s) for handling the requests.
Intuitive thing is to make 2 Controllers — one for each endpoint since these are for different Data Providers.

But… Here comes usage of Factory Pattern:
We can have 1 Controller that will recognise which data provider is requested, it will call Factory Class to get needed Service Instance which will handle the logic and then the Controller will return the response (or error…)

Let’s define our interfaces

<?php

namespace App\Interfaces\Services;

use App\Enums\DataProvidersEnum;
use Illuminate\Support\Collection;

/**
* Interface of a function parameter (can be for ValueObject, DTO
* or any custom object that will carry values and methods)
*/
interface ITestDataQuery
{
public function myQueryMethod(): mixed;
}

/**
* Interface which needs to be implemented by all desirect Service Classes that are about to get instantiated.
*/
interface IDebugDataService
{
public function fetchTestData( ITestDataQuery $query ): Collection;
}

/**
* Let's make sure our Factory implements that interface, so we have clear contract for it.
* That will also allow easier manipulations/extending for classes expecting that interface
* As param's type
*/
interface IServiceFactory {

public function getDebugDataServiceInstance( DataProvidersEnum $enum ): IDebugDataService;

}

Let’s define our Enum:

<?php

namespace App\Enums;

/**
* Let's use Enum for our data providers to make controlling easier
*/
enum DataProvidersEnum: string
{
case FIRST_DATA_PROVIDER = 'firstDataProvider';
case SECOND_DATA_PROVIDER = 'secondDataProvider';
case NOT_SUPPORTED_DATA_PROVIDER = 'notSupportedDataProvider';
}

Now it is time for our Services:

<?php

namespace App\Services\DebugData;
use App\Interfaces\Services\IDebugDataService;
use App\Interfaces\Tools\ValueObjects\Queries\DebugData\ITestDataQuery;
use Illuminate\Support\Collection;

/**
* Our Service Class(es) must implement desired Interface to avoid surprises in Controllers.
* These can also extend the same class if needed (the Interface can be on parent).
*/
class FirstDebugDataService implements IDebugDataService
{
public function fetchTestData( ITestDataQuery $query ): Collection
{
// here goes your implementation....
return collect();
}

// And your methods for the service...
}

class SecondDebugDataService implements IDebugDataService
{
public function fetchTestData( ITestDataQuery $query ): Collection
{
// here goes your implementation....
return collect();
}

// And your methods for the service...
}

At that stage, we already know what Interfaces we are dealing with, each of our classes does implement the expected one (so we can apply other ‘design patterns’ on top of these in the future, if needed) and we also have 2 different Services with the same starting function which’s implementation is forced by Interface.
Thanks to that Controller knows what methods it can call.

Let’s look at example of our Factory Class:

<?php

namespace App\Factories\DebugData;

class ServiceFactory implements IServiceFactory
{

/**
* We could also use DI in here for Services when no parameters are required
* That would also be a good thing for that code but let's make it a bit more flexible right away
*/
public function __construct() {}

/**
* Use match on Enum and return new Instance of desired Service.
* Beside only that one param, you might want to provide more params (best contracted by Interfaces)
* since Services might expect some params.
* Or you can extpect Service's methods to get some params, too.
*
* @param DataProvidersEnum $enum
*
* @return IDebugDataService
*/
public function getDebugDataServiceInstance( DataProvidersEnum $enum ): IDebugDataService
{
return match ($enum) {
DataProvidersEnum::FIRST_DATA_PROVIDER => $this->getFirstDebugDataService(),
DataProvidersEnum::SECOND_DATA_PROVIDER => $this->getSecondDebugDataService(),
default => throw new UnsupportedDataProviderException($enum), // In case we have some cases which we don't want to support...
};
}

/**
* These 2 methods below are protected because we don't want to allow direct access
*
* Also, if we find out at some point we need another version of that Factory,
* but the old one still needs to be available for time being,
* we can extend it and in the child class we can override these methods (f.ex. to require some params).
*
* That is quite common case when dealing with multiple versions of APIs
*
* @return IDebugDataService
*/
protected function getFirstDebugDataService(): IDebugDataService
{
return new FirstDebugDataService();
}

protected function getSecondDebugDataService(): IDebugDataService
{
return new SecondDebugDataService();
}
}

So, what happens here?

  • We make sure our class ServiceFactory implements IServiceFactory — that allows more flexibility thanks to using Interface and Parameter Type
  • We implement 2 protected methods: getFirstDebugDataService and getSecondDebugDataService
    Doing it this way, allows us to override them in child classes. That might be handy when we need to develop next version of our code but the old one still needs to be available for some time
  • We implement getDebugDataServiceInstance which expects our DataProvidersEnum as parameter. Using Enum in here, makes sure we don’t make any typos and we have fixed list of case(s) we can use.
    Next, we use match syntax on our Enum to discover which instance we want to return.
    In case of case which we don’t have implementation for (for some reason), we want to throw Exception to inform Controller what is wrong and it needs to handle it.

Now, let’s have a look at our Controller handling request and using Factory:

<?php

namespace App\Http\Controllers;

class FetchDataController extends Controller
{
/**
* Use DI to have access to factory class right away
*
* @param IServiceFactory $dataProviderServiceFactory
*/
public function __construct(
protected readonly IServiceFactory $dataProviderServiceFactory = new ServiceFactory(),
) {}

/**
* In Laravel, you would get Request instance in here but let's not complicate it
* for people who don't know Laravel.
* Let's assume we get our DataProvider case right away in here
*
* @param DataProvidersEnum $dataProviderEnum
*/
public function fetchTestData( DataProvidersEnum $dataProviderEnum ): JsonResponse
{
try {
return response() // This is Laravel's built-in function. Use whatever fits your app
->json(
$this->dataProviderServiceFactory
->getDebugDataServiceInstance($dataProviderEnum) // here we fetch our Service Instance
->fetchDebugData( // We know our Service implements particular Interface, so we can use method on it
new TestDataQuery('some data', 'goes here') // Instantiate our Query since Service method required it
)
->toArray() // this is because we get Laravel's Collection in here. Adjust to your app
);

} catch ( UnsupportedDataProviderException $e ) { // we got exception of unsupported data provider - handle it exclusively.
return response()->json(['error' => 'Unsupported data provider.']);
} catch( Exception $e ) { // some other exception - handle it as needed
return response()->json(['error' => 'Some other error.']);
}

}
}

Let’s explain it a bit:

  1. In our constructor, we use Class Constructor Property Promotion to have available our Factory Class as class attribute right away
  2. We implement fetchTestData function which expects DataProvidersEnum as parameter for further giving it to our Factory.
  3. We trigger getDebugDataServiceInstance method from the Factory to receive wanted instance of Service desired for requested third-party data provider.
  4. Since we know that Service Instance implements IDebugDataService , we can safely call fetchDebugData method on it which is entry point for fetching data on specific way aligned to third-party data provider.
    Since that moment, Service takes care of fetching data, parsing, validating etc. At the end, Service needs to return something to Controller
  5. Now, Controller can return response in desired format.
  6. In case of Exception — we have crafted new UnsupportedDataProviderException which we can use to recognise if very specific error occured. In case we try to use data provider which we have no Service implemented for, we can catch it and handle error on specific way.
    In other case, we can catch other Exceptions and act accordingly (these can be also custom Exceptions f.ex. from Services etc.)

By utilizing the Factory Pattern, we’ve constructed a streamlined, maintainable system capable of handling multiple data providers with ease. It not only enhances our application’s flexibility but also sets a robust foundation for future expansion. The Factory Pattern, with its ability to decouple instantiation logic, proves to be an indispensable tool in a developer’s toolkit.

So, what are benefits of using Factory Pattern?

  1. We get centralised logic for instantiating objects which allows for re-using it in multiple places. Change to it can be done in one place instead of multiple locations.
  2. Our code is much easier to test. Imagine logic from Factory Class being implemented in Controller’s method. That adds multiple scenarios in tests only for that one function.
    Then, also you might end up with multiple methods needing instantiating Service. Now you have multiple additional scenarios in multiple functions.
  3. By implementing it on a bit more complicated way, you can extend Factory’s behaviour in one place when needed. Sure, in perfect world scenario you might want to have only one way to do things but real-life example: you need to instantiate object allowing calculations of budget for each year. Since each year there were changes, you end up with multiple options which might require different parameters. It is worth to spend a bit more time on making code flexible for long-term projects.
  4. Decoupling. The pattern decouples the client code from the concrete classes, promoting the use of interfaces or abstract classes. This loose coupling is beneficial for maintainability and scalability.
  5. Control over Object Creation. Factories can control the number of instances created, potentially implementing features like object pooling or singletons.

A few words at the end

Using Design Patterns is beneficial for other developers who will be working on a project. It is much easier to recognise some of popular patterns used in the project instead of trying to understand somebody’s unique invention for common problems (and we know that is the case quite often).

While the Factory Pattern offers numerous advantages, it’s crucial to note that like any tool or pattern, it shouldn’t be used everywhere arbitrarily. It’s best suited for situations where there’s a clear benefit in centralizing the object creation process or when there’s a need for decoupling the client code from the specific classes being instantiated.

I have used Interfaces and Dependency Injection in this example. If you’re unfamiliar with these concepts, stay tuned for upcoming articles where we’ll dive deep into these topics.

Have you found this article helpful and want to keep me awake for writing more?
Here is your chance to get me a coffee: https://www.buymeacoffee.com/grzegorzlaT

Greetings,
Grzegorz

--

--

Grzegorz Lasak

Mastering the art of PHP, JS and GO on daily basis while working as a Web Developer. Still trying to master the art of adulting.