Skip to content
intermediate Phase 9 · DI & Patterns

Dependency Inversion Principle

DIP explained - depending on abstractions not concretions, enabling testing and flexibility in Magento applications

45m
0 problems
Topic Progress 0%

Understanding DIP

The Two Rules

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. 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:

Question 1 options

2. How does DIP help with testing?

Question 2 options

3. In Magento, how is DIP primarily achieved?

Question 3 options

Flashcards

Question

What are the two rules of DIP?

Answer

1) Both high and low modules depend on abstractions. 2) Abstractions don't depend on details.

Question

How does DIP differ from DI?

Answer

DI is the mechanism (injecting dependencies). DIP is the principle (depend on abstractions, not concretions).

Question

What Magento feature implements DIP?

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