shahghasiadil/laravel-api-versioning 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

shahghasiadil/laravel-api-versioning

最新稳定版本:v0.0.4

Composer 安装命令:

composer require shahghasiadil/laravel-api-versioning

包简介

Elegant attribute-based API versioning solution for Laravel applications with built-in deprecation management and version inheritance

README 文档

README

Latest Version on Packagist Total Downloads License

A powerful and elegant attribute-based API versioning solution for Laravel applications with strict type safety and comprehensive deprecation management.

✨ Features

  • 🎯 Attribute-based versioning - Use PHP 8+ attributes to define API versions
  • 🛡️ Type-safe - Full type annotations and strict type checking
  • 🔄 Multiple detection methods - Header, query parameter, path, and media type detection
  • 📦 Resource versioning - Smart version-aware JSON resources and collections
  • 🚫 Deprecation support - Built-in deprecation warnings and sunset dates
  • 🔗 Version inheritance - Fallback chains for backward compatibility
  • 🧪 Testing utilities - Comprehensive test helpers with Pest PHP
  • 📊 Enhanced Artisan commands - Route inspection, health checks, and controller generation
  • Performance optimized - Intelligent caching with 87% faster response times
  • 🔢 Version comparison - Built-in utilities for semantic version comparison

📋 Requirements

  • PHP 8.2+
  • Laravel 10.0|11.0|12.0+

🚀 Installation

composer require shahghasiadil/laravel-api-versioning
php artisan vendor:publish --provider="ShahGhasiAdil\LaravelApiVersioning\ApiVersioningServiceProvider" --tag="config"

⚡ Quick Start

1. Configure API Versions

Edit config/api-versioning.php:

return [
    'default_version' => '2.0',
    'supported_versions' => ['1.0', '1.1', '2.0', '2.1'],
    'detection_methods' => [
        'header' => ['enabled' => true, 'header_name' => 'X-API-Version'],
        'query' => ['enabled' => true, 'parameter_name' => 'api-version'],
        'path' => ['enabled' => true, 'prefix' => 'api/v'],
    ],
    'version_method_mapping' => [
        '1.0' => 'toArrayV1', '2.0' => 'toArrayV2', '2.1' => 'toArrayV21',
    ],
    'version_inheritance' => ['1.1' => '1.0', '2.1' => '2.0'],
];

2. Apply Middleware

// routes/api.php - Route Groups (Recommended)
Route::middleware('api.version')->group(function () {
    Route::apiResource('users', UserController::class);
});

// Direct Middleware Class
use ShahGhasiAdil\LaravelApiVersioning\Middleware\AttributeApiVersionMiddleware;
Route::middleware(AttributeApiVersionMiddleware::class)->group(function () {
    Route::get('users', [UserController::class, 'index']);
});

// Individual Routes
Route::get('users/{user}', [UserController::class, 'show'])->middleware('api.version');

// Global Middleware (Laravel 11+)
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->api(append: [AttributeApiVersionMiddleware::class]);
})

3. Create Controllers with Attributes

php artisan make:versioned-controller UserController --api-version=2.0
use ShahGhasiAdil\LaravelApiVersioning\Attributes\{ApiVersion, Deprecated, MapToApiVersion};
use ShahGhasiAdil\LaravelApiVersioning\Traits\HasApiVersionAttributes;

// Controller-level versioning
#[ApiVersion(['2.0', '2.1'])]
class UserController extends Controller
{
    use HasApiVersionAttributes;

    // Available in all controller versions (2.0, 2.1)
    public function index(): JsonResponse
    {
        return response()->json([
            'data' => User::all(),
            'version' => $this->getCurrentApiVersion(),
        ]);
    }

    // Method-specific versioning
    #[MapToApiVersion(['2.1'])]
    public function store(Request $request): JsonResponse
    {
        // Only available in v2.1
        return response()->json(['message' => 'User created']);
    }

    // Deprecated method
    #[Deprecated(message: 'Use store() instead', replacedBy: '2.1')]
    #[MapToApiVersion(['2.0'])]
    public function create(Request $request): JsonResponse
    {
        // Only available in v2.0, deprecated
        return $this->store($request);
    }
}

// Version-neutral endpoints (work with any version)
#[ApiVersionNeutral]
class HealthController extends Controller
{
    use HasApiVersionAttributes;

    public function check(): JsonResponse
    {
        return response()->json(['status' => 'healthy']);
    }
}

4. Create Versioned Resources

use ShahGhasiAdil\LaravelApiVersioning\Http\Resources\VersionedJsonResource;

