PHP and Object-Oriented Programming

OOP Concepts in Laravel

Interfaces, traits, abstract classes, and dependency injection

14 min read · Lesson 8 of 18

OOP in Practice, Not Theory

You don't need a computer science degree to build great Laravel apps. But you do need to understand the OOP concepts that Laravel is built on — because they show up in every controller, model, and service you write.


Interfaces: Contracts

An interface defines a contract — a set of methods that a class must implement, without specifying how:

interface PaymentGateway
{
    public function charge(int $amount, string $currency): PaymentResult;
    public function refund(string $transactionId): bool;
}

Any class that implements this interface MUST have both methods. This lets you swap implementations without changing the code that uses them:

class StripeGateway implements PaymentGateway { /* ... */ }
class PayPalGateway implements PaymentGateway { /* ... */ }

Laravel uses interfaces extensively — they're called Contracts in the Illuminate\Contracts namespace. When you type-hint Cache or Queue, you're often using an interface.


Abstract Classes: Partial Implementations

An abstract class is a class that can't be instantiated directly. It provides some implemented methods and requires subclasses to implement others:

abstract class Notification
{
    abstract public function via(): array;
    abstract public function toMail(): MailMessage;

    // Concrete method available to all subclasses
    public function shouldSend(): bool
    {
        return true;
    }
}

The difference from interfaces: abstract classes can have actual code (shared behavior), while interfaces only define method signatures.


Traits: Reusable Behavior

Traits let you share methods across classes without inheritance. PHP doesn't support multiple inheritance, so traits fill the gap:

trait HasSlug
{
    public function getRouteKeyName(): string
    {
        return 'slug';
    }

    public static function findBySlug(string $slug): ?static
    {
        return static::where('slug', $slug)->first();
    }
}

Laravel is full of traits: SoftDeletes, HasFactory, Notifiable, HasApiTokens. You use them by adding use TraitName; inside a class.


Dependency Injection

Dependency injection means passing objects a class needs rather than having it create them internally:

// Without DI — tightly coupled
class OrderService
{
    public function process()
    {
        $gateway = new StripeGateway(); // Hard-coded dependency
    }
}

// With DI — loosely coupled
class OrderService
{
    public function __construct(private PaymentGateway $gateway) {}

    public function process()
    {
        $this->gateway->charge(...); // Uses whatever was injected
    }
}

Laravel's service container handles the injection automatically. When you type-hint a class in a controller constructor or method, the container resolves it for you. This is why you never see new inside controllers — the container builds everything.


Key Takeaways

  • Interfaces define contracts. They enable swappable implementations and are core to Laravel's architecture.
  • Abstract classes provide partial implementations — shared behavior plus required methods.
  • Traits share methods across unrelated classes. Laravel uses them extensively (SoftDeletes, HasFactory, etc.).
  • Dependency injection keeps classes loosely coupled. Laravel's container does this automatically when you type-hint.
Ask about this lesson