PHPDoc Trong PHP: Hướng Dẫn Từ Cơ Bản Đến Nâng Cao
PHPDoc là một phần quan trọng trong việc xây dựng PHP codebase có type rõ ràng, dễ maintain và thân thiện với IDE/static analysis. Với các công cụ như PHPStan, Larastan và Psalm, PHPDoc không còn chỉ là comment mô tả code mà có thể cung cấp thêm thông tin type mà PHP native type chưa biểu diễn được.
Bài viết này đi từ những khái niệm cơ bản đến nâng cao, bao gồm:
- PHPDoc là gì?
- Khi nào nên dùng PHPDoc?
@param,@return,@var- Native Type vs PHPDoc
- Array types
list<T>- Union và nullable types
- Array Shape
- Object và Generic Types
- Collection Types
- Template Types
@property,@method,@throws- PHPDoc kết hợp PHPStan/Larastan/Psalm
- Best practices
1. PHPDoc là gì?
PHPDoc là một chuẩn documentation syntax được xây dựng dựa trên PHP comment để mô tả thông tin về code.
Ví dụ:
/**
* Get user by ID.
*
* @param int $id
* @return User|null
*/
function getUser(int $id): ?User
{
// ...
}
PHPDoc có thể cung cấp thông tin cho:
- IDE
- Static analyzer
- Documentation generator
- Developer trong team
- Code review
Đặc biệt, PHPDoc có thể mô tả những type phức tạp mà native PHP chưa thể hiện đầy đủ.
2. Tại sao PHPDoc vẫn quan trọng trong PHP hiện đại?
PHP ngày càng hỗ trợ nhiều native type.
Ví dụ:
function getUser(int $id): ?User
{
// ...
}
Đây là cách nên làm khi PHP có thể biểu diễn chính xác type.
Tuy nhiên, vẫn có rất nhiều trường hợp native type chưa đủ.
Ví dụ:
function getUsers(array $ids): array
{
// ...
}
Ta biết $ids là array, nhưng không biết:
- chứa
inthaystring? - có phải list tuần tự không?
- có được empty không?
Return cũng tương tự:
array
không cho biết array chứa:
User
string
int
mixed
...
PHPDoc có thể bổ sung thông tin này.
3. Native Type và PHPDoc nên kết hợp với nhau
Một nguyên tắc quan trọng:
Native PHP type nên được ưu tiên khi PHP có thể biểu diễn type đó. PHPDoc nên bổ sung thông tin mà native type chưa thể mô tả.
Ví dụ không cần thiết phải viết:
/**
* @param int $id
* @return User
*/
function getUser(int $id): User
{
}
Nếu native type đã thể hiện đầy đủ.
Thay vào đó:
function getUser(int $id): User
{
}
là đủ.
Nhưng với cấu trúc phức tạp:
/**
* @param list<int> $ids
* @return list<User>
*/
function getUsers(array $ids): array
{
}
PHPDoc bổ sung thông tin quan trọng mà array native type không thể thể hiện.
4. Các PHPDoc annotation cơ bản
@param
Dùng để mô tả parameter.
/**
* @param string $name
*/
function greet(string $name): void
{
}
Trong code hiện đại, nếu native type đã có:
function greet(string $name): void
{
}
thì @param string thường không cần thiết.
PHPDoc trở nên hữu ích khi type phức tạp hơn.
/**
* @param list<int> $ids
*/
function processUsers(array $ids): void
{
}
5. @return
Dùng để mô tả return value.
/**
* @return list<User>
*/
function getUsers(): array
{
return [];
}
Ở đây native PHP chỉ biết:
array
nhưng PHPDoc cho static analyzer biết:
list<User>
6. @var
@var thường được sử dụng cho:
- Property
- Local variable
- Expression
- Type information bổ sung
Ví dụ:
/**
* @var list<string>
*/
private array $tags = [];
Hoặc:
/** @var User $user */
$user = $repository->find($id);
7. Array Types
Array là một trong những nơi PHPDoc phát huy tác dụng rất rõ.
Native PHP:
array
quá chung chung.
PHPDoc có thể mô tả:
array<int, string>
Có nghĩa:
key → int
value → string
Ví dụ:
/**
* @var array<int, string>
*/
$users = [
1 => 'John',
2 => 'Jane',
];
Hoặc:
/**
* @var array<string, int>
*/
$scores = [
'math' => 90,
'english' => 85,
];
8. list<T>
list<T> dùng để biểu diễn một array tuần tự.
/**
* @var list<string>
*/
$names = [
'John',
'Jane',
'Bob',
];
Conceptually:
0 → John
1 → Jane
2 → Bob
Trong khi:
array<int, string>
không yêu cầu key phải liên tục.
Ví dụ:
[
10 => 'John',
20 => 'Jane',
]
là array<int, string> nhưng không phải list<string>.
Điểm quan trọng không phải là list tốt hơn array, mà là:
Chọn type phản ánh đúng cấu trúc dữ liệu thực tế.
9. non-empty-list<T>
Có những trường hợp business logic yêu cầu list không được empty.
Khi đó có thể sử dụng:
/**
* @param non-empty-list<int> $ids
*/
function processUsers(array $ids): void
{
}
Khác với:
list<int>
có thể là:
[]
non-empty-list<int> yêu cầu ít nhất một phần tử.
Đây là ví dụ cho thấy PHPDoc có thể truyền tải business constraint, không chỉ đơn thuần là data type.
10. Union Types
Một value có thể có nhiều type.
/**
* @var string|int $value
*/
$value = getValue();
Có nghĩa:
string OR int
Nếu PHP hỗ trợ native union type, nên sử dụng:
function findUser(int|string $id): User
{
}
thay vì chỉ dùng PHPDoc.
PHPDoc vẫn hữu ích khi union type phức tạp hơn.
11. Nullable Types
Một value có thể là null.
Native PHP:
function findUser(int $id): ?User
{
}
hoặc:
function findUser(int $id): User|null
{
}
PHPDoc cũng có thể biểu diễn:
/**
* @return User|null
*/
Nhưng nếu native type đã thể hiện được thì nên ưu tiên native type.
12. Array Shape
Đây là một tính năng rất mạnh của PHPDoc.
Thay vì:
/**
* @var array<string, mixed>
*/
$user = [];
có thể mô tả chính xác:
/**
* @var array{
* name: string,
* email: string,
* age: int
* }
*/
$user = [
'name' => 'John',
'email' => 'john@example.com',
'age' => 30,
];
Static analyzer có thể hiểu chính xác type của từng property.
Ví dụ:
$user['name'];
được hiểu là:
string
và:
$user['age'];
được hiểu là:
int
13. Optional Keys
Array shape cũng có thể mô tả key không bắt buộc:
/**
* @var array{
* name: string,
* email: string,
* phone?: string
* }
*/
$user = [
'name' => 'John',
'email' => 'john@example.com',
];
phone? có nghĩa key này có thể không tồn tại.
Điều này chính xác hơn rất nhiều so với:
array<string, mixed>
14. Object Types
PHPDoc có thể mô tả object cụ thể:
/**
* @return User
*/
function getUser(): User
{
}
Hoặc nullable:
/**
* @return User|null
*/
function findUser(): ?User
{
}
Với các class phức tạp, PHPDoc giúp static analyzer theo dõi type xuyên suốt application.
15. Generic Types
Generic type cho phép một class hoặc function giữ lại thông tin về type mà nó đang xử lý.
Ví dụ concept:
Repository<User>
Repository<Order>
Repository<Product>
Một generic repository có thể được mô tả bằng:
/**
* @template T
*/
class Repository
{
/**
* @param T $entity
* @return T
*/
public function save($entity)
{
return $entity;
}
}
Đây là nền tảng để xây dựng các abstraction có type safety tốt hơn.
16. Collection Types
Đặc biệt quan trọng trong Laravel hoặc các framework sử dụng collection.
Ví dụ:
/**
* @return Collection<int, User>
*/
public function getUsers(): Collection
{
return User::query()->get();
}
Thay vì chỉ:
Collection
static analyzer biết rằng collection này chứa:
User
Điều này giúp IDE hỗ trợ autocomplete và phát hiện type mismatch tốt hơn.
17. @property
PHPDoc có thể mô tả dynamic properties.
Ví dụ:
/**
* @property int $id
* @property string $name
* @property string $email
*/
class User
{
}
Điều này đặc biệt hữu ích với những framework có dynamic/magic properties.
18. @method
Có thể mô tả magic methods:
/**
* @method static User findByEmail(string $email)
*/
class UserRepository
{
}
IDE/static analyzer có thể hiểu method này tồn tại mặc dù nó không được khai báo trực tiếp theo cách thông thường.
19. @throws
Dùng để document exception:
/**
* @throws UserNotFoundException
*/
function getUser(int $id): User
{
}
Điều này giúp developer hiểu contract của function.
Ví dụ:
Input
↓
getUser()
↓
User
hoặc
UserNotFoundException
20. @deprecated
Khi một API không còn nên sử dụng:
/**
* @deprecated Use getUserById() instead.
*/
function getUser(int $id): User
{
}
IDE có thể hiển thị cảnh báo khi developer tiếp tục sử dụng API cũ.
21. @template
Template types là phần nâng cao của PHPDoc và thường được sử dụng với PHPStan hoặc Psalm.
Ví dụ:
/**
* @template T
*/
interface Repository
{
/**
* @param T $entity
* @return T
*/
public function save($entity);
}
T đại diện cho một type chưa được xác định tại thời điểm khai báo.
Sau đó abstraction có thể được sử dụng cho nhiều loại object khác nhau.
22. PHPDoc và Static Analysis
PHPDoc trở nên đặc biệt hữu ích khi kết hợp với static analyzer.
Các công cụ phổ biến:
- PHPStan
- Larastan
- Psalm
Ví dụ:
/**
* @param list<int> $ids
*/
function process(array $ids): void
{
}
Nếu developer viết:
process(['1', '2']);
static analyzer có thể phát hiện:
Expected list<int>
Given list<string>
PHP runtime có thể không phát hiện lỗi này tại thời điểm gọi function.
Static analysis giúp phát hiện vấn đề trước khi application chạy.
23. PHPDoc không thay thế Runtime Validation
Một hiểu lầm phổ biến:
/**
* @param list<int> $ids
*/
function process(array $ids): void
{
}
không có nghĩa PHP runtime sẽ tự động validate $ids.
PHPDoc chủ yếu phục vụ:
Developer
↓
IDE
↓
Static Analyzer
↓
Feedback
Nếu cần runtime validation, cần sử dụng:
- Native PHP type
- Validation
- DTO
- Value Object
- Assertion
- Schema validation
24. Khi nào nên dùng PHPDoc?
Có thể sử dụng PHPDoc khi:
Native PHP chưa đủ expressive
/**
* @return list<User>
*/
function getUsers(): array
Cần mô tả array structure
/**
* @return array{
* name: string,
* age: int
* }
*/
Làm việc với generic
/**
* @template T
*/
Framework có magic behavior
Ví dụ:
- Laravel Eloquent
- Magic methods
- Dynamic properties
Cần document contract
/**
* @throws SomeException
*/
25. Khi nào không nên lạm dụng PHPDoc?
Không nên viết PHPDoc chỉ để lặp lại native type.
Ví dụ:
/**
* @param int $id
* @return string
*/
function getName(int $id): string
{
}
Nếu không có thêm information, có thể bỏ PHPDoc.
Thay vào đó:
function getName(int $id): string
{
}
Code sẽ ngắn và rõ ràng hơn.
26. Best Practices
Yêu cầu đăng nhập
Vui lòng đăng nhập để truy cập nội dung này
Additional Resources
Course Guide
Comprehensive PDF guide with examples
GitHub Repository
Example code for all lessons
Discussion
Have a question about this lesson? Post it here and get answers from instructors and peers.
