How swagger-php turns PHP attributes into an OpenAPI document
A php swagger annotation and parsing library
At a glance
- What is it?
- swagger-php scans your own PHP source for attributes, doctrine annotations and phpdoc types, then writes an OpenAPI 3.0, 3.1 or 3.2 document to yaml or json. Spec attributes are the documented path, and classic attributes and docblock annotations are deprecated since 7.0 and removed in 8.0.
- Who is it for?
- Take swagger-php if your API is described in PHP source and you want the document produced by a build step you control, but write it with spec attributes rather than classic attributes or annotations. Walk away if you need runtime route discovery, a hosted viewer, or PHP older than 8.2.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- zircote/swagger-php5,303 ★ · -6
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| zircote/swagger-php | 5.3k | 5.3k |
-6 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Attributes are the path, annotations are on notice
swagger-php reads OpenAPI metadata out of your own source files, and it offers two ways to write it. The current one is spec attributes from the `OpenApi\Spec` namespace, which the usage example uses. The older one is classic attributes in `OpenApi\Attributes` and docblock annotations, which need the `doctrine/annotations` library pulled in on top. That path carries an explicit warning: it is deprecated since 7.0 and removed in 8.0. Nothing stops a team from shipping them today, so the real question is what you want to be rewriting a year from now. There is a floor under both paths too. The library requires at least PHP 8.2, so a host pinned to 8.0 or 8.1 cannot install the package, and that settles your framework upgrade order before any documentation work begins.
Composer require, then finding the openapi executable
Installation runs through Composer, and the global variant asks you to put Composer's binary directory on your PATH so the `openapi` executable can be found by your system. Use the command it gives you to print that directory rather than assuming the location. Requiring the package into a single project leaves you with the library and a local binary under vendor/bin, which is what the usage examples in the docs run against. Since version 4.8 the doctrine annotations library is optional and no longer installed by default, so a codebase still written with annotations needs that second require, and skipping it is the first thing to check when annotation parsing behaves strangely.
composer require zircote/swagger-php
composer global require zircote/swagger-php
composer global config bin-dir --absolute
composer require doctrine/annotations
./vendor/bin/openapi --helpClassic mode writes 3.0.0, spec and hybrid write 3.1.0
Two switches decide the shape of the document and they are easy to confuse. The first is the OpenAPI version. swagger-php can generate 3.0.0, 3.1.0 and 3.2.0, but the default depends on the mode you run: classic mode defaults to 3.0.0, while spec and hybrid mode default to 3.1.0. The command line `--version` option selects a different one, and `Builder::setVersion()` does the same job from PHP. The consequence shows up the first time two people generate a spec on different modes and diff the files, because the version field changes on its own. The second switch is the mode itself, and hybrid is the default.
$result = (new \OpenApi\Builder())
->addSource('src/')
->build();./vendor/bin/openapi --helpSpec attributes compile a document you wrote by hand
Spec attributes live in the `OpenApi\Spec` namespace. The point is that you build the document structure yourself, attribute by attribute, and compile it without a scan. Beside it sits a pipeline that reports when an attribute is wrong, with hints and context, and that is the part that matters for hand written documents: a mistake in an `OA\Response` gets named instead of vanishing into the output. Hybrid mode, the default, scans classic attributes and docblock annotations you already wrote and hands them to the same pipeline as the spec attributes, so existing `OpenApi\Attributes` code still works.
use OpenApi\Spec as OA;
#[OA\Info(title: 'My API', version: '1.0')]
class MyApi
{
#[OA\Operation\Get(path: '/api/resource')]
#[OA\Response(response: 200, description: 'An example resource')]
public function getResource() {}
}Type resolution hands generics to symfony/type-info
Type resolution is not swagger-php's own work: types are resolved with the `symfony/type-info` library, and that is what lets the project read native type hints plus generic type hints written in phpdoc blocks. The visible effect is that a plain `array` property described by a `@var` line can express a union of schemas as precisely as a hand built `oneOf` list, and the two spellings are equivalent. Generics in a docblock are worth the trade because schemas stop needing hand written wrappers.
class MyClass
{
/**
* @var list<SchemaOne|SchemaTwo>
*/
public array $values;
}The result is a static yaml or json file, not a live page
The deliverable is a document, not a running viewer. From the command line the `openapi` tool generates the documentation to a static yaml or json file, and from PHP you build the result object yourself and print it, for instance by setting a yaml content type header and echoing `toYaml()`. That distinction decides who keeps the docs honest. The promise of always up to date documentation only holds when something regenerates the file after every code change, and the README documents no watcher, no cache and no CI recipe for it. The same gap sits around failure handling: hints and context are listed as a feature, but no exit code contract and no switch for turning warnings into failures is documented, so a build that emits a thinner document without stopping the pipeline is yours to catch.
<?php
require("vendor/autoload.php");
$result = (new \OpenApi\Builder())
->addSource(['/path/to/project'])
->build();
header('Content-Type: application/x-yaml');
echo $result->toYaml();Everything it reports comes from what you already declared
swagger-php knows only what your source says, because attributes, doctrine annotations and existing phpdoc comments are all it reads. In the shortest example the 200 response and its description exist because someone put `#[OAT\Response(response: '200', description: 'An example resource')]` on the method. Delete that attribute and nothing takes its place: the route string, the class name and the return type do not turn into a documented response on their own. No framework router is consulted and no database schema is compared against the finished document, so the tool cannot tell you that the 201 you promised is the 200 you return, or that a field was dropped from a payload three releases ago. That gap belongs in code review, next to whoever writes the attributes, not in a tool you expect to catch it.
Editorial conclusion
Take swagger-php if your API is described in PHP source and you want the document produced by a build step you control, but write it with spec attributes rather than classic attributes or annotations. Walk away if you need runtime route discovery, a hosted viewer, or PHP older than 8.2. Before committing, check whether any file in your own tree still uses classic `OpenApi\Attributes` or doctrine annotations, since both are deprecated since 7.0 and removed in 8.0.
Frequently asked questions
Is Swagger now called OpenAPI?
The specification swagger-php targets is OpenAPI, and it can generate 3.0.0, 3.1.0 and 3.2.0 documents. The older word survived in the package name: the Composer package is zircote/swagger-php and the executable it installs is called openapi.
Is Swagger outdated?
The library is still shipping, with 7.1.0 released on 2026-11-02, 6.20.1 on 2026-10-30 and 7.0.0 on 2026-10-28, and the last push landed on 2026-11-02. What is being retired is the older metadata style, not the tool: classic attributes and docblock annotations are deprecated since 7.0 in favour of spec attributes.
Is Swagger UI like Postman?
swagger-php does not ship a user interface. The command line interface generates the documentation to a static yaml or json file, and the docs link readers to swagger.io and https://swagger.io/docs/ for the specification itself. Nothing in this package sends requests.
What is replacing Swagger?
Inside this project the shift is to OpenAPI and to native PHP attributes. swagger-php generates 3.0.0, 3.1.0 and 3.2.0, and its own metadata path is moving from classic attributes and doctrine annotations, which are deprecated since 7.0 and removed in 8.0, to spec attributes on your classes and methods.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zircote-swagger-php)Star history chart for your README
[](https://hysenlabs.com/projects/zircote-swagger-php)Was this useful?