Skip to content

Latest commit

 

History

History
642 lines (517 loc) · 15.6 KB

File metadata and controls

642 lines (517 loc) · 15.6 KB

PHPDoc Documentation - Self-Documenting Code

The Problem

Code without documentation AND with poor naming is:

  • Hard to understand - What does this function do?
  • Hard to use - What parameters does it need?
  • Hard to maintain - What does it return?
  • No IDE support - No autocomplete hints
  • Difficult for teams - Other developers must read implementation
  • Confusing - Generic names like get(), fetch(), Handler don't reveal intent

The Files

Both bad.php and good.php contain identical functionality - same logic, same behavior.

The differences:

  • bad.php - No PHPDoc comments + Ambiguous naming (DataManager, Handler, get(), fetch())
  • good.php - Complete PHPDoc documentation + Clear naming (PostRepository, PostService, find_by_id())

Bad Practice (bad.php)

No Documentation + Ambiguous Naming

class DataManager {
    private wpdb $db;
    
    public function get(int $id): ?WP_Post {
        $result = get_post($id);
        
        if (!$result instanceof WP_Post) {
            return null;
        }
        
        return $result;
    }
    
    public function fetch(int $user, int $num = 10, string $type = 'publish'): array {
        $num = max(1, min(100, $num));
        
        $sql = $this->db->prepare(
            "SELECT * FROM {$this->db->posts} 
            WHERE post_author = %d 
            AND post_status = %s 
            ORDER BY post_date DESC 
            LIMIT %d",
            $user,
            $type,
            $num
        );
        
        return $this->db->get_results($sql);
    }
}

class Handler {
    private DataManager $dm;
    private Checker $chk;
    
    public function make(array $input): int|WP_Error {
        // ...
    }
}

function getSnippet(WP_Post $p, int $len = 100): string {
    // ...
}

Multiple problems:

  1. Generic class names:

    • DataManager - manages what data? Posts? Users? Comments?
    • Handler - handles what? Requests? Posts? Events?
    • Checker - checks what?
  2. Ambiguous method names:

    • get() - get what? From where?
    • fetch() - what's the difference between get() and fetch()?
    • make() - make what?
    • getSimilar() - similar to what? How?
  3. Cryptic parameter names:

    • $user - user ID or user object?
    • $num - number of what?
    • $type - type of what? What values are valid?
    • $p - why abbreviate?
    • $len - length in characters or words?
  4. Unclear variable names:

    • $dm - DataManager? Direct Message? Dungeon Master?
    • $chk - why abbreviate "checker"?
    • $err - errors? error? error code?

Questions developers have:

  • What does DataManager::fetch() return? Posts? Users? Raw results?
  • What's the difference between get() and fetch()?
  • What does Handler::make() create?
  • Is $num in fetch() limited? What's the max?
  • What values can $type be?
  • What happens if nothing is found?

They must read every line of code to find answers!

Good Practice (good.php)

Complete PHPDoc + Descriptive Naming

/**
 * Repository for managing WordPress posts.
 *
 * Provides methods for retrieving, searching, and managing post data
 * with a clean, object-oriented interface.
 *
 * @package MyPlugin
 * @since 1.0.0
 */
class PostRepository {
    /**
     * WordPress database object.
     *
     * @var wpdb
     */
    private wpdb $wpdb;
    
    /**
     * Initialize the repository.
     *
     * Sets up the repository.
     *
     * @since 1.0.0
     */
    public function __construct() {}
    
    /**
     * Find posts by author.
     *
     * Retrieves posts written by a specific author, ordered by date.
     *
     * @since 1.0.0
     *
     * @param int    $author_id The author's user ID.
     * @param int    $limit     Maximum number of posts to return. Default 10. Max 100.
     * @param string $status    Post status to filter by. Default 'publish'.
     * @return array Array of post objects.
     */
    public function find_by_author(int $author_id, int $limit = 10, string $status = 'publish'): array {
        global $wpdb;
        $limit = max(1, min(100, $limit));
        
        $query = $wpdb->prepare(
            "SELECT * FROM {$wpdb->posts} 
            WHERE post_author = %d 
            AND post_status = %s 
            ORDER BY post_date DESC 
            LIMIT %d",
            $author_id,
            $status,
            $limit
        );
        
        return $wpdb->get_results($query);
    }
}

Everything is clear:

  1. Descriptive class names:

    • PostRepository - immediately know it manages posts
    • PostService - handles post-related business logic
    • PostValidator - validates post data
  2. Explicit method names:

    • find_by_id() - find post by ID
    • find_by_author() - find posts by author
    • create() - create a new post
    • update() - update existing post
  3. Clear parameter names:

    • $author_id - obviously an author's ID
    • $limit - clearly a limit count
    • $status - post status value
    • $post - the post object
  4. Comprehensive documentation:

    • Purpose: "Find posts by author"
    • Parameters: Each explained with type and purpose
    • Return: "Array of post objects"
    • Behavior: "ordered by date", "Max 100"

