Chế độ xem

Tin theo khu vực

Thị trường
Chứng khoánKinh doanhCông nghệXem tất cả →

Dữ liệu chỉ mang tính tham khảo · Cập nhật theo phiên

Trang chủ/Lập trình/SOLID Principles
PHPLaravelSOLIDClean CodePhỏng vấn

SOLID Principles trong Laravel & PHP: Giải Thích Dễ Hiểu Để Đi Làm & Phỏng Vấn

SOLID là 5 nguyên tắc vàng của lập trình hướng đối tượng. Code tuân thủ SOLID thì dễ đọc, dễ test, dễ mở rộng — và là thứ interviewer hỏi nhiều nhất trong các buổi phỏng vấn senior. Bài này giải thích từng nguyên tắc bằng ngôn ngữ đời thường kèm ví dụ Laravel thực tế.

15 phút đọc·Cập nhật 18/05/2026
SSingle Responsibility
OOpen / Closed
LLiskov Substitution
IInterface Segregation
DDependency Inversion
SOLID do ai đặt ra? Robert C. Martin (Uncle Bob) — tác giả cuốn Clean Code — đưa ra 5 nguyên tắc này vào đầu những năm 2000. Tên viết tắt SOLID do Michael Feathers tổng hợp lại. Đây là nền tảng của hầu hết mọi best practice trong lập trình OOP hiện đại.

SSingle Responsibility Principle

S

Single Responsibility Principle

Một class chỉ nên có một lý do để thay đổi

Một câu để nhớ: Đầu bếp chỉ nấu ăn — không giao hàng, không tính tiền. Mỗi class chỉ làm một việc duy nhất.

“Một lý do để thay đổi” nghĩa là: nếu phải sửa class này, lý do phải là cùng một chủ đề. Class UserController chỉ nên thay đổi khi logic HTTP thay đổi — không phải khi business logic hay email template thay đổi.

❌ Vi phạm SRP — UserService làm quá nhiều
class UserService {
    // ❌ Xử lý DB
    public function createUser(array $data): User {
        return User::create($data);
    }

    // ❌ Gửi email — không liên quan đến tạo user
    public function sendWelcomeEmail(User $user): void {
        Mail::to($user->email)->send(new WelcomeMail($user));
    }

    // ❌ Upload avatar — lại một việc khác
    public function uploadAvatar(User $user, UploadedFile $file): string {
        $path = $file->store('avatars', 'public');
        $user->update(['avatar' => $path]);
        return $path;
    }

    // ❌ Tạo PDF report — hoàn toàn không liên quan
    public function generateUserReport(User $user): string {
        return PDF::loadView('reports.user', compact('user'))->save();
    }
}
✅ Đúng SRP — mỗi class một trách nhiệm
// Chỉ xử lý CRUD user
class UserRepository {
    public function create(array $data): User {
        return User::create($data);
    }
}

// Chỉ gửi email
class UserMailer {
    public function sendWelcome(User $user): void {
        Mail::to($user->email)->send(new WelcomeMail($user));
    }
}

// Chỉ xử lý file upload
class AvatarUploader {
    public function upload(User $user, UploadedFile $file): string {
        $path = $file->store('avatars', 'public');
        $user->update(['avatar' => $path]);
        return $path;
    }
}

// Controller gọi đúng service cần thiết
class UserController {
    public function __construct(
        private UserRepository $users,
        private UserMailer     $mailer,
    ) {}

    public function store(Request $request) {
        $user = $this->users->create($request->validated());
        $this->mailer->sendWelcome($user);
        return response()->json($user, 201);
    }
}
  • Dễ test hơn — mỗi class nhỏ, mock dễ dàng
  • Thay đổi email template không ảnh hưởng đến DB logic
  • Tái sử dụng: UserMailer dùng được ở nhiều nơi khác
  • Dễ tìm bug — biết ngay vấn đề ở class nào

Câu hỏi phỏng vấn thường gặp

  • Q1.SRP là gì? Cho ví dụ vi phạm SRP trong thực tế?
  • Q2."Một lý do để thay đổi" có nghĩa là gì?
  • Q3.Làm sao biết một class đang vi phạm SRP? (class quá dài, method tên khác chủ đề)
  • Q4.SRP áp dụng cho method, module không — hay chỉ cho class?

