In one sentence

Abstraction means describing what something must be able to do while leaving how it does it to the classes that fill in the details.

πŸ”ŒA wall socket is an abstraction. It promises "two pins, 230 volts". Your lamp, laptop charger and kettle all plug in without caring whether the power comes from solar panels or a coal plant.

Why it matters

Code against promises, not details

πŸ“œ

Clear contracts

An interface lists exactly which methods a class must have. Anyone reading it knows what to expect.

πŸ”„

Swappable parts

Code that depends on a contract works with any class that keeps the promise: a real mailer, a fake one for tests, a new provider next year.

🧱

Shared code without copying

Abstract classes and traits let related or unrelated classes reuse logic instead of duplicating it.

The big picture

Three tools, three jobs

πŸ“œ

Interface

A pure promise: method signatures, no code. A class can implement many.


            interface Notifier
            {
                public function send(string $to, string $msg): void;
            }
          
🧩

Abstract class

A half-built parent: some real code, some "fill this in" methods. A class extends only one.


            abstract class Report
            {
                abstract protected function rows(): array;
            }
          
🧰

Trait

A bundle of methods pasted into any class with use. No "is a" relationship at all.


            trait HasSlug
            {
                public function slug(): string { /* ... */ }
            }
          
Interfaces define what, abstract classes share some how with related children, traits share how with anyone.
Step by step

From promise to implementation

  1. Write an interface

    An interface is a list of public method signatures with no bodies. It's a contract: "any class that signs this must have these methods".

    
              <?php
    
              interface Notifier
              {
                  public function send(string $to, string $message): void;
              }
            
  2. Implement it in real classes

    A class implements an interface by providing every method it lists. Miss one, or get a type wrong, and PHP refuses to load the class.

    
              class EmailNotifier implements Notifier
              {
                  public function send(string $to, string $message): void
                  {
                      echo "πŸ“§ Emailing {$to}: {$message}" . PHP_EOL;
                  }
              }
    
              class SmsNotifier implements Notifier
              {
                  public function send(string $to, string $message): void
                  {
                      echo "πŸ“± Texting {$to}: {$message}" . PHP_EOL;
                  }
              }
            
  3. Depend on the interface, not the class

    Here's the payoff. Type-hint the interface, and your code accepts any class that implements it. It has no idea, and doesn't care, which one it gets.

    
              class CommentAlert
              {
                  public function __construct(private Notifier $notifier) {}
    
                  public function newComment(string $author, string $postTitle): void
                  {
                      $this->notifier->send($author, "New comment on {$postTitle}");
                  }
              }
    
              (new CommentAlert(new EmailNotifier()))->newComment('ada@example.com', 'Hello Laravel');
              (new CommentAlert(new SmsNotifier()))->newComment('+15550100', 'Hello Laravel');
            

    A class can implement several interfaces at once: class Post implements JsonSerializable, Countable.

  4. Share code with an abstract class

    An abstract class can't be created with new. It holds shared code and declares abstract methods that each child must fill in. This "parent runs the recipe, children supply one ingredient" shape is called the template method pattern.

    
              abstract class Report
              {
                  abstract protected function rows(): array;
    
                  public function toCsv(): string
                  {
                      return implode(PHP_EOL, array_map(fn (array $r) => implode(',', $r), $this->rows()));
                  }
              }
    
              class PopularPostsReport extends Report
              {
                  protected function rows(): array
                  {
                      return [['Hello Laravel', 1250], ['Arrays 101', 830]];
                  }
              }
    
              echo (new PopularPostsReport())->toCsv();
              new Report();   // Error: Cannot instantiate abstract class Report
            
  5. Choose: interface or abstract class?

    They overlap, so here's a rule of thumb. You can also combine them: an abstract class can implement an interface and leave some methods for its children.

    InterfaceAbstract class
    Contains code?No (only signatures and constants)Yes, plus abstract methods
    Properties?NoYes
    How many per class?Many (implements A, B)One (extends)
    Use it when…Unrelated classes share a capabilityRelated classes share real code
  6. Reuse code anywhere with traits

    A trait is a reusable chunk of methods (and properties) that you "copy in" with use inside a class. It solves "I want this behaviour in classes that don't share a parent".

    
              trait HasSlug
              {
                  public function slug(): string
                  {
                      return strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $this->slugSource()), '-'));
                  }
    
                  abstract protected function slugSource(): string;
              }
    
              class Post
              {
                  use HasSlug;
                  public function __construct(public string $title) {}
                  protected function slugSource(): string { return $this->title; }
              }
    
              class Category
              {
                  use HasSlug;
                  public function __construct(public string $name) {}
                  protected function slugSource(): string { return $this->name; }
              }
    
              echo (new Category('PHP Tips'))->slug();   // php-tips
            
    ⚠️ Traits aren't types

    You can't type-hint a trait (function x(HasSlug $s) won't work). If code needs to accept "anything with a slug", pair the trait with an interface, such as class Post implements Sluggable { use HasSlug; }.

Common mistakes

Avoid this, do that

❌ Avoid

          public function __construct(private EmailNotifier $notifier) {}
        

Hard-wired to one concrete class. Switching to SMS, or a fake in tests, means editing this class.

βœ… Do

          public function __construct(private Notifier $notifier) {}
        

Depend on the interface, and any implementation can be plugged in from outside.

❌ Avoid

          interface PostManager
          {
              public function create(); public function publish();
              public function email(); public function exportPdf();
              public function tweet(); // ...12 more
          }
        

A giant interface forces every implementer to write methods it doesn't need.

βœ… Do

          interface Publishable { public function publish(): void; }
          interface Exportable  { public function export(): string; }
        

Keep interfaces small and focused. Classes implement only the ones that apply.

πŸ’‘ Pro tip: name the capability

Good interface names describe what a class can do or is: Notifier, Publishable, Cache. Avoid suffixes like PostInterface unless your team already uses them.

πŸ‹οΈ Try it yourself
  1. Write an interface Shape with area(): float and name(): string.
  2. Implement it in Circle and Rectangle using promoted constructor properties.
  3. Create an abstract class Exporter with a concrete export(array $shapes): string that calls an abstract format(Shape $shape): string. Make CsvExporter and JsonExporter children.
  4. Write a trait Timestamps with a touch() method that sets a ?DateTimeImmutable $updatedAt, and use it in both shapes.
ℹ️ Where you'll see this in Laravel
  • Contracts: Laravel's core services are defined as interfaces in Illuminate\Contracts, such as Cache\Repository and Mail\Mailer. The Service Container decides which class fills each contract.
  • Traits on models: use HasFactory, Notifiable, SoftDeletes; mixes features into your User and Post models.
  • Marker interfaces: class SendNewsletter implements ShouldQueue tells Laravel to run a job in the background (see Queues & Jobs).
  • Abstract bases: Illuminate\Database\Eloquent\Model is itself an abstract class you extend.
Recap

Key takeaways

Check yourself

Mini quiz