Skip to content

Latest commit

 

History

History
327 lines (257 loc) · 7.25 KB

File metadata and controls

327 lines (257 loc) · 7.25 KB

Magic Numbers vs Named Constants

The Problem

"Magic numbers" and "magic strings" are unexplained literals scattered throughout code:

  • Unclear meaning - What does 86400 mean?
  • Hard to change - Need to find every occurrence
  • Error-prone - Easy to type wrong number
  • No context - Is this 10 the same as that 10?
  • Maintenance nightmare - Where did this value come from?

Bad Practice (bad.php)

function cache_post( $post_id, $data ) {
    set_transient( 'post_' . $post_id, $data, 86400 );
}

Problems:

  • What is 86400? Seconds? Minutes? Days?
  • If we want to change cache duration, we need to find all occurrences
  • No explanation for why this specific number
  • Easy to make typos (84600 vs 86400)

Good Practice (good.php)

// Use WordPress built-in time constants
// MINUTE_IN_SECONDS, HOUR_IN_SECONDS, DAY_IN_SECONDS,
// WEEK_IN_SECONDS, MONTH_IN_SECONDS, YEAR_IN_SECONDS

function cache_post( int $post_id, array $data ): bool {
    return set_transient(
        'post_' . $post_id,
        $data,
        DAY_IN_SECONDS
    );
}

Benefits:

  • Crystal clear: we're caching for one day
  • WordPress provides these constants out of the box
  • Self-documenting code
  • No need to define your own time constants
  • Consistent with WordPress core

Common WordPress Examples

Time Periods

// BAD
set_transient( 'key', $data, 3600 );
wp_schedule_event( time(), 86400, 'my_hook' );

// GOOD - Use WordPress built-in constants
set_transient( 'key', $data, HOUR_IN_SECONDS );
wp_schedule_event( time(), DAY_IN_SECONDS, 'my_hook' );

// WordPress provides:
// MINUTE_IN_SECONDS (60)
// HOUR_IN_SECONDS (3600)
// DAY_IN_SECONDS (86400)
// WEEK_IN_SECONDS (604800)
// MONTH_IN_SECONDS (2592000)
// YEAR_IN_SECONDS (31536000)

Post Statuses

// BAD
wp_update_post( [ 'ID' => $id, 'post_status' => 'publish' ] );
if ( $post->post_status === 'draft' ) { }

// GOOD
class PostStatus {
    public const PUBLISH = 'publish';
    public const DRAFT = 'draft';
    public const PENDING = 'pending';
}

wp_update_post( [ 'ID' => $id, 'post_status' => PostStatus::PUBLISH ] );
if ( $post->post_status === PostStatus::DRAFT ) { }

User Roles

// BAD
if ( in_array( 'administrator', $user->roles ) ) { }
if ( in_array( 'editor', $user->roles ) ) { }

// GOOD
class UserRole {
    public const ADMIN = 'administrator';
    public const EDITOR = 'editor';
    
    public const PRIVILEGED = [ self::ADMIN, self::EDITOR ];
}

if ( in_array( UserRole::ADMIN, $user->roles ) ) { }
if ( in_array( UserRole::EDITOR, $user->roles ) ) { }

if ( in_array(UserRole::ADMIN, $user->roles ) ) { } foreach ( UserRole::PRIVILEGED as $role ) { }


