"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?
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)
// 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
// 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)// 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 ) { }// 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 ) { }
// 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 );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 errorclass CacheConfig {
public const ONE_DAY = 86400;
}
class QueryConfig {
public const POSTS_PER_PAGE = 10;
}class PostRepository {
private const DEFAULT_LIMIT = 10;
private const MAX_LIMIT = 100;
public function find_recent( int $limit = self::DEFAULT_LIMIT ): array {
// ...
}
}// 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';
}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' );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;
}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;
}✅ 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
Some numbers are universally understood:
- ✓
0and1for array indexes - ✓
-1for "not found" - ✓
100for 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?