Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function getReferenceSidebar() {
{
text: 'Api',
items: [
{ text: 'Builder', link: '/reference/builder' },
{ text: 'Generator', link: '/reference/generator' },
{ text: 'Processors', link: '/reference/processors' },
]
Expand Down
51 changes: 49 additions & 2 deletions docs/guide/generating-openapi-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,60 @@ Options:

Depending on your use case, PHP code can also be used to generate OpenAPI documents in a more dynamic way.

In its simplest form this may look something like
### Using the Builder

The `Builder` class is the recommended entry point for generating OpenAPI documents from PHP code.

```php
<?php
require('vendor/autoload.php');

$result = (new \OpenApi\Builder())
->addSource('/path/to/project')
->build();

header('Content-Type: application/x-yaml');
echo $result->toYaml();
```

The result object provides access to the generated spec in multiple formats, the list of scanned
files, and any validation warnings or errors collected during generation.

```php
$result->toYaml(); // YAML string
$result->toJson(); // JSON string
$result->toArray(); // PHP array
$result->files(); // list of scanned files
$result->warnings(); // validation warnings
$result->errors(); // validation errors
$result->isValid(); // true if spec was generated
```

For advanced Generator configuration (custom analysers, processors, aliases, etc.), use the
`withGenerator()` hook:

```php
$result = (new \OpenApi\Builder())
->addSource('/path/to/project')
->setVersion('3.1.0')
->withGenerator(function (\OpenApi\Generator $generator) {
$generator->setConfig(['operationId.hash' => false]);
$generator->withProcessorPipeline(function ($pipeline) {
$pipeline->add(new MyCustomProcessor());
});
})
->build();
```

### Using the Generator directly

The `Generator` class can also be used directly:

```php
<?php
require('vendor/autoload.php');

$openapi = (new \OpenApi\Generator)->generate(['/path/to/project']);
$openapi = (new \OpenApi\Generator())->generate(['/path/to/project']);

header('Content-Type: application/x-yaml');
echo $openapi->toYaml();
Expand Down
80 changes: 80 additions & 0 deletions docs/reference/builder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Using the `Builder`

## Introduction

The `Builder` class is the recommended entry point for generating OpenAPI documents from PHP code. It provides a clean, fluent API and returns a `Result` object with access to the generated spec, scanned files, and validation diagnostics.

## Basic usage

```php
$result = (new \OpenApi\Builder())
->addSource('src/Controllers')
->addSource('src/Models')
->build();

echo $result->toYaml();
```

## API

### Sources

```php
// Add sources one at a time
$builder->addSource('src/Controllers');
$builder->addSource(new \OpenApi\Utils\SourceFinder('src/', ['tests']));

// Or set all at once
$builder->setSources(['src/Controllers', 'src/Models']);
```

Sources can be directory paths, file paths, `\SplFileInfo`, `\Symfony\Component\Finder\Finder` instances, or nested iterables of these.

### Version

```php
$builder->setVersion('3.1.0');
```

Sets the target OpenAPI version. If not set, defaults to the version declared in your `#[OA\OpenApi]` attribute.

### Logger

```php
$builder->setLogger($psrLogger);
```

Accepts any PSR-3 logger. Defaults to `NullLogger` (silent). The CLI command sets its own console logger.

### Generator configuration

For advanced Generator configuration (custom analysers, processors, aliases, type resolvers), use `withGenerator()`:

```php
$builder->withGenerator(function (\OpenApi\Generator $generator) {
$generator->setAnalyser($customAnalyser);
$generator->setConfig(['operationId.hash' => false]);
$generator->withProcessorPipeline(function ($pipeline) {
$pipeline->remove(\OpenApi\Processors\CleanUnusedComponents::class);
});
});
```

The callable receives a pre-configured `Generator` instance and may either modify it in-place or return a new instance.

## Result

The `build()` method returns a `\OpenApi\Builder\Result` instance:

```php
$result = $builder->build();

$result->isValid(); // bool — true if a spec was generated
$result->toArray(); // array — the spec as a PHP array
$result->toJson(); // string — JSON output
$result->toYaml(); // string — YAML output
$result->files(); // string[] — scanned source files
$result->log(); // array — all log entries [{level, message}, ...]
$result->warnings(); // string[] — warning messages
$result->errors(); // string[] — error messages
```
6 changes: 5 additions & 1 deletion docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ However, `swagger-php` offers more.

The 'traditional' way of documenting your API.

* The [`Builder`](builder.md)

The `\OpenApi\Builder` class is the recommended entry point for generating OpenAPI documents from PHP code.

* The [`Generator`](generator.md)

The `\OpenAPI\Generator` class is the main entry point to programmatically generate OpenAPI documents from your code.
The `\OpenApi\Generator` class can be used directly for advanced use cases or via the Builder's `withGenerator()` method.

* [Processors](processors.md)

Expand Down
132 changes: 132 additions & 0 deletions src/Builder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php declare(strict_types=1);

/**
* @license Apache 2.0
*/

namespace OpenApi;

use OpenApi\Builder\CollectingLogger;
use OpenApi\Builder\Result;
use OpenApi\Utils\SourceScanner;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
* Unified entry point for generating OpenAPI specs.
*/
class Builder
{
/** @var list<string|iterable> */
protected array $sources = [];

protected string $mode = 'classic';

protected ?string $version = null;

protected ?LoggerInterface $logger = null;

/** @var callable|null */
protected $generatorHook;

public function addSource(string|iterable $source): static
{
$this->sources[] = $source;

return $this;
}

/**
* @param list<string|iterable> $sources
*/
public function setSources(array $sources): static
{
$this->sources = $sources;

return $this;
}

/**
* Select the processing mode.
*
* @param string $mode 'classic' (default) or 'spec'
*/
public function setMode(string $mode): static
{
$this->mode = $mode;

return $this;
}

public function setVersion(string $version): static
{
$this->version = $version;

return $this;
}

public function setLogger(LoggerInterface $logger): static
{
$this->logger = $logger;

return $this;
}

/**
* Hook to configure the underlying Generator.
*
* The callable receives a default Generator and may either modify it in-place
* or return a fully configured instance.
*
* @param callable(Generator): (Generator|void) $hook
*/
public function withGenerator(callable $hook): static
{
$this->generatorHook = $hook;

return $this;
}

public function build(): Result
{
return match ($this->mode) {
'classic' => $this->doBuild(),
default => throw new OpenApiException("Unsupported mode '{$this->mode}'"),
};
}

protected function getLogger(): LoggerInterface
{
$this->logger ??= new NullLogger();

return $this->logger;
}

protected function doBuild(): Result
{
$collecting = new CollectingLogger($this->getLogger());
$generator = new Generator($collecting);

if ($this->version !== null) {
$generator->setVersion($this->version);
}

if ($this->generatorHook !== null) {
$generator = ($this->generatorHook)($generator) ?? $generator;
}

$openApi = $generator->generate($this->sources);

return new Result($this->resolveFiles(), $openApi, $collecting->entries());
}

/**
* @return list<string>
*/
protected function resolveFiles(): array
{
$scanner = new SourceScanner($this->getLogger());

return $scanner->scan($this->sources);
}
}
37 changes: 37 additions & 0 deletions src/Builder/CollectingLogger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1);

/**
* @license Apache 2.0
*/

namespace OpenApi\Builder;

use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

/**
* Logger decorator that collects entries while forwarding to a wrapped logger.
*/
class CollectingLogger extends AbstractLogger
{
/** @var list<array{level: string, message: string}> */
protected array $entries = [];

public function __construct(protected LoggerInterface $delegate)
{
}

public function log($level, $message, array $context = []): void
{
$this->entries[] = ['level' => (string) $level, 'message' => (string) $message];
$this->delegate->log($level, $message, $context);
}

/**
* @return list<array{level: string, message: string}>
*/
public function entries(): array
{
return $this->entries;
}
}
Loading
Loading