class UserResource extends VersionedJsonResource
{
    // Method-based versioning (recommended)
    protected function toArrayV1(Request $request): array
    {
        return ['id' => $this->id, 'name' => $this->name];
    }

    protected function toArrayV2(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toISOString(),
        ];
    }

    protected function toArrayV21(Request $request): array
    {
        return array_merge($this->toArrayV2($request), [
            'updated_at' => $this->updated_at->toISOString(),
            'profile' => ['avatar' => $this->avatar_url],
        ]);
    }

    // Fallback method (optional)
    protected function toArrayDefault(Request $request): array
    {
        return $this->toArrayV2($request);
    }
}

🔧 Usage

Version Detection

# Header (Recommended)
curl -H "X-API-Version: 2.0" https://api.example.com/users

# Query Parameter
curl https://api.example.com/users?api-version=2.0

# Path
curl https://api.example.com/api/v2.0/users

# Media Type
curl -H "Accept: application/vnd.api+json;version=2.0" https://api.example.com/users

Attributes Usage

Controller-Level Attributes

// Single version support
#[ApiVersion('2.0')]
class V2UserController extends Controller {}

// Multiple versions support
#[ApiVersion(['1.0', '1.1', '2.0'])]
class UserController extends Controller {}

// Version-neutral (works with any API version)
#[ApiVersionNeutral]
class HealthController extends Controller {}

// Deprecated controller
#[ApiVersion('1.0')]
#[Deprecated(
    message: 'This controller is deprecated. Use v2.0 UserController instead.',
    sunsetDate: '2025-12-31',
    replacedBy: '2.0'
)]
class V1UserController extends Controller {}

Method-Level Attributes

#[ApiVersion(['1.0', '2.0', '2.1'])]
class UserController extends Controller
{
    // Available in all controller versions
    public function index() {}

    // Only available in specific versions
    #[MapToApiVersion(['2.0', '2.1'])]
    public function store() {}

    // Version-specific method with deprecation
    #[MapToApiVersion(['1.0'])]
    #[Deprecated(message: 'Use store() method instead', replacedBy: '2.0')]
    public function create() {}

    // Advanced features only in latest version
    #[MapToApiVersion(['2.1'])]
    public function bulkUpdate() {}
}

Combining Attributes

#[ApiVersion(['1.0', '2.0'])]
class PostController extends Controller
{
    // Method overrides controller version restriction
    #[MapToApiVersion(['2.0'])]
    #[Deprecated(sunsetDate: '2025-06-30', replacedBy: '2.1')]
    public function legacyUpdate() {}
}

📦 Versioned Resources

Method-Based Versioning (Recommended)

use ShahGhasiAdil\LaravelApiVersioning\Http\Resources\VersionedJsonResource;

class UserResource extends VersionedJsonResource
{
    protected function toArrayV1(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
        ];
    }

    protected function toArrayV2(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toISOString(),
        ];
    }

    protected function toArrayV21(Request $request): array
    {
        // Inherit from v2.0 and add new fields
        return array_merge($this->toArrayV2($request), [
            'updated_at' => $this->updated_at->toISOString(),
            'profile' => $this->buildProfile(),
            'preferences' => $this->user_preferences,
        ]);
    }

    // Default fallback method
    protected function toArrayDefault(Request $request): array
    {
        return $this->toArrayV21($request);
    }

    private function buildProfile(): array
    {
        return [
            'avatar' => $this->avatar_url,
            'bio' => $this->bio,
            'location' => $this->location,
        ];
    }
}

Configuration-Based Versioning

class PostResource extends VersionedJsonResource
{
    protected array $versionConfigs = [
        '1.0' => ['id', 'title', 'content'],
        '1.1' => ['id', 'title', 'content', 'author_name'],
        '2.0' => ['id', 'title', 'content', 'author', 'created_at'],
        '2.1' => ['id', 'title', 'content', 'author', 'created_at', 'updated_at', 'tags', 'meta'],
    ];

    protected function toArrayDefault(Request $request): array
    {
        $version = $this->getCurrentApiVersion();
        $config = $this->versionConfigs[$version] ?? $this->versionConfigs['2.1'];

        $data = [];
        foreach ($config as $field) {
            $data[$field] = $this->getFieldValue($field);
        }

        return $data;
    }

    private function getFieldValue(string $field): mixed
    {
        return match($field) {
            'author' => [
                'id' => $this->user_id,
                'name' => $this->user->name,
                'email' => $this->user->email,
            ],
            'author_name' => $this->user->name, // Legacy field for v1.1
            'tags' => $this->tags->pluck('name')->toArray(),
            'meta' => [
                'views' => $this->views_count,
                'likes' => $this->likes_count,
            ],
            default => $this->$field,
        };
    }
}

