Site logo

PHPDoc Trong PHP: Hướng Dẫn Từ Cơ Bản Đến Nâng Cao

5:00 read

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

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.