- Tác giả

- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
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
1. Native Type trước, PHPDoc sau
Ưu tiên:
function find(int $id): ?User
thay vì:
/**
* @param int $id
* @return User|null
*/
function find($id)
2. Tránh array quá chung chung
Thay vì:
array
nếu biết structure, hãy mô tả cụ thể hơn:
list<User>
hoặc:
array<string, int>
hoặc:
array{
name: string,
age: int
}
3. Chọn type phản ánh đúng data structure
Không phải lúc nào:
list<T>
cũng tốt hơn:
array<K, V>
Ví dụ:
Sequential data
→ list<T>
Associative data
→ array<string, T>
Integer indexed nhưng không sequential
→ array<int, T>
Fixed structure
→ array shape
4. Tránh mixed khi có thể
Không nên mặc định:
array<string, mixed>
Nếu structure thực tế đã biết, hãy mô tả chính xác hơn.
5. Dùng static analyzer
PHPDoc sẽ có giá trị lớn hơn rất nhiều khi project sử dụng:
PHP
+
PHPDoc
+
PHPStan / Psalm
+
IDE
Đây là một phần quan trọng của static type safety trong PHP.
27. PHPDoc Type Cheat Sheet
| Type | Ý nghĩa |
|---|---|
int |
Integer |
string |
String |
bool |
Boolean |
float |
Float |
mixed |
Bất kỳ type nào |
User |
Object thuộc class User |
?User |
User hoặc null |
User|Admin |
User hoặc Admin |
array |
Array bất kỳ |
array<int, string> |
Integer key → string value |
array<string, int> |
String key → integer value |
list<string> |
Sequential list của string |
non-empty-list<int> |
List int, không được empty |
array{...} |
Array shape |
Collection<int, User> |
Collection của User |
@template T |
Generic type |
callable |
Callable |
@param |
Parameter documentation |
@return |
Return documentation |
@var |
Variable/property documentation |
@property |
Dynamic property |
@method |
Magic method |
@throws |
Exception contract |
@deprecated |
API deprecated |
28. Cách tư duy khi viết PHPDoc
Thay vì đặt câu hỏi:
"Tôi nên dùng
array<string>haylist<string>?"
nên đặt câu hỏi rộng hơn:
"Type thực tế của dữ liệu này là gì?"
Ví dụ:
Một danh sách user
↓
list<User>
Map username → user
↓
array<string, User>
Configuration cố định
↓
array{
host: string,
port: int,
ssl: bool
}
Collection của User
↓
Collection<int, User>
Generic repository
↓
Repository<T>
Đây mới là cách tiếp cận hiệu quả khi sử dụng PHPDoc trong một codebase lớn.
29. Kết luận
PHPDoc trong PHP hiện đại không chỉ là công cụ để viết documentation. Khi kết hợp với native type và static analysis, PHPDoc trở thành một phần quan trọng của type system trong quá trình phát triển.
Có thể hình dung kiến trúc type như sau:
Native PHP Type
+
PHPDoc
+
Static Analysis
+
IDE
↓
Better Type Safety
↓
Fewer Bugs
↓
Better Maintainability
Từ những annotation đơn giản như:
@param
@return
@var
đến các type nâng cao:
list<T>
array<K, V>
array{...}
Collection<K, V>
@template T
mục tiêu cuối cùng vẫn là một nguyên tắc rất đơn giản:
Hãy mô tả type chính xác nhất có thể, nhưng chỉ sử dụng mức độ phức tạp thực sự cần thiết.
Một PHPDoc tốt không phải là PHPDoc dài nhất, mà là PHPDoc giúp developer, IDE và static analyzer hiểu chính xác contract của code.
