Understanding DIP
The Two Rules
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
Violation: Direct Database Access in Business Logic
namespace Vendor\Catalog\Model;
// HIGH-LEVEL: Business logic
class InventoryManager
{
private \PDO $db; // LOW-LEVEL: Direct database dependency
public function __construct()
{
$this->db = new \PDO('mysql:host=localhost;dbname=catalog', 'root', '');
}
public function deductStock(string $sku, int $qty): bool
{
$stmt = $this->db->prepare('UPDATE stock SET qty = qty - ? WHERE sku = ?');
return $stmt->execute([$qty, $sku]);
}
public function getStock(string $sku): int
{
$stmt = $this->db->prepare('SELECT qty FROM stock WHERE sku = ?');
$stmt->execute([$sku]);
return (int) $stmt->fetchColumn();
}
}
Problems: Cannot test without database, cannot switch to Elasticsearch/API, business logic tied to MySQL.
Correct Implementation
// ABSTRACTION: Both layers depend on this
namespace Vendor\Catalog\Api;
interface StockStorageInterface
{
public function getStock(string $sku): int;
public function setStock(string $sku, int $qty): void;
public function deductStock(string $sku, int $qty): bool;
}
// LOW-LEVEL: Detail depends on abstraction
namespace Vendor\Catalog\Model\Storage;
class MysqlStockStorage implements \Vendor\Catalog\Api\StockStorageInterface
{
public function __construct(
private \Magento\Framework\DB\Adapter\AdapterInterface $connection
) {}
public function getStock(string $sku): int
{
return (int) $this->connection->fetchOne(
'SELECT qty FROM catalog_stock WHERE sku = ?',
[$sku]
);
}
public function setStock(string $sku, int $qty): void
{
$this->connection->update(
'catalog_stock',
['qty' => $qty],
['sku = ?' => $sku]
);
}
public function deductStock(string $sku, int $qty): bool
{
return $this->connection->update(
'catalog_stock',
['qty' => new \Zend_Db_Expr("qty - {$qty}")],
['sku = ?' => $sku]
);
}
}
// HIGH-LEVEL: Detail depends on abstraction
namespace Vendor\Catalog\Model;
class InventoryManager
{
public function __construct(
private \Vendor\Catalog\Api\StockStorageInterface $storage
) {}
public function deductStock(string $sku, int $qty): bool
{
$current = $this->storage->getStock($sku);
if ($current < $qty) {
return false;
}
return $this->storage->deductStock($sku, $qty);
}
}
Now InventoryManager depends on an abstraction. The database detail is inverted — it depends on StockStorageInterface too.
DIP in Magento's Architecture
Magento's Service Contracts are DIP
Every service contract in Magento is an application of DIP:
// High-level: Your import module
class ProductImporter
{
public function __construct(
private \Magento\Catalog\Api\ProductRepositoryInterface $repo
) {}
}
// Low-level: Magento's catalog implementation
// Magento\Catalog\Model\ProductRepository implements ProductRepositoryInterface
Your module (high-level business logic) depends on ProductRepositoryInterface (abstraction). Magento's catalog module (low-level detail) also depends on that same interface. The dependency direction is inverted.
Configuring DIP in di.xml
<config>
<!-- Tell Magento which implementation to use -->
<type name="Vendor\Catalog\Api\StockStorageInterface">
<preferences>
<preference for="Vendor\Catalog\Api\StockStorageInterface"
type="Vendor\Catalog\Model\Storage\MysqlStockStorage"/>
</preferences>
</type>
<!-- Or per-class override -->
<type name="Vendor\Inventory\Model\InventoryManager">
<arguments>
<argument name="storage" xsi:type="object">
Vendor\Catalog\Model\Storage\ElasticSearchStockStorage
</argument>
</arguments>
</type>
</config>
Benefits in Practice
// In test: swap real storage for in-memory mock
class InventoryManagerTest extends \PHPUnit\Framework\TestCase
{
public function testDeductStock(): void
{
$storage = new InMemoryStockStorage(); // Implements same interface
$storage->setStock('SKU-1', 100);
$manager = new InventoryManager($storage);
$result = $manager->deductStock('SKU-1', 50);
$this->assertTrue($result);
$this->assertEquals(50, $storage->getStock('SKU-1'));
}
}
No database needed. No mocking frameworks. The test is fast, isolated, and deterministic.
Quiz
1. DIP states that abstractions should depend on:
2. How does DIP help with testing?
3. In Magento, how is DIP primarily achieved?
Flashcards
Question
What are the two rules of DIP?
Click to reveal answer
Answer
1) Both high and low modules depend on abstractions. 2) Abstractions don't depend on details.
Question
How does DIP differ from DI?
Click to reveal answer
Answer
DI is the mechanism (injecting dependencies). DIP is the principle (depend on abstractions, not concretions).
Question
What Magento feature implements DIP?
Click to reveal answer
Answer
Service contracts (interfaces) configured via di.xml preferences
Revision Notes
Key Takeaways
- 1. DIP: Both high-level and low-level modules depend on abstractions
- 2. Details (implementations) depend on abstractions, not the other way around
- 3. Magento service contracts (ProductRepositoryInterface, etc.) are DIP in action
- 4. di.xml preferences map interfaces to concrete implementations
- 5. DIP enables easy testing by allowing mock injection
Interview Tips
- • Distinguish DIP (principle) from DI (mechanism) — they're related but different
- • Give a concrete example: 'ProductImporter depends on ProductRepositoryInterface, not ProductRepository'
- • Explain how di.xml preferences implement the inversion
Cheat Sheet
DIP ≠DI
DI = Mechanism (injecting dependencies)
DIP = Principle (depend on abstractions)
Rule: High-level → Abstraction ↠Low-level
Both point TO the abstraction, not at each other.
Magento: interface + di.xml preference = DIP