Skip to content

Commit 8a1520f

Browse files
committed
Multiple API versions with backward compatibility
1 parent 9a03c98 commit 8a1520f

File tree

11 files changed

+630
-16
lines changed

11 files changed

+630
-16
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,35 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [1.3.0] - 2025-10-21
6+
7+
### Added
8+
- **API Versioning System** - Multiple API versions with backward compatibility
9+
- URI path versioning (/api/v1/, /api/v2/)
10+
- Header-based version detection (X-API-Version, Accept)
11+
- Version-specific controllers and responses
12+
- Automatic version middleware integration
13+
- Enhanced V2 response format with metadata
14+
- Structured error codes and messages
15+
- Backward compatibility support
16+
17+
### Features
18+
- `ApiVersionMiddleware` - Automatic version detection
19+
- `VersionedRouter` - Route handling for different versions
20+
- V1 Controllers - Standard JSON responses
21+
- V2 Controllers - Enhanced responses with metadata
22+
- Multiple version detection methods
23+
- Flexible response transformations
24+
25+
### API Versions
26+
- **V1**: Standard responses, basic error handling
27+
- **V2**: Enhanced metadata, structured errors, timestamps
28+
29+
### Version Detection
30+
- URI path: `/api/v1/users`, `/api/v2/users`
31+
- Header: `X-API-Version: v2`
32+
- Accept: `application/vnd.api+json;version=2`
33+
534
## [1.2.0] - 2025-10-21
635

736
### Added

README.md

Lines changed: 137 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,18 @@ A production-ready Raw PHP REST API Starter Kit with JWT authentication, user ma
2222
-**API Documentation** - Complete endpoint docs
2323
-**Debug Bar** - Development debugging toolbar with performance monitoring
2424
-**CLI Support** - Command-line interface for development tasks
25+
-**API Versioning** - Multiple API versions with backward compatibility
2526

2627
## 📁 Project Structure
2728

2829
```
2930
├── app/
31+
│ ├── api/ # API versioning system
3032
│ ├── cli/ # CLI commands and console
3133
│ ├── config/ # Configuration files
3234
│ ├── controllers/ # Request handlers
35+
│ │ ├── v1/ # Version 1 controllers
36+
│ │ └── v2/ # Version 2 controllers
3337
│ ├── core/ # Core framework classes
3438
│ ├── database/ # Migrations and seeders
3539
│ ├── debugbar/ # Debug bar system
@@ -38,6 +42,10 @@ A production-ready Raw PHP REST API Starter Kit with JWT authentication, user ma
3842
│ ├── middleware/ # Request middleware
3943
│ ├── models/ # Data models
4044
│ ├── routes/ # Route definitions
45+
│ │ ├── api.php # Legacy API routes (backward compatibility)
46+
│ │ ├── api_v1.php # Version 1 API routes
47+
│ │ ├── api_v2.php # Version 2 API routes
48+
│ │ └── web.php # Web routes
4149
│ ├── services/ # Business logic
4250
│ └── tests/ # Test files
4351
├── console # CLI entry point
@@ -130,41 +138,64 @@ Password: admin123
130138

131139
## 📚 API Endpoints
132140

133-
### Authentication
141+
### Versioned Endpoints
142+
143+
#### V1 API (Standard Format)
144+
- `GET /api/v1/health` - Health check
145+
- `POST /api/v1/auth/register` - Register new user
146+
- `POST /api/v1/auth/login` - Login user
147+
- `GET /api/v1/users` - Get all users (paginated)
148+
- `GET /api/v1/users/{id}` - Get user by ID
149+
- `POST /api/v1/users` - Create user
150+
- `PUT /api/v1/users/{id}` - Update user
151+
- `DELETE /api/v1/users/{id}` - Delete user
152+
153+
#### V2 API (Enhanced Format)
154+
- `GET /api/v2/health` - Health check with metadata
155+
- `POST /api/v2/auth/register` - Register with enhanced response
156+
- `POST /api/v2/auth/login` - Login with structured response
157+
- `GET /api/v2/users` - Get users with enhanced pagination
158+
- `GET /api/v2/users/{id}` - Get user with metadata
159+
- `POST /api/v2/users` - Create user with structured response
160+
- `PUT /api/v2/users/{id}` - Update user with action tracking
161+
- `DELETE /api/v2/users/{id}` - Delete user with confirmation
162+
163+
### Legacy Endpoints (Backward Compatibility)
164+
**Note:** These endpoints default to V1 behavior for backward compatibility
134165
- `POST /api/auth/register` - Register new user
135166
- `POST /api/auth/login` - Login user
136167
- `POST /api/auth/logout` - Logout user
137-
138-
### Users (Protected)
139168
- `GET /api/users` - Get all users (paginated)
140169
- `GET /api/users/{id}` - Get user by ID
141170
- `POST /api/users` - Create user
142171
- `PUT /api/users/{id}` - Update user
143172
- `DELETE /api/users/{id}` - Delete user
144-
145-
### Files (Protected)
146173
- `POST /api/files/upload` - Upload file
147174
- `DELETE /api/files/{id}` - Delete file
148-
149-
### System
150175
- `GET /api/health` - Health check
151176
- `GET /api/health/info` - System info
152177

153178
### Example Usage
154179
```bash
155-
# Register
156-
curl -X POST http://localhost:8000/api/auth/register \
180+
# V1 API (Explicit versioning - Recommended)
181+
curl -X POST http://localhost:8000/api/v1/auth/register \
157182
-H "Content-Type: application/json" \
158183
-d '{"name":"John Doe","email":"john@example.com","password":"password123"}'
159184

