Tìm hiểu cách sử dụng Custom Exceptions trong PHP để xử lý lỗi rõ ràng, dễ maintain và chuẩn kiến trúc. Kèm ví dụ thực tế, best practices, tips & interview questions.
#Custom Exceptions là gì?
Custom Exceptions (ngoại lệ tùy chỉnh) là việc bạn tạo ra các class Exception riêng biệt cho từng loại lỗi trong hệ thống, thay vì sử dụng chung một loại \Exception.
👉 Đây là một practice cực kỳ quan trọng ở level mid → senior → architect.
#Bad Example (Anti-pattern)
class UserService
{
public function register(array $data): User
{
if (empty($data['email'])) {
throw new \Exception('Email is required');
}
if ($this->repository->findByEmail($data['email'])) {
throw new \Exception('Email already exists');
}
if (!$this->gateway->charge($data['amount'])) {
throw new \Exception('Payment failed');
}
return $this->repository->create($data);
}
}
#Vấn đề
- Tất cả lỗi đều là
\Exception - Không phân biệt được lỗi gì ở phía caller
- Phải parse string message (anti-pattern nặng)
- Khó maintain khi hệ thống lớn
👉 Đây là dấu hiệu của code junior-level hoặc thiếu design
#Good Example (Best Practice)
#1. Tạo Domain Exceptions
class ValidationException extends \RuntimeException
{
public function __construct(private array $errors)
{
parent::__construct('Validation failed');
}
public function getErrors(): array
{
return $this->errors;
}
}
class DuplicateEmailException extends \RuntimeException
{
public function __construct(private string $email)
{
parent::__construct("Email already registered: {$email}");
}
public function getEmail(): string
{
return $this->email;
}
}
class PaymentFailedException extends \RuntimeException
{
public function __construct(private string $reason)
{
parent::__construct("Payment failed: {$reason}");
}
}
#2. Sử dụng trong Service
class UserService
{
public function register(array $data): User
{
if (empty($data['email'])) {
throw new ValidationException(['email' => 'Email is required']);
}
if ($this->repository->findByEmail($data['email'])) {
throw new DuplicateEmailException($data['email']);
}
if (!$this->gateway->charge($data['amount'])) {
throw new PaymentFailedException('Card declined');
}
return $this->repository->create($data);
}
}
#3. Handle ở Caller
try {
$service->register($data);
} catch (ValidationException $e) {
return response()->json(['errors' => $e->getErrors()], 422);
} catch (DuplicateEmailException $e) {
return response()->json(['error' => 'Email already taken'], 409);
} catch (PaymentFailedException $e) {
return response()->json(['error' => 'Payment failed'], 402);
}
#Giải thích sâu
#1. Tại sao không dùng \Exception?
\Exception quá generic → mất thông tin domain
👉 Ví dụ:
- Validation error ≠ Business rule error ≠ External API error
Nếu dùng chung → hệ thống không thể phân biệt logic xử lý
#2. Exception = Domain Language
Ở level senior:
👉 Exception KHÔNG phải chỉ là lỗi 👉 Nó là ngôn ngữ của domain
Ví dụ:
DuplicateEmailException→ business rulePaymentFailedException→ external system
➡️ Code trở nên self-documenting
#3. Carry Data (rất quan trọng)
Custom exception có thể mang theo data:
$e->getErrors();
$e->getEmail();
👉 Đây là điểm khác biệt cực lớn so với string message
#4. Type-safe handling
catch (ValidationException $e)
👉 IDE + static analysis:
- biết chắc loại lỗi
- autocomplete
- tránh bug runtime
#5. Mapping HTTP chuẩn REST
| Exception | HTTP Code |
|---|---|
| ValidationException | 422 |
| DuplicateEmailException | 409 |
| PaymentFailedException | 402 |
👉 Đây là cách design API chuẩn production
#Tips & Tricks
#1. Tạo Base Domain Exception
abstract class DomainException extends \RuntimeException {}
👉 Tất cả exception trong domain nên extend từ đây
#2. Không throw Exception ở Infrastructure bừa bãi
👉 Convert sang domain exception
Ví dụ:
try {
$paymentGateway->charge();
} catch (\Throwable $e) {
throw new PaymentFailedException('Gateway error');
}
#3. Không dùng Exception cho flow control
Sai:
try {
findUser();
} catch (NotFoundException $e) {
// logic bình thường
}
👉 Exception chỉ dùng cho unexpected errors hoặc business rule violation
#4. Naming chuẩn
- Luôn kết thúc bằng
Exception - Tên phải mô tả rõ business
#5. Laravel Best Practice
👉 Dùng Exception + Handler:
public function render($request, Throwable $e)
{
if ($e instanceof ValidationException) {
return response()->json([...], 422);
}
}
Centralized error handling
#Interview Questions
Basic
1. Exception và RuntimeException khác nhau như thế nào?
Trả lời ngắn (summary):
Exceptionlà base class chungRuntimeExceptionthường dùng cho lỗi xảy ra trong runtime
Trả lời chi tiết (deep): Trong PHP, cả hai đều là throwable. Tuy nhiên theo convention:
Exception: dùng cho lỗi tổng quátRuntimeException: dùng cho lỗi runtime (business, external, logic)
👉 Best practice: dùng RuntimeException cho domain exception
2. Tại sao không nên dùng generic Exception?
Summary:
-
Không phân biệt được loại lỗi
-
Khó maintain
Deep: Khi dùng
\Exception, bạn mất: -
Type safety
-
Context
-
Khả năng handle riêng từng case
👉 Hệ quả: phải parse message → fragile
Intermediate
3. Custom Exception giúp gì cho maintainability?
Summary:
-
Code rõ ràng
-
Dễ mở rộng
Deep:
-
Mỗi exception = 1 business rule
-
Thêm logic mới không phá code cũ (OCP)
4. Khi nào nên tạo một Exception mới?
Summary:
- Khi có logic xử lý khác biệt
Deep:
- Business rule riêng
- HTTP response khác
- Cần carry data
👉 Không tạo nếu chỉ khác message
5. Thiết kế hierarchy exception như thế nào?
Summary:
- Có base class
- Phân tầng rõ
Deep:
abstract class DomainException extends RuntimeException {}
class ValidationException extends DomainException {}
class BusinessException extends DomainException {}
Advanced
6. Mapping exception sang HTTP response như thế nào?
Summary:
- Map theo loại lỗi
Deep:
- Validation → 422
- Conflict → 409
- Payment → 402
👉 Centralize ở Handler
7. Exception có nên nằm trong Domain Layer không?
Summary:
- Có
Deep: Exception là domain language (DDD), thể hiện business rule violation
8. Xử lý exception trong distributed system?
Summary:
- Không expose trực tiếp
Deep:
- Convert → response
- Log + correlation ID
9. Exception vs Error Code?
Summary:
- Exception tốt hơn trong OOP
Deep:
| Exception | Error Code |
|---|---|
| Type-safe | Không |
| Clean | Messy |
#Kết luận
👉 Custom Exceptions là nền tảng của:
- Clean Architecture
- Domain-Driven Design
- Maintainable codebase
Nếu bạn vẫn đang dùng \Exception everywhere → bạn đang giới hạn khả năng scale hệ thống