No questions remain!

The Compounding Problem

Poor naming + No documentation = Maximum confusion

Example: What does this do?

// bad.php - Ambiguous naming, no docs
class Handler {
    public function make(array $input): int|WP_Error {
        // ... 30 lines of code
    }
}

Developer questions:

  • What kind of handler?
  • Make what?
  • What should $input contain?
  • Returns int - ID of what?
  • When does it return WP_Error?

Must read all 30 lines to understand!

Compare to:

// good.php - Clear naming + documentation
/**
 * Service for managing post operations.
 *
 * @package MyPlugin
 * @since 1.0.0
 */
class PostService {
    /**
     * Create a new post.
     *
     * Validates the data and creates a new post with the provided information.
     *
     * @param array $data {
     *     Post data.
     *     @type string $title   Post title. Required.
     *     @type string $content Post content. Required.
     *     @type string $status  Post status. Default 'draft'.
     * }
     * @return int|WP_Error Post ID on success, WP_Error on failure.
     */
    public function create(array $data): int|WP_Error {
        // ... 30 lines of code
    }
}

Everything answered immediately!

  • It's a post service
  • Creates a new post
  • Needs title and content
  • Returns post ID on success
  • Returns WP_Error on failure
  • No need to read the implementation!

PHPDoc Components

Class Documentation

/**
 * Brief description (one line).
 *
 * Longer description with more details about what
 * the class does and how to use it.
 *
 * @package MyPlugin
 * @since 1.0.0
 */
class MyClass {

Tags:

