Skip to content
Merged
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
45 changes: 45 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: "Tests"

on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'

jobs:
tests-on-linux:
name: "Tests on linux"
runs-on: "ubuntu-latest"
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
include:
- php-version: "8.5"
experimental: false
#composer-options: "--ignore-platform-reqs"

steps:
- name: "Checkout code"
uses: "actions/checkout@v7"
- name: "Setup PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: mbstring
ini-values: "memory_limit=-1"
tools: composer:v2
coverage: none
- name: "Install dependencies"
uses: nick-invision/retry@v4.0.0
with:
timeout_minutes: 5
max_attempts: 5
command: >-
composer install
--prefer-dist
--no-interaction
--no-progress
${{ matrix.composer-options }}
- name: "Run tests"
run: "composer phpunit"
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### 4.1.0 <small>(2026-07-??)</small>
Comment thread
freost marked this conversation as resolved.

#### Changes

* Removed dependency on `cebe/php-openapi`.

--------------------------------------------------------

### 4.0.1 <small>(2026-01-05)</small>

#### Bugfixes
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# OpenApi

[![Tests](https://github.com/mako-framework/open-api/actions/workflows/tests.yml/badge.svg)](https://github.com/mako-framework/open-api/actions/workflows/tests.yml)
[![Static analysis](https://github.com/mako-framework/open-api/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/mako-framework/open-api/actions/workflows/static-analysis.yml)

The `mako/open-api` package allows you to generate an [OpenApi](https://www.openapis.org) specification by documenting your code using PHP [attributes](https://www.php.net/manual/en/language.attributes.php), and to generate routes from an OpenAPI specification.
Expand Down
13 changes: 11 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@
"mako\\openapi\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"mako\\openapi\\tests\\": "tests"
}
},
"require": {
"php": ">=8.5.0",
"zircote/swagger-php": "^5.7",
"cebe/php-openapi": "^1.8"
"symfony/yaml": "^8.1"
},
"require-dev": {
"mako/framework": "^12.0.0",
"phpstan/phpstan": "^2.1.33"
"mockery/mockery": "^1.6.12",
"phpstan/phpstan": "^2.1.33",
"phpunit/phpunit": "^13.2"
},
"minimum-stability": "dev",
"prefer-stable": true,
Expand All @@ -32,8 +39,10 @@
}
},
"scripts": {
"phpunit": "phpunit --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warning --display-phpunit-deprecations",
"phpstan": "phpstan analyze src --no-progress --memory-limit=-1 -c phpstan.neon",
"qa": [
"@phpunit",
"@phpstan"
]
}
Expand Down
7 changes: 1 addition & 6 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
parameters:
ignoreErrors:
-
message: "#Access to an undefined property cebe\\\\openapi\\\\SpecObjectInterface::\\$paths#"
count: 1
path: src/generators/routing/Generator.php

7 changes: 7 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<phpunit colors="true" bootstrap="tests/bootstrap.php">
<testsuites>
<testsuite name="Mako OpenApi Tests">
<directory>tests/unit</directory>
</testsuite>
</testsuites>
</phpunit>
108 changes: 54 additions & 54 deletions src/generators/routing/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@

namespace mako\openapi\generators\routing;

use cebe\openapi\Reader;
use cebe\openapi\SpecObjectInterface;
use Closure;
use mako\openapi\parser\Operation;
use mako\openapi\parser\Parameter;
use mako\openapi\parser\Parser;
use Symfony\Component\Yaml\Yaml;

use function explode;
use function in_array;
use function str_replace;
use function strpos;

Expand All @@ -20,6 +23,18 @@
*/
abstract class Generator
{
/**
* Route methods.
*/
protected const array ROUTE_METHODS = [
'delete',
'get',
'patch',
'post',
'put',
'query',
];

/**
* Parameter patterns.
*
Expand All @@ -37,6 +52,8 @@ abstract class Generator
'byte' => '(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})',
],

// Number formats

'number' => [
'_' => '-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?',
'float' => '-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?',
Expand All @@ -60,22 +77,20 @@ abstract class Generator
];

/**
* Merges path and operation parameters.
* Returns the route path.
*
* @param \cebe\openapi\spec\Parameter[]|\cebe\openapi\spec\Reference[] $pathParameters
* @param \cebe\openapi\spec\Parameter[]|\cebe\openapi\spec\Reference[] $operationParameters
* @param string $path
* @param Parameter[] $parameters
*/
public function mergeParameters(array $pathParameters, array $operationParameters): array
protected function getRoutePath(string $path, array $parameters): string
Comment thread
freost marked this conversation as resolved.
{
$merged = [];

foreach ([$pathParameters, $operationParameters] as $parameters) {
foreach ($parameters as $parameter) {
$merged[$parameter->name] = $parameter;
foreach ($parameters as $parameter) {
if ($parameter->required === false) {
$path = str_replace("{{$parameter->name}}", "{{$parameter->name}}?", $path);
}
}

return $merged;
return $path;
}

/**
Expand All @@ -90,39 +105,28 @@ protected function getRouteAction(string $operationId): array|string
return $operationId;
}

/**
* Returns the route path.
*
* @param string $path
* @param \cebe\openapi\spec\Parameter[]|\cebe\openapi\spec\Reference[] $parameters
*/
protected function getRoutePath(string $path, array $parameters): string
{
foreach ($parameters as $parameter) {
if ($parameter->in === 'path' && $parameter->required === false) {
$path = str_replace("{{$parameter->name}}", "{{$parameter->name}}?", $path);
}
}

return $path;
}

/**
* Returns route parameter patterns.
*
* @param \cebe\openapi\spec\Parameter[]|\cebe\openapi\spec\Reference[] $parameters
* @param Parameter[] $parameters
*/
protected function getRoutePatterns(array $parameters): array
{
$patterns = [];

foreach ($parameters as $parameter) {
if ($parameter->in === 'path') {
if ($parameter->schema->type === 'string' && !empty($parameter->schema->pattern)) {
$patterns[$parameter->name] = $parameter->schema->pattern;
if (isset($parameter->schema['type'])) {
$type = $parameter->schema['type'];

if ($type === 'string' && !empty($parameter->schema['pattern'])) {
$patterns[$parameter->name] = $parameter->schema['pattern'];
}
elseif (isset($this->parameterPatterns[$parameter->schema->type][$parameter->schema->format ?? '_'])) {
$patterns[$parameter->name] = $this->parameterPatterns[$parameter->schema->type][$parameter->schema->format ?? '_'];
else {
$format = $parameter->schema['format'] ?? '_';

if (isset($this->parameterPatterns[$type][$format])) {
$patterns[$parameter->name] = $this->parameterPatterns[$type][$format];
}
}
}
}
Expand All @@ -138,26 +142,22 @@ abstract protected function registerRoute(string $method, string $path, array|Cl
/**
* Generates routes.
*
* @param \cebe\openapi\spec\OpenApi|SpecObjectInterface $openApi OpenApi object instance
* @param Operation[] $spec
*/
protected function generateRoutes(SpecObjectInterface $openApi): void
protected function generateRoutes(array $spec): void
{
$methods = ['get', 'post', 'put', 'patch', 'delete'];

foreach ($openApi->paths as $path => $definition) {
foreach ($methods as $method) {
if ($definition->{$method} !== null) {
$parameters = $this->mergeParameters($definition->parameters, $definition->{$method}->parameters);

$this->registerRoute(
$method,
$this->getRoutePath($path, $parameters),
$this->getRouteAction($definition->{$method}->operationId),
$definition->{$method}->operationId,
$this->getRoutePatterns($parameters),
);
}
foreach ($spec as $operation) {
if (!in_array($operation->method, static::ROUTE_METHODS, true)) {
continue;
}

$this->registerRoute(
$operation->method,
$this->getRoutePath($operation->path, $operation->pathParameters),
$this->getRouteAction($operation->operationId),
$operation->operationId,
$this->getRoutePatterns($operation->pathParameters)
);
}
}

Expand All @@ -166,14 +166,14 @@ protected function generateRoutes(SpecObjectInterface $openApi): void
*/
public function generateFromYamlFile(string $fileName): void
{
$this->generateRoutes(Reader::readFromYamlFile($fileName));
$this->generateRoutes((new Parser(Yaml::parseFile($fileName)))->parse());
}

/**
* Generates routes from a yaml string.
*/
public function generateFromYaml(string $yaml): void
{
$this->generateRoutes(Reader::readFromYaml($yaml));
$this->generateRoutes((new Parser(Yaml::parse($yaml)))->parse());
}
}
33 changes: 33 additions & 0 deletions src/parser/Operation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/**
* @copyright Frederic G. Østby
* @license http://www.makoframework.com/license
*/

namespace mako\openapi\parser;

/**
* Operation.
*/
readonly class Operation
{
/**
* Constructor.
*
* @param Parameter[] $pathParameters
* @param Parameter[] $queryParameters
* @param Parameter[] $cookies
* @param Parameter[] $headers
*/
public function __construct(
public string $path,
public string $method,
public string $operationId,
public array $pathParameters = [],
public array $queryParameters = [],
public array $cookies = [],
public array $headers = [],
) {
}
}
24 changes: 24 additions & 0 deletions src/parser/Parameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/**
* @copyright Frederic G. Østby
* @license http://www.makoframework.com/license
*/

namespace mako\openapi\parser;

/**
* Parameter.
*/
readonly class Parameter
{
/**
* Constructor.
*/
public function __construct(
public string $name,
public bool $required,
public array $schema
) {
}
}
Loading