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.