PHPackages                             fissible/drift - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. fissible/drift

ActiveLibrary[API Development](/categories/api)

fissible/drift
==============

API drift detection for PHP — compares live routes against OpenAPI specs and recommends version changes

v1.0.1(2mo ago)01.2k1MITPHPPHP ^8.2CI passing

Since Mar 25Pushed 2mo agoCompare

[ Source](https://github.com/fissible/drift)[ Packagist](https://packagist.org/packages/fissible/drift)[ Docs](https://github.com/fissible/drift)[ RSS](/packages/fissible-drift/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (2)Dependencies (10)Versions (3)Used By (1)

fissible/drift
==============

[](#fissibledrift)

OpenAPI drift detection and version analysis for PHP. Compares your live API routes against their spec, recommends semver bumps, and generates changelogs.

Part of the [Fissible](https://github.com/fissible) suite. **Depends on:** fissible/accord (reads specs via SpecSourceInterface).

```
  [forge]  ──────────────────────────────►  [accord]  ◄── [watch] ◄── [fault]
  generate spec                            validate at      cockpit UI   exception
      ▲                                    runtime │        (bolt-on)    tracking
      │                                            ▼
      └──────────────────────────────────  [drift]  ← you are here
                                           detect drift, bump version

```

---

Why drift detection matters
---------------------------

[](#why-drift-detection-matters)

As an API evolves, its spec and its actual routes can quietly fall out of sync. A route gets added but never documented. A route gets removed but the spec still describes it. Clients that rely on the spec to understand your API get a false picture of what's actually available.

**drift** surfaces that gap before it causes problems. It compares the routes your application actually serves against the routes your OpenAPI spec describes, tells you exactly what has been added or removed, and recommends how to version the change — so clients always know what to expect.

---

Requirements
------------

[](#requirements)

- PHP ^8.2
- OpenAPI 3.0.x spec files (YAML or JSON)
- fissible/accord ^1.0

Installation
------------

[](#installation)

```
composer require fissible/drift
```

### Laravel auto-discovery

[](#laravel-auto-discovery)

The service provider registers automatically. Console commands are registered with Artisan:

```
php artisan accord:validate
php artisan accord:version
php artisan drift:coverage
```

---

How it works
------------

[](#how-it-works)

Drift enumerates your application's routes via a `RouteInspectorInterface` implementation and compares them against the paths defined in your OpenAPI spec. Each route is normalised to a canonical form (`GET /v1/users/{param}`) so that parameter names don't cause false positives.

The result is a `DriftReport` describing:

- **Matched** routes — present in both the app and the spec
- **Added** routes — live in the app but not yet documented
- **Removed** routes — in the spec but no longer served by the app

From that report, drift recommends a semver bump and generates a changelog entry.

---

Console commands
----------------

[](#console-commands)

### `accord:validate`

[](#accordvalidate)

Checks for drift between your live routes and your OpenAPI spec:

```
php artisan accord:validate
php artisan accord:validate --api-version=v2
```

Output is a table showing each route's status. Exits with a non-zero code if any drift is detected — useful in CI pipelines to catch undocumented or removed routes before they ship.

```
 Version  Method  Path                  Status
 v1       GET     /v1/users             PASS
 v1       POST    /v1/users             PASS
 v1       GET     /v1/users/{id}        WARN  (undocumented — not in spec)
 v1       DELETE  /v1/orders/{id}       FAIL  (removed — in spec but not routed)

```

### `accord:version`

[](#accordversion)

Runs the full drift-analyse-changelog pipeline:

```
php artisan accord:version
php artisan accord:version --api-version=v1 --dry-run
php artisan accord:version --yes          # skip confirmation prompt
```

1. Detects drift for the given version
2. Reads the current `info.version` from the spec
3. Recommends a semver bump (major / minor / patch / none)
4. Confirms with you before writing any changes
5. Updates the spec's `info.version` in place
6. Prepends a changelog entry to `CHANGELOG.md`

When drift introduces breaking changes (removed routes), the command also notes that a new URI version (`/v2/`) should be considered.

### `drift:coverage`

[](#driftcoverage)

Checks that every registered route resolves to an existing controller class and method:

```
php artisan drift:coverage
php artisan drift:coverage --api-version=v1
```

Output is a table showing each route's implementation status. Exits with a non-zero code if any routes are unimplemented, making it suitable for CI.

```
 Coverage       Method  Path               Action
 IMPLEMENTED    GET     /api/v1/posts      App\Http\Controllers\V1\PostController@index
 IMPLEMENTED    POST    /api/v1/posts      App\Http\Controllers\V1\PostController@store
 MISSING        DELETE  /api/v1/posts/{id} App\Http\Controllers\V1\PostController@destroy
 UNKNOWN        GET     /api/v1/ping       (closure)

```

- **IMPLEMENTED** — controller class and method both exist
- **MISSING** — class or method cannot be found; the route would throw a server error if called
- **UNKNOWN** — route uses a closure or has no resolvable action string

---

Laravel
-------

[](#laravel)

### Route inspector

[](#route-inspector)

The bundled `LaravelRouteInspector` enumerates routes in your application's `api` middleware group, skipping HEAD routes:

```
// Registered automatically by DriftServiceProvider
use Fissible\Drift\Drivers\Laravel\Inspectors\LaravelRouteInspector;
```

To filter routes differently, bind your own `RouteInspectorInterface` implementation in a service provider:

```
$this->app->singleton(RouteInspectorInterface::class, function () {
    return new LaravelRouteInspector(
        router: $this->app['router'],
        filter: fn($route) => str_starts_with($route->uri, 'api/'),
    );
});
```

---

Core API
--------

[](#core-api)

### DriftDetector

[](#driftdetector)

```
use Fissible\Drift\DriftDetector;
use Fissible\Accord\FileSpecSource;

$source   = new FileSpecSource('/var/www/app');
$detector = new DriftDetector($source);
$report   = $detector->detect($routes, 'v1');

$report->isClean();            // true if no drift
$report->hasBreakingChanges(); // true if routes were removed
$report->hasAdditiveChanges(); // true if routes were added
$report->summary();            // human-readable string
```

### VersionAnalyser

[](#versionanalyser)

```
use Fissible\Drift\VersionAnalyser;

$analyser       = new VersionAnalyser($source);
$recommendation = $analyser->analyse($report);

$recommendation->bumpType;             // 'major' | 'minor' | 'patch' | 'none'
$recommendation->recommendedVersion;   // '1.2.0'
$recommendation->requiresNewUriVersion; // true when breaking changes are present
$recommendation->label();             // '1.1.0 → 1.2.0 (minor)'
```

### ChangelogGenerator

[](#changeloggenerator)

```
use Fissible\Drift\ChangelogGenerator;

$generator = new ChangelogGenerator();
$entry     = $generator->generate($report, $recommendation);

// Prepend the entry to CHANGELOG.md (creates the file if missing)
$generator->prepend($entry, base_path('CHANGELOG.md'));
```

---

Custom route inspectors
-----------------------

[](#custom-route-inspectors)

Implement `RouteInspectorInterface` to enumerate routes from any framework:

```
use Fissible\Drift\RouteInspectorInterface;
use Fissible\Drift\RouteDefinition;

class MyFrameworkInspector implements RouteInspectorInterface
{
    public function getRoutes(): array
    {
        return array_map(
            fn($route) => new RouteDefinition($route->method, $route->path),
            $this->router->getRoutes(),
        );
    }
}
```

`RouteDefinition` normalises parameter syntax automatically — `:id`, `{id}`, and `` all resolve to the same canonical path for comparison.

---

CI integration
--------------

[](#ci-integration)

Add both commands to your CI pipeline:

```
# .github/workflows/ci.yml
- name: Check API drift
  run: php artisan accord:validate

- name: Check for unimplemented routes
  run: php artisan drift:coverage
```

`accord:validate` fails the build if any routes are undocumented or have been removed from the spec without a version bump. `drift:coverage` fails the build if any route's controller or method is missing — catching spec-first development gaps before they reach production.

---

License
-------

[](#license)

MIT

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance86

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~18 days

Total

2

Last Release

71d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/ef298fbffac2225e36ab3a6e089af020f76885bc65d5f6afda831c64939e1e4b?d=identicon)[fissible](/maintainers/fissible)

---

Top Contributors

[![fissible](https://avatars.githubusercontent.com/u/1410914?v=4)](https://github.com/fissible "fissible (20 commits)")

---

Tags

apilaravelopenapisemverdrift

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/fissible-drift/health.svg)

```
[![Health](https://phpackages.com/badges/fissible-drift/health.svg)](https://phpackages.com/packages/fissible-drift)
```

###  Alternatives

[darkaonline/l5-swagger

OpenApi or Swagger integration to Laravel

2.9k36.4M126](/packages/darkaonline-l5-swagger)[laravel/sail

Docker files for running a basic Laravel application.

1.9k199.2M1.2k](/packages/laravel-sail)[knuckleswtf/scribe

Generate API documentation for humans from your Laravel codebase.✍

2.3k13.5M59](/packages/knuckleswtf-scribe)[friendsoftypo3/content-blocks

TYPO3 CMS Content Blocks - Content Types API | Define reusable components via YAML

101466.4k45](/packages/friendsoftypo3-content-blocks)[psx/api

Parse and generate API specification formats

37181.6k5](/packages/psx-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