160-
# Login
161-
curl -X POST http://localhost:8000/api/auth/login \
185+
# V2 API (Enhanced responses)
186+
curl -X POST http://localhost:8000/api/v2/auth/register \
162187
-H "Content-Type: application/json" \
163-
-d '{"email":"john@example.com","password":"password123"}'
188+
-d '{"name":"John Doe","email":"john@example.com","password":"password123"}'
189+
190+
# Legacy API (Backward compatibility)
191+
curl -X POST http://localhost:8000/api/auth/register \
192+
-H "Content-Type: application/json" \
193+
-d '{"name":"John Doe","email":"john@example.com","password":"password123"}'
164194

165-
# Get users (with token)
195+
# Version via header
166196
curl -X GET http://localhost:8000/api/users \
167-
-H "Authorization: Bearer YOUR_JWT_TOKEN"
197+
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
198+
-H "X-API-Version: v2"
168199
```
169200

170201
## 🗄️ Database Schema
@@ -259,6 +290,98 @@ timer_stop('api_call');
259290
### Test Debug Bar
260291
Visit `http://localhost:8000/welcome` to see the debug bar in action.
261292

293+
## 🔄 API Versioning
294+
295+
The framework supports multiple API versions with backward compatibility and flexible version detection.
296+
297+
### Version Detection Methods
298+
299+
1. **URI Path** (Recommended)
300+
```bash
301+
GET /api/v1/users
302+
GET /api/v2/users
303+
```
304+
305+
2. **X-API-Version Header**
306+
```bash
307+
curl -H "X-API-Version: v2" http://localhost:8000/api/users
308+
```
309+
310+
3. **Accept Header**
311+
```bash
312+
curl -H "Accept: application/vnd.api+json;version=2" http://localhost:8000/api/users
313+
```
314+
315+
### Available Versions
316+
317+
#### Version 1 (v1)
318+
- Standard JSON responses
319+
- Basic error handling
320+
- Simple data structure
321+
322+
```json
323+
{
324+
"status": "success",
325+
"data": {...},
326+
"version": "v1"
327+
}
328+
```
329+
330+
#### Version 2 (v2)
331+
- Enhanced response format
332+
- Structured error codes
333+
- Metadata inclusion
334+
- Timestamp tracking
335+
336+
```json
337+
{
338+
"success": true,
339+
"data": {...},
340+
"meta": {
341+
"version": "v2",
342+
"timestamp": "2024-10-21T10:30:00+00:00",
343+
"action": "created"
344+
}
345+
}
346+
```
347+
348+
### Version-Specific Features
349+
350+
**V1 Features:**
351+
- Basic CRUD operations
352+
- Simple response format
353+
- Standard HTTP status codes
354+
355+
**V2 Features:**
356+
- Enhanced error handling with error codes
357+
- Metadata in responses
358+
- Improved pagination info
359+
- Structured error responses
360+
361+
### Creating New Versions
362+
363+
1. Create version directory: `app/controllers/v3/`
364+
2. Create versioned controllers
365+
3. Add route file: `app/routes/api_v3.php`
366+
4. Update Application.php to load new routes
367+
368+
### Migration Strategy
369+
370+
**For New Projects:**
371+
- Use explicit versioning from the start: `/api/v1/`
372+
- Avoid legacy endpoints
373+
374+
**For Existing Projects:**
375+
- Legacy endpoints (`/api/`) remain unchanged
376+
- Gradually migrate clients to versioned endpoints
377+
- Deprecate legacy endpoints in future versions
378+
379+
**Best Practices:**
380+
- Always specify version in new integrations
381+
- Use semantic versioning for major changes
382+
- Maintain at least 2 versions simultaneously
383+
- Provide migration guides for version changes
384+
262385
## 💻 CLI Support
263386