OOpen/Closed Principle

O

Open/Closed Principle

Mở để mở rộng, đóng để chỉnh sửa

Một câu để nhớ: Cắm thêm thiết bị vào ổ cắm — không cần đục tường thêm dây điện. Class nên mở để thêm tính năng mới nhưng đóng để không sửa code cũ.

❌ Vi phạm OCP — thêm payment phải sửa PaymentProcessor
class PaymentProcessor {
    public function process(string $method, float $amount): bool {
        // ❌ Mỗi lần thêm payment mới phải sửa class này
        if ($method === 'stripe') {
            // Stripe logic...
            return true;
        } elseif ($method === 'paypal') {
            // PayPal logic...
            return true;
        } elseif ($method === 'momo') {
            // Thêm MoMo → phải sửa class cũ ❌
            return true;
        }
        // Rủi ro break code cũ mỗi lần thêm
        return false;
    }
}
✅ Đúng OCP — thêm payment mới chỉ cần thêm class mới
// Interface — "hợp đồng" cố định, không đổi
interface PaymentGateway {
    public function charge(float $amount): bool;
    public function refund(string $transactionId): bool;
}

// Mỗi payment = 1 class riêng
class StripeGateway implements PaymentGateway {
    public function charge(float $amount): bool { /* ... */ return true; }
    public function refund(string $id): bool    { /* ... */ return true; }
}

class PayPalGateway implements PaymentGateway {
    public function charge(float $amount): bool { /* ... */ return true; }
    public function refund(string $id): bool    { /* ... */ return true; }
}

// Thêm MoMo → chỉ tạo class mới, KHÔNG sửa code cũ ✅
class MoMoGateway implements PaymentGateway {
    public function charge(float $amount): bool { /* ... */ return true; }
    public function refund(string $id): bool    { /* ... */ return true; }
}

// Processor không cần biết gateway cụ thể là gì
class PaymentProcessor {
    public function __construct(private PaymentGateway $gateway) {}

    public function process(float $amount): bool {
        return $this->gateway->charge($amount);
    }
}

// Laravel binding — đổi gateway chỉ sửa 1 dòng này
$this->app->bind(PaymentGateway::class, StripeGateway::class);
OCP trong Laravel: Đây chính xác là lý do Laravel dùng Interface + Service Container. Bạn có thể đổi từ StripeGateway sang MoMoGateway chỉ bằng cách sửa 1 dòng binding — không đụng đến PaymentProcessor hay Controller.
  • Thêm tính năng mới không làm hỏng code cũ
  • Giảm rủi ro regression bug khi release
  • Team có thể làm việc song song — mỗi người viết 1 implementation
  • Code dễ unit test hơn vì mỗi class nhỏ, độc lập

