Skip to content
beginner Phase 5 · PHP OOP

PHP Interfaces: Contracts and Dependency Injection

Master PHP interfaces, interface contracts, multiple interface implementation, and why interfaces matter for dependency injection in Magento.

1h
0 problems
Topic Progress 0%

Interface Basics and Contracts

What is an Interface?

An interface defines a contract - a set of methods that implementing classes MUST provide. It specifies WHAT a class can do, not HOW.

<?php
namespace Vendor\Module\Api;

// Interface defines the contract
interface ProductInterface
{
    public function getId(): int;
    public function getName(): string;
    public function getPrice(): float;
    public function isActive(): bool;
}

// Class implements the interface
class SimpleProduct implements ProductInterface
{
    public function __construct(
        private int $id,
        private string $name,
        private float $price,
        private bool $active = true
    ) {}

    public function getId(): int
    {
        return $this->id;
    }

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

    public function getPrice(): float
    {
        return $this->price;
    }

    public function isActive(): bool
    {
        return $this->active;
    }
}

Interface Rules

<?php
interface LoggerInterface
{
    // All methods must be public
    public function log(string $message): void;
    public function error(string $message): void;
    public function info(string $message): void;
}

// WRONG - interface methods cannot be private or protected
// interface BadInterface {
//     private function log(); // Error!
//     protected function log(); // Error!
// }

// Interface can define constants
interface StatusInterface
{
    const STATUS_ACTIVE = 1;
    const STATUS_DISABLED = 2;
    const STATUS_DELETED = 3;

    public function getStatus(): int;
}

class Product implements StatusInterface
{
    private int $status = self::STATUS_ACTIVE;

    public function getStatus(): int
    {
        return $this->status;
    }
}

Why Interfaces Matter

<?php
// WITHOUT interface - tightly coupled
$logger = new FileLogger('/var/log/app.log');
$service = new ProductService($logger); // Depends on FileLogger

// WITH interface - loosely coupled
interface LoggerInterface
{
    public function log(string $message): void;
}

class FileLogger implements LoggerInterface
{
    public function log(string $message): void
    {
        file_put_contents('/var/log/app.log', $message . "\n", FILE_APPEND);
    }
}

class DatabaseLogger implements LoggerInterface
{
    public function log(string $message): void
    {
        // Save to database
    }
}

// ProductService depends on interface, not concrete class
class ProductService
{
    public function __construct(
        private LoggerInterface $logger  // Any logger works!
    ) {}

    public function createProduct(array $data): void
    {
        // Create product...
        $this->logger->log('Product created');
    }
}

// Easily switch implementations
$service = new ProductService(new FileLogger());
$service = new ProductService(new DatabaseLogger());
$service = new ProductService(new CloudLogger());

Key Takeaway

Interfaces define contracts that implementing classes must follow. They enable loose coupling, making code flexible and testable. In Magento, interfaces are used extensively for dependency injection.

Multiple Interface Implementation

Implementing Multiple Interfaces

A class can implement multiple interfaces, combining different contracts.

<?php
namespace Vendor\Module\Api;

// Define multiple interfaces
interface NameableInterface
{
    public function getName(): string;
    public function setName(string $name): self;
}

interface PriceableInterface
{
    public function getPrice(): float;
    public function setPrice(float $price): self;
}

interface StorableInterface
{
    public function save(): bool;
    public function delete(): bool;
}

// Implement multiple interfaces
class Product implements NameableInterface, PriceableInterface, StorableInterface
{
    public function __construct(
        private string $name,
        private float $price
    ) {}

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

    public function setName(string $name): self
    {
        $this->name = $name;
        return $this;
    }

    public function getPrice(): float
    {
        return $this->price;
    }

    public function setPrice(float $price): self
    {
        $this->price = $price;
        return $this;
    }

    public function save(): bool
    {
        // Save to database
        return true;
    }

    public function delete(): bool
    {
        // Delete from database
        return true;
    }
}

// Type hint to specific interface
function renameItem(NameableInterface $item, string $newName): void
{
    $item->setName($newName);
}

function discountPrice(PriceableInterface $item, float $percent): void
{
    $item->setPrice($item->getPrice() * (1 - $percent / 100));
}

function persistItem(StorableInterface $item): void
{
    $item->save();
}