### HTTP Status Codes
```php
// BAD - what's 404? 201? 422?
wp_send_json( $data, 404 );
if ( $response['code'] === 200 ) { }

// GOOD
class HttpStatus {
    public const OK = 200;
    public const CREATED = 201;
    public const NOT_FOUND = 404;
    public const UNPROCESSABLE = 422;
}

wp_send_json( $data, HttpStatus::NOT_FOUND );
if ( $response['code'] === HttpStatus::OK ) { }

Image Sizes

// BAD
$image->resize( 800, 600, true );
$image->resize( 150, 150, true );

// GOOD
class ImageSize {
    public const LARGE_WIDTH = 800;
    public const LARGE_HEIGHT = 600;
    
    public const THUMB_SIZE = 150;
}

$image->resize( ImageSize::LARGE_WIDTH, ImageSize::LARGE_HEIGHT, true );
$image->resize( ImageSize::THUMB_SIZE, ImageSize::THUMB_SIZE, true );

Advanced: PHP 8.1+ Enums

For related constants, use enums:

// GOOD: Type-safe enum
enum PostType: string {
    case POST = 'post';
    case PAGE = 'page';
    case ATTACHMENT = 'attachment';
    
    public function getLabel(): string {
        return match( $this ) {
            self::POST => 'Blog Post',
            self::PAGE => 'Page',
            self::ATTACHMENT => 'Media',
        };
    }
}

// Usage - IDE knows exactly what's allowed
function get_posts_by_type( PostType $type ): array {
    return get_posts( ['post_type' => $type->value ] );
}

// Can't pass wrong value!
$posts = get_posts_by_type( PostType::POST ); // ✓
$posts = get_posts_by_type( 'invalid' );      // ✗ Type error

Where to Put Constants

Option 1: Dedicated Config Classes

class CacheConfig {
    public const ONE_DAY = 86400;
}

class QueryConfig {
    public const POSTS_PER_PAGE = 10;
}

Option 2: Related to Specific Class

class PostRepository {
    private const DEFAULT_LIMIT = 10;
    private const MAX_LIMIT = 100;
    
    public function find_recent( int $limit = self::DEFAULT_LIMIT ): array {
        // ...
    }
}

Option 3: Plugin/Theme Constants

// In your main plugin file
define( 'MY_PLUGIN_CACHE_TIME', 86400 );
define( 'MY_PLUGIN_VERSION', '1.0.0' );

// Or as class constants
class MyPlugin {
    public const CACHE_TIME = 86400;
    public const VERSION = '1.0.0';
}

Configuration Pattern

For values that might change between environments:

class Config {
    // Defaults
    private const DEFAULTS = [
        'cache_time' => 86400,
        'posts_per_page' => 10,
        'enable_debug' => false,
    ];
    
    private array $config;
    
    public function __construct( array $overrides = [] ) {
        $this->config = array_merge( self::DEFAULTS, $overrides );
    }
    
    public function get( string $key ): mixed {
        return $this->config[ $key ] ?? null;
    }
}

// Usage
$config = new Config([
    'enable_debug' => true, // Override in development
]);

$cache_time = $config->get( 'cache_time' );

Real-World Example

Before (Magic Numbers Everywhere)

function process_post( $post_id ) {
    $post = get_post( $post_id );
    
    if ( $post->post_status !== 'publish' ) {
        return false;
    }
    
    $excerpt = substr( $post->post_content, 0, 100 );
    
    set_transient( 'post_' . $post_id, $excerpt, 3600 );
    
    return true;
}

After (Named Constants)

class PostConfig {
    public const STATUS_PUBLISH = 'publish';
    public const EXCERPT_LENGTH = 100;
    public const CACHE_DURATION = 3600; // 1 hour
}

function process_post( int $post_id ): bool {
    $post = get_post($post_id);
    
    if ( $post->post_status !== PostConfig::STATUS_PUBLISH ) {
        return false;
    }
    
    $excerpt = substr(
        $post->post_content,
        0,
        PostConfig::EXCERPT_LENGTH
    );
    
    set_transient(
        'post_' . $post_id,
        $excerpt,
        PostConfig::CACHE_DURATION
    );
    
    return true;
}

Key Takeaways

Replace magic numbers with named constants
Use descriptive constant names (ONE_DAY not TD)
Group related constants in classes
Use enums for related values (PHP 8.1+)
Document why the value is what it is

❌ Don't use unexplained numbers (except 0, 1, -1)
❌ Don't repeat string literals
❌ Don't use abbreviations in constant names
❌ Don't mix constants and magic numbers

The "Obvious" Exception

Some numbers are universally understood:

  • 0 and 1 for array indexes
  • -1 for "not found"
  • 100 for percentages

But when in doubt, make it a constant!

// These are OK
if ( count( $array ) > 0 ) { }
$first = $items[0];

// These need constants
if ( $user_level >= 7 ) { } // ✗ What's 7?
sleep( 300 ); // ✗ How long?