Câu hỏi phỏng vấn thường gặp

  • Q1.OCP là gì? Ví dụ thực tế bạn từng áp dụng?
  • Q2."Mở để mở rộng, đóng để sửa" — làm thế nào đạt được điều này trong PHP?
  • Q3.OCP liên quan thế nào đến Interface và Polymorphism?
  • Q4.Khi nào KHÔNG cần áp dụng OCP? (YAGNI — You Ain't Gonna Need It)

LLiskov Substitution Principle

L

Liskov Substitution Principle

Subclass phải thay thế được parent class mà không phá vỡ chương trình

Một câu để nhớ: Nếu bạn đặt xe máy điện vào chỗ xe máy xăng, nó phải chạy được bình thường — không được bất ngờ phát nổ hay đứng im.

Nói đơn giản: nếu code dùng Animal, thì thay bằng Dog extends Animal hay Cat extends Animal đều phải hoạt động đúng.

❌ Vi phạm LSP — ReadOnlyFile không thực sự là File
class File {
    public function read(): string  { return file_get_contents($this->path); }
    public function write(string $content): void {
        file_put_contents($this->path, $content);
    }
}

// ❌ ReadOnlyFile extends File nhưng không hỗ trợ write()
class ReadOnlyFile extends File {
    public function write(string $content): void {
        // Vi phạm LSP: throw exception thay vì hoạt động bình thường
        throw new Exception("Cannot write to read-only file!");
    }
}

// Code này sẽ crash nếu truyền ReadOnlyFile
function processFile(File $file): void {
    $content = $file->read();
    $file->write($content . "
---processed---"); // 💥 Nổ!
}
✅ Đúng LSP — tách interface phù hợp
// Tách thành 2 interface rõ ràng
interface Readable {
    public function read(): string;
}

interface Writable {
    public function write(string $content): void;
}

// File đầy đủ implement cả 2
class File implements Readable, Writable {
    public function __construct(private string $path) {}
    public function read(): string { return file_get_contents($this->path); }
    public function write(string $content): void {
        file_put_contents($this->path, $content);
    }
}

// ReadOnlyFile chỉ implement Readable — trung thực về khả năng
class ReadOnlyFile implements Readable {
    public function __construct(private string $path) {}
    public function read(): string { return file_get_contents($this->path); }
}

// Function chỉ nhận thứ nó thực sự dùng
function readFile(Readable $file): string {
    return $file->read(); // ✅ An toàn với cả File lẫn ReadOnlyFile
}

function saveFile(Writable $file, string $content): void {
    $file->write($content); // ✅ Chỉ nhận class có thể write
}
Dấu hiệu vi phạm LSP: Subclass có method throw new Exception("Not supported"), hoặc override method để không làm gì ({}), hoặc phải dùng instanceof để check type trước khi gọi method.
  • Code an toàn hơn khi dùng polymorphism
  • Tránh bug khó tìm do subclass hoạt động bất ngờ
  • Dễ thay thế implementation mà không lo crash
  • Thiết kế hierarchy rõ ràng, hợp logic

Câu hỏi phỏng vấn thường gặp

  • Q1.LSP là gì? Cho ví dụ vi phạm LSP?
  • Q2.Tại sao Square extends Rectangle lại vi phạm LSP? (classic interview question)
  • Q3.LSP liên quan thế nào đến thiết kế Interface?
  • Q4.Làm sao phát hiện code đang vi phạm LSP?

IInterface Segregation Principle

I

Interface Segregation Principle

Không nên ép class implement những method nó không dùng

Một câu để nhớ: Nhân viên văn phòng không cần biết lái xe tải chỉ vì cùng là nhân viên công ty. Interface nên được chia nhỏ thay vì một interface khổng lồ.

❌ Vi phạm ISP — Interface quá béo
// ❌ Một interface ôm đồm mọi thứ
interface UserInterface {
    public function find(int $id): User;
    public function create(array $data): User;
    public function update(int $id, array $data): bool;
    public function delete(int $id): bool;
    public function sendEmail(User $user, string $msg): void;  // ❌ không phải DB
    public function uploadAvatar(User $user, $file): string;   // ❌ không phải DB
    public function generateReport(User $user): string;        // ❌ không phải DB
    public function exportToCsv(array $users): string;         // ❌ không phải DB
}

// Class bị ép implement những method không liên quan
class UserRepository implements UserInterface {
    public function find(int $id): User { /* ... */ }
    public function create(array $data): User { /* ... */ }
    // ...
    public function sendEmail(User $user, string $msg): void {
        // ❌ Repository không gửi email! Nhưng interface ép phải có
        throw new Exception("Not implemented");
    }
    public function generateReport(User $user): string {
        throw new Exception("Not implemented"); // ❌
    }
}
✅ Đúng ISP — chia nhỏ interface theo mục đích
// Mỗi interface chỉ 1 nhóm trách nhiệm
interface UserCrudInterface {
    public function find(int $id): ?User;
    public function create(array $data): User;
    public function update(int $id, array $data): bool;
    public function delete(int $id): bool;
}

interface UserNotifiableInterface {
    public function sendEmail(User $user, string $message): void;
    public function sendSms(User $user, string $message): void;
}

interface UserReportableInterface {
    public function generateReport(User $user): string;
    public function exportToCsv(array $users): string;
}

// Mỗi class chỉ implement những gì nó thực sự làm
class UserRepository implements UserCrudInterface {
    public function find(int $id): ?User   { return User::find($id); }
    public function create(array $data): User { return User::create($data); }
    public function update(int $id, array $data): bool { /* ... */ return true; }
    public function delete(int $id): bool  { return User::destroy($id) > 0; }
    // Không bị ép viết sendEmail hay generateReport ✅
}

class UserNotificationService implements UserNotifiableInterface {
    public function sendEmail(User $user, string $message): void {
        Mail::to($user->email)->send(new GenericMail($message));
    }
    public function sendSms(User $user, string $message): void { /* ... */ }
}

// Controller chỉ inject interface nó cần
class UserController {
    public function __construct(
        private UserCrudInterface         $users,
        private UserNotifiableInterface   $notifier,
    ) {}
}
  • Không phải implement method "rỗng" hay throw exception
  • Interface nhỏ gọn, dễ mock trong test
  • Thay đổi một interface không ảnh hưởng các class khác
  • Code rõ ràng — nhìn vào interface biết ngay class làm gì

Câu hỏi phỏng vấn thường gặp

  • Q1.ISP là gì? Ví dụ về "fat interface" trong thực tế?
  • Q2.ISP và SRP khác nhau thế nào? (SRP cho class, ISP cho interface)
  • Q3.Khi nào nên tách interface? Khi nào không cần?
  • Q4.ISP liên quan thế nào đến LSP?

DDependency Inversion Principle

D

Dependency Inversion Principle

Phụ thuộc vào abstraction, không phụ thuộc vào implementation cụ thể

Một câu để nhớ: Ổ cắm điện phụ thuộc vào tiêu chuẩn phích cắm (interface), không phụ thuộc vào thương hiệu cụ thể. Bất kỳ thiết bị đúng tiêu chuẩn đều cắm được.

DIP có 2 phần: (1) Module cấp cao không phụ thuộc module cấp thấp — cả 2 phụ thuộc vào abstraction. (2) Abstraction không phụ thuộc vào chi tiết — chi tiết phụ thuộc vào abstraction.

❌ Vi phạm DIP — OrderService phụ thuộc vào class cụ thể
class OrderService {
    private MySQLOrderRepository $repository; // ❌ Cụ thể
    private StripePayment $payment;           // ❌ Cụ thể
    private SmtpMailer $mailer;               // ❌ Cụ thể

    public function __construct() {
        // ❌ Tự new — không inject từ ngoài
        $this->repository = new MySQLOrderRepository();
        $this->payment    = new StripePayment();
        $this->mailer     = new SmtpMailer();
    }

    public function placeOrder(array $data): Order {
        $order = $this->repository->save($data);
        $this->payment->charge($order->total);
        $this->mailer->send($order->user_email, 'Order confirmed');
        return $order;
    }
}

// Muốn test? Phải có MySQL thật, Stripe thật, SMTP thật — nightmare!
✅ Đúng DIP — phụ thuộc vào Interface
// Định nghĩa abstraction (interface)
interface OrderRepositoryInterface {
    public function save(array $data): Order;
    public function find(int $id): ?Order;
}

interface PaymentInterface {
    public function charge(float $amount): bool;
}

interface MailerInterface {
    public function send(string $to, string $subject): void;
}

// High-level module phụ thuộc vào abstraction
class OrderService {
    public function __construct(
        private OrderRepositoryInterface $repository, // ✅ Interface
        private PaymentInterface         $payment,    // ✅ Interface
        private MailerInterface          $mailer,     // ✅ Interface
    ) {}

    public function placeOrder(array $data): Order {
        $order = $this->repository->save($data);
        $this->payment->charge($order->total);
        $this->mailer->send($order->user_email, 'Order confirmed');
        return $order;
    }
}

// Low-level modules implement interface
class MySQLOrderRepository implements OrderRepositoryInterface { /* ... */ }
class StripePayment          implements PaymentInterface       { /* ... */ }
class SmtpMailer             implements MailerInterface        { /* ... */ }

// Binding trong AppServiceProvider
$this->app->bind(OrderRepositoryInterface::class, MySQLOrderRepository::class);
$this->app->bind(PaymentInterface::class,         StripePayment::class);
$this->app->bind(MailerInterface::class,          SmtpMailer::class);

// Test dễ dàng — mock interface
class OrderServiceTest extends TestCase {
    public function test_place_order() {
        $mockRepo    = Mockery::mock(OrderRepositoryInterface::class);
        $mockPayment = Mockery::mock(PaymentInterface::class);
        $mockMailer  = Mockery::mock(MailerInterface::class);

        $mockRepo->shouldReceive('save')->once()->andReturn(new Order());
        $mockPayment->shouldReceive('charge')->once()->andReturn(true);
        $mockMailer->shouldReceive('send')->once();

        $service = new OrderService($mockRepo, $mockPayment, $mockMailer);
        $service->placeOrder(['product_id' => 1, 'total' => 100]);
    }
}
DIP chính là nền tảng của Laravel Service Container. Toàn bộ hệ thống inject dependency của Laravel hoạt động dựa trên nguyên tắc này — bạn bind interface vào container, Laravel resolve implementation phù hợp tự động.
  • Test cực dễ — mock interface thay vì class thật
  • Đổi database, payment, mailer không cần sửa business logic
  • Code có thể tái sử dụng ở context khác nhau
  • Giảm coupling — các module độc lập hơn

Câu hỏi phỏng vấn thường gặp

  • Q1.DIP là gì? Khác gì với Dependency Injection?
  • Q2."Phụ thuộc vào abstraction" có nghĩa cụ thể là gì?
  • Q3.DIP và Service Container trong Laravel liên quan thế nào?
  • Q4.Tại sao DIP giúp việc test dễ hơn? Cho ví dụ.

Tổng kết & Checklist thực hành

ChữNguyên tắcCâu hỏi tự kiểm tra
SSingle ResponsibilityClass này có nhiều hơn 1 lý do để thay đổi không?
OOpen/ClosedThêm tính năng mới có cần sửa class cũ không?
LLiskov SubstitutionSubclass có thể thay thế parent mà không crash không?
IInterface SegregationClass có phải implement method mà nó không dùng không?
DDependency InversionClass có phụ thuộc vào concrete class không?
Mối liên hệ giữa SOLID và Design Pattern: SOLID là nguyên tắc, Design Pattern là giải pháp cụ thể để đạt được các nguyên tắc đó. Ví dụ: Repository Pattern giúp đạt SRP + DIP. Factory Pattern giúp đạt OCP. Observer Pattern giúp đạt OCP + SRP.

Checklist khi review code

SClass dài hơn ~200 dòng? → Có thể vi phạm SRP
SMethod tên không liên quan chủ đề class? → Tách ra
OCó switch/if-else kiểm tra type để chọn hành vi? → Dùng interface
OThêm loại mới buộc phải sửa file cũ? → Refactor
LOverride method chỉ để throw exception? → Vi phạm LSP
LDùng instanceof trước khi gọi method? → Mùi LSP violation
IInterface có method mà class không dùng? → Tách interface
DConstructor có `new SomeClass()`? → Dùng DI thay thế

💡 Lời khuyên cho phỏng vấn

  • Khi được hỏi về SOLID, đừng chỉ đọc định nghĩa — hãy kể ví dụ từ dự án thực tế của bạn.
  • Nhớ mối liên hệ: DI (Design Pattern) implement DIP (nguyên tắc SOLID).
  • Câu hỏi phổ biến nhất: "Square extends Rectangle có vi phạm LSP không?" → Có, vì setWidth/setHeight của Square thay đổi cả 2 chiều, phá vỡ kỳ vọng của Rectangle.
  • Thừa nhận trade-off: SOLID tốt nhưng over-engineer cũng có hại. App nhỏ không nhất thiết cần repository cho mọi model.

Bài liên quan

Design Pattern trong Laravel & PHP

DI, Repository, Singleton, Factory, Observer — kèm code thực tế

Đọc ngay →

Góc nhìn của bạn

Trang này hữu ích với bạn không?

hoặc
♥ VNEWS
Menu điều hướng
Đã lưu
Thông báo
Đang xem tin trong nước

© 2026 VNnews · Báo tổng hợp từ nhiều nguồn