264387
The framework includes a powerful command-line interface for development tasks.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace App\Api\Versioning;
4+
5+
use App\Core\Request;
6+
use App\Core\Response;
7+
8+
class ApiVersionMiddleware
9+
{
10+
public function handle(Request $request, callable $next)
11+
{
12+
$version = $this->extractVersion($request);
13+
$request->setApiVersion($version);
14+
15+
return $next($request);
16+
}
17+
18+
private function extractVersion(Request $request): string
19+
{
20+
// Check Accept header first (e.g., application/vnd.api+json;version=1)
21+
$acceptHeader = $request->getHeader('Accept');
22+
if ($acceptHeader && preg_match('/version=(\d+)/', $acceptHeader, $matches)) {
23+
return 'v' . $matches[1];
24+
}
25+
26+
// Check X-API-Version header
27+
$versionHeader = $request->getHeader('X-API-Version');
28+
if ($versionHeader) {
29+
return 'v' . ltrim($versionHeader, 'v');
30+
}
31+
32+
// Check URI path (e.g., /api/v1/users)
33+
$uri = $request->getUri();
34+
if (preg_match('/\/api\/v(\d+)\//', $uri, $matches)) {
35+
return 'v' . $matches[1];
36+
}
37+
38+
// Default to v1
39+
return 'v1';
40+
}
41+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace App\Api\Versioning;
4+
5+
use App\Core\Router;
6+
use App\Core\Request;
7+
8+
class VersionedRouter extends Router
9+
{
10+
private $versionedRoutes = [];
11+
12+
public function addVersionedRoute(string $version, string $method, string $uri, array $handler, array $middleware = []): void
13+
{
14+
$this->versionedRoutes[$version][] = [
15+
'method' => $method,
16+
'uri' => $uri,
17+
'handler' => $handler,
18+
'middleware' => $middleware
19+
];
20+
}
21+
22+
public function dispatch(Request $request)
23+
{
24+
$version = $request->getApiVersion() ?? 'v1';
25+
26+
// Try versioned routes first
27+
if (isset($this->versionedRoutes[$version])) {
28+
foreach ($this->versionedRoutes[$version] as $route) {
29+
if ($this->matchRoute($request, $route)) {
30+
return $this->executeRoute($request, $route);
31+
}
32+
}
33+
}
34+
35+
// Fallback to regular routes
36+
parent::dispatch($request);
37+
}
38+
39+
private function matchRoute(Request $request, array $route): bool
40+
{
41+
$requestUri = $request->getUri();
42+
$requestMethod = $request->getMethod();
43+
44+
// Remove version from URI for matching
45+
$cleanUri = preg_replace('/\/v\d+/', '', $requestUri);
46+
$pattern = $this->buildRegexPattern($route['uri']);
47+
48+
return preg_match($pattern, $cleanUri) && $route['method'] === $requestMethod;
49+
}
50+
51+
private function executeRoute(Request $request, array $route): void
52+
{
53+
$requestUri = $request->getUri();
54+
$cleanUri = preg_replace('/\/v\d+/', '', $requestUri);
55+
$pattern = $this->buildRegexPattern($route['uri']);
56+
57+
if (preg_match($pattern, $cleanUri, $matches)) {
58+
array_shift($matches);
59+
60+
$handler = $route['handler'];
61+
$middleware = $route['middleware'];
62+
63+
$this->runMiddleware($request, $middleware, function (Request $request) use ($handler, $matches) {
64+
if (is_array($handler) && count($handler) === 2) {
65+
list($controllerName, $methodName) = $handler;
66+
$controller = new $controllerName();
67+
$response = call_user_func_array([$controller, $methodName], array_merge([$request], $matches));
68+
if ($response) $response->send();
69+
}
70+
});
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)