Skip to content
advanced Phase 119 · Senior Projects

Legacy Magento Refactoring

Refactor a legacy Magento module to modern architecture patterns, service contracts, and testable code.

3h
0 problems
Topic Progress 0%

Assessing Legacy Code

Assessing Legacy Code

Before refactoring, you need a clear picture of the current state.

Common Legacy Patterns in Magento

// BAD: Direct ObjectManager usage
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$product = $objectManager->create(\Magento\Catalog\Model\Product::class);

// BAD: Static method calls
$product = \Magento\Catalog\Model\Product::load($id);

// BAD: Direct database queries
$connection = $resource->getConnection();
$connection->query('SELECT * FROM catalog_product_entity WHERE sku = ?', [$sku]);

Assessment Checklist

Area What to Check
DI ObjectManager usage, constructor injection quality
Models Direct model load/save, no service contracts
Database Raw SQL queries, missing index usage
Testing Test coverage, test quality
Architecture Layer violations, circular dependencies
Frontend jQuery spaghetti, no RequireJS

Refactoring Priority Matrix

Impact Effort Priority
High Low Do First
High High Plan
Low Low When Available
Low High Avoid

Incremental Refactoring Strategy

Incremental Refactoring Strategy

Use the Strangler Fig pattern - gradually replace old code with new code.

Step 1: Add Service Contracts

// Create API interface
namespace Vendor\Module\Api;

interface ProductRepositoryInterface
{
    public function getBySku(string $sku): \Vendor\Module\Api\Data\ProductInterface;
    public function save(\Vendor\Module\Api\Data\ProductInterface $product): void;
}

// Create data interface
namespace Vendor\Module\Api\Data;

interface ProductInterface
{
    public function getId(): ?int;
    public function getSku(): string;
    public function getName(): string;
    // ...
}

Step 2: Implement Repository

namespace Vendor\Module\Model;

class ProductRepository implements \Vendor\Module\Api\ProductRepositoryInterface
{
    private $productFactory;
    private $resource;
    
    public function __construct(
        \Vendor\Module\Model\ProductFactory $productFactory,
        \Vendor\Module\Model\ResourceModel\Product $resource
    ) {
        $this->productFactory = $productFactory;
        $this->resource = $resource;
    }
    
    public function getBySku(string $sku): \Vendor\Module\Api\Data\ProductInterface
    {
        $product = $this->productFactory->create();
        $this->resource->load($product, $sku, 'sku');
        return $product;
    }
}

Step 3: Update Callers Gradually

// OLD: Direct model loading
$product = $product->load($id);

// NEW: Use repository
$product = $productRepository->getById($id);

Step 4: Remove Old Code

Once all callers are updated, remove the legacy code.

DI Modernization

DI Modernization

Removing ObjectManager

// BAD: ObjectManager everywhere
public function execute()
{
    $product = ObjectManager::getInstance()->create(Product::class);
    // ...
}

// GOOD: Constructor injection
public function __construct(
    ProductFactory $productFactory,
    LoggerInterface $logger
) {
    $this->productFactory = $productFactory;
    $this->logger = $logger;
}

public function execute()
{
    $product = $this->productFactory->create();
    // ...
}

Replacing Preferences with Plugins

<!-- OLD: Preference (replaces entire class) -->
<config>
    <preference for="Magento\Catalog\Api\ProductRepositoryInterface"
                type="Vendor\Module\Model\ProductRepository" />
</config>

<!-- NEW: Plugin (extends behavior) -->
<config>
    <type name="Magento\Catalog\Api\ProductRepositoryInterface">
        <plugin name="vendor_module_product"
                type="Vendor\Module\Plugin\ProductRepository"
                sortOrder="10" />
    </type>
</config>

Using Factories Instead of Direct Creation

// BAD: Direct object creation
$product = new Product();

// GOOD: Factory pattern
$product = $this->productFactory->create();

Backward Compatibility During Refactoring

  1. Keep old methods, mark as @deprecated
  2. Add new methods alongside old ones
  3. Update callers gradually
  4. Remove old methods in next major version

Testing Refactored Code

Testing Refactored Code

Unit Tests for Refactored Code

namespace Vendor\Module\Test\Unit\Model;

use PHPUnit\Framework\TestCase;
use Vendor\Module\Model\ProductRepository;

class ProductRepositoryTest extends TestCase
{
    private $repository;
    private $productFactoryMock;
    private $resourceMock;
    
    protected function setUp(): void
    {
        $this->productFactoryMock = $this->createMock(
            \Magento\Catalog\Model\ProductFactory::class
        );
        $this->resourceMock = $this->createMock(
            \Magento\Catalog\Model\ResourceModel\Product::class
        );
        $this->repository = new ProductRepository(
            $this->productFactoryMock,
            $this->resourceMock
        );
    }
    
    public function testGetBySku()
    {
        $productMock = $this->createMock(
            \Magento\Catalog\Model\Product::class
        );
        $this->productFactoryMock->expects($this->once())
            ->method('create')
            ->willReturn($productMock);
        $this->resourceMock->expects($this->once())
            ->method('load')
            ->with($productMock, 'TEST-SKU', 'sku');
        
        $result = $this->repository->getBySku('TEST-SKU');
        $this->assertSame($productMock, $result);
    }
}

Integration Tests

namespace Vendor\Module\Test\Integration\Model;

use PHPUnit\Framework\TestCase;
use Magento\TestFramework\Helper\Bootstrap;

class ProductRepositoryTest extends TestCase
{
    public function testGetBySku()
    {
        $repository = Bootstrap::getObjectManager()->create(
            \Vendor\Module\Api\ProductRepositoryInterface::class
        );
        $product = $repository->getBySku('test-product');
        $this->assertNotNull($product);
    }
}

Refactoring Checklist

  • Service contracts defined
  • Repository implementation created
  • Unit tests written
  • Integration tests pass
  • Old code marked deprecated
  • Documentation updated
  • Performance tested
  • Backward compatibility verified

Quiz

1. What is the Strangler Fig pattern?

Question 1 options

2. Why should you prefer plugins over preferences for extending behavior?

Question 2 options

3. When refactoring, what should you do with existing callers of old methods?

Question 3 options

Flashcards

Question

What is the Strangler Fig pattern?

Answer

Gradually replacing parts of a legacy system with new code incrementally

Question

Why remove ObjectManager direct usage?

Answer

It breaks DI, makes testing harder, and violates SOLID principles

Question

What replaces direct model load() calls?

Answer

Repository pattern with getById() or getBySku() methods

Question

How to maintain backward compatibility during refactoring?

Answer

Keep old methods @deprecated, add new methods, update callers gradually

Question

What is the priority matrix for refactoring?

Answer

High impact + Low effort = Do First; High impact + High effort = Plan

Revision Notes

Key Takeaways

  • 1. Use the Strangler Fig pattern for incremental migration
  • 2. Add service contracts before refactoring existing code
  • 3. Replace ObjectManager with constructor injection
  • 4. Convert direct model loading to repository pattern
  • 5. Maintain backward compatibility during refactoring
  • 6. Write tests before, during, and after refactoring

Interview Tips

  • Explain the Strangler Fig pattern and when to use it
  • Discuss how you would modernize a Magento codebase
  • Talk about backward compatibility strategies
  • Explain the trade-offs of incremental vs big-bang refactoring

Cheat Sheet

Refactoring Checklist\n1. Assess legacy code\n2. Define service contracts\n3. Implement repositories\n4. Update callers gradually\n5. Remove old code\n6. Verify backward compatibility\n7. Test thoroughly