timeinc/swagger-bundle
A Symfony bundle that generates Swagger/OpenAPI documentation for your REST API from PHP annotations, keeping interactive specifications in sync with your code and making it easy to expose and maintain API docs alongside your application.
Install: composer require timeinc/swagger-bundle — this pulls in zircote/swagger-php (check which major version it resolves; the bundle's annotations style depends on it).
Register the bundle: classic Symfony apps add new TimeInc\SwaggerBundle\TimeIncSwaggerBundle() to AppKernel.php; Flex apps need the entry in config/bundles.php.
Configure scan paths and output in config.yml/config/packages/timeinc_swagger.yaml. The key shape looks roughly like:
timeinc_swagger:
output: "%kernel.project_dir%/web/swagger.json"
scan:
- "%kernel.project_dir%/src"
Confirm exact key names against the README — this is the first thing to check, since the bundle is thin and the config is where almost all behavior lives.
First use case: add a single docblock to one controller action:
/**
* @SWG\Get(
* path="/api/health",
* @SWG\Response(response=200, description="OK")
* )
*/
public function healthAction() {}
Then run the bundle's console command (typically bin/console swagger:generate — run bin/console list to confirm the exact name) and open the generated JSON. If you get a valid swagger: "2.0" document with your path, the wiring is correct.
Where to look first, in order: the README's config example → Command/ directory for the command name and options → the bundle's DependencyInjection/Configuration.php for authoritative config keys.
Centralize the root document. Put @SWG\Info, @SWG\SecurityDefinitions, and global @SWG\Tag declarations in one dedicated file (e.g. src/Swagger/ApiDoc.php) that contains no logic. swagger-php scans every file in the configured paths, so this becomes your single source of truth for version, title, host, basePath, and auth schemes.
Annotate at the controller/action level, not the entity. The idiomatic pattern is one @SWG\Get/@SWG\Post block per action referencing a shared @SWG\Schema definition via $ref. Keep request/response schemas in a Schemas/ folder of annotation-only classes so the scan set stays predictable.
Generate in CI, not at request time. Treat the generated swagger.json as a build artifact: run the generator as a Composer script ("swagger": "bin/console swagger:generate"), fail the build if it errors, and commit or publish the output. This catches annotation drift before deploy and keeps the runtime free of reflection overhead.
Serve the file separately. The bundle's job is generation; serving is usually simplest via a static file in public/ or a tiny controller that readfile()s it. Pair with Swagger UI (a separate swagger-ui bundle, an nginx location, or Docker image) pointed at the JSON URL — don't try to make the bundle serve UI for you.
Environment-scoped config. Scan src/ in all environments, but point output at a build directory in CI and at public/swagger.json locally. Use %kernel.project_dir% in paths so the config is portable across machines and containers.
Version the spec alongside the API. Tie @SWG\Info(version=...) to a value you can bump with each release tag; combined with the CI-generated artifact, you get a reviewable spec diff per release.
Compose with existing routers cautiously. If you already expose API routes under a prefix, use basePath and host in the @SWG\Info annotation (or config) rather than duplicating the prefix in every path=.
%kernel.project_dir%.doctrine/annotations-style parsing; a missing use Swagger\Annotations as SWG; at the top of a controller silently drops @SWG\* tags (they parse as @SWG\Get with no valid class). A "successful" run producing an empty paths object is almost always a missing use statement or a typo in the alias.@SWG\Info is mandatory. Without it, generation fails validation with an unhelpful error. If you see validation failures on a fresh install, add the ApiDoc.php file first before debugging anything else.file_put_contents; a missing public/ subdirectory or wrong permissions gives a generic write error rather than a path error.OpenApi\Annotations; if you upgrade swagger-php independently, the bundle's annotations (Swagger\Annotations) will stop resolving. Pin swagger-php via Composer if you can't upgrade the bundle too.@SWG\Operation(operationId="getUser") produce invalid client SDKs later even though the JSON generates fine. Let swagger-php derive them, or set them explicitly and lint for duplicates in CI (jq over paths[][].operationId | sort | uniq -d).-v to see which files were scanned; a common surprise is that vendor/ or a tests/ fixture contains docblocks that pollute the spec. Narrow scan to src/ only.cache:clear is not required after editing annotations — but it is required if you changed the bundle's YAML config. Don't waste time clearing cache for annotation changes.@SWG\Get annotations on the same path will emit duplicate keys; the last one wins silently. Use @SWG\Path in a single docblock when multiple methods share a path.Generator::UNDEFINED/processor API directly rather than expecting the bundle to expose it. Consider forking the bundle's command if you need extra CLI flags.How can I help you explore Laravel packages today?