  • @package - Which package/namespace
  • @since - When it was introduced
  • @author - Who wrote it (optional)

Method Documentation

/**
 * Brief description of what the method does.
 *
 * Longer description with implementation details,
 * usage examples, or important notes.
 *
 * @since 1.0.0
 *
 * @param int    $param1 Description of first parameter.
 * @param string $param2 Description of second parameter. Default 'value'.
 * @return bool True on success, false on failure.
 */
public function my_method(int $param1, string $param2 = 'value'): bool {

Tags:

  • @since - Version introduced
  • @param - Each parameter with type, name, description
  • @return - What it returns
  • @throws - Exceptions it might throw

Property Documentation

/**
 * Description of the property.
 *
 * @var string
 */
private string $my_property;

Complex Parameters

/**
 * Create a new post.
 *
 * @param array $data {
 *     Post data.
 *
 *     @type string   $title      Post title. Required.
 *     @type string   $content    Post content. Required.
 *     @type string   $status     Post status. Default 'draft'.
 *     @type int      $author_id  Author user ID. Default current user.
 *     @type int[]    $categories Array of category IDs. Optional.
 *     @type string[] $tags       Array of tag names. Optional.
 * }
 * @return int|WP_Error Post ID on success, WP_Error on failure.
 */
public function create(array $data): int|WP_Error {

Nested @type tags document array structure!

Benefits of PHPDoc

1. IDE Support

Without PHPDoc:

$repository->find_by_author(  // IDE shows: (int $author_id, int $limit, string $status)

With PHPDoc:

$repository->find_by_author(  
// IDE shows:
// @param int    $author_id The author's user ID
// @param int    $limit     Maximum number of posts. Default 10. Max 100
// @param string $status    Post status to filter by. Default 'publish'
// @return array Array of post objects

2. Auto-Complete

When you type $repository->, IDE shows:

  • All available methods
  • Brief description of each
  • Parameter hints
  • Return type hints

3. Quick Documentation

Hover over method name → See full documentation without opening file!

4. Type Hinting for Arrays

/**
 * @return WP_Post[] Array of post objects
 */
public function get_posts(): array {

Now IDE knows it's an array OF WP_Post objects, enabling autocomplete on array elements!

5. Generated Documentation

Tools like phpDocumentor can generate HTML documentation from PHPDoc:

phpdoc -d src/ -t docs/

Creates browsable documentation website!

Real-World Example Comparison

Without PHPDoc

function get_post_excerpt(WP_Post $post, int $length = 100): string {
    $content = strip_tags($post->post_content);
    
    if (strlen($content) <= $length) {
        return $content;
    }
    
    return substr($content, 0, $length) . '...';
}

Developer thinking:

  • "What does this return?"
  • "Is the length in characters or words?"
  • "Does it preserve HTML?"
  • "What if post is empty?"

Must read code to answer!

With PHPDoc

/**
 * Get an excerpt from a post.
 *
 * Generates a truncated version of the post content with
 * HTML tags stripped.
 *
 * @since 1.0.0
 *
 * @param WP_Post $post   The post object.
 * @param int     $length Maximum length of excerpt in characters. Default 100.
 * @return string The post excerpt, truncated with '...' if needed.
 */
function get_post_excerpt(WP_Post $post, int $length = 100): string {
    $content = strip_tags($post->post_content);
    
    if (strlen($content) <= $length) {
        return $content;
    }
    
    return substr($content, 0, $length) . '...';
}

All questions answered immediately!

Common PHPDoc Tags

Basic Tags

Tag Purpose Example
@param Parameter description @param int $id The user ID
@return Return value @return bool True on success
@var Variable type @var string
@since Version introduced @since 1.0.0
@deprecated Mark as deprecated @deprecated 2.0.0 Use new_method() instead

Advanced Tags

Tag Purpose Example
@throws Exceptions thrown @throws InvalidArgumentException
@see Related items @see PostRepository::find_by_id()
@link External link @link https://developer.wordpress.org/
@example Usage example @example $repo->find_by_author(1, 5)
@package Package name @package MyPlugin\Posts
@author Author @author John Doe <john@example.com>

WordPress-Specific

Tag Purpose
@since WordPress version introduced
@global Global variable used
@action WordPress action hook
@filter WordPress filter hook

WordPress Documentation Standards

WordPress has specific standards:

/**
 * Retrieve the post title.
 *
 * If the post is protected and the visitor is not an admin, then "Protected"
 * will be prepended to the post title. If the post is private, then "Private"
 * will be prepended to the post title.
 *
 * @since 0.71
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 * @return string The post title.
 */
function get_the_title($post = 0): string {

Notice:

  • Clear description of behavior
  • @since tag (when introduced)
  • @global for globals used
  • Full @param documentation
  • Specific @return description

When to Document

Always Document

✅ Public classes
✅ Public methods
✅ Public functions
✅ Complex private methods
✅ Non-obvious behavior
✅ Arrays with specific structure

Optional for

⚠️ Simple getters/setters (though still recommended)
⚠️ Very obvious private methods
⚠️ Test methods (though @test tag is useful)

Never Skip

Public API - Users need to know how to use it
Complex logic - Future you will thank you
Team projects - Others need to understand

Quick Templates

Class

/**
 * Brief class description.
 *
 * @package MyPlugin
 * @since 1.0.0
 */
class MyClass {

Method

/**
 * Brief method description.
 *
 * @since 1.0.0
 *
 * @param type $param Description.
 * @return type Description.
 */
public function method($param): type {

Function

/**
 * Brief function description.
 *
 * @since 1.0.0
 *
 * @param type $param Description.
 * @return type Description.
 */
function my_function($param): type {

Property

/**
 * Brief property description.
 *
 * @var type
 */
private type $property;

Key Takeaways

Document all public APIs
Use descriptive names - not generic ones
Use @param for each parameter
Use @return for return values
Add @since version tags
Document array structures with @type
Name things clearly - PostRepository not DataManager
Write for your future self
Explain WHY, not just WHAT

❌ Don't use generic names like Handler, Manager, Data
❌ Don't abbreviate variable names ($p, $dm, $chk)
❌ Don't skip documentation "because it's obvious"
❌ Don't use vague method names (get(), fetch(), make())
❌ Don't document bad code instead of fixing it
❌ Don't let documentation get out of sync
❌ Don't write useless comments like @param int $id The ID

The Bottom Line

The code in bad.php and good.php has identical functionality.

The differences:

  • bad.php - Generic names (DataManager, Handler) + No documentation
  • good.php - Clear names (PostRepository, PostService) + Complete documentation

Comparison

// BAD: Generic name, ambiguous method, no docs
class DataManager {
    public function fetch(int $user, int $num = 10, string $type = 'publish'): array {
        // What does this return? How is it different from get()?
        // What's the max for $num? What types are valid?
    }
}

// GOOD: Descriptive name, clear method, documented
/**
 * Repository for managing WordPress posts.
 */
class PostRepository {
    /**
     * Find posts by author.
     *
     * @param int    $author_id The author's user ID.
     * @param int    $limit     Maximum posts to return. Default 10. Max 100.
     * @param string $status    Post status to filter by. Default 'publish'.
     * @return array Array of WP_Post objects.
     */
    public function find_by_author(int $author_id, int $limit = 10, string $status = 'publish'): array {
        // Everything is crystal clear!
    }
}

Which would you rather work with?

Good naming + Good documentation = Self-documenting code.

Name things clearly. Document thoroughly. Your team (and future you) will thank you.