Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
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 int hay string?
  • 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',
]

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> hay list<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.