Interface Inheritance

<?php
// Interface can extend other interfaces
interface SerializableInterface
{
    public function serialize(): string;
}

interface CacheableInterface extends SerializableInterface
{
    public function getCacheKey(): string;
    public function getCacheLifetime(): int;
}

// Must implement ALL methods from parent + own
class CacheableProduct implements CacheableInterface
{
    public function serialize(): string
    {
        return json_encode(get_object_vars($this));
    }

    public function getCacheKey(): string
    {
        return 'product_' . $this->getId();
    }

    public function getCacheLifetime(): int
    {
        return 3600; // 1 hour
    }
}

Magento Interface Patterns

<?php
// Magento repository pattern uses interfaces
namespace Magento\Catalog\Api;

interface ProductRepositoryInterface
{
    public function get(string $sku, bool $editMode = false, ?int $storeId = null, $forceReload = false);
    public function getById(int $productId);
    public function save(\Magento\Catalog\Api\Data\ProductInterface $product);
    public function delete(\Magento\Catalog\Api\Data\ProductInterface $product);
    public function deleteById(int $productId);
}

interface ProductInterface
{
    public function getId(): ?int;
    public function getSku(): ?string;
    public function getName(): ?string;
    public function getPrice(): float;
    public function getStatus(): int;
    public function getVisibility(): int;
    // ... many more methods
}

// You depend on interfaces, Magento provides concrete implementations
class CustomModule
{
    public function __construct(
        private ProductRepositoryInterface $productRepo,  // Interface
        private ProductInterface $product                  // Interface
    ) {}
}

Type Checking with Interfaces

<?php
$product = new Product('Widget', 29.99);

// Check if object implements interface
if ($product instanceof NameableInterface) {
    echo $product->getName();
}

// Check all interfaces
$interfaces = class_implements($product);
// ['Vendor\Module\Api\NameableInterface', ...]

// Type hint accepts any implementation
function processItem(NameableInterface $item): void
{
    // Works with Product, Category, or any class implementing the interface
}

Key Takeaway

Classes can implement multiple interfaces, combining different contracts. Interface inheritance allows building layered contracts. Magento uses interfaces extensively for its repository and service layer patterns.

Interfaces for Dependency Injection

Dependency Injection with Interfaces

DI is the practice of receiving dependencies through constructor parameters rather than creating them internally.

<?php
// BAD: Tightly coupled - creates dependencies internally
class OrderService
{
    public function processOrder(array $orderData): void
    {
        $mailer = new Mailer();          // Hard dependency
        $logger = new FileLogger();      // Hard dependency
        $payment = new StripeGateway();  // Hard dependency

        // Can't easily test or swap implementations
    }
}

// GOOD: Loosely coupled - dependencies injected via constructor
class OrderService
{
    public function __construct(
        private MailerInterface $mailer,
        private LoggerInterface $logger,
        private PaymentGatewayInterface $payment
    ) {}

    public function processOrder(array $orderData): void
    {
        $this->logger->log('Processing order');
        $result = $this->payment->charge($orderData['total']);
        $this->mailer->send($orderData['email'], 'Order confirmed');
    }
}

Magento DI Configuration

<!-- app/code/Vendor/Module/etc/di.xml -->
<config>
    <!-- Interface to implementation mapping -->
    <type name="Vendor\Module\Api\ProductRepositoryInterface">
        <arguments>
            <argument name="config" xsi:type="object">Vendor\Module\Model\ProductRepository</argument>
        </arguments>
    </type>

    <!-- Virtual type - different implementation for different use -->
    <virtualType name="ProductRepositoryCache" type="Vendor\Module\Model\ProductRepository">
        <arguments>
            <argument name="cacheEnabled" xsi:type="boolean">true</argument>
        </arguments>
    </virtualType>

    <type name="Vendor\Module\Service\ProductCache">
        <arguments>
            <argument name="repository" xsi:type="object">ProductRepositoryCache</argument>
        </arguments>
    </type>
</config>

Testing with Interfaces

<?php
namespace Vendor\Module\Test\Unit\Service;

use PHPUnit\Framework\TestCase;
use Vendor\Module\Service\OrderService;
use Vendor\Module\Api\MailerInterface;

class OrderServiceTest extends TestCase
{
    private OrderService $service;

