What Are the 4 Object-Oriented Principles?
A clear exploration of the four foundational object-oriented programming principles and how they directly apply to modern Laravel applications.
Object-Oriented Programming (OOP) is a software design paradigm that models real-world domain concepts using cohesive objects containing both internal state (attributes) and behavior (methods). Modern PHP and the Laravel framework are deeply rooted in object-oriented design patterns.
Mastering the four foundational OOP principles—Encapsulation, Abstraction, Inheritance, and Polymorphism—enables developers to author Laravel applications that are testable, maintainable, and resilient to business change.
1. Encapsulation
Encapsulation is the practice of restricting direct external access to an object's internal state, requiring all interactions to pass through explicit public methods. This protects internal invariants and prevents unintended state corruption.
Bad Example: Exposed Internal State
Code • php// Bad example: Public mutable fields permit arbitrary external modifications namespace App\DTO; class UserProfile { public string $email; public string $passwordHash; } // In external calling code: $profile = new UserProfile(); $profile->email = "invalid-email-string"; // No validation possible!
Good Example: Guarded State via Public Interfaces
Code • php// Good example: Internal properties are private; mutations are validated namespace App\DTO; use InvalidArgumentException; class UserProfile { private string $email; private string $passwordHash; public function setEmail(string $email): void { if (! filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException("Invalid email format provided."); } $this->email = strtolower(trim($email)); } public function getEmail(): string { return $this->email; } public function setPasswordHash(string $hash): void { $this->passwordHash = $hash; } public function getPasswordHash(): string { return $this->passwordHash; } }
Encapsulation guarantees that every instance of UserProfile throughout the system satisfies your domain validation rules.
2. Abstraction
Abstraction means exposing what an object does while hiding the intricate details of how it achieves that behavior. In Laravel, abstraction is commonly achieved through PHP interfaces and service contracts.
Bad Example: Concrete Implementation Bleed
Code • php// Bad example: Service tightly bound to concrete SMTP mailers and SMS APIs class NotificationDispatcher { public function notifyByEmail(string $to, string $message): void { // 40 lines of direct SMTP socket commands or vendor SDK calls... } public function notifyBySms(string $to, string $message): void { // 30 lines of Twilio curl calls... } }
Good Example: Contract-Driven Architecture
Code • php// Good example: Abstract notification mechanism behind a clean contract namespace App\Contracts; interface NotificationChannel { public function send(string $recipient, string $body): bool; } namespace App\Services\Notifications; use App\Contracts\NotificationChannel; class EmailChannel implements NotificationChannel { public function send(string $recipient, string $body): bool { // SMTP or Postmark delivery logic return true; } } class SmsChannel implements NotificationChannel { public function send(string $recipient, string $body): bool { // Twilio SMS delivery logic return true; } }
Higher-level application services depend solely on the NotificationChannel contract, insulating business logic from changes to third-party communication providers.
3. Inheritance
Inheritance enables a child class to inherit attributes and methods from a parent class, facilitating code reuse and establishing clear domain taxonomies. In Laravel, every Eloquent model and Artisan command relies on inheritance.
Bad Example: Bloated God Base Class
Code • php// Bad example: Base class accumulates specialized logic irrelevant to all users class User { public function authenticate(string $password): bool { return true; } public function verifyDriverLicense(string $licenseNo): bool { // Driver-only check forced into the generic User base class! return true; } } class Admin extends User { // Inherits irrelevant driver license validation methods! }
Good Example: Clean Hierarchy
Code • php// Good example: Base class defines common attributes; specialized subclasses add unique behaviors namespace App\Models; abstract class AuthenticatableEntity { public function authenticate(string $password): bool { // General authentication logic return true; } } class DriverUser extends AuthenticatableEntity { public function verifyDriverLicense(string $licenseNumber): bool { // Domain verification specific to transport drivers return true; } } class AdministratorUser extends AuthenticatableEntity { public function auditSystemLogs(): array { // Privileged operations specific to system administrators return []; } }
4. Polymorphism
Polymorphism allows different classes to implement the same interface while providing their own distinct behavior. Consumers can invoke methods on the interface without knowing or caring which concrete class is actively executing.
Bad Example: Rigid Type-Checking Conditionals
Code • php// Bad example: Codebase littered with switch statements over provider names class PaymentService { public function process(string $vendor, float $amount): bool { switch ($vendor) { case 'stripe': // Stripe API call return true; case 'paypal': // PayPal API call return true; default: throw new \Exception("Unsupported gateway"); } } }
Good Example: Polymorphic Gateway Substitution
Code • php// Good example: Unified contract with interchangeable implementations namespace App\Contracts; interface PaymentGateway { public function charge(float $amount): bool; } namespace App\Services\Gateways; use App\Contracts\PaymentGateway; class StripeGateway implements PaymentGateway { public function charge(float $amount): bool { // Executes Stripe charge API call return true; } } class PayPalGateway implements PaymentGateway { public function charge(float $amount): bool { // Executes PayPal charge API call return true; } } // Controller or Service: class CheckoutController { public function checkout(PaymentGateway $gateway, float $orderTotal): bool { // Polymorphic invocation: works seamlessly with Stripe, PayPal, or any future gateway return $gateway->charge($orderTotal); } }
Why These Principles Matter in Laravel
The architectural brilliance of Laravel—its Service Container, Service Providers, Eloquent ORM, and Contracts—is built directly on these four principles:
- Encapsulation protects data integrity within Eloquent models and Data Transfer Objects.
- Abstraction powers Laravel Contracts (
Illuminate\Contracts\*), freeing developers to swap drivers (e.g., Redis, SQS, SES) with configuration tweaks. - Inheritance allows controllers, commands, and migrations to share rich foundational framework utilities.
- Polymorphism powers Laravel's dependency injection container, enabling effortless mocking in PHPUnit or Pest unit tests.
Mastering these four principles transforms everyday Laravel programming into cohesive, resilient software engineering.
Ersin Demirtas
Software Engineer based in London, UK. Building web applications, APIs, and tools with Laravel, TypeScript, React, and Next.js.
Related Articles
Understanding SOLID Principles in Laravel Development
Feb 20, 2024 —A deep dive into how SOLID principles can be practically applied to Laravel applications for cleaner, scalable, and testable architectures.
Understanding and Solving the N+1 Problem in Laravel
May 20, 2025 —Learn about the N+1 problem, why it degrades application performance, and how to detect and resolve it in Laravel using eager loading.