Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2dacd59
Rebuild on swagger-php's spec pipeline
DerManoMann Sep 19, 2026
9b45a38
Replace the options-array config with fluent setters
DerManoMann Sep 19, 2026
b42b29b
Modernise the toolchain and add phpstan at level 7
DerManoMann Sep 19, 2026
e89e9db
Reject misplaced Middleware, and tighten docblocks
DerManoMann Sep 20, 2026
ce97de1
Let callers supply the builder, and stop double-logging diagnostics
DerManoMann Sep 20, 2026
8b076a7
Drop scan(); registerRoutes() builds inline
DerManoMann Sep 20, 2026
652814a
Drop the gitignore entry for a file nothing generates
DerManoMann Sep 20, 2026
9ca09a4
Rewrite the docs for 5.x
DerManoMann Sep 20, 2026
ad4b7c0
Point the README at swagger-php's attribute docs
DerManoMann Sep 20, 2026
0331618
Take a builder hook rather than a builder, and widen the source type
DerManoMann Sep 20, 2026
9b45470
Use set* for values and with* for hooks
DerManoMann Sep 20, 2026
f5f2187
Add CONTEXT.md
DerManoMann Sep 20, 2026
5952747
State the spec-pipeline constraint in the introduction
DerManoMann Sep 20, 2026
817abc9
Move attribute usage out of Configuration, correct the vendor extensions
DerManoMann Sep 20, 2026
87a9202
Drop internal framing from the user-facing docs
DerManoMann Sep 20, 2026
25447a9
Prose pass over the docs
DerManoMann Sep 20, 2026
5dff5e1
Resolve routing metadata against the PathItem hierarchy
DerManoMann Sep 20, 2026
59d2528
Cover the 5.x configuration API, and stop the caching sets reading ba…
DerManoMann Sep 20, 2026
3f1e8ac
Document duplicate routes, and correct the package metadata
DerManoMann Sep 20, 2026
8d08215
Merge main into v2/spec-pipeline
DerManoMann Sep 20, 2026
b57b0b9
Carry the checkout bump into the workflows this branch rewrote
DerManoMann Sep 20, 2026
fed09fe
refactor: take the PathItem chain from swagger-php instead of walking…
DerManoMann Sep 26, 2026
9e713ea
fix: route a path parameter a PathItem declares by reference
DerManoMann Oct 14, 2026
07425a7
docs: say what swagger-php v7 and v8 change for 5.x
DerManoMann Oct 20, 2026
751ab4f
Require laravel/framework for the tests, and state the supported fram…
DerManoMann Oct 20, 2026
6afaeb6
Constrain path parameters by enum and format, and apply schema defaults
DerManoMann Oct 20, 2026
aef2e3c
Allow swagger-php 7.0
DerManoMann Oct 21, 2026
a47a07a
Suggest openapi-verifier for checking responses against the document …
DerManoMann Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 50 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,53 @@ on:
- main

jobs:
call-laravel:
uses: ./.github/workflows/laravel.yml
with:
php-versions: '8.2,8.3,8.4,8.5'
call-slim:
uses: ./.github/workflows/slim.yml
with:
php-versions: '8.2,8.3,8.4,8.5'
test:
runs-on: ${{ matrix.operating-system }}
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GITHUB_TOKEN }}"}}'
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
fail-fast: true
matrix:
operating-system: [ ubuntu-latest ]
php: [ '8.2', '8.3', '8.4', '8.5' ]
# both frameworks are in require-dev, so a single run covers both adapters; Laravel is
# the only one with more than one supported major, so it is the only axis varied here
laravel: [ '12.0', '13.0' ]
dependencies: [ 'lowest', 'highest' ]
exclude:
- php: '8.2'
laravel: '13.0'

name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies

steps:
- uses: actions/checkout@v7
name: Checkout repository

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: pcov

- name: Pin Laravel version
run: composer require --dev laravel/framework "^${{ matrix.laravel }}" -W --no-update

- name: Composer install
uses: ramsey/composer-install@v4
with:
dependency-versions: ${{ matrix.dependencies }}

- name: PHPUnit + Code coverage
run: |
mkdir -p build/logs
vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml

- name: Code coverage upload to Coveralls
if: matrix.php == '8.3' && matrix.dependencies == 'highest' && matrix.laravel == '13.0'
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
composer require php-coveralls/php-coveralls -W
vendor/bin/php-coveralls -v --coverage_clover=build/logs/clover.xml
2 changes: 1 addition & 1 deletion .github/workflows/code-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ jobs:
with:
dependency-versions: 'highest'

- name: Static Code Analysis
- name: Check Code Style
run: composer lint
60 changes: 0 additions & 60 deletions .github/workflows/laravel.yml

This file was deleted.

49 changes: 0 additions & 49 deletions .github/workflows/slim.yml

This file was deleted.

28 changes: 28 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: static-analysis

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
name: Checkout repository

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'

- uses: ramsey/composer-install@v4
with:
dependency-versions: 'highest'