Versioned Resource Collections

use ShahGhasiAdil\LaravelApiVersioning\Http\Resources\VersionedResourceCollection;

class UserCollection extends VersionedResourceCollection
{
    protected function toArrayV1(Request $request): array
    {
        return [
            'data' => $this->collection,
            'count' => $this->collection->count(),
        ];
    }

    protected function toArrayV2(Request $request): array
    {
        return [
            'data' => $this->collection,
            'pagination' => [
                'total' => $this->collection->count(),
                'per_page' => 15,
            ],
        ];
    }

    protected function toArrayDefault(Request $request): array
    {
        return $this->toArrayV2($request);
    }

    protected function getMeta(Request $request): array
    {
        return [
            'total' => $this->collection->count(),
        ];
    }
}

// Usage in controller
public function index()
{
    return new UserCollection(User::all());
}

Version Comparison Utilities

use ShahGhasiAdil\LaravelApiVersioning\Traits\HasApiVersionAttributes;

class UserController extends Controller
{
    use HasApiVersionAttributes;

    public function index()
    {
        // Basic version info
        $version = $this->getCurrentApiVersion();      // '2.0'
        $isDeprecated = $this->isVersionDeprecated();  // false
        $message = $this->getDeprecationMessage();     // null
        $sunset = $this->getSunsetDate();              // null

        // Version comparison helpers
        if ($this->isVersionGreaterThanOrEqual('2.0')) {
            // New features for v2.0+
            return $this->advancedIndex();
        }

        if ($this->isVersionBetween('1.0', '1.5')) {
            // Legacy behavior for v1.0-1.5
            return $this->legacyIndex();
        }

        return $this->basicIndex();
    }
}

Available Helper Methods

Version Information:

  • getCurrentApiVersion(): ?string - Get current API version
  • isVersionDeprecated(): bool - Check if current version is deprecated
  • getDeprecationMessage(): ?string - Get deprecation message
  • getSunsetDate(): ?string - Get sunset date
  • getReplacedByVersion(): ?string - Get replacement version

Version Comparison:

  • isVersionGreaterThan(string $version): bool
  • isVersionGreaterThanOrEqual(string $version): bool
  • isVersionLessThan(string $version): bool
  • isVersionLessThanOrEqual(string $version): bool
  • isVersionBetween(string $min, string $max): bool

Direct VersionComparator Usage:

use ShahGhasiAdil\LaravelApiVersioning\Services\VersionComparator;

$comparator = app(VersionComparator::class);

// Comparisons
$comparator->isGreaterThan('2.0', '1.0');           // true
$comparator->equals('2.0', '2.0');                  // true
$comparator->isBetween('1.5', '1.0', '2.0');        // true

// Array operations
$comparator->getHighest(['1.0', '2.0', '1.5']);     // '2.0'
$comparator->getLowest(['1.0', '2.0', '1.5']);      // '1.0'
$comparator->sort(['2.0', '1.0', '1.5']);           // ['1.0', '1.5', '2.0']

// Constraint satisfaction (composer-style)
$comparator->satisfies('2.1', '>=2.0');             // true
$comparator->satisfies('2.1', '^2.0');              // true (>=2.0 && <3.0)
$comparator->satisfies('2.1.5', '~2.1');            // true (>=2.1 && <2.2)

🛠️ Artisan Commands

# Generate controllers
php artisan make:versioned-controller UserController --api-version=2.0
php artisan make:versioned-controller V1UserController --api-version=1.0 --deprecated

# Inspect API versions
php artisan api:versions                    # All routes with details
php artisan api:versions --route=users      # Filter by route pattern
php artisan api:versions --api-version=2.0  # Filter by version
php artisan api:versions --deprecated       # Show only deprecated
php artisan api:versions --json             # JSON output for CI/CD
php artisan api:versions --compact          # Compact table format

# Health check
php artisan api:version:health              # Validate configuration

# Cache management
php artisan api:cache:clear                 # Clear attribute cache

# Configuration management
php artisan api:version-config --show       # Show config

Command Examples

JSON Output for CI/CD:

php artisan api:versions --json
{
  "routes": [
    {
      "Method": "GET|HEAD",
      "URI": "api/users",
      "Controller": "UserController@index",
      "Versions": "1.0, 2.0, 2.1",
      "Deprecated": "No",
      "Sunset Date": "-"
    }
  ],
  "supported_versions": ["1.0", "1.1", "2.0", "2.1"],
  "total_routes": 15
}

Health Check Output:

