Skip to content

Latest commit

 

History

History
308 lines (244 loc) · 7.71 KB

File metadata and controls

308 lines (244 loc) · 7.71 KB

Semantic Versioning

The Problem

Arbitrary version numbers make it impossible to understand the impact of updates:

  • Unclear changes - Is 2.5 safe to update from 2.4?
  • Hidden breaking changes - Changed API but only bumped minor version
  • Risky updates - Security fix or breaking change?
  • No migration path - When will deprecated features be removed?
  • Dependency hell - Can't specify compatible versions

Bad Practice (bad.php)

/**
 * Plugin Name: My Plugin
 * Version: 2.5
 *
 * Changelog:
 * 2.5 - Added new feature and broke backward compatibility (oops!)
 * 2.4 - Fixed critical security bug
 * 2.3 - Minor tweaks
 * 2.2.1.5 - ???
 */

Problems:

  • Version 2.5: Breaking change but no indication (should be 3.0.0)
  • Version 2.4: Security fix (should be 2.3.1, not 2.4)
  • Version 2.2.1.5: Invalid semver format
  • No clear pattern for users to understand risk

Good Practice (good.php)

/**
 * Plugin Name: My Plugin
 * Version: 2.5.1
 *
 * Semantic Version Format: MAJOR.MINOR.PATCH
 * - MAJOR: Breaking changes (incompatible API changes)
 * - MINOR: New features (backward compatible)
 * - PATCH: Bug fixes (backward compatible)
 *
 * Changelog:
 * 2.5.1 - 2024-11-20 - PATCH: Fixed cache invalidation bug
 * 2.5.0 - 2024-11-15 - MINOR: Added caching support
 * 2.4.1 - 2024-11-10 - PATCH: Fixed SQL injection vulnerability
 * 2.0.0 - 2024-10-01 - MAJOR: Changed return type (breaking change)
 */

Benefits:

  • Clear communication of change impact
  • Users know update safety at a glance
  • Automated tools can manage dependencies
  • Professional appearance

Semantic Versioning Rules

Format: MAJOR.MINOR.PATCH

Given version number MAJOR.MINOR.PATCH:

MAJOR - Increment when you make incompatible API changes

  • Changing function signatures
  • Removing methods or parameters
  • Changing return types
  • Breaking backward compatibility

MINOR - Increment when you add functionality in a backward compatible manner

  • New features
  • New methods
  • New optional parameters (with defaults)
  • Deprecating (not removing) features

PATCH - Increment when you make backward compatible bug fixes

  • Bug fixes
  • Security fixes
  • Performance improvements
  • Documentation updates

Examples

// PATCH: Security fix (2.4.0 → 2.4.1)
/**
 * @since 2.4.0 Initial implementation
 * @since 2.4.1 SECURITY: Fixed SQL injection
 */
function get_post_by_slug( string $slug ): ?object {
    global $wpdb;
    return $wpdb->get_row(
        $wpdb->prepare(
            "SELECT * FROM {$wpdb->posts} WHERE post_name = %s",
            $slug
        )
    );
}

// MINOR: New feature (2.4.0 → 2.5.0)
/**
 * @since 2.5.0 Added caching (backward compatible)
 */
function get_popular_posts(): array {
    $cached = wp_cache_get( 'popular_posts' );
    if ( $cached ) {
        return $cached;
    }
    // Implementation...
}

// MAJOR: Breaking change (1.5.2 → 2.0.0)
/**
 * @since 1.0.0 Returned array
 * @since 2.0.0 BREAKING: Now returns Collection
 */
function get_user_posts( int $user_id ): Collection {
    return new Collection( get_posts( [ 'author' => $user_id ] ) );
}

Pre-release Versions

For alpha, beta, and release candidates:

const VERSION_ALPHA_1 = '3.0.0-alpha.1';  // First alpha
const VERSION_ALPHA_2 = '3.0.0-alpha.2';  // Second alpha
const VERSION_BETA_1  = '3.0.0-beta.1';   // First beta
const VERSION_RC_1    = '3.0.0-rc.1';     // Release candidate
const VERSION_STABLE  = '3.0.0';          // Stable release

Format: MAJOR.MINOR.PATCH-prerelease.number

Build Metadata

Include build information without affecting version precedence:

