Magento 2: Understanding the Factory Pattern with Complete Examples

Magento 2 Factory Pattern Example
Photo by xyzcharlize on Unsplash

Magento 2 uses several design patterns to ensure code modularity, flexibility, and scalability. One of the most widely used patterns is the Factory Pattern. In this post, we will explore the Factory Pattern in Magento 2, why it is used, and compare it with directly creating objects using the new keyword. We’ll also provide practical examples, including proper routing, to help you implement the Factory Pattern.


What is the Factory Pattern?

The Factory Pattern is a creational design pattern that simplifies object creation. Instead of directly creating objects with the new keyword, you use a factory class that handles the object creation process. This approach promotes loose coupling between classes and makes your code more scalable and maintainable.


Why Use the Factory Pattern in Magento 2?

Magento 2's reliance on Dependency Injection (DI) benefits greatly from the Factory Pattern. Here's why factories are preferred:

  1. Decoupling: You don’t need to know how an object is constructed; you rely on the factory, which abstracts the object creation process.
  2. Dependency Injection: Factories enable Magento’s DI system to manage dependencies automatically, making it easier to inject classes as needed.
  3. Dynamic Object Creation: Factories allow for the creation of objects on demand, which is especially useful when different objects need to be created under different conditions.
  4. Easier Maintenance: The factory pattern simplifies changes in object creation logic without affecting the dependent code.

Creating Objects with new Keyword: Drawbacks

Before jumping into factories, let’s first see how creating objects with the new keyword works and why it’s generally discouraged in Magento 2.

Example: Creating an Object with the new Keyword

Suppose we have a simple Product class in our module. You can create an instance of this class using the new keyword as follows:

<?php
namespace Vendor\Module\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;

class Index extends Action
{
    public function __construct(Context $context)
    {
        parent::__construct($context);
    }

    public function execute()
    {
        // Creating object using the new keyword
        $product = new \Vendor\Module\Model\Product('Sample Product');
        
        // Output the product name
        echo 'Product Name: ' . $product->getName();
    }
}

Here’s what’s happening:

  • We manually create a Product object using the new keyword.
  • The constructor argument ('Sample Product') is passed manually during instantiation.

Drawbacks of Using new Keyword:

  1. Hard Dependencies: When you use new, you hardcode the class name into the controller. If the constructor signature or dependencies change, you’ll need to update all occurrences of new.
  2. No Dependency Injection: The new keyword bypasses Magento’s DI system, which means that any dependencies the object needs must be manually injected, increasing maintenance overhead.
  3. Limited Flexibility: With factories, you can dynamically create objects based on runtime conditions. With new, you cannot change object instantiation logic easily.
  4. Scalability Issues: As the application grows, managing objects manually with new becomes difficult and less efficient.

Factory Pattern in Magento 2: A Better Approach

Now, let’s explore how using the Factory Pattern improves object creation.


Step-by-Step Example: Implementing the Factory Pattern in Magento 2

In this section, we’ll implement the Factory Pattern by creating a simple Magento 2 module that uses a factory to instantiate a model class.

Step 1: Module Setup

Create a new module in Magento 2:

module.xml file to define the module:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_Module" setup_version="1.0.0"/>
</config>

registration.php file to register the module:

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Vendor_Module',
    __DIR__
);

Create the folder structure:

app/code/Vendor/Module

Step 2: Define the Model

Next, create a simple model class that will be instantiated using the Factory Pattern.

  1. Create the following directory: app/code/Vendor/Module/Model.

Create a file named Product.php:

<?php
namespace Vendor\Module\Model;

class Product
{
    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

Step 3: Define the Controller to Use the Factory

Now, let's define a controller that uses the factory to create an instance of the Product model.

  1. Create the controller directory: app/code/Vendor/Module/Controller/Index.

Inside that directory, create Index.php:

<?php
namespace Vendor\Module\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Vendor\Module\Model\ProductFactory;

class Index extends Action
{
    protected $productFactory;

    public function __construct(Context $context, ProductFactory $productFactory)
    {
        $this->productFactory = $productFactory;
        parent::__construct($context);
    }

    public function execute()
    {
        // Create a product object using the factory
        $product = $this->productFactory->create(['name' => 'Sample Product']);
        
        // Output the product name
        echo 'Product Name: ' . $product->getName();
    }
}

Step 4: Define the Routes

Now, let’s define routing to connect the URL with the controller.

Create routes.xml in app/code/Vendor/Module/etc/frontend:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="vendor_module" frontName="vendor_module">
            <module name="Vendor_Module" />
        </route>
    </router>
</config>

Step 5: Enabling the Module

Run the following commands to enable your new module and clear the cache:

php bin/magento setup:upgrade
php bin/magento cache:flush

Step 6: Test the Factory Implementation

Visit the following URL to test the factory:

http://your-magento-url/vendor_module/index/index

You should see:

Product Name: Sample Product

Comparison: Factory vs. new Keyword

Using new Keyword:

$product = new \Vendor\Module\Model\Product('Sample Product');
  • Hard Dependencies: You have to hardcode the class name into the controller, which makes it difficult to manage dependencies or switch implementations later.
  • Manual Dependency Management: You must handle all dependencies manually.
  • No Dependency Injection: The DI system is bypassed.

Using Factory:

$product = $this->productFactory->create(['name' => 'Sample Product']);
  • Loose Coupling: You rely on the factory to create objects, making your code more flexible and scalable.
  • Automatic Dependency Injection: Magento’s DI system automatically handles dependencies.
  • Dynamic Object Creation: Factories allow you to create objects on the fly, enabling dynamic behavior based on runtime conditions.

Conclusion

The Factory Pattern in Magento 2 provides an elegant way to create objects without hard dependencies, promoting cleaner and more maintainable code. While creating objects with the new keyword is simple, it comes with significant drawbacks in terms of flexibility and scalability, especially in a large application like Magento 2. Using factories ensures that objects are created dynamically, dependencies are handled by the DI system, and your code remains decoupled and modular.


Have any questions or comments about using the Factory Pattern in Magento 2? Feel free to share them below!