Skip to content

Latest commit

 

History

History
187 lines (148 loc) · 4.95 KB

File metadata and controls

187 lines (148 loc) · 4.95 KB

Strict Types vs Mixed Types

The Problem

WordPress core functions often accept int|string|WP_Post|null which seems flexible but creates problems:

  • Defensive programming everywhere - Every function needs to handle all possible input types
  • Unclear responsibilities - Who validates? Who converts?
  • Runtime errors - Type mismatches only caught when code executes
  • Difficult testing - Need to test all type combinations

Bad Practice (bad.php)

function display_post_title( $post ) {
    // Could be ID, could be post object, could be null...
    $post_obj = get_post( $post );
    
    if ( $post_obj ) {
        echo $post_obj->post_title;
    } else {
        echo 'No title';
    }
}

Problems:

  • Function accepts anything ($post has no type)
  • Has to call get_post() to normalize input
  • Defensive checks throughout
  • Caller doesn't know what to pass
  • Multiple responsibilities: validation + display

Good Practice (good.php)

Approach 1: Strict Types

function display_post_title( WP_Post $post ): void {
    echo esc_html( $post->post_title );
}

Benefits:

  • Crystal clear: expects WP_Post object
  • No validation needed inside
  • Single responsibility: display only
  • Impossible to call with wrong type
  • Easy to test

Approach 2: Validation at Boundaries (Factory Pattern)

class PostFactory {
    public static function create( int|string|WP_Post|null $input ): ?WP_Post {
        if ( $input instanceof WP_Post ) {
            return $input;
        }
        
        if ( is_numeric( $input ) ) {
            $post = get_post( (int) $input );
            return $post instanceof WP_Post ? $post : null;
        }
        
        // Handle other cases...
        return null;
    }
}

// Usage: validate ONCE at the boundary
function display_post_safely( $input ): void {
    $post = PostFactory::create( $input );
    
    if (!$post) {
        echo 'Post not found';
        return;
    }
    
    // From here on, we work with WP_Post only
    display_post_title($post);
}

Benefits:

  • Validation happens in ONE place (the factory)
  • Rest of the code works with strict WP_Post
  • Clear separation of concerns
  • Easy to add new validation logic

More Examples

Mixed Return Types (BAD)

function get_post_author_info($post) {
    // Returns WP_User, or null, or false... great!
    $author = get_userdata($post->post_author);
    return $author ?: false;
}

Strict Return Types (GOOD)

// If author MUST exist, throw exception
function get_post_author(WP_Post $post): WP_User {
    $author = get_userdata($post->post_author);
    
    if (!$author instanceof WP_User) {
        throw new RuntimeException('Invalid author');
    }
    
    return $author;
}

// If author might not exist, use nullable type
function find_post_author(WP_Post $post): ?WP_User {
    $author = get_userdata($post->post_author);
    return $author instanceof WP_User ? $author : null;
}

Real-World Pattern

// Controller/Route - validate input here
function handle_post_request(WP_REST_Request $request): WP_REST_Response {
    $post_id = $request->get_param('post_id');
    $post = PostFactory::createOrFail($post_id); // Validation boundary
    
    // Service layer - works with strict types
    $processor = new PostProcessor($post);
    $result = $processor->process();
    
    return new WP_REST_Response($result);
}

// Service layer - no validation, assumes valid input
class PostProcessor {
    public function __construct(
        private WP_Post $post
    ) {}
    
    public function process(): array {
        // Clean code, no type checking needed
        return [
            'title' => $this->post->post_title,
            'author' => $this->get_author()->display_name,
        ];
    }
    
    private function get_author(): WP_User {
        // Strict return type - no null, no false
    }
}

Key Takeaways

Use declare(strict_types=1);
Accept ONE specific type per function
Validate at boundaries (controllers, factories)
Internal code uses strict types
Be explicit about nullable (?Type)
Throw exceptions for invalid states

❌ Don't accept mixed unless absolutely necessary
❌ Don't return different types based on success/failure
❌ Don't use false for "not found" - use null or throw
❌ Don't replicate WordPress's mixed-type antipattern

The WordPress Compatibility Question

Q: "But WordPress core uses mixed types everywhere!"

A: That doesn't mean YOU should. WordPress maintains backwards compatibility with PHP 5.6 code from 2014. Your modern plugin/theme doesn't have that constraint.

Wrap WordPress functions at your boundaries:

// Your clean API
function get_validated_post(int $post_id): WP_Post {
    $post = get_post($post_id); // WordPress function
    
    if (!$post instanceof WP_Post) {
        throw new PostNotFoundException($post_id);
    }
    
    return $post; // Now strictly typed
}