const VERSION_WITH_BUILD = '2.5.1+20241122.abc123';

Format: MAJOR.MINOR.PATCH+build.metadata

Deprecation Strategy

Deprecate in MINOR, remove in MAJOR:

/**
 * Process payment (legacy).
 *
 * @since      1.0.0
 * @deprecated 2.5.0 Use process_payment_v2() instead.
 *                   Will be removed in 3.0.0.
 */
public function old_process(): void {
    _deprecated_function( __METHOD__, '2.5.0', 'process_payment_v2' );
    $this->process_payment_v2();
}

Timeline:

  1. v2.5.0 (MINOR): Deprecate, show warning, still works
  2. v2.6.0 - v2.x.x: Grace period for migration
  3. v3.0.0 (MAJOR): Remove deprecated method

Changelog Best Practices

Follow Keep a Changelog format:

## [2.5.1] - 2024-11-20
### Fixed
- Cache invalidation bug in get_popular_posts()
- Memory leak in image processing

### Security
- Fixed XSS vulnerability in user input

## [2.5.0] - 2024-11-15
### Added
- Caching support for popular posts
- Currency parameter to calculate_price()

### Deprecated
- PaymentGateway::old_process() in favor of process_payment_v2()

## [2.0.0] - 2024-10-01
### Changed
- **BREAKING**: get_user_posts() now returns Collection instead of array

### Migration Guide
Before (v1.x):
$posts = get_user_posts($user_id);
foreach ($posts as $post) { }

After (v2.x):
$posts = get_user_posts($user_id);
foreach ($posts->all() as $post) { }

Composer Version Constraints

Understanding semver in dependencies:

{
  "require": {
    "my-plugin/core": "^2.5"
  }
}

^2.5 means: >= 2.5.0 and < 3.0.0

  • ✅ Allows: 2.5.0, 2.5.1, 2.6.0, 2.99.0
  • ❌ Blocks: 3.0.0 (breaking changes)

~2.5 means: >= 2.5.0 and < 2.6.0

  • ✅ Allows: 2.5.0, 2.5.1, 2.5.99
  • ❌ Blocks: 2.6.0 (new features)

Release Checklist

PATCH Release (2.5.0 → 2.5.1)

  • ✅ Bug fixes only
  • ✅ No API changes
  • ✅ No new features
  • ✅ Update changelog
  • ✅ Tag: v2.5.1

MINOR Release (2.5.1 → 2.6.0)

  • ✅ New features (backward compatible)
  • ✅ Optional new parameters with defaults
  • ✅ New methods/classes
  • ✅ Deprecation notices (but not removal)
  • ✅ Update changelog
  • ✅ Tag: v2.6.0

MAJOR Release (2.6.0 → 3.0.0)

  • ✅ Breaking changes documented
  • ✅ Remove deprecated features
  • ✅ Change method signatures
  • ✅ Update migration guide
  • ✅ Update changelog
  • ✅ Tag: v3.0.0
  • ✅ Announce breaking changes prominently

The Semver Promise

When you follow semver, you promise:

PATCH releases are safe to update automatically

  • Fix bugs only
  • No API changes
  • No new features

MINOR releases are safe to update with confidence

  • Add new features
  • Maintain backward compatibility
  • May deprecate (but not remove)

MAJOR releases require review before updating

  • May break backward compatibility
  • May remove deprecated features
  • Should have migration guide

WordPress Context

WordPress itself doesn't strictly follow semver (6.4 → 6.5 can have breaking changes), but YOUR plugin/theme should because:

Users need to know update safety
Automated tools rely on it
Professional appearance
Easier dependency management

Key Takeaways

Use MAJOR.MINOR.PATCH format
MAJOR for breaking changes
MINOR for new features
PATCH for bug fixes
Deprecate in MINOR, remove in MAJOR
Document changes clearly
Provide migration guides for breaking changes

❌ Don't use arbitrary version numbers
❌ Don't hide breaking changes in MINOR versions
❌ Don't bump MAJOR for small changes
❌ Don't remove features without deprecation period
❌ Don't use invalid semver formats (2.2.1.5)

Resources

The Bottom Line

Semantic versioning is a communication tool. It tells users at a glance whether an update is safe, exciting, or requires careful review. Follow it consistently, and your users will trust your updates.