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
- Keep old methods, mark as @deprecated
- Add new methods alongside old ones
- Update callers gradually
- 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?
2. Why should you prefer plugins over preferences for extending behavior?
3. When refactoring, what should you do with existing callers of old methods?
Flashcards
Question
What is the Strangler Fig pattern?
Click to reveal answer
Answer
Gradually replacing parts of a legacy system with new code incrementally
Question
Why remove ObjectManager direct usage?
Click to reveal answer
Answer
It breaks DI, makes testing harder, and violates SOLID principles
Question
What replaces direct model load() calls?
Click to reveal answer
Answer
Repository pattern with getById() or getBySku() methods
Question
How to maintain backward compatibility during refactoring?
Click to reveal answer
Answer
Keep old methods @deprecated, add new methods, update callers gradually
Question
What is the priority matrix for refactoring?
Click to reveal answer
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