php artisan api:version:health
Running API Versioning Health Check...

✓ Supported versions: 1.0, 1.1, 2.0, 2.1
✓ Default version: 2.0
✓ Enabled detection methods: header, query, path
✓ Found 15 versioned routes
✓ Attribute caching enabled

✅ All health checks passed!

🧪 Testing (Pest PHP)

test('user endpoint versions', function () {
    $response = getWithVersion('/api/users', '2.0');
    $response->assertOk();
    assertApiVersion($response, '2.0');
});

test('deprecated endpoints', function () {
    $response = getWithVersion('/api/users', '1.0');
    assertApiVersionDeprecated($response);
    assertReplacedBy($response, '2.0');
});

Test Helpers

  • getWithVersion(), postWithVersion(), putWithVersion(), deleteWithVersion()
  • assertApiVersion(), assertApiVersionDeprecated(), assertReplacedBy()
composer test                    # Run all tests
composer test-coverage           # With coverage
./vendor/bin/pest --filter="v1"  # Specific tests

📋 Response Headers

X-API-Version: 2.0
X-API-Supported-Versions: 1.0, 1.1, 2.0, 2.1
X-API-Route-Versions: 2.0, 2.1
X-API-Deprecated: true
X-API-Sunset: 2025-12-31
X-API-Replaced-By: 2.0

⚠️ Error Handling

RFC 7807 Problem Details

Error responses follow the RFC 7807 standard for HTTP APIs:

Response Headers:

Content-Type: application/problem+json

Response Body:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "Unsupported API Version",
    "status": 400,
    "detail": "API version '3.0' is not supported for this endpoint.",
    "requested_version": "3.0",
    "supported_versions": ["1.0", "1.1", "2.0", "2.1"],
    "endpoint_versions": ["2.0", "2.1"],
    "documentation": "https://docs.example.com/api"
}

Benefits:

  • Standards-compliant format recognized by API tools
  • Machine-readable error responses
  • Includes helpful context and documentation links
  • Better integration with API clients

⚙️ Configuration

Detection Methods

'detection_methods' => [
    'header' => ['enabled' => true, 'header_name' => 'X-API-Version'],
    'query' => ['enabled' => true, 'parameter_name' => 'api-version'],
    'path' => ['enabled' => true, 'prefix' => 'api/v'],
    'media_type' => ['enabled' => false],
],

Version Inheritance & Mapping

'version_inheritance' => [
    '1.1' => '1.0',  // v1.1 falls back to v1.0
    '2.1' => '2.0',  // v2.1 falls back to v2.0
],
'version_method_mapping' => [
    '1.0' => 'toArrayV1', '2.0' => 'toArrayV2', '2.1' => 'toArrayV21',
],

Performance & Caching

'cache' => [
    'enabled' => env('API_VERSIONING_CACHE_ENABLED', true),
    'ttl' => env('API_VERSIONING_CACHE_TTL', 3600), // seconds
],

Environment Variables:

API_VERSIONING_CACHE_ENABLED=true
API_VERSIONING_CACHE_TTL=3600

Performance Improvements:

  • 87% faster response times with caching enabled
  • 🔄 Intelligent cache invalidation
  • 📊 Reduces reflection overhead from ~50 calls to 0
  • 🎯 ~95% cache hit rate on production

Cache Management:

# Clear cache after deployment
php artisan api:cache:clear

# Disable caching in development
API_VERSIONING_CACHE_ENABLED=false

📚 Best Practices

  • Use semantic versioning - 1.0, 1.1, 2.0
  • Enable caching in production - 87% performance improvement
  • Leverage version inheritance - For backward compatibility
  • Use version comparison helpers - Instead of string comparison
  • Provide clear deprecation info - Include sunset dates and replacement versions
  • Test all supported versions - Use php artisan api:version:health
  • Implement resource collections - For consistent pagination
  • Use health checks in CI/CD - Validate configuration automatically
  • Clear cache after deployment - php artisan api:cache:clear
  • Monitor with JSON output - For automated API version tracking

🔄 Migration Guide

  1. Install package and publish configuration
  2. Apply api.version middleware to routes
  3. Add attributes to controllers
  4. Extend resources from VersionedJsonResource
  5. Add comprehensive tests

🤝 Contributing

  1. Follow PSR-12 coding standards
  2. Add Pest tests for new features
  3. Run: composer test, composer analyse, composer format

📄 License

MIT License. See LICENSE.md for details.

👨‍💻 Credits

Made with ❤️ for the Laravel community

统计信息

  • 总下载量: 5.47k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 23
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 23
  • Watchers: 1
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-08-15