- name: Static Code Analysis
run: composer analyse
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
.idea/
.php_cs.cache
.php-cs-fixer.cache
.phpunit.cache/
.phpunit.result.cache
composer.lock
vendor/
tests/storage/
tests/*/openapi.yaml
/.claude/settings.local.json
14 changes: 10 additions & 4 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@

return (new PhpCsFixer\Config())
->setRules([
'@PSR2' => true,
'@DoctrineAnnotation' => true,
'@PSR12' => true,
'blank_line_after_opening_tag' => false,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => true,
'no_unused_imports' => true,
'blank_line_before_statement' => ['statements' => ['return']],
'visibility_required' => true,
'modifier_keywords' => true,
'cast_spaces' => ['space' => 'single'],
'concat_space' => ['spacing' => 'one'],
'type_declaration_spaces' => true,
Expand All @@ -40,9 +41,10 @@
'trim_array_spaces' => true,
'single_space_around_construct' => true,
'single_line_comment_spacing' => true,
'fully_qualified_strict_types' => true,
'fully_qualified_strict_types' => ['import_symbols' => true, 'leading_backslash_in_global_namespace' => true],
'global_namespace_import' => ['import_classes' => false, 'import_constants' => null, 'import_functions' => null],

'attribute_empty_parentheses' => ['use_parentheses' => false],
'nullable_type_declaration_for_default_null_value' => true,

'no_empty_phpdoc' => true,
Expand All @@ -64,6 +66,10 @@
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_no_empty_return' => true,
'phpdoc_no_alias_tag' => true,
'phpdoc_param_order' => true,
'multiline_comment_opening_closing' => true,

'php_unit_attributes' => true,
])
->setFinder($finder)
;
107 changes: 107 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# openapi-router

Configures a PHP framework's router from the swagger-php **spec attributes**
describing an API, so the routing table and the OpenAPI document cannot drift apart.

Terminology for this package. swagger-php's own vocabulary is defined in
[its CONTEXT.md](https://github.com/zircote/swagger-php/blob/master/CONTEXT.md) and is not
repeated here — where a word belongs to both, theirs wins.

## Language

### Core concepts

**Route**:
One framework-level mapping of method + path to a handler. What this package produces.
_Avoid_: endpoint (an API-design word, not a framework one), path (means the URL alone)

**Operation**:
A swagger-php `Spec\Operation` — one HTTP method on one path, as declared in an attribute.
The **input** side of what this package does; a route is the output.
_Avoid_: using it for the registered route, or as a synonym for **action**

**Adapter**:
The per-framework translator that registers a route. One implementation per framework,
behind `RoutingAdapterInterface`.
_Avoid_: driver, bridge, integration

**Registration**:
The act of handing one route to the framework's router, and the `RouteRegistration` value
object carrying everything needed to do it.
_Avoid_: binding, mapping (both mean something else in Laravel)

**Controller**:
The `Class::method` string a route dispatches to. Written as a **handler** by some
frameworks.
_Avoid_: action, callable — `RouteRegistration::$controller` is the name, and it is always
`Class::method`, never a closure

### Attributes

**Middleware**:
This package's own attribute, and the only one it defines. An `Attachable`, so it is routing
metadata that never reaches the OpenAPI document.
_Avoid_: calling it an OpenAPI attribute — it is not one, which is why it lives outside the
`OpenApi\` namespace

**Vendor property**:
A vendor extension on an operation that this package reads. Declared unprefixed —
`x: ['name' => ...]` — and emitted with the prefix, as `x-name`. Say which form you mean.
_Avoid_: naming the declared key `x-name`; that is the emitted spelling, and the constants
(`X_NAME`, `X_MIDDLEWARE`) hold the unprefixed one

**Attachable**:
swagger-php's extension point for metadata that rides along with an attribute without
appearing in the output. Theirs, not ours — see their CONTEXT.md.

### Configuration

**`set*`**:
A fluent setter taking a **value**. Follows swagger-php, where the prefix means exactly this.
_Avoid_: `with*` for a value — the prefix is load-bearing

**`with*`**:
A fluent setter taking a **callable** that configures something. `withBuilder()` is the only
one here.
_Avoid_: `with*` taking the configured object itself; swagger-php has no such method and it
reintroduces the question of what the caller must supply

**Source**:
Whatever is scanned for attributes — directories, files or reflectors. Passed to the
constructor and forwarded to `Builder::addSource()`.
_Avoid_: path, directory (both too narrow), input

## Relationships

- An **adapter** receives a **registration** per **route** and translates it for its framework
- A **registration** is derived from one **operation**, plus the **middleware** and path
parameters on the `PathItem` chain governing it — its own and every ancestor's — plus any
**vendor properties**
- **Sources** feed swagger-php's `Builder`, which produces the `Specification` the
**operations** are read from
- `withBuilder()` configures that `Builder`; it matters to routing only when it changes
*which operations are discovered*

## Example dialogue

> **Dev:** "My middleware isn't being applied."
> **Domain expert:** "Is the `#[Middleware]` beside an **operation** or a `PathItem`? It has
> nothing to merge into anywhere else, so the scan raises an error."

> **Dev:** "Can I get the OpenAPI document out of the router?"
> **Domain expert:** "No — this package contributes nothing to it. Run swagger-php over the
> same **sources**; the output is identical."

## Flagged ambiguities

- "route" vs "operation" — **resolved**: an **operation** is declared (swagger-php's input), a
**route** is registered (the framework's output). This package turns the first into the
second. Never use them interchangeably, because the whole package is the mapping between
them.
- "middleware" — **resolved**: always means the framework's middleware, named by the
`Middleware` attribute or `x-middleware`. Never swagger-php's pipeline stages, which are
**processors** (classic) or **augmenters** (spec).
- "name" — **resolved**: the *route* name, which `setOperationIdAsName()` derives from the
`operationId` and `x-name` overrides. Not the operation id itself, and not a path.
- "version" — **resolved**: this release line is **5.x**, the previous one 4.x. Say "5.x" in
anything public; never a milestone or branch name.
Loading
Loading