Designing Versioned APIs: URL and Header Strategies with Modular PHP Architecture
This guide explains why API versioning is essential, compares URL‑based and Accept‑header approaches, and presents a practical modular strategy with code examples for organizing versioned controllers, models, and services in a PHP/Yii application.
Overview
A well‑designed API should be versioned so that changes and new features are introduced in a new API version rather than altering an existing one, ensuring backward compatibility for existing clients while allowing upgraded clients to use new functionality.
Implementation Approaches
One common method embeds the version number in the URL, e.g., http://example.com/v1/users represents version 1 of the /users endpoint.
Another popular technique places the version in the Accept HTTP header, for example:
// via parameter
Accept: application/json; version=v1
// via vendor MIME type
Accept: application/vnd.company.myapp-v1+jsonBoth methods have pros and cons; a hybrid strategy can combine them for flexibility.
Hybrid Strategy
Implement each major API version in a separate module identified by its major version number (e.g., v1, v2). The URL naturally contains the major version.
Within each major version module, use the Accept header to specify the minor version and route requests to the appropriate code.
Each module should include resource and controller classes specific to that service version.
Common base resources and controllers can be shared across versions, while version‑specific subclasses implement the actual logic, such as Model::fields().
Your code can be organized as follows:
api/
├── common/ # shared base layer (cross‑version resources)
│ ├── controllers/ # abstract/base controllers
│ │ ├── BaseUserController.php # user‑related base logic
│ │ └── BasePostController.php # content‑related base logic
│ ├── models/ # shared base models
│ │ ├── BaseUser.php
│ │ └── BasePost.php
│ └── services/ # shared services
│ ├── UserService.php
│ └── PostService.php
│
├── modules/ # version modules
│ ├── v1/ # version 1 API
│ │ ├── controllers/ # version‑specific controllers
│ │ │ ├── UserController.php # v1 user API
│ │ │ └── PostController.php # v1 content API
│ │ ├── models/ # version‑specific models
│ │ │ ├── User.php
│ │ │ └── Post.php
│ │ └── services/ # version‑specific services
│ │ ├── UserService.php
│ │ └── PostService.php
│ │ └── Module.php # module configuration (routes, dependencies)
│ └── v2/ # version 2 API (structure mirrors v1)
│ ├── controllers/ # v2 controllers
│ │ ├── UserController.php
│ │ └── PostController.php
│ ├── models/ # v2 models
│ │ ├── User.php
│ │ └── Post.php
│ ├── services/ # v2 services
│ │ ├── UserService.php
│ │ └── PostService.php
│ └── Module.php
│
└── routes/ # unified routing layer
├── v1.php # routes for v1
└── v2.php # routes for v2The application configuration should look like this:
return [
'modules' => [
'v1' => ['class' => 'app\modules\v1\Module'],
'v2' => ['class' => 'app\modules\v2\Module'],
],
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']],
['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']],
],
],
],
];Consequently, http://example.com/v1/users returns the user list from version 1, while http://example.com/v2/users returns the version 2 list.
Using modules isolates version‑specific code, and shared base classes enable code reuse across versions.
Benefits of Versioned Modules
Responsibility Separation : Adding a services layer isolates business logic, letting controllers focus on request handling and models on data access, adhering to the Single Responsibility Principle.
Inheritance Reuse : Base classes ( BaseXXX) provide cross‑version functionality; version‑specific subclasses only implement differences, reducing duplication.
Version Isolation : Each module contains a complete controller‑model‑service loop, allowing independent iteration and maintenance (e.g., refactoring v2 without affecting v1).
Centralized Routing : Extracting routing configuration into a single place simplifies API entry management and documentation generation.
Extensibility : Adding middleware, permission checks, or other features becomes straightforward after the service and routing layers are in place.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