    protected function setUp(): void
    {
        // Create a mock (fake) implementation of the interface
        $mailer = $this->createMock(MailerInterface::class);
        $mailer->method('send')->willReturn(true);

        $logger = $this->createMock(LoggerInterface::class);
        $payment = $this->createMock(PaymentGatewayInterface::class);

        // Inject mocks
        $this->service = new OrderService($mailer, $logger, $payment);
    }

    public function testProcessOrderSendsEmail(): void
    {
        // Verify the service calls send() on the mailer
        $this->mailer->expects($this->once())
            ->method('send')
            ->with('customer@example.com', $this->anything());

        $this->service->processOrder([
            'email' => 'customer@example.com',
            'total' => 29.99
        ]);
    }
}

Common Magento Interfaces

Interface Purpose
ProductRepositoryInterface CRUD for products
CustomerRepositoryInterface CRUD for customers
OrderRepositoryInterface CRUD for orders
LoggerInterface Logging
CacheInterface Cache operations
EventManagerInterface Event dispatch
StoreManagerInterface Store information
FileManagerInterface File operations

Key Takeaway

Interfaces enable loose coupling through dependency injection. You depend on the contract (interface), not the implementation. This makes code testable, flexible, and maintainable. Magento's DI system resolves interfaces to concrete classes automatically.

Quiz

1. What is the main purpose of a PHP interface?

Question 1 options

2. Can a PHP class implement multiple interfaces?

Question 2 options

3. Why are interfaces important for dependency injection?

Question 3 options

4. What visibility must interface methods have?

Question 4 options

5. How does Magento use interfaces?

Question 5 options

Flashcards

Question

What is a PHP interface?

Answer

A contract that defines methods a class must implement. Contains only method signatures (no implementation). Enables polymorphism and loose coupling.

Question

Can a class implement multiple interfaces?

Answer

Yes. Use comma separation: class Product implements Nameable, Priceable, Storable. Must implement all methods from all interfaces.

Question

Why do interface methods have to be public?

Answer

Interfaces define the public API contract. If methods were private, they couldn't be accessed through the interface type, defeating the purpose.

Question

What is dependency injection?

Answer

Receiving dependencies through constructor parameters instead of creating them internally. Reduces coupling and enables testing with mocks.

Question

How does Magento resolve interfaces to implementations?

Answer

Through di.xml configuration. Magento's DI container reads the configuration and injects the correct implementation when an interface type is requested.

Question

What is the instanceof operator used for?

Answer

Checks if an object implements a specific interface or class. Example: $obj instanceof LoggerInterface returns true if the object has that interface.

Question

What is an interface constant?

Answer

A constant defined in an interface (const STATUS_ACTIVE = 1). Accessible via InterfaceName::CONSTANT or implementing class.

Question

What is the difference between an interface and an abstract class?

Answer

Interface: only method signatures, no state, multiple implementation allowed. Abstract class: can have method implementations and properties, single inheritance only.

Revision Notes

Key Takeaways

  • 1. Interfaces define contracts - method signatures that implementing classes must provide
  • 2. All interface methods must be public
  • 3. Classes can implement multiple interfaces
  • 4. Interfaces enable loose coupling through dependency injection
  • 5. Magento uses interfaces for repositories, services, and DI configuration
  • 6. Use instanceof to check if an object implements an interface
  • 7. Interface constants provide shared values across implementations

Interview Tips

  • Explain what an interface is and why it's useful
  • Describe how interfaces enable dependency injection
  • Know the difference between interfaces and abstract classes
  • Explain how Magento uses interfaces for DI
  • Give examples of when to use interfaces vs concrete classes

Cheat Sheet

PHP Interfaces Cheat Sheet

Definition:

interface LoggerInterface {
    public function log(string $message): void;
}

Implementation:

class FileLogger implements LoggerInterface {
    public function log(string $message): void { /* ... */ }
}

Multiple Interfaces:

class Product implements Nameable, Priceable, Storable {
    // Must implement ALL methods from ALL interfaces
}

Interface Inheritance:

interface Cacheable extends Serializable {
    public function getCacheKey(): string;
}

Type Checking:

$obj instanceof LoggerInterface // true/false
class_implements($obj) // Array of interfaces

Magento DI:
di.xml maps interfaces to implementations