mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-04-03 13:53:33 +03:00
Compare commits
410 commits
Author | SHA1 | Date | |
---|---|---|---|
|
10ee31bdaa | ||
|
6946e8bdc9 | ||
|
a896abb3e2 | ||
|
a9fceca8df | ||
|
8135dec035 | ||
|
5db963666a | ||
|
990b781322 | ||
|
e7c9fd4f56 | ||
|
abbf032483 | ||
|
49f4161b23 | ||
|
3ef60f2926 | ||
|
636eeb7cae | ||
|
e97eba7c1b | ||
|
73f9b9e8a2 | ||
|
4b6137f618 | ||
|
eba661dcb6 | ||
|
d761d890a0 | ||
|
551366412e | ||
|
e06fb926f5 | ||
|
cb69478c78 | ||
|
5bad727da1 | ||
|
45d1ed1735 | ||
|
4d4758cb40 | ||
|
6334d2e40f | ||
|
b72dfea35c | ||
|
7de616a283 | ||
|
0f7ecac668 | ||
|
0808e8421a | ||
|
ed2e185fe2 | ||
|
56dd45e0d1 | ||
|
1cd20df360 | ||
|
c8e33918a2 | ||
|
5cbcba78df | ||
|
72441d6bf3 | ||
|
b603381139 | ||
|
0765d2b453 | ||
|
c764717de4 | ||
|
66bcfd82e3 | ||
|
b49a0eb8e7 | ||
|
6abd901696 | ||
|
d8b5ab9f71 | ||
|
8d699084aa | ||
|
056087cbbf | ||
|
b9718f3400 | ||
|
33964a4bfc | ||
|
d43396c930 | ||
|
99b9d4f486 | ||
|
8a6213cbd2 | ||
|
39d640cce8 | ||
|
d78f8887a9 | ||
|
befd12e3e5 | ||
|
81274509da | ||
|
b7464a93b9 | ||
|
cd87930d29 | ||
|
c6fa1e893e | ||
|
1c4f003e76 | ||
|
f0a606b636 | ||
|
f4b599f6d0 | ||
|
8886e6eff8 | ||
|
83ed0a3407 | ||
|
17b6c3aeba | ||
|
adcdd91950 | ||
|
ae18441763 | ||
|
e5268e3f7f | ||
|
572c69a509 | ||
|
33307d750e | ||
|
e2c3d7ce94 | ||
|
3727d043cb | ||
|
ad1b9eec31 | ||
|
eab2d5d8fc | ||
|
b01128dc13 | ||
|
7a7d743ff6 | ||
|
ff3bf88bed | ||
|
2d70b08021 | ||
|
95017bf780 | ||
|
8f9d096448 | ||
|
66e8e15ff1 | ||
|
958e697981 | ||
|
c531fcc037 | ||
|
13c25eb4c4 | ||
|
73d4db51e1 | ||
|
9387638079 | ||
|
b5d371d984 | ||
|
e6e424687e | ||
|
97c6b1e857 | ||
|
463dea9af6 | ||
|
f5e38a283a | ||
|
a3a9bb3b70 | ||
|
79716d49f5 | ||
|
4b16f4e068 | ||
|
7aeea0871c | ||
|
9444172abc | ||
|
e6ea85157e | ||
|
1256275185 | ||
|
725e4d9dda | ||
|
a009e97382 | ||
|
05d3a51259 | ||
|
6d16486abb | ||
|
4a37d638f8 | ||
|
d0eaafadcb | ||
|
8eee7fec49 | ||
|
970a78c614 | ||
|
12c067154c | ||
|
df04b5c871 | ||
|
7288ccad07 | ||
|
5123b49bf7 | ||
|
d5c1c57cc9 | ||
|
e141ffd291 | ||
|
a7ad7ff144 | ||
|
aada3151aa | ||
|
b494d8e1ab | ||
|
6c77f6f422 | ||
|
9dfd984c68 | ||
|
4660805291 | ||
|
8921c48b32 | ||
|
2d214fc934 | ||
|
9caa2816e7 | ||
|
96cfab973c | ||
|
246babf209 | ||
|
fcd4d8fa2a | ||
|
b510dd2c70 | ||
|
718dfc6e9c | ||
|
8cb2847af4 | ||
|
4ed06b40f8 | ||
|
f6ec8ba6b1 | ||
|
6793b70157 | ||
|
4a95c81914 | ||
|
476348de54 | ||
|
4986e02ec1 | ||
|
df10a4769c | ||
|
aef6a89329 | ||
|
db5303fcd7 | ||
|
2a0f95eac0 | ||
|
89f459dc07 | ||
|
4381f065cb | ||
|
cec5108133 | ||
|
364ef73680 | ||
|
65a5159ce9 | ||
|
dd6a5e32c3 | ||
|
4e65cd3bc3 | ||
|
f6e952ffe1 | ||
|
028b09e90a | ||
|
c1c711bc26 | ||
|
07545629aa | ||
|
18c9fb8c29 | ||
|
abb100b29b | ||
|
0ed53788d2 | ||
|
f2fc6b47c0 | ||
|
9b734b22bd | ||
|
7919b24971 | ||
|
3bc98723fe | ||
|
5d9c47bbad | ||
|
306f2c47cf | ||
|
4744de98fb | ||
|
8aa5e479d1 | ||
|
1ae2cfa9a5 | ||
|
8c336487e7 | ||
|
93b189b16a | ||
|
d3bc49c3d1 | ||
|
19d4e37365 | ||
|
24fa5dfbc0 | ||
|
49238f44aa | ||
|
d5395cc0ea | ||
|
1df3ddf49f | ||
|
0461f5ce1d | ||
|
5541412e69 | ||
|
67b7b46627 | ||
|
97707ea5f4 | ||
|
dd8bdd2070 | ||
|
4fad200d7b | ||
|
224af02f7f | ||
|
eafa2ade0b | ||
|
aa10f9a2da | ||
|
a24d81f766 | ||
|
ac07a59ed4 | ||
|
3dea122b98 | ||
|
e8a2f690ab | ||
|
be90e8aad6 | ||
|
e2d2e41f7b | ||
|
8b7dfdd913 | ||
|
0b7c5d6749 | ||
|
9df40af264 | ||
|
d25dd30453 | ||
|
d49ac45866 | ||
|
09bb83e149 | ||
|
329e226426 | ||
|
490a9081ec | ||
|
f8b038faf2 | ||
|
e0a001081a | ||
|
fb863954d8 | ||
|
7f5f8bd258 | ||
|
57b9aa659e | ||
|
beaa25b21c | ||
|
0dc1bc4469 | ||
|
4b72190f4d | ||
|
e66fb209b5 | ||
|
b37924d4b1 | ||
|
716f3f19de | ||
|
96e7d1a201 | ||
|
c8cea500fa | ||
|
ce71bf0629 | ||
|
3c47357a0e | ||
|
32de80f97f | ||
|
189197dc88 | ||
|
f08b957670 | ||
|
c3092d5e36 | ||
|
b39b7df0d8 | ||
|
d60da7a734 | ||
|
5f61d5faf4 | ||
|
ca0fbe38d7 | ||
|
f815162ba7 | ||
|
9af37448fa | ||
|
082b2fa86f | ||
|
6dc6e7c55a | ||
|
2cbd828973 | ||
|
9c0c533b4b | ||
|
874c8752e6 | ||
|
6f4373cda4 | ||
|
f57a8bf7ae | ||
|
4351ca66d3 | ||
|
f77f52c041 | ||
|
840b96f8f3 | ||
|
be682f7d18 | ||
|
02007e957d | ||
|
0699c45dcc | ||
|
787f69561b | ||
|
406a4e1b5b | ||
|
37c6465700 | ||
|
a343bb7c45 | ||
|
7eccee0a36 | ||
|
20f4cc673f | ||
|
7c8b010215 | ||
|
65345912a9 | ||
|
a05ed38501 | ||
|
a967fce6c7 | ||
|
c7909c576e | ||
|
b07db95eaf | ||
|
b7dbccbc96 | ||
|
a679eb8089 | ||
|
b672df0aca | ||
|
12372cca00 | ||
|
213dbdfd1c | ||
|
a68dcfe829 | ||
|
6be99b53ca | ||
|
839e9ed7e0 | ||
|
a080692961 | ||
|
f5b45607c6 | ||
|
f305905928 | ||
|
3072cc2917 | ||
|
36c9f8b7fc | ||
|
38a3137465 | ||
|
5de5d530dd | ||
|
4bee3afe9a | ||
|
1a92a112bc | ||
|
2d1c9693a1 | ||
|
e505139f98 | ||
|
b801ddbf48 | ||
|
de31760fd8 | ||
|
cc93ddb306 | ||
|
647131ab8e | ||
|
a0c0398ca9 | ||
|
4398a8861c | ||
|
14aba75938 | ||
|
1e33a8821c | ||
|
e04981356b | ||
|
e46bd73a32 | ||
|
c71fa155d5 | ||
|
94ba751848 | ||
|
f601dc17fb | ||
|
6052643b9f | ||
|
b3e7c1498d | ||
|
9c465e37ca | ||
|
1d7b45e1e7 | ||
|
dc20b249e7 | ||
|
a8f047c78a | ||
|
54be9c3bf1 | ||
|
c1d7edd162 | ||
|
a1ea71e1c3 | ||
|
f7611613af | ||
|
a53b061baa | ||
|
768f45641e | ||
|
7336771f87 | ||
|
79c1c4e4f4 | ||
|
8ee26388c0 | ||
|
ad70ff96fa | ||
|
226e38851f | ||
|
186d673517 | ||
|
8b6186500a | ||
|
c47265bb71 | ||
|
ef917f257d | ||
|
01f4efc4e0 | ||
|
d73531aeb5 | ||
|
6a9ca36bb4 | ||
|
d516de98f9 | ||
|
752b953a07 | ||
|
26fe302a5a | ||
|
adc03ba26d | ||
|
ff38cd4568 | ||
|
d8c05dbf57 | ||
|
d121e28d69 | ||
|
f23351b132 | ||
|
0a1cef77ad | ||
|
e23a7d7d66 | ||
|
6990a11e3a | ||
|
d641bbf32f | ||
|
e0ff981653 | ||
|
10d9172d26 | ||
|
a84c53bf6a | ||
|
a939fef59b | ||
|
8ff30b7dec | ||
|
95b99a1129 | ||
|
c6b57a22d1 | ||
|
fa4b8f3805 | ||
|
847b1fe757 | ||
|
e7d3c803bb | ||
|
4beb08e587 | ||
|
cb6df68d8a | ||
|
888705cd23 | ||
|
859421df9a | ||
|
6d50c200ba | ||
|
c69ab200d0 | ||
|
5950acd73c | ||
|
ff5c201880 | ||
|
b8cc4d9264 | ||
|
b4a6825db7 | ||
|
3889e7ff24 | ||
|
f625d9671c | ||
|
eaaa54bf11 | ||
|
37ae52ba0a | ||
|
6bcd5e8d81 | ||
|
59ea615b7b | ||
|
3f3580f9c3 | ||
|
8861fb9d95 | ||
|
4b163e1b80 | ||
|
f503b73dc4 | ||
|
01fb243751 | ||
|
0d17c10b70 | ||
|
5fa69a0504 | ||
|
c56aceaef5 | ||
|
4b7dbcd478 | ||
|
928a23e2c8 | ||
|
f5c1b06807 | ||
|
06cfe9d48b | ||
|
8fe99a9c45 | ||
|
2d87ad0fe1 | ||
|
ca0dd69752 | ||
|
08153a3071 | ||
|
493e6066b3 | ||
|
16b104edec | ||
|
72a1418b7d | ||
|
4be579f7a2 | ||
|
9b544ef535 | ||
|
d6777b881b | ||
|
f9d713f9b4 | ||
|
0d45e7f186 | ||
|
480fcc5ecd | ||
|
a5a13501e2 | ||
|
fad6f576ee | ||
|
b2a996e047 | ||
|
e1c7e8a5bd | ||
|
f8793d2439 | ||
|
87d269fedb | ||
|
ae2c62fad1 | ||
|
6952c4b32c | ||
|
ce54848c31 | ||
|
83fc7d08bc | ||
|
a1ec98376b | ||
|
313f0af195 | ||
|
068d2f1a32 | ||
|
d99c209f7d | ||
|
fd0d78e0a8 | ||
|
57ef437d25 | ||
|
b6f7179b59 | ||
|
3e3ef87b79 | ||
|
d32381d18d | ||
|
3e6a47818e | ||
|
b289a6e846 | ||
|
bc1d3f6f7f | ||
|
18004189b3 | ||
|
40c76339b6 | ||
|
d53028098f | ||
|
38f0bec705 | ||
|
5d43f463cf | ||
|
eb08b7af27 | ||
|
80d1c266f6 | ||
|
b7f5fb58d4 | ||
|
bcaaf28d61 | ||
|
e3103c073a | ||
|
b4ca14618a | ||
|
0e01a00aaf | ||
|
4306a1a4a3 | ||
|
df7e97a941 | ||
|
ea41c41c7c | ||
|
7a364db571 | ||
|
cc0d445601 | ||
|
310a1f8cfd | ||
|
75eb7ca356 | ||
|
fb35704d5f | ||
|
07c6557fc5 | ||
|
a8221d4515 | ||
|
9824a6ba3c | ||
|
abaeb374e8 | ||
|
9d3c0a8c29 | ||
|
bb723bdb40 | ||
|
ee0496af65 | ||
|
af5cb3dd76 | ||
|
04818b00e5 | ||
|
cfe6cbc134 | ||
|
6f85aed33c | ||
|
afb8536b41 |
126 changed files with 11115 additions and 5463 deletions
47
.github/workflows/ci.yml
vendored
Normal file
47
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- "*.*"
|
||||
- master
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: PHPUnit PHP ${{ matrix.php-version }} ${{ matrix.dependency }} (Symfony ${{ matrix.symfony-version }})
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- '8.1'
|
||||
- '8.2'
|
||||
- '8.3'
|
||||
symfony-version:
|
||||
- '5.4.*'
|
||||
- '6.4.*'
|
||||
coverage: [ 'none' ]
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
|
||||
- name: Configure Symfony
|
||||
run: composer config extra.symfony.require "${{ matrix.symfony-version }}"
|
||||
|
||||
- name: Update project dependencies
|
||||
run: composer update --no-progress --ansi --prefer-stable
|
||||
|
||||
- name: Validate composer
|
||||
run: composer validate --strict --no-check-lock
|
||||
|
||||
- name: "Run code-style check"
|
||||
run: vendor/bin/php-cs-fixer fix --dry-run --config=.php-cs-fixer.dist.php --using-cache=no --show-progress=none -v
|
||||
|
||||
- name: Run tests
|
||||
run: vendor/bin/phpunit
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
.idea
|
||||
.phpunit.result.cache
|
||||
|
|
11
.php-cs-fixer.dist.php
Normal file
11
.php-cs-fixer.dist.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
return Retailcrm\PhpCsFixer\Defaults::rules()
|
||||
->setFinder($finder)
|
||||
->setCacheFile(__DIR__ . '/.php_cs.cache');
|
29
.travis.yml
29
.travis.yml
|
@ -1,29 +0,0 @@
|
|||
language: php
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.5
|
||||
env: SYMFONY_VERSION='2.1.*'
|
||||
- php: 5.5
|
||||
env: SYMFONY_VERSION='2.2.*'
|
||||
- php: 5.5
|
||||
env: SYMFONY_VERSION='2.3.*'
|
||||
- php: 5.5
|
||||
env: SYMFONY_VERSION='2.4.*'
|
||||
- php: 5.5
|
||||
env: SYMFONY_VERSION='dev-master'
|
||||
allow_failures:
|
||||
- env: SYMFONY_VERSION=dev-master
|
||||
|
||||
before_script:
|
||||
- composer self-update
|
||||
- sh -c 'if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/symfony=$SYMFONY_VERSION; fi;'
|
||||
- composer install
|
||||
|
||||
script: phpunit --coverage-text
|
|
@ -1,616 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Annotation;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
class ApiDoc
|
||||
{
|
||||
/**
|
||||
* Requirements are mandatory parameters in a route.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $requirements = array();
|
||||
|
||||
/**
|
||||
* Filters are optional parameters in the query string.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $filters = array();
|
||||
|
||||
/**
|
||||
* Parameters are data a client can send.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $parameters = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $input = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $output = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $link = null;
|
||||
|
||||
/**
|
||||
* Most of the time, a single line of text describing the action.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $description = null;
|
||||
|
||||
/**
|
||||
* Section to group actions together.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $section = null;
|
||||
|
||||
/**
|
||||
* Extended documentation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $documentation = null;
|
||||
|
||||
/**
|
||||
* @var Boolean
|
||||
*/
|
||||
private $resource = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $host;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $response = array();
|
||||
|
||||
/**
|
||||
* @var Route
|
||||
*/
|
||||
private $route;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $https = false;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $authentication = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $authenticationRoles = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $deprecated = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $statusCodes = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $tags = array();
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->resource = !empty($data['resource']) ? $data['resource'] : false;
|
||||
|
||||
if (isset($data['description'])) {
|
||||
$this->description = $data['description'];
|
||||
}
|
||||
|
||||
if (isset($data['input'])) {
|
||||
$this->input = $data['input'];
|
||||
} elseif (isset($data['filters'])) {
|
||||
foreach ($data['filters'] as $filter) {
|
||||
if (!isset($filter['name'])) {
|
||||
throw new \InvalidArgumentException('A "filter" element has to contain a "name" attribute');
|
||||
}
|
||||
|
||||
$name = $filter['name'];
|
||||
unset($filter['name']);
|
||||
|
||||
$this->addFilter($name, $filter);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['requirements'])) {
|
||||
foreach ($data['requirements'] as $requirement) {
|
||||
if (!isset($requirement['name'])) {
|
||||
throw new \InvalidArgumentException('A "requirement" element has to contain a "name" attribute');
|
||||
}
|
||||
|
||||
$name = $requirement['name'];
|
||||
unset($requirement['name']);
|
||||
|
||||
$this->addRequirement($name, $requirement);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['parameters'])) {
|
||||
foreach ($data['parameters'] as $parameter) {
|
||||
if (!isset($parameter['name'])) {
|
||||
throw new \InvalidArgumentException('A "parameter" element has to contain a "name" attribute');
|
||||
}
|
||||
|
||||
if (!isset($parameter['dataType'])) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'"%s" parameter element has to contain a "dataType" attribute',
|
||||
$parameter['name']
|
||||
));
|
||||
}
|
||||
|
||||
$name = $parameter['name'];
|
||||
unset($parameter['name']);
|
||||
|
||||
$this->addParameter($name, $parameter);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['output'])) {
|
||||
$this->output = $data['output'];
|
||||
}
|
||||
|
||||
if (isset($data['statusCodes'])) {
|
||||
foreach ($data['statusCodes'] as $statusCode => $description) {
|
||||
$this->addStatusCode($statusCode, $description);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['authentication'])) {
|
||||
$this->setAuthentication((bool) $data['authentication']);
|
||||
}
|
||||
|
||||
if (isset($data['authenticationRoles'])) {
|
||||
foreach ($data['authenticationRoles'] as $key => $role) {
|
||||
$this->authenticationRoles[] = $role;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['cache'])) {
|
||||
$this->setCache($data['cache']);
|
||||
}
|
||||
|
||||
if (isset($data['section'])) {
|
||||
$this->section = $data['section'];
|
||||
}
|
||||
|
||||
if (isset($data['deprecated'])) {
|
||||
$this->deprecated = $data['deprecated'];
|
||||
}
|
||||
|
||||
if (isset($data['tags'])) {
|
||||
$tags = $data['tags'];
|
||||
|
||||
if (!is_array($tags)) {
|
||||
$tags = array($tags);
|
||||
}
|
||||
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
if (isset($data['https'])) {
|
||||
$this->https = $data['https'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $filter
|
||||
*/
|
||||
public function addFilter($name, array $filter)
|
||||
{
|
||||
$this->filters[$name] = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $statusCode
|
||||
* @param mixed $description
|
||||
*/
|
||||
public function addStatusCode($statusCode, $description)
|
||||
{
|
||||
$this->statusCodes[$statusCode] = !is_array($description) ? array($description) : $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $requirement
|
||||
*/
|
||||
public function addRequirement($name, array $requirement)
|
||||
{
|
||||
$this->requirements[$name] = $requirement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $requirements
|
||||
*/
|
||||
public function setRequirements(array $requirements)
|
||||
{
|
||||
$this->requirements = array_merge($this->requirements, $requirements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getInput()
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $link
|
||||
*/
|
||||
public function setLink($link)
|
||||
{
|
||||
$this->link = $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $section
|
||||
*/
|
||||
public function setSection($section)
|
||||
{
|
||||
$this->section = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSection()
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $documentation
|
||||
*/
|
||||
public function setDocumentation($documentation)
|
||||
{
|
||||
$this->documentation = $documentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDocumentation()
|
||||
{
|
||||
return $this->documentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isResource()
|
||||
{
|
||||
return (bool) $this->resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->resource && is_string($this->resource) ? $this->resource : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $parameter
|
||||
*/
|
||||
public function addParameter($name, array $parameter)
|
||||
{
|
||||
$this->parameters[$name] = $parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
*/
|
||||
public function setParameters(array $parameters)
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the response data as processed by the parsers - same format as parameters
|
||||
*
|
||||
* @param array $response
|
||||
*/
|
||||
public function setResponse(array $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Route $route
|
||||
*/
|
||||
public function setRoute(Route $route)
|
||||
{
|
||||
$this->route = $route;
|
||||
|
||||
if (method_exists($route, 'getHost')) {
|
||||
$this->host = $route->getHost() ? : null;
|
||||
} else {
|
||||
$this->host = null;
|
||||
}
|
||||
|
||||
$this->uri = $route->getPattern();
|
||||
$this->method = $route->getRequirement('_method') ?: 'ANY';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Route
|
||||
*/
|
||||
public function getRoute()
|
||||
{
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
*/
|
||||
public function setHost($host)
|
||||
{
|
||||
$this->host = $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHttps()
|
||||
{
|
||||
return $this->https;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $https
|
||||
*/
|
||||
public function setHttps($https)
|
||||
{
|
||||
$this->https = $https;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getAuthentication()
|
||||
{
|
||||
return $this->authentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $authentication
|
||||
*/
|
||||
public function setAuthentication($authentication)
|
||||
{
|
||||
$this->authentication = $authentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAuthenticationRoles()
|
||||
{
|
||||
return $this->authenticationRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $authenticationRoles
|
||||
*/
|
||||
public function setAuthenticationRoles($authenticationRoles)
|
||||
{
|
||||
$this->authenticationRoles = $authenticationRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $cache
|
||||
*/
|
||||
public function setCache($cache)
|
||||
{
|
||||
$this->cache = (int) $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getDeprecated()
|
||||
{
|
||||
return $this->deprecated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRequirements()
|
||||
{
|
||||
return $this->requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $deprecated
|
||||
*/
|
||||
public function setDeprecated($deprecated)
|
||||
{
|
||||
$this->deprecated = (bool) $deprecated;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$data = array(
|
||||
'method' => $this->method,
|
||||
'uri' => $this->uri,
|
||||
);
|
||||
|
||||
if ($host = $this->host) {
|
||||
$data['host'] = $host;
|
||||
}
|
||||
|
||||
if ($description = $this->description) {
|
||||
$data['description'] = $description;
|
||||
}
|
||||
|
||||
if ($link = $this->link) {
|
||||
$data['link'] = $link;
|
||||
}
|
||||
|
||||
if ($documentation = $this->documentation) {
|
||||
$data['documentation'] = $documentation;
|
||||
}
|
||||
|
||||
if ($filters = $this->filters) {
|
||||
$data['filters'] = $filters;
|
||||
}
|
||||
|
||||
if ($parameters = $this->parameters) {
|
||||
$data['parameters'] = $parameters;
|
||||
}
|
||||
|
||||
if ($requirements = $this->requirements) {
|
||||
$data['requirements'] = $requirements;
|
||||
}
|
||||
|
||||
if ($response = $this->response) {
|
||||
$data['response'] = $response;
|
||||
}
|
||||
|
||||
if ($statusCodes = $this->statusCodes) {
|
||||
$data['statusCodes'] = $statusCodes;
|
||||
}
|
||||
|
||||
if ($section = $this->section) {
|
||||
$data['section'] = $section;
|
||||
}
|
||||
|
||||
if ($cache = $this->cache) {
|
||||
$data['cache'] = $cache;
|
||||
}
|
||||
|
||||
if ($tags = $this->tags) {
|
||||
$data['tags'] = $tags;
|
||||
}
|
||||
|
||||
$data['https'] = $this->https;
|
||||
$data['authentication'] = $this->authentication;
|
||||
$data['authenticationRoles'] = $this->authenticationRoles;
|
||||
$data['deprecated'] = $this->deprecated;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
500
Attribute/ApiDoc.php
Normal file
500
Attribute/ApiDoc.php
Normal file
|
@ -0,0 +1,500 @@
|
|||
<?php
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Attribute;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
class ApiDoc
|
||||
{
|
||||
public const DEFAULT_VIEW = 'default';
|
||||
|
||||
/**
|
||||
* Requirements are mandatory parameters in a route.
|
||||
*
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
private array $requirements = [];
|
||||
|
||||
/**
|
||||
* Which views is this route used. Defaults to "Default"
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private array $views = [];
|
||||
|
||||
/**
|
||||
* Filters are optional parameters in the query string.
|
||||
*
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
private array $filters = [];
|
||||
|
||||
/**
|
||||
* Parameters are data a client can send.
|
||||
*
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
private array $parameters = [];
|
||||
/**
|
||||
* Headers that client can send.
|
||||
*
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
private array $headers = [];
|
||||
|
||||
private ?string $link = null;
|
||||
|
||||
/**
|
||||
* Extended documentation.
|
||||
*/
|
||||
private ?string $documentation = null;
|
||||
|
||||
private Route $route;
|
||||
private ?string $host = null;
|
||||
private string $method;
|
||||
private string $uri;
|
||||
|
||||
private array $response = [];
|
||||
|
||||
/**
|
||||
* @var array<int|string, string[]>
|
||||
*/
|
||||
private array $statusCodes = [];
|
||||
|
||||
/**
|
||||
* @var array<int, array<mixed>>
|
||||
*/
|
||||
private array $responseMap = [];
|
||||
|
||||
private array $parsedResponseMap = [];
|
||||
|
||||
/**
|
||||
* @var array<string|int, string>
|
||||
*/
|
||||
private array $tags = [];
|
||||
|
||||
private ?string $scope = null;
|
||||
|
||||
/**
|
||||
* @param string[]|string|null $description
|
||||
*/
|
||||
public function __construct(
|
||||
private string|bool $resource = false,
|
||||
private array|string|null $description = null,
|
||||
private string|array|null $input = null,
|
||||
private ?array $inputs = null,
|
||||
private string|array|null $output = null,
|
||||
private ?string $section = null,
|
||||
private bool $deprecated = false,
|
||||
private ?string $resourceDescription = null,
|
||||
?array $filters = null,
|
||||
?array $requirements = null,
|
||||
array|string|null $views = null,
|
||||
?array $parameters = null,
|
||||
?array $headers = null,
|
||||
?array $statusCodes = null,
|
||||
array|string|int|null $tags = null,
|
||||
?array $responseMap = null,
|
||||
) {
|
||||
if (null !== $filters) {
|
||||
foreach ($filters as $filter) {
|
||||
if (!isset($filter['name'])) {
|
||||
throw new \InvalidArgumentException('A "filter" element has to contain a "name" attribute');
|
||||
}
|
||||
|
||||
$name = $filter['name'];
|
||||
unset($filter['name']);
|
||||
|
||||
$this->addFilter($name, $filter);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $requirements) {
|
||||
foreach ($requirements as $requirement) {
|
||||
if (!isset($requirement['name'])) {
|
||||
throw new \InvalidArgumentException('A "requirement" element has to contain a "name" attribute');
|
||||
}
|
||||
|
||||
$name = $requirement['name'];
|
||||
unset($requirement['name']);
|
||||
|
||||
$this->addRequirement($name, $requirement);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $views) {
|
||||
if (!is_array($views)) {
|
||||
$views = [$views];
|
||||
}
|
||||
|
||||
foreach ($views as $view) {
|
||||
$this->addView($view);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $parameters) {
|
||||
foreach ($parameters as $parameter) {
|
||||
if (!isset($parameter['name'])) {
|
||||
throw new \InvalidArgumentException('A "parameter" element has to contain a "name" attribute');
|
||||
}
|
||||
|
||||
if (!isset($parameter['dataType'])) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'"%s" parameter element has to contain a "dataType" attribute',
|
||||
$parameter['name']
|
||||
));
|
||||
}
|
||||
|
||||
$name = $parameter['name'];
|
||||
unset($parameter['name']);
|
||||
|
||||
$this->addParameter($name, $parameter);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $headers) {
|
||||
foreach ($headers as $header) {
|
||||
if (!isset($header['name'])) {
|
||||
throw new \InvalidArgumentException('A "header" element has to contain a "name" attribute');
|
||||
}
|
||||
|
||||
$name = $header['name'];
|
||||
unset($header['name']);
|
||||
|
||||
$this->addHeader($name, $header);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $statusCodes) {
|
||||
foreach ($statusCodes as $statusCode => $statusDescription) {
|
||||
$this->addStatusCode($statusCode, $statusDescription);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $tags) {
|
||||
if (is_array($tags)) {
|
||||
foreach ($tags as $tag => $colorCode) {
|
||||
if (is_numeric($tag)) {
|
||||
$this->addTag($colorCode);
|
||||
} else {
|
||||
$this->addTag($tag, $colorCode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->tags[] = $tags;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $responseMap) {
|
||||
$this->responseMap = $responseMap;
|
||||
if (isset($this->responseMap[200])) {
|
||||
$this->output = $this->responseMap[200];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addFilter(string $name, array $filter): void
|
||||
{
|
||||
$this->filters[$name] = $filter;
|
||||
}
|
||||
|
||||
public function addStatusCode(int|string $statusCode, string|array $description): void
|
||||
{
|
||||
$this->statusCodes[$statusCode] = !is_array($description) ? [$description] : $description;
|
||||
}
|
||||
|
||||
public function addTag(int|string $tag, string $colorCode = '#d9534f'): void
|
||||
{
|
||||
$this->tags[$tag] = $colorCode;
|
||||
}
|
||||
|
||||
public function addRequirement(string $name, array $requirement): void
|
||||
{
|
||||
$this->requirements[$name] = $requirement;
|
||||
}
|
||||
|
||||
public function setRequirements(array $requirements): void
|
||||
{
|
||||
$this->requirements = array_merge($this->requirements, $requirements);
|
||||
}
|
||||
|
||||
public function getInput(): string|array|null
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
public function getInputs(): ?array
|
||||
{
|
||||
return $this->inputs;
|
||||
}
|
||||
|
||||
public function getOutput(): array|string|null
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]|string|null
|
||||
*/
|
||||
public function getDescription(): array|string|null
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|string|null $description
|
||||
*/
|
||||
public function setDescription(array|string|null $description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public function setLink(?string $link): void
|
||||
{
|
||||
$this->link = $link;
|
||||
}
|
||||
|
||||
public function getSection(): ?string
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
public function addView(string $view): void
|
||||
{
|
||||
$this->views[] = $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getViews(): array
|
||||
{
|
||||
return $this->views;
|
||||
}
|
||||
|
||||
public function setDocumentation(?string $documentation): void
|
||||
{
|
||||
$this->documentation = $documentation;
|
||||
}
|
||||
|
||||
public function getDocumentation(): ?string
|
||||
{
|
||||
return $this->documentation;
|
||||
}
|
||||
|
||||
public function isResource(): bool
|
||||
{
|
||||
return (bool) $this->resource;
|
||||
}
|
||||
|
||||
public function getResource(): string|bool
|
||||
{
|
||||
return $this->resource && is_string($this->resource) ? $this->resource : false;
|
||||
}
|
||||
|
||||
public function addParameter(string $name, array $parameter): void
|
||||
{
|
||||
$this->parameters[$name] = $parameter;
|
||||
}
|
||||
|
||||
public function setParameters(array $parameters): void
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
public function addHeader($name, array $header): void
|
||||
{
|
||||
$this->headers[$name] = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the response data as processed by the parsers - same format as parameters
|
||||
*/
|
||||
public function setResponse(array $response): void
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function setRoute(Route $route): void
|
||||
{
|
||||
$this->route = $route;
|
||||
|
||||
if (method_exists($route, 'getHost')) {
|
||||
$this->host = $route->getHost() ?: null;
|
||||
|
||||
// replace route placeholders
|
||||
foreach ($route->getDefaults() as $key => $value) {
|
||||
if (null !== $this->host && is_string($value)) {
|
||||
$this->host = str_replace('{' . $key . '}', $value, $this->host);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->host = null;
|
||||
}
|
||||
|
||||
$this->uri = $route->getPath();
|
||||
$this->method = $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY';
|
||||
}
|
||||
|
||||
public function getRoute(): Route
|
||||
{
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
public function getHost(): ?string
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function getDeprecated(): bool
|
||||
{
|
||||
return $this->deprecated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, string>>
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
public function getRequirements(): array
|
||||
{
|
||||
return $this->requirements;
|
||||
}
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function setDeprecated(bool $deprecated): void
|
||||
{
|
||||
$this->deprecated = $deprecated;
|
||||
}
|
||||
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function setScope(string $scope): void
|
||||
{
|
||||
$this->scope = $scope;
|
||||
}
|
||||
|
||||
public function getScope(): ?string
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$data = [
|
||||
'method' => $this->method ?? null,
|
||||
'uri' => $this->uri ?? null,
|
||||
];
|
||||
|
||||
if ($host = $this->host) {
|
||||
$data['host'] = $host;
|
||||
}
|
||||
|
||||
if ($description = $this->description) {
|
||||
$data['description'] = $description;
|
||||
}
|
||||
|
||||
if ($link = $this->link) {
|
||||
$data['link'] = $link;
|
||||
}
|
||||
|
||||
if ($documentation = $this->documentation) {
|
||||
$data['documentation'] = $documentation;
|
||||
}
|
||||
|
||||
if ($filters = $this->filters) {
|
||||
$data['filters'] = $filters;
|
||||
}
|
||||
|
||||
if ($parameters = $this->parameters) {
|
||||
$data['parameters'] = $parameters;
|
||||
}
|
||||
|
||||
if ($headers = $this->headers) {
|
||||
$data['headers'] = $headers;
|
||||
}
|
||||
|
||||
if ($requirements = $this->requirements) {
|
||||
$data['requirements'] = $requirements;
|
||||
}
|
||||
|
||||
if ($views = $this->views) {
|
||||
$data['views'] = $views;
|
||||
}
|
||||
|
||||
if ($response = $this->response) {
|
||||
$data['response'] = $response;
|
||||
}
|
||||
|
||||
if ($parsedResponseMap = $this->parsedResponseMap) {
|
||||
$data['parsedResponseMap'] = $parsedResponseMap;
|
||||
}
|
||||
|
||||
if ($statusCodes = $this->statusCodes) {
|
||||
$data['statusCodes'] = $statusCodes;
|
||||
}
|
||||
|
||||
if ($section = $this->section) {
|
||||
$data['section'] = $section;
|
||||
}
|
||||
|
||||
if ($tags = $this->tags) {
|
||||
$data['tags'] = $tags;
|
||||
}
|
||||
|
||||
if ($resourceDescription = $this->resourceDescription) {
|
||||
$data['resourceDescription'] = $resourceDescription;
|
||||
}
|
||||
|
||||
$data['deprecated'] = $this->deprecated;
|
||||
$data['scope'] = $this->scope;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getResourceDescription(): ?string
|
||||
{
|
||||
return $this->resourceDescription;
|
||||
}
|
||||
|
||||
public function getResponseMap(): array
|
||||
{
|
||||
if (!isset($this->responseMap[200]) && null !== $this->output) {
|
||||
$this->responseMap[200] = $this->output;
|
||||
}
|
||||
|
||||
return $this->responseMap;
|
||||
}
|
||||
|
||||
public function getParsedResponseMap(): array
|
||||
{
|
||||
return $this->parsedResponseMap;
|
||||
}
|
||||
|
||||
public function setResponseForStatusCode(array $model, array $type, int $statusCode = 200): void
|
||||
{
|
||||
$this->parsedResponseMap[$statusCode] = ['type' => $type, 'model' => $model];
|
||||
if (200 === $statusCode && $this->response !== $model) {
|
||||
$this->response = $model;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,64 +11,91 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Command;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
|
||||
use Nelmio\ApiDocBundle\Formatter\HtmlFormatter;
|
||||
use Nelmio\ApiDocBundle\Formatter\MarkdownFormatter;
|
||||
use Nelmio\ApiDocBundle\Formatter\SimpleFormatter;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class DumpCommand extends ContainerAwareCommand
|
||||
#[AsCommand(
|
||||
name: 'api:doc:dump',
|
||||
description: 'Dumps API documentation in various formats',
|
||||
)]
|
||||
class DumpCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $availableFormats = array('markdown', 'json', 'html');
|
||||
private const AVAILABLE_FORMATS = ['markdown', 'json', 'html'];
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription('')
|
||||
->addOption(
|
||||
'format', '', InputOption::VALUE_REQUIRED,
|
||||
'Output format like: ' . implode(', ', $this->availableFormats),
|
||||
$this->availableFormats[0]
|
||||
)
|
||||
->addOption('no-sandbox', '', InputOption::VALUE_NONE)
|
||||
->setName('api:doc:dump')
|
||||
;
|
||||
/**
|
||||
* @param TranslatorInterface&LocaleAwareInterface $translator
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly SimpleFormatter $simpleFormatter,
|
||||
private readonly MarkdownFormatter $markdownFormatter,
|
||||
private readonly HtmlFormatter $htmlFormatter,
|
||||
private readonly ApiDocExtractor $apiDocExtractor,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addOption(
|
||||
'format', '', InputOption::VALUE_REQUIRED,
|
||||
'Output format like: ' . implode(', ', self::AVAILABLE_FORMATS),
|
||||
self::AVAILABLE_FORMATS[0]
|
||||
)
|
||||
->addOption('api-version', null, InputOption::VALUE_REQUIRED, 'The API version')
|
||||
->addOption('locale', null, InputOption::VALUE_REQUIRED, 'Locale for translation')
|
||||
->addOption('view', '', InputOption::VALUE_OPTIONAL, '', ApiDoc::DEFAULT_VIEW)
|
||||
->addOption('no-sandbox', '', InputOption::VALUE_NONE)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$format = $input->getOption('format');
|
||||
$routeCollection = $this->getContainer()->get('router')->getRouteCollection();
|
||||
$view = $input->getOption('view');
|
||||
|
||||
if (!$input->hasOption('format') || in_array($format, array('json'))) {
|
||||
$formatter = $this->getContainer()->get('nelmio_api_doc.formatter.simple_formatter');
|
||||
} else {
|
||||
if (!in_array($format, $this->availableFormats)) {
|
||||
throw new \RuntimeException(sprintf('Format "%s" not supported.', $format));
|
||||
}
|
||||
$formatter = match ($format) {
|
||||
'json' => $this->simpleFormatter,
|
||||
'markdown' => $this->markdownFormatter,
|
||||
'html' => $this->htmlFormatter,
|
||||
default => throw new \RuntimeException(sprintf('Format "%s" not supported.', $format)),
|
||||
};
|
||||
|
||||
$formatter = $this->getContainer()->get(sprintf('nelmio_api_doc.formatter.%s_formatter', $format));
|
||||
if ($input->hasOption('locale')) {
|
||||
$this->translator->setLocale($input->getOption('locale') ?? '');
|
||||
}
|
||||
|
||||
if ($input->getOption('no-sandbox') && 'html' === $format) {
|
||||
if ($input->hasOption('api-version')) {
|
||||
$formatter->setVersion($input->getOption('api-version'));
|
||||
}
|
||||
|
||||
if ($formatter instanceof HtmlFormatter && $input->getOption('no-sandbox')) {
|
||||
$formatter->setEnableSandbox(false);
|
||||
}
|
||||
|
||||
if ('html' === $format) {
|
||||
$this->getContainer()->enterScope('request');
|
||||
$this->getContainer()->set('request', new Request(), 'request');
|
||||
}
|
||||
$extractedDoc = $input->hasOption('api-version') ?
|
||||
$this->apiDocExtractor->allForVersion($input->getOption('api-version'), $view) :
|
||||
$this->apiDocExtractor->all($view);
|
||||
|
||||
$extractedDoc = $this->getContainer()->get('nelmio_api_doc.extractor.api_doc_extractor')->all();
|
||||
$formattedDoc = $formatter->format($extractedDoc);
|
||||
|
||||
if ('json' === $format) {
|
||||
$output->writeln(json_encode($formattedDoc));
|
||||
$output->writeln(json_encode($formattedDoc, JSON_THROW_ON_ERROR));
|
||||
} else {
|
||||
$output->writeln($formattedDoc);
|
||||
$output->writeln($formattedDoc, OutputInterface::OUTPUT_RAW);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
164
Command/SwaggerDumpCommand.php
Normal file
164
Command/SwaggerDumpCommand.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Command;
|
||||
|
||||
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
|
||||
use Nelmio\ApiDocBundle\Formatter\SwaggerFormatter;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
/**
|
||||
* Console command to dump Swagger-compliant API definitions.
|
||||
*
|
||||
* @author Bez Hermoso <bez@activelamp.com>
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'api:swagger:dump',
|
||||
description: 'Dumps Swagger-compliant API definitions.',
|
||||
)]
|
||||
class SwaggerDumpCommand extends Command
|
||||
{
|
||||
private Filesystem $filesystem;
|
||||
|
||||
public function __construct(
|
||||
private readonly ApiDocExtractor $extractor,
|
||||
private readonly SwaggerFormatter $formatter,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->filesystem = new Filesystem();
|
||||
|
||||
$this
|
||||
->addOption('resource', 'r', InputOption::VALUE_OPTIONAL, 'A specific resource API declaration to dump.')
|
||||
->addOption('list-only', 'l', InputOption::VALUE_NONE, 'Dump resource list only.')
|
||||
->addOption('pretty', 'p', InputOption::VALUE_NONE, 'Dump as prettified JSON.')
|
||||
->addArgument('destination', InputArgument::OPTIONAL, 'Directory to dump JSON files in.', null)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if ($input->getOption('list-only') && $input->getOption('resource')) {
|
||||
throw new \RuntimeException('Cannot selectively dump a resource with the --list-only flag.');
|
||||
}
|
||||
|
||||
$apiDocs = $this->extractor->all();
|
||||
|
||||
if ($input->getOption('list-only')) {
|
||||
$data = $this->getResourceList($apiDocs);
|
||||
$this->dump($data, null, $input, $output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (false !== ($resource = $input->getOption('resource'))) {
|
||||
$data = $this->getApiDeclaration($apiDocs, $resource);
|
||||
if (0 === count($data['apis'])) {
|
||||
throw new \InvalidArgumentException(sprintf('Resource "%s" does not exist.', $resource));
|
||||
}
|
||||
$this->dump($data, $resource, $input, $output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If --list-only and --resource is not specified, dump everything.
|
||||
*/
|
||||
$data = $this->getResourceList($apiDocs);
|
||||
|
||||
if (!$input->getArguments('destination')) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>Resource list: </comment>');
|
||||
}
|
||||
|
||||
$this->dump($data, null, $input, $output, false);
|
||||
|
||||
foreach ($data['apis'] as $api) {
|
||||
$resource = substr($api['path'], 1);
|
||||
if (!$input->getArgument('destination')) {
|
||||
$output->writeln('');
|
||||
$output->writeln(sprintf('<comment>API declaration for <info>"%s"</info> resource: </comment>', $resource));
|
||||
}
|
||||
$data = $this->getApiDeclaration($apiDocs, $resource, $output);
|
||||
$this->dump($data, $resource, $input, $output, false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function dump(array $data, $resource, InputInterface $input, OutputInterface $output, $treatAsFile = true): void
|
||||
{
|
||||
$destination = $input->getArgument('destination');
|
||||
|
||||
$content = json_encode($data, $input->getOption('pretty') ? JSON_PRETTY_PRINT : 0);
|
||||
|
||||
if (!$destination) {
|
||||
$output->writeln($content);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (false === $treatAsFile) {
|
||||
if (!$this->filesystem->exists($destination)) {
|
||||
$this->filesystem->mkdir($destination);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$resource) {
|
||||
if (!$treatAsFile) {
|
||||
$destination = sprintf('%s/api-docs.json', rtrim($destination, '\\/'));
|
||||
}
|
||||
$message = sprintf('<comment>Dumping resource list to %s: </comment>', $destination);
|
||||
$this->writeToFile($content, $destination, $output, $message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (false === $treatAsFile) {
|
||||
$destination = sprintf('%s/%s.json', rtrim($destination, '\\/'), $resource);
|
||||
}
|
||||
|
||||
$message = sprintf('<comment>Dump API declaration to %s: </comment>', $destination);
|
||||
$this->writeToFile($content, $destination, $output, $message);
|
||||
}
|
||||
|
||||
protected function writeToFile($content, $file, OutputInterface $output, $message): void
|
||||
{
|
||||
try {
|
||||
$this->filesystem->dumpFile($file, $content);
|
||||
$message .= ' <info>OK</info>';
|
||||
} catch (IOException $e) {
|
||||
$message .= sprintf(' <error>NOT OK - %s</error>', $e->getMessage());
|
||||
}
|
||||
|
||||
$output->writeln($message);
|
||||
}
|
||||
|
||||
protected function getResourceList(array $data)
|
||||
{
|
||||
return $this->formatter->format($data);
|
||||
}
|
||||
|
||||
protected function getApiDeclaration(array $data, $resource)
|
||||
{
|
||||
return $this->formatter->format($data, '/' . $resource);
|
||||
}
|
||||
}
|
|
@ -11,16 +11,51 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
|
||||
use Nelmio\ApiDocBundle\Formatter\HtmlFormatter;
|
||||
use Nelmio\ApiDocBundle\Formatter\RequestAwareSwaggerFormatter;
|
||||
use Nelmio\ApiDocBundle\Formatter\SwaggerFormatter;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ApiDocController extends Controller
|
||||
class ApiDocController extends AbstractController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$extractedDoc = $this->get('nelmio_api_doc.extractor.api_doc_extractor')->all();
|
||||
$htmlContent = $this->get('nelmio_api_doc.formatter.html_formatter')->format($extractedDoc);
|
||||
public function __construct(
|
||||
private readonly ApiDocExtractor $extractor,
|
||||
private readonly HtmlFormatter $htmlFormatter,
|
||||
private readonly SwaggerFormatter $swaggerFormatter,
|
||||
) {
|
||||
}
|
||||
|
||||
return new Response($htmlContent, 200, array('Content-Type' => 'text/html'));
|
||||
public function index(Request $request, $view = ApiDoc::DEFAULT_VIEW)
|
||||
{
|
||||
$apiVersion = $request->query->get('_version', null);
|
||||
|
||||
if ($apiVersion) {
|
||||
$this->htmlFormatter->setVersion($apiVersion);
|
||||
$extractedDoc = $this->extractor->allForVersion($apiVersion, $view);
|
||||
} else {
|
||||
$extractedDoc = $this->extractor->all($view);
|
||||
}
|
||||
$htmlContent = $this->htmlFormatter->format($extractedDoc);
|
||||
|
||||
return new Response($htmlContent, 200, ['Content-Type' => 'text/html']);
|
||||
}
|
||||
|
||||
public function swagger(Request $request, $resource = null)
|
||||
{
|
||||
$docs = $this->extractor->all();
|
||||
$formatter = new RequestAwareSwaggerFormatter($request, $this->swaggerFormatter);
|
||||
|
||||
$spec = $formatter->format($docs, $resource ? '/' . $resource : null);
|
||||
|
||||
if (null !== $resource && 0 === count($spec['apis'])) {
|
||||
throw $this->createNotFoundException(sprintf('Cannot find resource "%s"', $resource));
|
||||
}
|
||||
|
||||
return new JsonResponse($spec);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,37 +18,38 @@ namespace Nelmio\ApiDocBundle;
|
|||
*/
|
||||
class DataTypes
|
||||
{
|
||||
const INTEGER = 'integer';
|
||||
public const INTEGER = 'integer';
|
||||
|
||||
const FLOAT = 'float';
|
||||
public const FLOAT = 'float';
|
||||
|
||||
const STRING = 'string';
|
||||
public const STRING = 'string';
|
||||
|
||||
const BOOLEAN = 'boolean';
|
||||
public const BOOLEAN = 'boolean';
|
||||
|
||||
const FILE = 'file';
|
||||
public const FILE = 'file';
|
||||
|
||||
const ENUM = 'choice';
|
||||
public const ENUM = 'choice';
|
||||
|
||||
const COLLECTION = 'collection';
|
||||
public const COLLECTION = 'collection';
|
||||
|
||||
const MODEL = 'model';
|
||||
public const MODEL = 'model';
|
||||
|
||||
const DATE = 'date';
|
||||
public const DATE = 'date';
|
||||
|
||||
const DATETIME = 'datetime';
|
||||
public const DATETIME = 'datetime';
|
||||
|
||||
const TIME = 'time';
|
||||
public const TIME = 'time';
|
||||
|
||||
/**
|
||||
* Returns true if the supplied `actualType` value is considered a primitive type. Returns false, otherwise.
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPrimitive($type)
|
||||
{
|
||||
return in_array(strtolower($type), array(
|
||||
return in_array(strtolower($type), [
|
||||
static::INTEGER,
|
||||
static::FLOAT,
|
||||
static::STRING,
|
||||
|
@ -58,6 +59,6 @@ class DataTypes
|
|||
static::DATETIME,
|
||||
static::TIME,
|
||||
static::ENUM,
|
||||
));
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,27 +16,35 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
|
|||
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
public function getConfigTreeBuilder()
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$treeBuilder = new TreeBuilder();
|
||||
$treeBuilder
|
||||
->root('nelmio_api_doc')
|
||||
$treeBuilder = new TreeBuilder('nelmio_api_doc');
|
||||
|
||||
if (method_exists($treeBuilder, 'getRootNode')) {
|
||||
$rootNode = $treeBuilder->getRootNode();
|
||||
} else {
|
||||
// symfony < 4.2 support
|
||||
$rootNode = $treeBuilder->root('nelmio_api_doc');
|
||||
}
|
||||
|
||||
$rootNode
|
||||
->children()
|
||||
->scalarNode('name')->defaultValue('API documentation')->end()
|
||||
->arrayNode('exclude_sections')
|
||||
->prototype('scalar')
|
||||
->end()
|
||||
->end()
|
||||
->booleanNode('default_sections_opened')->defaultTrue()->end()
|
||||
->arrayNode('motd')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('template')->defaultValue('NelmioApiDocBundle::Components/motd.html.twig')->end()
|
||||
->scalarNode('template')->defaultValue('@NelmioApiDoc/Components/motd.html.twig')->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('request_listener')
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($a) { return is_bool($a); })
|
||||
->then(function ($a) { return array('enabled' => $a); })
|
||||
->then(function ($a) { return ['enabled' => $a]; })
|
||||
->end()
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
|
@ -54,15 +62,15 @@ class Configuration implements ConfigurationInterface
|
|||
->addDefaultsIfNotSet()
|
||||
->beforeNormalization()
|
||||
->ifString()
|
||||
->then(function ($v) { return array('default_format' => $v); })
|
||||
->then(function ($v) { return ['default_format' => $v]; })
|
||||
->end()
|
||||
->children()
|
||||
->arrayNode('formats')
|
||||
->defaultValue(array('form', 'json'))
|
||||
->defaultValue(['form', 'json'])
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->enumNode('default_format')
|
||||
->values(array('form', 'json'))
|
||||
->values(['form', 'json'])
|
||||
->defaultValue('form')
|
||||
->end()
|
||||
->end()
|
||||
|
@ -71,14 +79,14 @@ class Configuration implements ConfigurationInterface
|
|||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->arrayNode('formats')
|
||||
->defaultValue(array(
|
||||
->defaultValue([
|
||||
'json' => 'application/json',
|
||||
'xml' => 'application/xml'
|
||||
))
|
||||
'xml' => 'application/xml',
|
||||
])
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->enumNode('method')
|
||||
->values(array('format_param', 'accept_header'))
|
||||
->values(['format_param', 'accept_header'])
|
||||
->defaultValue('format_param')
|
||||
->end()
|
||||
->scalarNode('default_format')->defaultValue('json')->end()
|
||||
|
@ -89,24 +97,24 @@ class Configuration implements ConfigurationInterface
|
|||
->scalarNode('delivery')
|
||||
->isRequired()
|
||||
->validate()
|
||||
->ifNotInArray(array('query', 'http', 'header'))
|
||||
->ifNotInArray(['query', 'http', 'header'])
|
||||
->thenInvalid("Unknown authentication delivery type '%s'.")
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('name')->isRequired()->end()
|
||||
->enumNode('type')
|
||||
->info('Required if http delivery is selected.')
|
||||
->values(array('basic', 'bearer'))
|
||||
->values(['basic', 'bearer'])
|
||||
->end()
|
||||
->booleanNode('custom_endpoint')->defaultFalse()->end()
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(function ($v) {
|
||||
return 'http' === $v['delivery'] && !$v['type'] ;
|
||||
return 'http' === $v['delivery'] && !$v['type'];
|
||||
})
|
||||
->thenInvalid('"type" is required when using http delivery.')
|
||||
->end()
|
||||
# http_basic BC
|
||||
// http_basic BC
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($v) {
|
||||
return 'http_basic' === $v['delivery'];
|
||||
|
@ -131,9 +139,38 @@ class Configuration implements ConfigurationInterface
|
|||
})
|
||||
->end()
|
||||
->end()
|
||||
->booleanNode('entity_to_choice')->defaultTrue()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
->arrayNode('swagger')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('model_naming_strategy')->defaultValue('dot_notation')->end()
|
||||
->scalarNode('api_base_path')->defaultValue('/api')->end()
|
||||
->scalarNode('swagger_version')->defaultValue('1.2')->end()
|
||||
->scalarNode('api_version')->defaultValue('0.1')->end()
|
||||
->arrayNode('info')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('title')->defaultValue('Symfony2')->end()
|
||||
->scalarNode('description')->defaultValue('My awesome Symfony2 app!')->end()
|
||||
->scalarNode('TermsOfServiceUrl')->defaultNull()->end()
|
||||
->scalarNode('contact')->defaultNull()->end()
|
||||
->scalarNode('license')->defaultNull()->end()
|
||||
->scalarNode('licenseUrl')->defaultNull()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('cache')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->booleanNode('enabled')->defaultFalse()->end()
|
||||
->scalarNode('file')->defaultValue('%kernel.cache_dir%/api-doc.cache')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
|
|
|
@ -11,24 +11,22 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class ExtractorHandlerCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$handlers = array();
|
||||
$handlers = [];
|
||||
foreach ($container->findTaggedServiceIds('nelmio_api_doc.extractor.handler') as $id => $attributes) {
|
||||
$handlers[] = new Reference($id);
|
||||
}
|
||||
|
||||
$container
|
||||
->getDefinition('nelmio_api_doc.extractor.api_doc_extractor')
|
||||
->replaceArgument(4, $handlers);
|
||||
->replaceArgument(2, $handlers)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
24
DependencyInjection/FormInfoParserCompilerPass.php
Normal file
24
DependencyInjection/FormInfoParserCompilerPass.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Nelmio\ApiDocBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class FormInfoParserCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public const TAG_NAME = 'nelmio_api_doc.extractor.form_info_parser';
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->has('nelmio_api_doc.parser.form_type_parser')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$formParser = $container->findDefinition('nelmio_api_doc.parser.form_type_parser');
|
||||
foreach ($container->findTaggedServiceIds(self::TAG_NAME) as $id => $tags) {
|
||||
$formParser->addMethodCall('addFormInfoParser', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
|
||||
/**
|
||||
* Loads parsers to extract information from different libraries.
|
||||
|
@ -14,9 +14,9 @@ use Symfony\Component\Config\FileLocator;
|
|||
*/
|
||||
class LoadExtractorParsersPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
|
||||
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
|
||||
|
||||
// forms may not be installed/enabled, if it is, load that config as well
|
||||
if ($container->hasDefinition('form.factory')) {
|
||||
|
|
|
@ -11,18 +11,18 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Nelmio\ApiDocBundle\Parser\FormInfoParser;
|
||||
use Symfony\Component\Config\Definition\Processor;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
|
||||
class NelmioApiDocExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$processor = new Processor();
|
||||
$configuration = new Configuration();
|
||||
|
@ -30,8 +30,9 @@ class NelmioApiDocExtension extends Extension
|
|||
|
||||
$container->setParameter('nelmio_api_doc.motd.template', $config['motd']['template']);
|
||||
$container->setParameter('nelmio_api_doc.exclude_sections', $config['exclude_sections']);
|
||||
$container->setParameter('nelmio_api_doc.default_sections_opened', $config['default_sections_opened']);
|
||||
$container->setParameter('nelmio_api_doc.api_name', $config['name']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.enabled', $config['sandbox']['enabled']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.enabled', $config['sandbox']['enabled']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.endpoint', $config['sandbox']['endpoint']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.accept_type', $config['sandbox']['accept_type']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.body_format.formats', $config['sandbox']['body_format']['formats']);
|
||||
|
@ -39,8 +40,15 @@ class NelmioApiDocExtension extends Extension
|
|||
$container->setParameter('nelmio_api_doc.sandbox.request_format.method', $config['sandbox']['request_format']['method']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.request_format.default_format', $config['sandbox']['request_format']['default_format']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.request_format.formats', $config['sandbox']['request_format']['formats']);
|
||||
$container->setParameter('nelmio_api_doc.sandbox.entity_to_choice', $config['sandbox']['entity_to_choice']);
|
||||
|
||||
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
|
||||
if (method_exists($container, 'registerForAutoconfiguration')) {
|
||||
$container->registerForAutoconfiguration(FormInfoParser::class)
|
||||
->addTag(FormInfoParserCompilerPass::TAG_NAME)
|
||||
;
|
||||
}
|
||||
|
||||
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
|
||||
$loader->load('formatters.xml');
|
||||
$loader->load('services.xml');
|
||||
|
||||
|
@ -57,6 +65,25 @@ class NelmioApiDocExtension extends Extension
|
|||
if (!interface_exists('\Symfony\Component\Validator\MetadataFactoryInterface')) {
|
||||
$container->setParameter('nelmio_api_doc.parser.validation_parser.class', 'Nelmio\ApiDocBundle\Parser\ValidationParserLegacy');
|
||||
}
|
||||
|
||||
$container->setParameter('nelmio_api_doc.swagger.base_path', $config['swagger']['api_base_path']);
|
||||
$container->setParameter('nelmio_api_doc.swagger.swagger_version', $config['swagger']['swagger_version']);
|
||||
$container->setParameter('nelmio_api_doc.swagger.api_version', $config['swagger']['api_version']);
|
||||
$container->setParameter('nelmio_api_doc.swagger.info', $config['swagger']['info']);
|
||||
$container->setParameter('nelmio_api_doc.swagger.model_naming_strategy', $config['swagger']['model_naming_strategy']);
|
||||
|
||||
if (true === $config['cache']['enabled']) {
|
||||
$arguments = $container->getDefinition('nelmio_api_doc.extractor.api_doc_extractor')->getArguments();
|
||||
$caching = new Definition('Nelmio\ApiDocBundle\Extractor\CachingApiDocExtractor');
|
||||
$arguments[] = $config['cache']['file'];
|
||||
$arguments[] = '%kernel.debug%';
|
||||
$caching->setArguments($arguments);
|
||||
$caching->setPublic(true);
|
||||
$container->setDefinition('nelmio_api_doc.extractor.api_doc_extractor', $caching);
|
||||
}
|
||||
|
||||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
|
||||
$loader->load('autowired.yaml');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class RegisterExtractorParsersPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (false === $container->hasDefinition('nelmio_api_doc.extractor.api_doc_extractor')) {
|
||||
return;
|
||||
|
@ -16,23 +16,23 @@ class RegisterExtractorParsersPass implements CompilerPassInterface
|
|||
|
||||
$definition = $container->getDefinition('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
|
||||
//find registered parsers and sort by priority
|
||||
$sortedParsers = array();
|
||||
// find registered parsers and sort by priority
|
||||
$sortedParsers = [];
|
||||
foreach ($container->findTaggedServiceIds('nelmio_api_doc.extractor.parser') as $id => $tagAttributes) {
|
||||
foreach ($tagAttributes as $attributes) {
|
||||
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
|
||||
$priority = $attributes['priority'] ?? 0;
|
||||
$sortedParsers[$priority][] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
//add parsers if any
|
||||
// add parsers if any
|
||||
if (!empty($sortedParsers)) {
|
||||
krsort($sortedParsers);
|
||||
$sortedParsers = call_user_func_array('array_merge', $sortedParsers);
|
||||
|
||||
//add method call for each registered parsers
|
||||
// add method call for each registered parsers
|
||||
foreach ($sortedParsers as $id) {
|
||||
$definition->addMethodCall('addParser', array(new Reference($id)));
|
||||
$definition->addMethodCall('addParser', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
48
DependencyInjection/SwaggerConfigCompilerPass.php
Normal file
48
DependencyInjection/SwaggerConfigCompilerPass.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Compiler pass that configures the SwaggerFormatter instance.
|
||||
*
|
||||
* @author Bez Hermoso <bez@activelamp.com>
|
||||
*/
|
||||
class SwaggerConfigCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* You can modify the container here before it is dumped to PHP code.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$formatter = $container->getDefinition('nelmio_api_doc.formatter.swagger_formatter');
|
||||
|
||||
$formatter->addMethodCall('setBasePath', [$container->getParameter('nelmio_api_doc.swagger.base_path')]);
|
||||
$formatter->addMethodCall('setApiVersion', [$container->getParameter('nelmio_api_doc.swagger.api_version')]);
|
||||
$formatter->addMethodCall('setSwaggerVersion', [$container->getParameter('nelmio_api_doc.swagger.swagger_version')]);
|
||||
$formatter->addMethodCall('setInfo', [$container->getParameter('nelmio_api_doc.swagger.info')]);
|
||||
|
||||
$authentication = $container->getParameter('nelmio_api_doc.sandbox.authentication');
|
||||
|
||||
$formatter->setArguments([
|
||||
$container->getParameter('nelmio_api_doc.swagger.model_naming_strategy'),
|
||||
]);
|
||||
|
||||
if (null !== $authentication) {
|
||||
$formatter->addMethodCall('setAuthenticationConfig', [$authentication]);
|
||||
}
|
||||
}
|
||||
}
|
6
Dockerfile
Normal file
6
Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
ARG PHP_IMAGE_TAG
|
||||
FROM php:${PHP_IMAGE_TAG}-cli-alpine
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /opt/test
|
|
@ -14,18 +14,18 @@ namespace Nelmio\ApiDocBundle\EventListener;
|
|||
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
|
||||
use Nelmio\ApiDocBundle\Formatter\FormatterInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
|
||||
class RequestListener
|
||||
{
|
||||
/**
|
||||
* @var \Nelmio\ApiDocBundle\Extractor\ApiDocExtractor
|
||||
* @var ApiDocExtractor
|
||||
*/
|
||||
protected $extractor;
|
||||
|
||||
/**
|
||||
* @var \Nelmio\ApiDocBundle\Formatter\FormatterInterface
|
||||
* @var FormatterInterface
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
|
@ -41,12 +41,9 @@ class RequestListener
|
|||
$this->parameter = $parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onKernelRequest(GetResponseEvent $event)
|
||||
public function onKernelRequest(RequestEvent $event): void
|
||||
{
|
||||
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
|
||||
if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -57,14 +54,14 @@ class RequestListener
|
|||
}
|
||||
|
||||
$controller = $request->attributes->get('_controller');
|
||||
$route = $request->attributes->get('_route');
|
||||
$route = $request->attributes->get('_route');
|
||||
|
||||
if (null !== $annotation = $this->extractor->get($controller, $route)) {
|
||||
$result = $this->formatter->formatOne($annotation);
|
||||
|
||||
$event->setResponse(new Response($result, 200, array(
|
||||
'Content-Type' => 'text/html'
|
||||
)));
|
||||
$event->setResponse(new Response($result, 200, [
|
||||
'Content-Type' => 'text/html',
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,59 +11,31 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Extractor;
|
||||
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
use Nelmio\ApiDocBundle\Parser\ParserInterface;
|
||||
use Nelmio\ApiDocBundle\Parser\PostParserInterface;
|
||||
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
|
||||
|
||||
class ApiDocExtractor
|
||||
{
|
||||
const ANNOTATION_CLASS = 'Nelmio\\ApiDocBundle\\Annotation\\ApiDoc';
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var RouterInterface
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* @var Reader
|
||||
*/
|
||||
protected $reader;
|
||||
|
||||
/**
|
||||
* @var DocCommentExtractor
|
||||
*/
|
||||
private $commentExtractor;
|
||||
|
||||
/**
|
||||
* @var ParserInterface[]
|
||||
*/
|
||||
protected $parsers = array();
|
||||
protected array $parsers = [];
|
||||
|
||||
/**
|
||||
* @var HandlerInterface[]
|
||||
* @param HandlerInterface[] $handlers
|
||||
* @param string[] $excludeSections
|
||||
*/
|
||||
protected $handlers;
|
||||
|
||||
public function __construct(ContainerInterface $container, RouterInterface $router, Reader $reader, DocCommentExtractor $commentExtractor, array $handlers)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->router = $router;
|
||||
$this->reader = $reader;
|
||||
$this->commentExtractor = $commentExtractor;
|
||||
$this->handlers = $handlers;
|
||||
public function __construct(
|
||||
protected RouterInterface $router,
|
||||
protected DocCommentExtractor $commentExtractor,
|
||||
protected array $handlers,
|
||||
protected array $excludeSections,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,19 +45,39 @@ class ApiDocExtractor
|
|||
*
|
||||
* @return Route[] An array of routes
|
||||
*/
|
||||
public function getRoutes()
|
||||
public function getRoutes(): array
|
||||
{
|
||||
return $this->router->getRouteCollection()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Extracts annotations from all known routes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function all()
|
||||
public function all($view = ApiDoc::DEFAULT_VIEW): array
|
||||
{
|
||||
return $this->extractAnnotations($this->getRoutes());
|
||||
return $this->extractAnnotations($this->getRoutes(), $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts annotations from routes for specific version
|
||||
*
|
||||
* @param string $apiVersion API version
|
||||
* @param string $view
|
||||
*/
|
||||
public function allForVersion($apiVersion, $view = ApiDoc::DEFAULT_VIEW): array
|
||||
{
|
||||
$data = $this->all($view);
|
||||
foreach ($data as $k => $a) {
|
||||
// ignore other api version's routes
|
||||
if (
|
||||
$a['annotation']->getRoute()->getDefault('_version')
|
||||
&& !version_compare($apiVersion ?? '', $a['annotation']->getRoute()->getDefault('_version'), '=')
|
||||
) {
|
||||
unset($data[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,14 +86,11 @@ class ApiDocExtractor
|
|||
* - resource
|
||||
*
|
||||
* @param array $routes array of Route-objects for which the annotations should be extracted
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extractAnnotations(array $routes)
|
||||
public function extractAnnotations(array $routes, $view = ApiDoc::DEFAULT_VIEW): array
|
||||
{
|
||||
$array = array();
|
||||
$resources = array();
|
||||
$excludeSections = $this->container->getParameter('nelmio_api_doc.exclude_sections');
|
||||
$array = [];
|
||||
$resources = [];
|
||||
|
||||
foreach ($routes as $route) {
|
||||
if (!$route instanceof Route) {
|
||||
|
@ -109,18 +98,21 @@ class ApiDocExtractor
|
|||
}
|
||||
|
||||
if ($method = $this->getReflectionMethod($route->getDefault('_controller'))) {
|
||||
$annotation = $this->reader->getMethodAnnotation($method, self::ANNOTATION_CLASS);
|
||||
if ($annotation && !in_array($annotation->getSection(), $excludeSections)) {
|
||||
$annotation = $this->getMethodApiDoc($method);
|
||||
if (
|
||||
$annotation && !in_array($annotation->getSection(), $this->excludeSections)
|
||||
&& (in_array($view, $annotation->getViews()) || (0 === count($annotation->getViews()) && ApiDoc::DEFAULT_VIEW === $view))
|
||||
) {
|
||||
if ($annotation->isResource()) {
|
||||
if ($resource = $annotation->getResource()) {
|
||||
$resources[] = $resource;
|
||||
} else {
|
||||
// remove format from routes used for resource grouping
|
||||
$resources[] = str_replace('.{_format}', '', $route->getPattern());
|
||||
$resources[] = str_replace('.{_format}', '', $route->getPath() ?: '');
|
||||
}
|
||||
}
|
||||
|
||||
$array[] = array('annotation' => $this->extractData($annotation, $route, $method));
|
||||
$array[] = ['annotation' => $this->extractData($annotation, $route, $method)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,10 +120,10 @@ class ApiDocExtractor
|
|||
rsort($resources);
|
||||
foreach ($array as $index => $element) {
|
||||
$hasResource = false;
|
||||
$pattern = $element['annotation']->getRoute()->getPattern();
|
||||
$path = $element['annotation']->getRoute()->getPath() ?: '';
|
||||
|
||||
foreach ($resources as $resource) {
|
||||
if (0 === strpos($pattern, $resource) || $resource === $element['annotation']->getResource()) {
|
||||
if (str_starts_with($path, $resource) || $resource === $element['annotation']->getResource()) {
|
||||
$array[$index]['resource'] = $resource;
|
||||
|
||||
$hasResource = true;
|
||||
|
@ -144,17 +136,17 @@ class ApiDocExtractor
|
|||
}
|
||||
}
|
||||
|
||||
$methodOrder = array('GET', 'POST', 'PUT', 'DELETE');
|
||||
$methodOrder = ['GET', 'POST', 'PUT', 'DELETE'];
|
||||
usort($array, function ($a, $b) use ($methodOrder) {
|
||||
if ($a['resource'] === $b['resource']) {
|
||||
if ($a['annotation']->getRoute()->getPattern() === $b['annotation']->getRoute()->getPattern()) {
|
||||
$methodA = array_search($a['annotation']->getRoute()->getRequirement('_method'), $methodOrder);
|
||||
$methodB = array_search($b['annotation']->getRoute()->getRequirement('_method'), $methodOrder);
|
||||
if ($a['annotation']->getRoute()->getPath() === $b['annotation']->getRoute()->getPath()) {
|
||||
$methodA = array_search($a['annotation']->getRoute()->getMethods(), $methodOrder);
|
||||
$methodB = array_search($b['annotation']->getRoute()->getMethods(), $methodOrder);
|
||||
|
||||
if ($methodA === $methodB) {
|
||||
return strcmp(
|
||||
$a['annotation']->getRoute()->getRequirement('_method'),
|
||||
$b['annotation']->getRoute()->getRequirement('_method')
|
||||
implode('|', $a['annotation']->getRoute()->getMethods()),
|
||||
implode('|', $b['annotation']->getRoute()->getMethods())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -162,8 +154,8 @@ class ApiDocExtractor
|
|||
}
|
||||
|
||||
return strcmp(
|
||||
$a['annotation']->getRoute()->getPattern(),
|
||||
$b['annotation']->getRoute()->getPattern()
|
||||
$a['annotation']->getRoute()->getPath(),
|
||||
$b['annotation']->getRoute()->getPath()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -177,22 +169,18 @@ class ApiDocExtractor
|
|||
* Returns the ReflectionMethod for the given controller string.
|
||||
*
|
||||
* @param string $controller
|
||||
* @return \ReflectionMethod|null
|
||||
*
|
||||
* @return \ReflectionMethod|null
|
||||
*/
|
||||
public function getReflectionMethod($controller)
|
||||
{
|
||||
if (null === $controller) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('#(.+)::([\w]+)#', $controller, $matches)) {
|
||||
$class = $matches[1];
|
||||
$method = $matches[2];
|
||||
} elseif (preg_match('#(.+):([\w]+)#', $controller, $matches)) {
|
||||
$controller = $matches[1];
|
||||
$method = $matches[2];
|
||||
if ($this->container->has($controller)) {
|
||||
$this->container->enterScope('request');
|
||||
$this->container->set('request', new Request(), 'request');
|
||||
$class = ClassUtils::getRealClass(get_class($this->container->get($controller)));
|
||||
$this->container->leaveScope('request');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($class) && isset($method)) {
|
||||
|
@ -210,12 +198,13 @@ class ApiDocExtractor
|
|||
*
|
||||
* @param string $controller
|
||||
* @param string $route
|
||||
* @return ApiDoc|null
|
||||
*
|
||||
* @return ApiDoc|null
|
||||
*/
|
||||
public function get($controller, $route)
|
||||
{
|
||||
if ($method = $this->getReflectionMethod($controller)) {
|
||||
if ($annotation = $this->reader->getMethodAnnotation($method, self::ANNOTATION_CLASS)) {
|
||||
if ($annotation = $this->getMethodApiDoc($method)) {
|
||||
if ($route = $this->router->getRouteCollection()->get($route)) {
|
||||
return $this->extractData($annotation, $route, $method);
|
||||
}
|
||||
|
@ -225,12 +214,20 @@ class ApiDocExtractor
|
|||
return null;
|
||||
}
|
||||
|
||||
protected function getMethodApiDoc(\ReflectionMethod $method): ?ApiDoc
|
||||
{
|
||||
$attributes = $method->getAttributes(ApiDoc::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
if (!$attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $attributes[0]->newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a class parser to use for parsing input class metadata
|
||||
*
|
||||
* @param ParserInterface $parser
|
||||
*/
|
||||
public function addParser(ParserInterface $parser)
|
||||
public function addParser(ParserInterface $parser): void
|
||||
{
|
||||
$this->parsers[] = $parser;
|
||||
}
|
||||
|
@ -238,9 +235,6 @@ class ApiDocExtractor
|
|||
/**
|
||||
* Returns a new ApiDoc instance with more data.
|
||||
*
|
||||
* @param ApiDoc $annotation
|
||||
* @param Route $route
|
||||
* @param \ReflectionMethod $method
|
||||
* @return ApiDoc
|
||||
*/
|
||||
protected function extractData(ApiDoc $annotation, Route $route, \ReflectionMethod $method)
|
||||
|
@ -257,45 +251,56 @@ class ApiDocExtractor
|
|||
// route
|
||||
$annotation->setRoute($route);
|
||||
|
||||
$inputs = [];
|
||||
if (null !== $annotation->getInputs()) {
|
||||
$inputs = $annotation->getInputs();
|
||||
} elseif (null !== $annotation->getInput()) {
|
||||
$inputs[] = $annotation->getInput();
|
||||
}
|
||||
|
||||
// input (populates 'parameters' for the formatters)
|
||||
if (null !== $input = $annotation->getInput()) {
|
||||
$parameters = array();
|
||||
$normalizedInput = $this->normalizeClassParameter($input);
|
||||
if (count($inputs)) {
|
||||
$parameters = [];
|
||||
foreach ($inputs as $input) {
|
||||
$normalizedInput = $this->normalizeClassParameter($input);
|
||||
$supportedParsers = [];
|
||||
foreach ($this->getParsers($normalizedInput) as $parser) {
|
||||
if ($parser->supports($normalizedInput)) {
|
||||
$supportedParsers[] = $parser;
|
||||
$parameters = $this->mergeParameters($parameters, $parser->parse($normalizedInput));
|
||||
}
|
||||
}
|
||||
|
||||
$supportedParsers = array();
|
||||
foreach ($this->getParsers($normalizedInput) as $parser) {
|
||||
if ($parser->supports($normalizedInput)) {
|
||||
$supportedParsers[] = $parser;
|
||||
$parameters = $this->mergeParameters($parameters, $parser->parse($normalizedInput));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($supportedParsers as $parser) {
|
||||
if ($parser instanceof PostParserInterface) {
|
||||
$parameters = $this->mergeParameters(
|
||||
$parameters,
|
||||
$parser->postParse($normalizedInput, $parameters)
|
||||
);
|
||||
foreach ($supportedParsers as $parser) {
|
||||
if ($parser instanceof PostParserInterface) {
|
||||
$parameters = $this->mergeParameters(
|
||||
$parameters,
|
||||
$parser->postParse($normalizedInput, $parameters)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$parameters = $this->setParentClasses($parameters);
|
||||
$parameters = $this->clearClasses($parameters);
|
||||
$parameters = $this->generateHumanReadableTypes($parameters);
|
||||
|
||||
if ('PUT' === $method) {
|
||||
// All parameters are optional with PUT (update)
|
||||
array_walk($parameters, function ($val, $key) use (&$data) {
|
||||
if ('PATCH' === $annotation->getMethod()) {
|
||||
// All parameters are optional with PATCH (update)
|
||||
foreach ($parameters as $key => $val) {
|
||||
$parameters[$key]['required'] = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// merge parameters with parameters block from ApiDoc annotation in controller method
|
||||
$parameters = $this->mergeParameters($parameters, $annotation->getParameters());
|
||||
$annotation->setParameters($parameters);
|
||||
}
|
||||
|
||||
// output (populates 'response' for the formatters)
|
||||
if (null !== $output = $annotation->getOutput()) {
|
||||
$response = array();
|
||||
$supportedParsers = array();
|
||||
$response = [];
|
||||
$supportedParsers = [];
|
||||
|
||||
$normalizedOutput = $this->normalizeClassParameter($output);
|
||||
|
||||
|
@ -317,6 +322,42 @@ class ApiDocExtractor
|
|||
$response = $this->generateHumanReadableTypes($response);
|
||||
|
||||
$annotation->setResponse($response);
|
||||
$annotation->setResponseForStatusCode($response, $normalizedOutput, 200);
|
||||
}
|
||||
|
||||
if (count($annotation->getResponseMap()) > 0) {
|
||||
foreach ($annotation->getResponseMap() as $code => $modelName) {
|
||||
if ('200' === (string) $code && isset($modelName['type']) && isset($modelName['model'])) {
|
||||
/*
|
||||
* Model was already parsed as the default `output` for this ApiDoc.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalizedModel = $this->normalizeClassParameter($modelName);
|
||||
|
||||
$parameters = [];
|
||||
$supportedParsers = [];
|
||||
foreach ($this->getParsers($normalizedModel) as $parser) {
|
||||
if ($parser->supports($normalizedModel)) {
|
||||
$supportedParsers[] = $parser;
|
||||
$parameters = $this->mergeParameters($parameters, $parser->parse($normalizedModel));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($supportedParsers as $parser) {
|
||||
if ($parser instanceof PostParserInterface) {
|
||||
$mp = $parser->postParse($normalizedModel, $parameters);
|
||||
$parameters = $this->mergeParameters($parameters, $mp);
|
||||
}
|
||||
}
|
||||
|
||||
$parameters = $this->setParentClasses($parameters);
|
||||
$parameters = $this->clearClasses($parameters);
|
||||
$parameters = $this->generateHumanReadableTypes($parameters);
|
||||
|
||||
$annotation->setResponseForStatusCode($parameters, $normalizedModel, $code);
|
||||
}
|
||||
}
|
||||
|
||||
return $annotation;
|
||||
|
@ -324,14 +365,33 @@ class ApiDocExtractor
|
|||
|
||||
protected function normalizeClassParameter($input)
|
||||
{
|
||||
$defaults = array(
|
||||
'class' => '',
|
||||
'groups' => array(),
|
||||
);
|
||||
$defaults = [
|
||||
'class' => '',
|
||||
'groups' => [],
|
||||
'options' => [],
|
||||
];
|
||||
|
||||
// normalize strings
|
||||
if (is_string($input)) {
|
||||
$input = array('class' => $input);
|
||||
$input = ['class' => $input];
|
||||
}
|
||||
|
||||
$collectionData = [];
|
||||
|
||||
/*
|
||||
* Match array<Fully\Qualified\ClassName> as alias; "as alias" optional.
|
||||
*/
|
||||
if (preg_match_all("/^array<([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)>(?:\\s+as\\s+(.+))?$/", $input['class'], $collectionData)) {
|
||||
$input['class'] = $collectionData[1][0];
|
||||
$input['collection'] = true;
|
||||
$input['collectionName'] = $collectionData[2][0];
|
||||
} elseif (preg_match('/^array</', $input['class'])) { // See if a collection directive was attempted. Must be malformed.
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Malformed collection directive: %s. Proper format is: array<Fully\\Qualified\\ClassName> or array<Fully\\Qualified\\ClassName> as collectionName',
|
||||
$input['class']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// normalize groups
|
||||
|
@ -351,8 +411,12 @@ class ApiDocExtractor
|
|||
* - Array parameters are recursively merged.
|
||||
* - Non-null default values prevail over null default values. Later values overrides previous defaults.
|
||||
*
|
||||
* @param array $p1 The pre-existing parameters array.
|
||||
* @param array $p2 The newly-returned parameters array.
|
||||
* However, if newly-returned parameter array contains a parameter with NULL, the parameter is removed from the merged results.
|
||||
* If the parameter is not present in the newly-returned array, then it is left as-is.
|
||||
*
|
||||
* @param array $p1 The pre-existing parameters array.
|
||||
* @param array $p2 The newly-returned parameters array.
|
||||
*
|
||||
* @return array The resulting, merged array.
|
||||
*/
|
||||
protected function mergeParameters($p1, $p2)
|
||||
|
@ -360,9 +424,14 @@ class ApiDocExtractor
|
|||
$params = $p1;
|
||||
|
||||
foreach ($p2 as $propname => $propvalue) {
|
||||
if (null === $propvalue) {
|
||||
unset($params[$propname]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($p1[$propname])) {
|
||||
$params[$propname] = $propvalue;
|
||||
} else {
|
||||
} elseif (is_array($propvalue)) {
|
||||
$v1 = $p1[$propname];
|
||||
|
||||
foreach ($propvalue as $name => $value) {
|
||||
|
@ -372,17 +441,21 @@ class ApiDocExtractor
|
|||
} else {
|
||||
$v1[$name] = $value;
|
||||
}
|
||||
} elseif (!is_null($value)) {
|
||||
if (in_array($name, array('required', 'readonly'))) {
|
||||
} elseif (null !== $value) {
|
||||
if (in_array($name, ['required', 'readonly'])) {
|
||||
$v1[$name] = $v1[$name] || $value;
|
||||
} elseif (in_array($name, array('requirement'))) {
|
||||
} elseif ('requirement' === $name) {
|
||||
if (isset($v1[$name])) {
|
||||
$v1[$name] .= ', ' . $value;
|
||||
} else {
|
||||
$v1[$name] = $value;
|
||||
}
|
||||
} elseif ($name == 'default') {
|
||||
$v1[$name] = $value ?: $v1[$name];
|
||||
} elseif ('default' === $name) {
|
||||
if (isset($v1[$name])) {
|
||||
$v1[$name] = $value ?? $v1[$name];
|
||||
} else {
|
||||
$v1[$name] = $value ?? null;
|
||||
}
|
||||
} else {
|
||||
$v1[$name] = $value;
|
||||
}
|
||||
|
@ -399,23 +472,48 @@ class ApiDocExtractor
|
|||
/**
|
||||
* Parses annotations for a given method, and adds new information to the given ApiDoc
|
||||
* annotation. Useful to extract information from the FOSRestBundle annotations.
|
||||
*
|
||||
* @param ApiDoc $annotation
|
||||
* @param Route $route
|
||||
* @param ReflectionMethod $method
|
||||
*/
|
||||
protected function parseAnnotations(ApiDoc $annotation, Route $route, \ReflectionMethod $method)
|
||||
protected function parseAnnotations(ApiDoc $annotation, Route $route, \ReflectionMethod $method): void
|
||||
{
|
||||
$annots = $this->reader->getMethodAnnotations($method);
|
||||
foreach ($this->handlers as $handler) {
|
||||
$handler->handle($annotation, $annots, $route, $method);
|
||||
$handler->handle($annotation, $route, $method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parent class to children
|
||||
*
|
||||
* @param array $array The source array.
|
||||
*
|
||||
* @return array The updated array.
|
||||
*/
|
||||
protected function setParentClasses($array)
|
||||
{
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $k => $v) {
|
||||
if (isset($v['children'])) {
|
||||
if (isset($v['class'])) {
|
||||
foreach ($v['children'] as $key => $item) {
|
||||
if (empty($item['parentClass'] ?? null)) {
|
||||
$array[$k]['children'][$key]['parentClass'] = $v['class'];
|
||||
}
|
||||
$array[$k]['children'][$key]['field'] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$array[$k]['children'] = $this->setParentClasses($array[$k]['children']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the temporary 'class' parameter from the parameters array before it is returned.
|
||||
*
|
||||
* @param array $array The source array.
|
||||
* @param array $array The source array.
|
||||
*
|
||||
* @return array The cleared array.
|
||||
*/
|
||||
protected function clearClasses($array)
|
||||
|
@ -433,14 +531,12 @@ class ApiDocExtractor
|
|||
/**
|
||||
* Populates the `dataType` properties in the parameter array if empty. Recurses through children when necessary.
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
protected function generateHumanReadableTypes(array $array)
|
||||
{
|
||||
foreach ($array as $name => $info) {
|
||||
|
||||
if (empty($info['dataType'])) {
|
||||
if (empty($info['dataType']) && array_key_exists('subType', $info)) {
|
||||
$array[$name]['dataType'] = $this->generateHumanReadableType($info['actualType'], $info['subType']);
|
||||
}
|
||||
|
||||
|
@ -455,25 +551,29 @@ class ApiDocExtractor
|
|||
/**
|
||||
* Creates a human-readable version of the `actualType`. `subType` is taken into account.
|
||||
*
|
||||
* @param string $actualType
|
||||
* @param string $subType
|
||||
* @param string $actualType
|
||||
* @param string $subType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateHumanReadableType($actualType, $subType)
|
||||
{
|
||||
if ($actualType == DataTypes::MODEL) {
|
||||
$parts = explode('\\', $subType);
|
||||
if (DataTypes::MODEL == $actualType) {
|
||||
if ($subType && class_exists($subType)) {
|
||||
$parts = explode('\\', $subType);
|
||||
|
||||
return sprintf('object (%s)', end($parts));
|
||||
return sprintf('object (%s)', end($parts));
|
||||
}
|
||||
|
||||
return sprintf('object (%s)', $subType);
|
||||
}
|
||||
|
||||
if ($actualType == DataTypes::COLLECTION) {
|
||||
|
||||
if (DataTypes::COLLECTION == $actualType) {
|
||||
if (DataTypes::isPrimitive($subType)) {
|
||||
return sprintf('array of %ss', $subType);
|
||||
}
|
||||
|
||||
if (class_exists($subType)) {
|
||||
if ($subType && class_exists($subType)) {
|
||||
$parts = explode('\\', $subType);
|
||||
|
||||
return sprintf('array of objects (%s)', end($parts));
|
||||
|
@ -488,9 +588,9 @@ class ApiDocExtractor
|
|||
private function getParsers(array $parameters)
|
||||
{
|
||||
if (isset($parameters['parsers'])) {
|
||||
$parsers = array();
|
||||
$parsers = [];
|
||||
foreach ($this->parsers as $parser) {
|
||||
if (in_array(get_class($parser), $parameters['parsers'])) {
|
||||
if (in_array($parser::class, $parameters['parsers'])) {
|
||||
$parsers[] = $parser;
|
||||
}
|
||||
}
|
||||
|
|
92
Extractor/CachingApiDocExtractor.php
Normal file
92
Extractor/CachingApiDocExtractor.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Extractor;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
|
||||
use Symfony\Component\Config\ConfigCache;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* Class CachingApiDocExtractor
|
||||
*
|
||||
* @author Bez Hermoso <bez@activelamp.com>
|
||||
*/
|
||||
class CachingApiDocExtractor extends ApiDocExtractor
|
||||
{
|
||||
/**
|
||||
* @param HandlerInterface[] $handlers
|
||||
* @param string[] $excludeSections
|
||||
* @param bool|false $debug
|
||||
*/
|
||||
public function __construct(
|
||||
RouterInterface $router,
|
||||
DocCommentExtractor $commentExtractor,
|
||||
array $handlers,
|
||||
array $excludeSections,
|
||||
private string $cacheFile,
|
||||
private bool $debug = false,
|
||||
) {
|
||||
parent::__construct($router, $commentExtractor, $handlers, $excludeSections);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $view View name
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function all($view = ApiDoc::DEFAULT_VIEW): array
|
||||
{
|
||||
$cache = $this->getViewCache($view);
|
||||
|
||||
if (!$cache->isFresh()) {
|
||||
$resources = [];
|
||||
foreach ($this->getRoutes() as $route) {
|
||||
if (
|
||||
null !== ($method = $this->getReflectionMethod($route->getDefault('_controller')))
|
||||
&& null !== $this->getMethodApiDoc($method)
|
||||
) {
|
||||
$file = $method->getDeclaringClass()->getFileName();
|
||||
$resources[] = new FileResource($file);
|
||||
}
|
||||
}
|
||||
|
||||
$resources = array_merge($resources, $this->router->getRouteCollection()->getResources());
|
||||
|
||||
$data = parent::all($view);
|
||||
|
||||
$cache->write(serialize($data), $resources);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// For BC
|
||||
if (method_exists($cache, 'getPath')) {
|
||||
$cachePath = $cache->getPath();
|
||||
} else {
|
||||
$cachePath = (string) $cache;
|
||||
}
|
||||
|
||||
return unserialize(file_get_contents($cachePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $view
|
||||
*
|
||||
* @return ConfigCache
|
||||
*/
|
||||
private function getViewCache($view)
|
||||
{
|
||||
return new ConfigCache($this->cacheFile . '.' . $view, $this->debug);
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Extractor\Handler;
|
||||
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
use Nelmio\ApiDocBundle\Extractor\HandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Constraints\Regex;
|
||||
use FOS\RestBundle\Controller\Annotations\RequestParam;
|
||||
use FOS\RestBundle\Controller\Annotations\QueryParam;
|
||||
|
||||
class FosRestHandler implements HandlerInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function handle(ApiDoc $annotation, array $annotations, Route $route, \ReflectionMethod $method)
|
||||
{
|
||||
foreach ($annotations as $annot) {
|
||||
if ($annot instanceof RequestParam) {
|
||||
|
||||
$requirements = $this->handleRequirements($annot->requirements);
|
||||
$data = array(
|
||||
'required' => $annot->strict && $annot->nullable === false && $annot->default === null,
|
||||
'dataType' => $requirements,
|
||||
'actualType' => $this->inferType($requirements),
|
||||
'subType' => null,
|
||||
'description' => $annot->description,
|
||||
'readonly' => false
|
||||
);
|
||||
if ($annot->strict === false) {
|
||||
$data['default'] = $annot->default;
|
||||
}
|
||||
$annotation->addParameter($annot->name, $data);
|
||||
} elseif ($annot instanceof QueryParam) {
|
||||
if ($annot->strict && $annot->nullable === false && $annot->default === null) {
|
||||
$annotation->addRequirement($annot->name, array(
|
||||
'requirement' => $this->handleRequirements($annot->requirements),
|
||||
'dataType' => '',
|
||||
'description' => $annot->description,
|
||||
));
|
||||
} elseif ($annot->default !== null) {
|
||||
$annotation->addFilter($annot->name, array(
|
||||
'requirement' => $this->handleRequirements($annot->requirements),
|
||||
'description' => $annot->description,
|
||||
'default' => $annot->default,
|
||||
));
|
||||
} else {
|
||||
$annotation->addFilter($annot->name, array(
|
||||
'requirement' => $this->handleRequirements($annot->requirements),
|
||||
'description' => $annot->description,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle FOSRestBundle requirements in order to return a string.
|
||||
*
|
||||
* @param mixed $requirements
|
||||
* @return string
|
||||
*/
|
||||
private function handleRequirements($requirements)
|
||||
{
|
||||
if (is_object($requirements) && $requirements instanceof Constraint) {
|
||||
if ($requirements instanceof Regex) {
|
||||
return $requirements->getHtmlPattern();
|
||||
}
|
||||
$class = get_class($requirements);
|
||||
|
||||
return substr($class, strrpos($class, '\\')+1);
|
||||
}
|
||||
|
||||
return (string) $requirements;
|
||||
}
|
||||
|
||||
public function inferType($requirement)
|
||||
{
|
||||
if (DataTypes::isPrimitive($requirement)) {
|
||||
return $requirement;
|
||||
}
|
||||
|
||||
return DataTypes::STRING;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Extractor\Handler;
|
||||
|
||||
use JMS\SecurityExtraBundle\Annotation\PreAuthorize;
|
||||
use Nelmio\ApiDocBundle\Extractor\HandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use JMS\SecurityExtraBundle\Annotation\Secure;
|
||||
|
||||
class JmsSecurityExtraHandler implements HandlerInterface
|
||||
{
|
||||
public function handle(ApiDoc $annotation, array $annotations, Route $route, \ReflectionMethod $method)
|
||||
{
|
||||
foreach ($annotations as $annot) {
|
||||
if ($annot instanceof PreAuthorize) {
|
||||
$annotation->setAuthentication(true);
|
||||
} elseif ($annot instanceof Secure) {
|
||||
$annotation->setAuthentication(true);
|
||||
$annotation->setAuthenticationRoles(is_array($annot->roles) ? $annot->roles : explode(',', $annot->roles));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,10 +11,10 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Extractor\Handler;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Extractor\HandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
class PhpDocHandler implements HandlerInterface
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ class PhpDocHandler implements HandlerInterface
|
|||
$this->commentExtractor = $commentExtractor;
|
||||
}
|
||||
|
||||
public function handle(ApiDoc $annotation, array $annotations, Route $route, \ReflectionMethod $method)
|
||||
public function handle(ApiDoc $annotation, Route $route, \ReflectionMethod $method): void
|
||||
{
|
||||
// description
|
||||
if (null === $annotation->getDescription()) {
|
||||
|
@ -48,33 +48,24 @@ class PhpDocHandler implements HandlerInterface
|
|||
$requirements = $annotation->getRequirements();
|
||||
foreach ($route->getRequirements() as $name => $value) {
|
||||
if (!isset($requirements[$name]) && '_method' !== $name && '_scheme' !== $name) {
|
||||
$requirements[$name] = array(
|
||||
$requirements[$name] = [
|
||||
'requirement' => $value,
|
||||
'dataType' => '',
|
||||
'description' => '',
|
||||
);
|
||||
}
|
||||
|
||||
if ('_scheme' === $name) {
|
||||
$https = ('https' == $value);
|
||||
$annotation->setHttps($https);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($route, 'getSchemes')) {
|
||||
$annotation->setHttps(in_array('https', $route->getSchemes()));
|
||||
}
|
||||
|
||||
$paramDocs = array();
|
||||
$paramDocs = [];
|
||||
foreach (explode("\n", $this->commentExtractor->getDocComment($method)) as $line) {
|
||||
if (preg_match('{^@param (.+)}', trim($line), $matches)) {
|
||||
$paramDocs[] = $matches[1];
|
||||
}
|
||||
if (preg_match('{^@deprecated\b(.*)}', trim($line), $matches)) {
|
||||
if (preg_match('{^@deprecated}', trim($line))) {
|
||||
$annotation->setDeprecated(true);
|
||||
}
|
||||
if (preg_match('{^@link\b(.*)}', trim($line), $matches)) {
|
||||
$annotation->setLink($matches[1]);
|
||||
if (preg_match('{^@(link|see) (.+)}', trim($line), $matches)) {
|
||||
$annotation->setLink($matches[2]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,10 +74,17 @@ class PhpDocHandler implements HandlerInterface
|
|||
$found = false;
|
||||
foreach ($paramDocs as $paramDoc) {
|
||||
if (preg_match(sprintf($regexp, preg_quote($var)), $paramDoc, $matches)) {
|
||||
$requirements[$var]['dataType'] = isset($matches[1]) ? $matches[1] : '';
|
||||
$requirements[$var]['description'] = $matches[2];
|
||||
$annotationRequirements = $annotation->getRequirements();
|
||||
|
||||
if (!isset($requirements[$var]['requirement'])) {
|
||||
if (!isset($annotationRequirements[$var]['dataType'])) {
|
||||
$requirements[$var]['dataType'] = $matches[1] ?? '';
|
||||
}
|
||||
|
||||
if (!isset($annotationRequirements[$var]['description'])) {
|
||||
$requirements[$var]['description'] = $matches[2];
|
||||
}
|
||||
|
||||
if (!isset($requirements[$var]['requirement']) && !isset($annotationRequirements[$var]['requirement'])) {
|
||||
$requirements[$var]['requirement'] = '';
|
||||
}
|
||||
|
||||
|
@ -96,7 +94,7 @@ class PhpDocHandler implements HandlerInterface
|
|||
}
|
||||
|
||||
if (!isset($requirements[$var]) && false === $found) {
|
||||
$requirements[$var] = array('requirement' => '', 'dataType' => '', 'description' => '');
|
||||
$requirements[$var] = ['requirement' => '', 'dataType' => '', 'description' => ''];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Extractor\Handler;
|
||||
|
||||
use Nelmio\ApiDocBundle\Extractor\HandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
||||
|
||||
class SensioFrameworkExtraHandler implements HandlerInterface
|
||||
{
|
||||
public function handle(ApiDoc $annotation, array $annotations, Route $route, \ReflectionMethod $method)
|
||||
{
|
||||
foreach ($annotations as $annot) {
|
||||
if ($annot instanceof Cache) {
|
||||
$annotation->setCache($annot->getMaxAge());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,18 +11,13 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Extractor;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
interface HandlerInterface
|
||||
{
|
||||
/**
|
||||
* Parse route parameters in order to populate ApiDoc.
|
||||
*
|
||||
* @param Nelmio\ApiDocBundle\Annotation\ApiDoc $annotation
|
||||
* @param array $annotations
|
||||
* @param Symfony\Component\Routing\Route $route
|
||||
* @param ReflectionMethod $method
|
||||
*/
|
||||
public function handle(ApiDoc $annotation, array $annotations, Route $route, \ReflectionMethod $method);
|
||||
public function handle(ApiDoc $annotation, Route $route, \ReflectionMethod $method);
|
||||
}
|
||||
|
|
|
@ -11,45 +11,43 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Form\Extension;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class DescriptionFormTypeExtension extends AbstractTypeExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->setAttribute('description', $options['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['description'] = $options['description'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @deprecated Remove it when bumping requirements to Symfony 2.7+
|
||||
*/
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(array(
|
||||
'description' => '',
|
||||
));
|
||||
$this->configureOptions($resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExtendedType()
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
return 'form';
|
||||
$resolver->setDefaults([
|
||||
'description' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\FormType')];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,18 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Formatter;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
|
||||
abstract class AbstractFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
public function setVersion($version): void
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
public function formatOne(ApiDoc $annotation)
|
||||
{
|
||||
return $this->renderOne(
|
||||
|
@ -26,9 +30,6 @@ abstract class AbstractFormatter implements FormatterInterface
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $collection)
|
||||
{
|
||||
return $this->render(
|
||||
|
@ -39,7 +40,6 @@ abstract class AbstractFormatter implements FormatterInterface
|
|||
/**
|
||||
* Format a single array of data
|
||||
*
|
||||
* @param array $data
|
||||
* @return string|array
|
||||
*/
|
||||
abstract protected function renderOne(array $data);
|
||||
|
@ -47,40 +47,70 @@ abstract class AbstractFormatter implements FormatterInterface
|
|||
/**
|
||||
* Format a set of resource sections.
|
||||
*
|
||||
* @param array $collection
|
||||
* @return string|array
|
||||
*/
|
||||
abstract protected function render(array $collection);
|
||||
|
||||
/**
|
||||
* Check that the versions range includes current version
|
||||
*
|
||||
* @param string $fromVersion (default: null)
|
||||
* @param string $toVersion (default: null)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function rangeIncludesVersion($fromVersion = null, $toVersion = null)
|
||||
{
|
||||
if (!$fromVersion && !$toVersion) {
|
||||
return true;
|
||||
}
|
||||
if ($fromVersion && version_compare($fromVersion, $this->version, '>')) {
|
||||
return false;
|
||||
}
|
||||
if ($toVersion && version_compare($toVersion, $this->version, '<')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses nested parameters into a flat by changing the parameter
|
||||
* names to strings which contain the nested property names, for example:
|
||||
* `user[group][name]`
|
||||
*
|
||||
* @param string $parentName
|
||||
* @param bool $ignoreNestedReadOnly
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $parentName
|
||||
* @param boolean $ignoreNestedReadOnly
|
||||
* @return array
|
||||
*/
|
||||
protected function compressNestedParameters(array $data, $parentName = null, $ignoreNestedReadOnly = false)
|
||||
{
|
||||
$newParams = array();
|
||||
$newParams = [];
|
||||
foreach ($data as $name => $info) {
|
||||
if ($this->version && !$this->rangeIncludesVersion(
|
||||
$info['sinceVersion'] ?? null,
|
||||
$info['untilVersion'] ?? null
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newName = $this->getNewName($name, $info, $parentName);
|
||||
|
||||
$newParams[$newName] = array(
|
||||
'dataType' => $info['dataType'],
|
||||
'readonly' => array_key_exists('readonly', $info) ? $info['readonly'] : null,
|
||||
'required' => $info['required'],
|
||||
'default' => array_key_exists('default', $info) ? $info['default'] : null,
|
||||
'description' => array_key_exists('description', $info) ? $info['description'] : null,
|
||||
'format' => array_key_exists('format', $info) ? $info['format'] : null,
|
||||
$newParams[$newName] = [
|
||||
'dataType' => $info['dataType'],
|
||||
'readonly' => array_key_exists('readonly', $info) ? $info['readonly'] : null,
|
||||
'required' => $info['required'],
|
||||
'default' => array_key_exists('default', $info) ? $info['default'] : null,
|
||||
'description' => array_key_exists('description', $info) ? $info['description'] : null,
|
||||
'format' => array_key_exists('format', $info) ? $info['format'] : null,
|
||||
'sinceVersion' => array_key_exists('sinceVersion', $info) ? $info['sinceVersion'] : null,
|
||||
'untilVersion' => array_key_exists('untilVersion', $info) ? $info['untilVersion'] : null,
|
||||
'actualType' => array_key_exists('actualType', $info) ? $info['actualType'] : null,
|
||||
'subType' => array_key_exists('subType', $info) ? $info['subType'] : null,
|
||||
);
|
||||
'actualType' => array_key_exists('actualType', $info) ? $info['actualType'] : null,
|
||||
'subType' => array_key_exists('subType', $info) ? $info['subType'] : null,
|
||||
'parentClass' => array_key_exists('parentClass', $info) ? $info['parentClass'] : null,
|
||||
'field' => array_key_exists('field', $info) ? $info['field'] : null,
|
||||
];
|
||||
|
||||
if (isset($info['children']) && (!$info['readonly'] || !$ignoreNestedReadOnly)) {
|
||||
foreach ($this->compressNestedParameters($info['children'], $newName, $ignoreNestedReadOnly) as $nestedItemName => $nestedItemData) {
|
||||
|
@ -96,27 +126,29 @@ abstract class AbstractFormatter implements FormatterInterface
|
|||
* Returns a new property name, taking into account whether or not the property
|
||||
* is an array of some other data type.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @param string $parentName
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @param string $parentName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getNewName($name, $data, $parentName = null)
|
||||
{
|
||||
$array = '';
|
||||
$newName = ($parentName) ? sprintf("%s[%s]", $parentName, $name) : $name;
|
||||
$array = '';
|
||||
$newName = ($parentName) ? sprintf('%s[%s]', $parentName, $name) : $name;
|
||||
|
||||
if (isset($data['actualType']) && $data['actualType'] == DataTypes::COLLECTION
|
||||
&& isset($data['subType']) && $data['subType'] !== null
|
||||
if (isset($data['actualType']) && DataTypes::COLLECTION == $data['actualType']
|
||||
&& isset($data['subType']) && null !== $data['subType']
|
||||
) {
|
||||
$array = '[]';
|
||||
}
|
||||
|
||||
return sprintf("%s%s", $newName, $array);
|
||||
return sprintf('%s%s', $newName, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $annotation
|
||||
* @param array $annotation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function processAnnotation($annotation)
|
||||
|
@ -129,23 +161,30 @@ abstract class AbstractFormatter implements FormatterInterface
|
|||
$annotation['response'] = $this->compressNestedParameters($annotation['response']);
|
||||
}
|
||||
|
||||
$annotation['id'] = strtolower($annotation['method']).'-'.str_replace('/', '-', $annotation['uri']);
|
||||
if (isset($annotation['parsedResponseMap'])) {
|
||||
foreach ($annotation['parsedResponseMap'] as $statusCode => &$data) {
|
||||
$data['model'] = $this->compressNestedParameters($data['model']);
|
||||
}
|
||||
}
|
||||
|
||||
$annotation['id'] = strtolower($annotation['method'] ?? '') . '-' . str_replace('/', '-', $annotation['uri'] ?? '');
|
||||
|
||||
return $annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array[ApiDoc] $collection
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function processCollection(array $collection)
|
||||
{
|
||||
$array = array();
|
||||
$array = [];
|
||||
foreach ($collection as $coll) {
|
||||
$array[$coll['annotation']->getSection()][$coll['resource']][] = $coll['annotation']->toArray();
|
||||
}
|
||||
|
||||
$processedCollection = array();
|
||||
$processedCollection = [];
|
||||
foreach ($array as $section => $resources) {
|
||||
foreach ($resources as $path => $annotations) {
|
||||
foreach ($annotations as $annotation) {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Formatter;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
|
||||
interface FormatterInterface
|
||||
{
|
||||
|
@ -19,6 +19,7 @@ interface FormatterInterface
|
|||
* Format a collection of documentation data.
|
||||
*
|
||||
* @param array[ApiDoc] $collection
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function format(array $collection);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Nelmio\ApiDocBundle\Formatter;
|
||||
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
use Twig\Environment as TwigEnvironment;
|
||||
|
||||
class HtmlFormatter extends AbstractFormatter
|
||||
{
|
||||
|
@ -31,12 +32,12 @@ class HtmlFormatter extends AbstractFormatter
|
|||
protected $defaultRequestFormat;
|
||||
|
||||
/**
|
||||
* @var EngineInterface
|
||||
* @var EngineInterface|TwigEnvironment
|
||||
*/
|
||||
protected $engine;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* @var bool
|
||||
*/
|
||||
private $enableSandbox;
|
||||
|
||||
|
@ -76,9 +77,11 @@ class HtmlFormatter extends AbstractFormatter
|
|||
private $motdTemplate;
|
||||
|
||||
/**
|
||||
* @param array $authentication
|
||||
* @var bool
|
||||
*/
|
||||
public function setAuthentication(array $authentication = null)
|
||||
private $defaultSectionsOpened;
|
||||
|
||||
public function setAuthentication(?array $authentication = null): void
|
||||
{
|
||||
$this->authentication = $authentication;
|
||||
}
|
||||
|
@ -86,7 +89,7 @@ class HtmlFormatter extends AbstractFormatter
|
|||
/**
|
||||
* @param string $apiName
|
||||
*/
|
||||
public function setApiName($apiName)
|
||||
public function setApiName($apiName): void
|
||||
{
|
||||
$this->apiName = $apiName;
|
||||
}
|
||||
|
@ -94,23 +97,23 @@ class HtmlFormatter extends AbstractFormatter
|
|||
/**
|
||||
* @param string $endpoint
|
||||
*/
|
||||
public function setEndpoint($endpoint)
|
||||
public function setEndpoint($endpoint): void
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $enableSandbox
|
||||
* @param bool $enableSandbox
|
||||
*/
|
||||
public function setEnableSandbox($enableSandbox)
|
||||
public function setEnableSandbox($enableSandbox): void
|
||||
{
|
||||
$this->enableSandbox = $enableSandbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EngineInterface $engine
|
||||
* @param EngineInterface|TwigEnvironment $engine
|
||||
*/
|
||||
public function setTemplatingEngine(EngineInterface $engine)
|
||||
public function setTemplatingEngine($engine): void
|
||||
{
|
||||
$this->engine = $engine;
|
||||
}
|
||||
|
@ -118,15 +121,12 @@ class HtmlFormatter extends AbstractFormatter
|
|||
/**
|
||||
* @param string $acceptType
|
||||
*/
|
||||
public function setAcceptType($acceptType)
|
||||
public function setAcceptType($acceptType): void
|
||||
{
|
||||
$this->acceptType = $acceptType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $bodyFormats
|
||||
*/
|
||||
public function setBodyFormats(array $bodyFormats)
|
||||
public function setBodyFormats(array $bodyFormats): void
|
||||
{
|
||||
$this->bodyFormats = $bodyFormats;
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ class HtmlFormatter extends AbstractFormatter
|
|||
/**
|
||||
* @param string $defaultBodyFormat
|
||||
*/
|
||||
public function setDefaultBodyFormat($defaultBodyFormat)
|
||||
public function setDefaultBodyFormat($defaultBodyFormat): void
|
||||
{
|
||||
$this->defaultBodyFormat = $defaultBodyFormat;
|
||||
}
|
||||
|
@ -142,15 +142,12 @@ class HtmlFormatter extends AbstractFormatter
|
|||
/**
|
||||
* @param string $method
|
||||
*/
|
||||
public function setRequestFormatMethod($method)
|
||||
public function setRequestFormatMethod($method): void
|
||||
{
|
||||
$this->requestFormatMethod = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $formats
|
||||
*/
|
||||
public function setRequestFormats(array $formats)
|
||||
public function setRequestFormats(array $formats): void
|
||||
{
|
||||
$this->requestFormats = $formats;
|
||||
}
|
||||
|
@ -158,7 +155,7 @@ class HtmlFormatter extends AbstractFormatter
|
|||
/**
|
||||
* @param string $format
|
||||
*/
|
||||
public function setDefaultRequestFormat($format)
|
||||
public function setDefaultRequestFormat($format): void
|
||||
{
|
||||
$this->defaultRequestFormat = $format;
|
||||
}
|
||||
|
@ -166,7 +163,7 @@ class HtmlFormatter extends AbstractFormatter
|
|||
/**
|
||||
* @param string $motdTemplate
|
||||
*/
|
||||
public function setMotdTemplate($motdTemplate)
|
||||
public function setMotdTemplate($motdTemplate): void
|
||||
{
|
||||
$this->motdTemplate = $motdTemplate;
|
||||
}
|
||||
|
@ -180,28 +177,30 @@ class HtmlFormatter extends AbstractFormatter
|
|||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param bool $defaultSectionsOpened
|
||||
*/
|
||||
public function setDefaultSectionsOpened($defaultSectionsOpened): void
|
||||
{
|
||||
$this->defaultSectionsOpened = $defaultSectionsOpened;
|
||||
}
|
||||
|
||||
protected function renderOne(array $data)
|
||||
{
|
||||
return $this->engine->render('NelmioApiDocBundle::resource.html.twig', array_merge(
|
||||
array(
|
||||
'data' => $data,
|
||||
return $this->engine->render('@NelmioApiDoc/resource.html.twig', array_merge(
|
||||
[
|
||||
'data' => $data,
|
||||
'displayContent' => true,
|
||||
),
|
||||
],
|
||||
$this->getGlobalVars()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function render(array $collection)
|
||||
{
|
||||
return $this->engine->render('NelmioApiDocBundle::resources.html.twig', array_merge(
|
||||
array(
|
||||
return $this->engine->render('@NelmioApiDoc/resources.html.twig', array_merge(
|
||||
[
|
||||
'resources' => $collection,
|
||||
),
|
||||
],
|
||||
$this->getGlobalVars()
|
||||
));
|
||||
}
|
||||
|
@ -211,21 +210,22 @@ class HtmlFormatter extends AbstractFormatter
|
|||
*/
|
||||
private function getGlobalVars()
|
||||
{
|
||||
return array(
|
||||
'apiName' => $this->apiName,
|
||||
'authentication' => $this->authentication,
|
||||
'endpoint' => $this->endpoint,
|
||||
'enableSandbox' => $this->enableSandbox,
|
||||
'requestFormatMethod' => $this->requestFormatMethod,
|
||||
'acceptType' => $this->acceptType,
|
||||
'bodyFormats' => $this->bodyFormats,
|
||||
'defaultBodyFormat' => $this->defaultBodyFormat,
|
||||
'requestFormats' => $this->requestFormats,
|
||||
return [
|
||||
'apiName' => $this->apiName,
|
||||
'authentication' => $this->authentication,
|
||||
'endpoint' => $this->endpoint,
|
||||
'enableSandbox' => $this->enableSandbox,
|
||||
'requestFormatMethod' => $this->requestFormatMethod,
|
||||
'acceptType' => $this->acceptType,
|
||||
'bodyFormats' => $this->bodyFormats,
|
||||
'defaultBodyFormat' => $this->defaultBodyFormat,
|
||||
'requestFormats' => $this->requestFormats,
|
||||
'defaultRequestFormat' => $this->defaultRequestFormat,
|
||||
'date' => date(DATE_RFC822),
|
||||
'css' => file_get_contents(__DIR__ . '/../Resources/public/css/screen.css'),
|
||||
'js' => file_get_contents(__DIR__ . '/../Resources/public/js/all.js'),
|
||||
'motdTemplate' => $this->motdTemplate
|
||||
);
|
||||
'date' => date(DATE_RFC822),
|
||||
'css' => file_get_contents(__DIR__ . '/../Resources/public/css/screen.css'),
|
||||
'js' => file_get_contents(__DIR__ . '/../Resources/public/js/all.js'),
|
||||
'motdTemplate' => $this->motdTemplate,
|
||||
'defaultSectionsOpened' => $this->defaultSectionsOpened,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,12 @@ namespace Nelmio\ApiDocBundle\Formatter;
|
|||
|
||||
class MarkdownFormatter extends AbstractFormatter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function renderOne(array $data)
|
||||
{
|
||||
$markdown = sprintf("### `%s` %s ###\n", $data['method'], $data['uri']);
|
||||
|
||||
if (isset($data['deprecated']) && false !== $data['deprecated']) {
|
||||
$markdown .= "### This method is deprecated ###";
|
||||
$markdown .= '### This method is deprecated ###';
|
||||
$markdown .= "\n\n";
|
||||
}
|
||||
|
||||
|
@ -107,15 +104,15 @@ class MarkdownFormatter extends AbstractFormatter
|
|||
}
|
||||
|
||||
if (null !== $parameter['sinceVersion'] || null !== $parameter['untilVersion']) {
|
||||
$markdown .= " * versions: ";
|
||||
$markdown .= ' * versions: ';
|
||||
if ($parameter['sinceVersion']) {
|
||||
$markdown .= '>='.$parameter['sinceVersion'];
|
||||
$markdown .= '>=' . $parameter['sinceVersion'];
|
||||
}
|
||||
if ($parameter['untilVersion']) {
|
||||
if ($parameter['sinceVersion']) {
|
||||
$markdown .= ',';
|
||||
}
|
||||
$markdown .= '<='.$parameter['untilVersion'];
|
||||
$markdown .= '<=' . $parameter['untilVersion'];
|
||||
}
|
||||
$markdown .= "\n";
|
||||
}
|
||||
|
@ -127,9 +124,6 @@ class MarkdownFormatter extends AbstractFormatter
|
|||
return $markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function render(array $collection)
|
||||
{
|
||||
$markdown = '';
|
||||
|
|
70
Formatter/RequestAwareSwaggerFormatter.php
Normal file
70
Formatter/RequestAwareSwaggerFormatter.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Formatter;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Extends SwaggerFormatter which takes into account the request's base URL when generating the documents for direct swagger-ui consumption.
|
||||
*
|
||||
* @author Bezalel Hermoso <bezalelhermoso@gmail.com>
|
||||
*/
|
||||
class RequestAwareSwaggerFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var SwaggerFormatter
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
public function __construct(Request $request, SwaggerFormatter $formatter)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a collection of documentation data.
|
||||
*
|
||||
* @param null $resource
|
||||
*
|
||||
* @internal param $array [ApiDoc] $collection
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function format(array $collection, $resource = null)
|
||||
{
|
||||
$result = $this->formatter->format($collection, $resource);
|
||||
|
||||
if (null !== $resource) {
|
||||
$result['basePath'] = $this->request->getBaseUrl() . $result['basePath'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format documentation data for one route.
|
||||
*
|
||||
* @param ApiDoc $annotation
|
||||
* return string|array
|
||||
*/
|
||||
public function formatOne(ApiDoc $annotation)
|
||||
{
|
||||
return $this->formatter->formatOne($annotation);
|
||||
}
|
||||
}
|
|
@ -11,42 +11,33 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Formatter;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
|
||||
class SimpleFormatter extends AbstractFormatter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatOne(ApiDoc $annotation)
|
||||
{
|
||||
return $annotation->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $collection)
|
||||
{
|
||||
$array = array();
|
||||
$array = [];
|
||||
foreach ($collection as $coll) {
|
||||
$array[$coll['resource']][] = $coll['annotation']->toArray();
|
||||
$annotationArray = $coll['annotation']->toArray();
|
||||
unset($annotationArray['parsedResponseMap']);
|
||||
|
||||
$array[$coll['resource']][] = $annotationArray;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function renderOne(array $data)
|
||||
protected function renderOne(array $data): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function render(array $collection)
|
||||
protected function render(array $collection): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
565
Formatter/SwaggerFormatter.php
Normal file
565
Formatter/SwaggerFormatter.php
Normal file
|
@ -0,0 +1,565 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Formatter;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
use Nelmio\ApiDocBundle\Swagger\ModelRegistry;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Produces Swagger-compliant resource lists and API declarations as defined here:
|
||||
* https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md
|
||||
*
|
||||
* This formatter produces an array. Therefore output still needs to be `json_encode`d before passing on as HTTP response.
|
||||
*
|
||||
* @author Bezalel Hermoso <bezalelhermoso@gmail.com>
|
||||
*/
|
||||
class SwaggerFormatter implements FormatterInterface
|
||||
{
|
||||
protected $basePath;
|
||||
|
||||
protected $apiVersion;
|
||||
|
||||
protected $swaggerVersion;
|
||||
|
||||
protected $info = [];
|
||||
|
||||
protected $typeMap = [
|
||||
DataTypes::INTEGER => 'integer',
|
||||
DataTypes::FLOAT => 'number',
|
||||
DataTypes::STRING => 'string',
|
||||
DataTypes::BOOLEAN => 'boolean',
|
||||
DataTypes::FILE => 'string',
|
||||
DataTypes::DATE => 'string',
|
||||
DataTypes::DATETIME => 'string',
|
||||
];
|
||||
|
||||
protected $formatMap = [
|
||||
DataTypes::INTEGER => 'int32',
|
||||
DataTypes::FLOAT => 'float',
|
||||
DataTypes::FILE => 'byte',
|
||||
DataTypes::DATE => 'date',
|
||||
DataTypes::DATETIME => 'date-time',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var ModelRegistry
|
||||
*/
|
||||
protected $modelRegistry;
|
||||
|
||||
public function __construct($namingStategy)
|
||||
{
|
||||
$this->modelRegistry = new ModelRegistry($namingStategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $authConfig;
|
||||
|
||||
public function setAuthenticationConfig(array $config): void
|
||||
{
|
||||
$this->authConfig = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a collection of documentation data.
|
||||
*
|
||||
* If resource is provided, an API declaration for that resource is produced. Otherwise, a resource listing is returned.
|
||||
*
|
||||
* @param array|ApiDoc[] $collection
|
||||
* @param string|null $resource
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function format(array $collection, $resource = null)
|
||||
{
|
||||
if (null === $resource) {
|
||||
return $this->produceResourceListing($collection);
|
||||
} else {
|
||||
return $this->produceApiDeclaration($collection, $resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the collection into Swagger-compliant output.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function produceResourceListing(array $collection)
|
||||
{
|
||||
$resourceList = [
|
||||
'swaggerVersion' => (string) $this->swaggerVersion,
|
||||
'apis' => [],
|
||||
'apiVersion' => (string) $this->apiVersion,
|
||||
'info' => $this->getInfo(),
|
||||
'authorizations' => $this->getAuthorizations(),
|
||||
];
|
||||
|
||||
$apis = &$resourceList['apis'];
|
||||
|
||||
foreach ($collection as $item) {
|
||||
/** @var $apiDoc ApiDoc */
|
||||
$apiDoc = $item['annotation'];
|
||||
$resource = $item['resource'];
|
||||
|
||||
if (!$apiDoc->isResource()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subPath = $this->stripBasePath($resource);
|
||||
$normalizedName = $this->normalizeResourcePath($subPath);
|
||||
|
||||
$apis[] = [
|
||||
'path' => '/' . $normalizedName,
|
||||
'description' => $apiDoc->getResourceDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
return $resourceList;
|
||||
}
|
||||
|
||||
protected function getAuthorizations()
|
||||
{
|
||||
$auth = [];
|
||||
|
||||
if (null === $this->authConfig) {
|
||||
return $auth;
|
||||
}
|
||||
|
||||
$config = $this->authConfig;
|
||||
|
||||
if ('http' === $config['delivery']) {
|
||||
return $auth;
|
||||
}
|
||||
|
||||
$auth['apiKey'] = [
|
||||
'type' => 'apiKey',
|
||||
'passAs' => $config['delivery'],
|
||||
'keyname' => $config['name'],
|
||||
];
|
||||
|
||||
return $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getInfo()
|
||||
{
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format documentation data for one route.
|
||||
*
|
||||
* @param ApiDoc $annotation
|
||||
* return string|array
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function formatOne(ApiDoc $annotation): void
|
||||
{
|
||||
throw new \BadMethodCallException(sprintf('%s does not support formatting a single ApiDoc only.', __CLASS__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats collection to produce a Swagger-compliant API declaration for the given resource.
|
||||
*
|
||||
* @param string $resource
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function produceApiDeclaration(array $collection, $resource)
|
||||
{
|
||||
$apiDeclaration = [
|
||||
'swaggerVersion' => (string) $this->swaggerVersion,
|
||||
'apiVersion' => (string) $this->apiVersion,
|
||||
'basePath' => $this->basePath,
|
||||
'resourcePath' => $resource,
|
||||
'apis' => [],
|
||||
'models' => [],
|
||||
'produces' => [],
|
||||
'consumes' => [],
|
||||
'authorizations' => $this->getAuthorizations(),
|
||||
];
|
||||
|
||||
$main = null;
|
||||
|
||||
$apiBag = [];
|
||||
|
||||
foreach ($collection as $item) {
|
||||
/** @var $apiDoc ApiDoc */
|
||||
$apiDoc = $item['annotation'];
|
||||
$itemResource = $this->stripBasePath($item['resource']);
|
||||
$input = $apiDoc->getInput();
|
||||
|
||||
if (!is_array($input)) {
|
||||
$input = [
|
||||
'class' => $input,
|
||||
'paramType' => 'form',
|
||||
];
|
||||
} elseif (empty($input['paramType'])) {
|
||||
$input['paramType'] = 'form';
|
||||
}
|
||||
|
||||
$route = $apiDoc->getRoute();
|
||||
|
||||
$itemResource = $this->normalizeResourcePath($itemResource);
|
||||
|
||||
if ('/' . $itemResource !== $resource) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$compiled = $route->compile();
|
||||
|
||||
$path = $this->stripBasePath($route->getPath());
|
||||
|
||||
if (!isset($apiBag[$path])) {
|
||||
$apiBag[$path] = [];
|
||||
}
|
||||
|
||||
$parameters = [];
|
||||
$responseMessages = [];
|
||||
|
||||
foreach ($compiled->getPathVariables() as $paramValue) {
|
||||
$parameter = [
|
||||
'paramType' => 'path',
|
||||
'name' => $paramValue,
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
];
|
||||
|
||||
if ('_format' === $paramValue && false != ($req = $route->getRequirement('_format'))) {
|
||||
$parameter['enum'] = explode('|', $req);
|
||||
}
|
||||
|
||||
$parameters[] = $parameter;
|
||||
}
|
||||
|
||||
$data = $apiDoc->toArray();
|
||||
|
||||
if (isset($data['filters'])) {
|
||||
$parameters = array_merge($parameters, $this->deriveQueryParameters($data['filters']));
|
||||
}
|
||||
|
||||
if (isset($data['parameters'])) {
|
||||
$parameters = array_merge($parameters, $this->deriveParameters($data['parameters'], $input['paramType']));
|
||||
}
|
||||
|
||||
$responseMap = $apiDoc->getParsedResponseMap();
|
||||
|
||||
$statusMessages = $data['statusCodes'] ?? [];
|
||||
|
||||
foreach ($responseMap as $statusCode => $prop) {
|
||||
if (isset($statusMessages[$statusCode])) {
|
||||
$message = is_array($statusMessages[$statusCode]) ? implode('; ', $statusMessages[$statusCode]) : $statusCode[$statusCode];
|
||||
} else {
|
||||
$message = sprintf('See standard HTTP status code reason for %s', $statusCode);
|
||||
}
|
||||
|
||||
$className = !empty($prop['type']['form_errors']) ? $prop['type']['class'] . '.ErrorResponse' : $prop['type']['class'];
|
||||
|
||||
if (isset($prop['type']['collection']) && true === $prop['type']['collection']) {
|
||||
/*
|
||||
* Without alias: Fully\Qualified\Class\Name[]
|
||||
* With alias: Fully\Qualified\Class\Name[alias]
|
||||
*/
|
||||
$alias = $prop['type']['collectionName'];
|
||||
|
||||
$newName = sprintf('%s[%s]', $className, $alias);
|
||||
$collId =
|
||||
$this->registerModel(
|
||||
$newName,
|
||||
[
|
||||
$alias => [
|
||||
'dataType' => null,
|
||||
'subType' => $className,
|
||||
'actualType' => DataTypes::COLLECTION,
|
||||
'required' => true,
|
||||
'readonly' => true,
|
||||
'description' => null,
|
||||
'default' => null,
|
||||
'children' => $prop['model'][$alias]['children'],
|
||||
],
|
||||
],
|
||||
''
|
||||
);
|
||||
$responseModel = [
|
||||
'code' => $statusCode,
|
||||
'message' => $message,
|
||||
'responseModel' => $collId,
|
||||
];
|
||||
} else {
|
||||
$responseModel = [
|
||||
'code' => $statusCode,
|
||||
'message' => $message,
|
||||
'responseModel' => $this->registerModel($className, $prop['model'], ''),
|
||||
];
|
||||
}
|
||||
$responseMessages[$statusCode] = $responseModel;
|
||||
}
|
||||
|
||||
$unmappedMessages = array_diff(array_keys($statusMessages), array_keys($responseMessages));
|
||||
|
||||
foreach ($unmappedMessages as $code) {
|
||||
$responseMessages[$code] = [
|
||||
'code' => $code,
|
||||
'message' => is_array($statusMessages[$code]) ? implode('; ', $statusMessages[$code]) : $statusMessages[$code],
|
||||
];
|
||||
}
|
||||
|
||||
$type = $responseMessages[200]['responseModel'] ?? null;
|
||||
|
||||
foreach ($apiDoc->getRoute()->getMethods() as $method) {
|
||||
$operation = [
|
||||
'method' => $method,
|
||||
'summary' => $apiDoc->getDescription(),
|
||||
'nickname' => $this->generateNickname($method, $itemResource),
|
||||
'parameters' => $parameters,
|
||||
'responseMessages' => array_values($responseMessages),
|
||||
];
|
||||
|
||||
if (null !== $type) {
|
||||
$operation['type'] = $type;
|
||||
}
|
||||
|
||||
$apiBag[$path][] = $operation;
|
||||
}
|
||||
}
|
||||
|
||||
$apiDeclaration['resourcePath'] = $resource;
|
||||
|
||||
foreach ($apiBag as $path => $operations) {
|
||||
$apiDeclaration['apis'][] = [
|
||||
'path' => $path,
|
||||
'operations' => $operations,
|
||||
];
|
||||
}
|
||||
|
||||
$apiDeclaration['models'] = $this->modelRegistry->getModels();
|
||||
$this->modelRegistry->clear();
|
||||
|
||||
return $apiDeclaration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slugify a URL path. Trims out path parameters wrapped in curly brackets.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeResourcePath($path)
|
||||
{
|
||||
$path = preg_replace('/({.*?})/', '', $path);
|
||||
$path = trim(preg_replace('/[^0-9a-zA-Z]/', '-', $path), '-');
|
||||
$path = preg_replace('/-+/', '-', $path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function setBasePath($path): void
|
||||
{
|
||||
$this->basePath = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats query parameters to Swagger-compliant form.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function deriveQueryParameters(array $input)
|
||||
{
|
||||
$parameters = [];
|
||||
|
||||
foreach ($input as $name => $prop) {
|
||||
if (!isset($prop['dataType'])) {
|
||||
$prop['dataType'] = 'string';
|
||||
}
|
||||
$parameters[] = [
|
||||
'paramType' => 'query',
|
||||
'name' => $name,
|
||||
'type' => $this->typeMap[$prop['dataType']] ?? 'string',
|
||||
'description' => $prop['description'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Swagger-compliant parameter list from the provided parameter array. Models are built when necessary.
|
||||
*
|
||||
* @param string $paramType
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function deriveParameters(array $input, $paramType = 'form')
|
||||
{
|
||||
$parameters = [];
|
||||
|
||||
foreach ($input as $name => $prop) {
|
||||
$type = null;
|
||||
$format = null;
|
||||
$ref = null;
|
||||
$enum = null;
|
||||
$items = null;
|
||||
|
||||
if (!isset($prop['actualType'])) {
|
||||
$prop['actualType'] = 'string';
|
||||
}
|
||||
|
||||
if (isset($this->typeMap[$prop['actualType']])) {
|
||||
$type = $this->typeMap[$prop['actualType']];
|
||||
} else {
|
||||
switch ($prop['actualType']) {
|
||||
case DataTypes::ENUM:
|
||||
$type = 'string';
|
||||
if (isset($prop['format'])) {
|
||||
$enum = explode('|', rtrim(ltrim($prop['format'], '['), ']'));
|
||||
}
|
||||
break;
|
||||
|
||||
case DataTypes::MODEL:
|
||||
$ref =
|
||||
$this->registerModel(
|
||||
$prop['subType'],
|
||||
$prop['children'] ?? null,
|
||||
$prop['description'] ?: $prop['dataType']
|
||||
);
|
||||
break;
|
||||
|
||||
case DataTypes::COLLECTION:
|
||||
$type = 'array';
|
||||
if (null === $prop['subType']) {
|
||||
$items = ['type' => 'string'];
|
||||
} elseif (isset($this->typeMap[$prop['subType']])) {
|
||||
$items = ['type' => $this->typeMap[$prop['subType']]];
|
||||
} else {
|
||||
$ref =
|
||||
$this->registerModel(
|
||||
$prop['subType'],
|
||||
$prop['children'] ?? null,
|
||||
$prop['description'] ?: $prop['dataType']
|
||||
);
|
||||
$items = [
|
||||
'$ref' => $ref,
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->formatMap[$prop['actualType']])) {
|
||||
$format = $this->formatMap[$prop['actualType']];
|
||||
}
|
||||
|
||||
if (null === $type && null === $ref) {
|
||||
/* `type` or `$ref` is required. Continue to next of none of these was determined. */
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameter = [
|
||||
'paramType' => $paramType,
|
||||
'name' => $name,
|
||||
];
|
||||
|
||||
if (null !== $type) {
|
||||
$parameter['type'] = $type;
|
||||
}
|
||||
|
||||
if (null !== $ref) {
|
||||
$parameter['$ref'] = $ref;
|
||||
$parameter['type'] = $ref;
|
||||
}
|
||||
|
||||
if (null !== $format) {
|
||||
$parameter['format'] = $format;
|
||||
}
|
||||
|
||||
if (is_array($enum) && count($enum) > 0) {
|
||||
$parameter['enum'] = $enum;
|
||||
}
|
||||
|
||||
if (isset($prop['default'])) {
|
||||
$parameter['defaultValue'] = $prop['default'];
|
||||
}
|
||||
|
||||
if (isset($items)) {
|
||||
$parameter['items'] = $items;
|
||||
}
|
||||
|
||||
if (isset($prop['description'])) {
|
||||
$parameter['description'] = $prop['description'];
|
||||
}
|
||||
|
||||
$parameters[] = $parameter;
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a model into the model array. Returns a unique identifier for the model to be used in `$ref` properties.
|
||||
*
|
||||
* @param string $description
|
||||
*
|
||||
* @internal param $models
|
||||
*/
|
||||
public function registerModel($className, ?array $parameters = null, $description = '')
|
||||
{
|
||||
return $this->modelRegistry->register($className, $parameters, $description);
|
||||
}
|
||||
|
||||
public function setSwaggerVersion($swaggerVersion): void
|
||||
{
|
||||
$this->swaggerVersion = $swaggerVersion;
|
||||
}
|
||||
|
||||
public function setApiVersion($apiVersion): void
|
||||
{
|
||||
$this->apiVersion = $apiVersion;
|
||||
}
|
||||
|
||||
public function setInfo($info): void
|
||||
{
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the base path from a URL path.
|
||||
*/
|
||||
protected function stripBasePath($basePath)
|
||||
{
|
||||
if ('/' === $this->basePath) {
|
||||
return $basePath;
|
||||
}
|
||||
|
||||
$path = sprintf('#^%s#', preg_quote($this->basePath));
|
||||
$subPath = preg_replace($path, '', $basePath);
|
||||
|
||||
return $subPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate nicknames based on support HTTP methods and the resource name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateNickname($method, $resource)
|
||||
{
|
||||
$resource = preg_replace('#/^#', '', $resource);
|
||||
$resource = $this->normalizeResourcePath($resource);
|
||||
|
||||
return sprintf('%s_%s', strtolower($method ?: ''), $resource);
|
||||
}
|
||||
}
|
19
Makefile
Normal file
19
Makefile
Normal file
|
@ -0,0 +1,19 @@
|
|||
ifneq (,$(shell (type docker-compose 2>&1 >/dev/null && echo 1) || true))
|
||||
PHP=docker-compose run --rm --no-deps php
|
||||
else
|
||||
PHP=php
|
||||
endif
|
||||
|
||||
PHP_CONSOLE_DEPS=vendor
|
||||
|
||||
vendor: composer.json
|
||||
@$(PHP) composer install -o -n --no-ansi
|
||||
@touch vendor || true
|
||||
|
||||
php-cs: $(PHP_CONSOLE_DEPS)
|
||||
@$(PHP) vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no -v
|
||||
|
||||
phpunit: $(PHP_CONSOLE_DEPS)
|
||||
@$(PHP) vendor/bin/phpunit --color=always
|
||||
|
||||
check: phpunit
|
|
@ -2,20 +2,24 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Nelmio\ApiDocBundle\DependencyInjection\ExtractorHandlerCompilerPass;
|
||||
use Nelmio\ApiDocBundle\DependencyInjection\FormInfoParserCompilerPass;
|
||||
use Nelmio\ApiDocBundle\DependencyInjection\LoadExtractorParsersPass;
|
||||
use Nelmio\ApiDocBundle\DependencyInjection\RegisterExtractorParsersPass;
|
||||
use Nelmio\ApiDocBundle\DependencyInjection\ExtractorHandlerCompilerPass;
|
||||
use Nelmio\ApiDocBundle\DependencyInjection\SwaggerConfigCompilerPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class NelmioApiDocBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container)
|
||||
public function build(ContainerBuilder $container): void
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
$container->addCompilerPass(new LoadExtractorParsersPass());
|
||||
$container->addCompilerPass(new RegisterExtractorParsersPass());
|
||||
$container->addCompilerPass(new ExtractorHandlerCompilerPass());
|
||||
$container->addCompilerPass(new SwaggerConfigCompilerPass());
|
||||
$container->addCompilerPass(new FormInfoParserCompilerPass());
|
||||
}
|
||||
}
|
||||
|
|
74
Parser/CollectionParser.php
Normal file
74
Parser/CollectionParser.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
|
||||
/**
|
||||
* Handles models that are specified as collections.
|
||||
*
|
||||
* @author Bez Hermoso <bez@activelamp.com>
|
||||
*/
|
||||
class CollectionParser implements ParserInterface, PostParserInterface
|
||||
{
|
||||
/**
|
||||
* Return true/false whether this class supports parsing the given class.
|
||||
*
|
||||
* @param array $item containing the following fields: class, groups. Of which groups is optional
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(array $item)
|
||||
{
|
||||
return isset($item['collection']) && true === $item['collection'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This doesn't parse anything at this stage.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse(array $item)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $item The string type of input to parse.
|
||||
* @param array $parameters The previously-parsed parameters array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function postParse(array $item, array $parameters)
|
||||
{
|
||||
$origParameters = $parameters;
|
||||
|
||||
foreach ($parameters as $name => $body) {
|
||||
$parameters[$name] = null;
|
||||
}
|
||||
|
||||
$collectionName = $item['collectionName'] ?? '';
|
||||
|
||||
$parameters[$collectionName] = [
|
||||
'dataType' => null, // Delegates to ApiDocExtractor#generateHumanReadableTypes
|
||||
'subType' => $item['class'],
|
||||
'actualType' => DataTypes::COLLECTION,
|
||||
'readonly' => true,
|
||||
'required' => true,
|
||||
'default' => true,
|
||||
'description' => '',
|
||||
'children' => $origParameters,
|
||||
];
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
}
|
124
Parser/FormErrorsParser.php
Normal file
124
Parser/FormErrorsParser.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
|
||||
/**
|
||||
* @author Bez Hermoso <bezalelhermoso@gmail.com>
|
||||
*/
|
||||
class FormErrorsParser implements ParserInterface, PostParserInterface
|
||||
{
|
||||
/**
|
||||
* Return true/false whether this class supports parsing the given class.
|
||||
*
|
||||
* @param array $item containing the following fields: class, groups. Of which groups is optional
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(array $item)
|
||||
{
|
||||
return isset($item['form_errors']) && true === $item['form_errors'];
|
||||
}
|
||||
|
||||
public function parse(array $item)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the root parameters to contain these parameters instead:
|
||||
* - status_code: 400
|
||||
* - message: "Validation failed"
|
||||
* - errors: contains the original parameters, but all types are changed to array of strings (array of errors for each field)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function postParse(array $item, array $parameters)
|
||||
{
|
||||
$params = $parameters;
|
||||
|
||||
foreach ($params as $name => $data) {
|
||||
$params[$name] = null;
|
||||
}
|
||||
|
||||
$params['status_code'] = [
|
||||
'dataType' => 'integer',
|
||||
'actualType' => DataTypes::INTEGER,
|
||||
'subType' => null,
|
||||
'required' => false,
|
||||
'description' => 'The status code',
|
||||
'readonly' => true,
|
||||
'default' => 400,
|
||||
];
|
||||
|
||||
$params['message'] = [
|
||||
'dataType' => 'string',
|
||||
'actualType' => DataTypes::STRING,
|
||||
'subType' => null,
|
||||
'required' => false,
|
||||
'description' => 'The error message',
|
||||
'default' => 'Validation failed.',
|
||||
];
|
||||
|
||||
$params['errors'] = [
|
||||
'dataType' => 'errors',
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'subType' => sprintf('%s.FormErrors', $item['class']),
|
||||
'required' => false,
|
||||
'description' => 'Errors',
|
||||
'readonly' => true,
|
||||
'children' => $this->doPostParse($parameters),
|
||||
];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
protected function doPostParse(array $parameters, $attachFieldErrors = true, array $propertyPath = [])
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($parameters as $name => $parameter) {
|
||||
$data[$name] = [
|
||||
'dataType' => 'parameter errors',
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'subType' => 'FieldErrors',
|
||||
'required' => false,
|
||||
'description' => 'Errors on the parameter',
|
||||
'readonly' => true,
|
||||
'children' => [
|
||||
'errors' => [
|
||||
'dataType' => 'array of errors',
|
||||
'actualType' => DataTypes::COLLECTION,
|
||||
'subType' => 'string',
|
||||
'required' => false,
|
||||
'dscription' => '',
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (DataTypes::MODEL === $parameter['actualType']) {
|
||||
$propertyPath[] = $name;
|
||||
$data[$name]['subType'] = sprintf('%s.FieldErrors[%s]', $parameter['subType'], implode('.', $propertyPath));
|
||||
$data[$name]['children'] = $this->doPostParse($parameter['children'], $attachFieldErrors, $propertyPath);
|
||||
} else {
|
||||
if (false === $attachFieldErrors) {
|
||||
unset($data[$name]['children']);
|
||||
}
|
||||
$attachFieldErrors = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
11
Parser/FormInfoParser.php
Normal file
11
Parser/FormInfoParser.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
use Symfony\Component\Form\FormConfigInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
interface FormInfoParser
|
||||
{
|
||||
public function parseFormType(FormTypeInterface $type, FormConfigInterface $config): ?array;
|
||||
}
|
|
@ -12,60 +12,125 @@
|
|||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\Exception\FormException;
|
||||
use Symfony\Component\Form\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FormConfigInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
use Symfony\Component\Form\ResolvedFormTypeInterface;
|
||||
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\Exception\FormException;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class FormTypeParser implements ParserInterface
|
||||
{
|
||||
/**
|
||||
* @var \Symfony\Component\Form\FormFactoryInterface
|
||||
* @var FormFactoryInterface
|
||||
*/
|
||||
protected $formFactory;
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\Form\FormRegistry
|
||||
* @var \Symfony\Component\Form\FormRegistry
|
||||
*/
|
||||
protected $formRegistry;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @var \Symfony\Component\Translation\TranslatorInterface
|
||||
*/
|
||||
protected $mapTypes = array(
|
||||
'text' => DataTypes::STRING,
|
||||
'date' => DataTypes::DATE,
|
||||
'datetime' => DataTypes::DATETIME,
|
||||
'checkbox' => DataTypes::BOOLEAN,
|
||||
'time' => DataTypes::TIME,
|
||||
'number' => DataTypes::FLOAT,
|
||||
'integer' => DataTypes::INTEGER,
|
||||
'textarea' => DataTypes::STRING,
|
||||
'country' => DataTypes::STRING,
|
||||
'choice' => DataTypes::ENUM,
|
||||
'file' => DataTypes::FILE,
|
||||
);
|
||||
|
||||
public function __construct(FormFactoryInterface $formFactory)
|
||||
{
|
||||
$this->formFactory = $formFactory;
|
||||
}
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @var bool
|
||||
*/
|
||||
protected $entityToChoice;
|
||||
|
||||
/**
|
||||
* @var array|FormInfoParser[]
|
||||
*/
|
||||
protected $formInfoParsers = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @deprecated since 2.12, to be removed in 3.0. Use $extendedMapTypes instead.
|
||||
*/
|
||||
protected $mapTypes = [
|
||||
'text' => DataTypes::STRING,
|
||||
'date' => DataTypes::DATE,
|
||||
'datetime' => DataTypes::DATETIME,
|
||||
'checkbox' => DataTypes::BOOLEAN,
|
||||
'time' => DataTypes::TIME,
|
||||
'number' => DataTypes::FLOAT,
|
||||
'integer' => DataTypes::INTEGER,
|
||||
'textarea' => DataTypes::STRING,
|
||||
'country' => DataTypes::STRING,
|
||||
'choice' => DataTypes::ENUM,
|
||||
'file' => DataTypes::FILE,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $extendedMapTypes = [
|
||||
DataTypes::STRING => [
|
||||
'text',
|
||||
'Symfony\Component\Form\Extension\Core\Type\TextType',
|
||||
'textarea',
|
||||
'Symfony\Component\Form\Extension\Core\Type\TextareaType',
|
||||
'country',
|
||||
'Symfony\Component\Form\Extension\Core\Type\CountryType',
|
||||
],
|
||||
DataTypes::DATE => [
|
||||
'date',
|
||||
'Symfony\Component\Form\Extension\Core\Type\DateType',
|
||||
],
|
||||
DataTypes::DATETIME => [
|
||||
'datetime',
|
||||
'Symfony\Component\Form\Extension\Core\Type\DatetimeType',
|
||||
],
|
||||
DataTypes::BOOLEAN => [
|
||||
'checkbox',
|
||||
'Symfony\Component\Form\Extension\Core\Type\CheckboxType',
|
||||
],
|
||||
DataTypes::TIME => [
|
||||
'time',
|
||||
'Symfony\Component\Form\Extension\Core\Type\TimeType',
|
||||
],
|
||||
DataTypes::FLOAT => [
|
||||
'number',
|
||||
'Symfony\Component\Form\Extension\Core\Type\NumberType',
|
||||
],
|
||||
DataTypes::INTEGER => [
|
||||
'integer',
|
||||
'Symfony\Component\Form\Extension\Core\Type\IntegerType',
|
||||
],
|
||||
DataTypes::ENUM => [
|
||||
'choice',
|
||||
'Symfony\Component\Form\Extension\Core\Type\ChoiceType',
|
||||
],
|
||||
DataTypes::FILE => [
|
||||
'file',
|
||||
'Symfony\Component\Form\Extension\Core\Type\FileType',
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(FormFactoryInterface $formFactory, TranslatorInterface $translator, $entityToChoice)
|
||||
{
|
||||
$this->formFactory = $formFactory;
|
||||
$this->translator = $translator;
|
||||
$this->entityToChoice = (bool) $entityToChoice;
|
||||
}
|
||||
|
||||
public function supports(array $item)
|
||||
{
|
||||
$className = $item['class'];
|
||||
$options = $item['options'];
|
||||
|
||||
try {
|
||||
if ($this->createForm($className)) {
|
||||
if ($this->createForm($className, null, $options)) {
|
||||
return true;
|
||||
}
|
||||
} catch (FormException $e) {
|
||||
|
@ -77,81 +142,153 @@ class FormTypeParser implements ParserInterface
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(array $item)
|
||||
{
|
||||
$type = $item['class'];
|
||||
$options = $item['options'];
|
||||
|
||||
if ($this->implementsType($type)) {
|
||||
$type = $this->getTypeInstance($type);
|
||||
try {
|
||||
$form = $this->formFactory->create($type, null, $options);
|
||||
}
|
||||
// TODO: find a better exception to catch
|
||||
catch (\Exception $exception) {
|
||||
if (!LegacyFormHelper::isLegacy()) {
|
||||
@trigger_error('Using FormTypeInterface instance with required arguments without defining them as service is deprecated in symfony 2.8 and removed in 3.0.', E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create($type);
|
||||
if (!isset($form)) {
|
||||
if (!LegacyFormHelper::hasBCBreaks() && $this->implementsType($type)) {
|
||||
$type = $this->getTypeInstance($type);
|
||||
$form = $this->formFactory->create($type, null, $options);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Unsupported form type class.');
|
||||
}
|
||||
}
|
||||
|
||||
$name = array_key_exists('name', $item) ? $item['name'] : $form->getName();
|
||||
|
||||
$options = null;
|
||||
$name = array_key_exists('name', $item)
|
||||
? $item['name']
|
||||
: (method_exists($form, 'getBlockPrefix') ? $form->getBlockPrefix() : $form->getName());
|
||||
|
||||
if (empty($name)) {
|
||||
return $this->parseForm($form);
|
||||
}
|
||||
|
||||
$subType = is_object($type) ? get_class($type) : $type;
|
||||
$subType = is_object($type) ? $type::class : $type;
|
||||
|
||||
if (class_exists($subType)) {
|
||||
if ($subType && class_exists($subType)) {
|
||||
$parts = explode('\\', $subType);
|
||||
$dataType = sprintf('object (%s)', end($parts));
|
||||
} else {
|
||||
$dataType = sprintf('object (%s)', $subType);
|
||||
}
|
||||
|
||||
return array(
|
||||
$name => array(
|
||||
'required' => true,
|
||||
'readonly' => false,
|
||||
return [
|
||||
$name => [
|
||||
'required' => true,
|
||||
'readonly' => false,
|
||||
'description' => '',
|
||||
'default' => null,
|
||||
'dataType' => $dataType,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'subType' => $subType,
|
||||
'children' => $this->parseForm($form),
|
||||
),
|
||||
);
|
||||
'default' => null,
|
||||
'dataType' => $dataType,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'subType' => $subType,
|
||||
'children' => $this->parseForm($form),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function addFormInfoParser(FormInfoParser $formInfoParser): void
|
||||
{
|
||||
$class = $formInfoParser::class;
|
||||
if (isset($this->formInfoParsers[$class])) {
|
||||
throw new \InvalidArgumentException($class . ' already added');
|
||||
}
|
||||
|
||||
$this->formInfoParsers[$class] = $formInfoParser;
|
||||
}
|
||||
|
||||
private function parseFormType(FormTypeInterface $type, FormConfigInterface $config): ?array
|
||||
{
|
||||
foreach ($this->formInfoParsers as $parser) {
|
||||
$customInfo = $parser->parseFormType($type, $config);
|
||||
if ($customInfo) {
|
||||
return $customInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getDataType($type)
|
||||
{
|
||||
foreach ($this->extendedMapTypes as $data => $types) {
|
||||
if (in_array($type, $types)) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseForm($form)
|
||||
{
|
||||
$parameters = array();
|
||||
$parameters = [];
|
||||
$domain = $form->getConfig()->getOption('translation_domain');
|
||||
|
||||
foreach ($form as $name => $child) {
|
||||
$config = $child->getConfig();
|
||||
$bestType = '';
|
||||
$config = $child->getConfig();
|
||||
$options = $config->getOptions();
|
||||
$bestType = '';
|
||||
$actualType = null;
|
||||
$subType = null;
|
||||
$children = null;
|
||||
$subType = null;
|
||||
$children = null;
|
||||
|
||||
for ($type = $config->getType();
|
||||
$type instanceof FormInterface || $type instanceof ResolvedFormTypeInterface;
|
||||
$type = $type->getParent()
|
||||
$type instanceof FormInterface || $type instanceof ResolvedFormTypeInterface;
|
||||
$type = $type->getParent()
|
||||
) {
|
||||
if (isset($this->mapTypes[$type->getName()])) {
|
||||
$bestType = $this->mapTypes[$type->getName()];
|
||||
$actualType = $bestType;
|
||||
} elseif ('collection' === $type->getName()) {
|
||||
if (is_string($config->getOption('type')) && isset($this->mapTypes[$config->getOption('type')])) {
|
||||
$subType = $this->mapTypes[$config->getOption('type')];
|
||||
$customInfo = $this->parseFormType($type->getInnerType(), $config);
|
||||
if ($customInfo) {
|
||||
$parameters[$name] = array_merge([
|
||||
'dataType' => 'string',
|
||||
'actualType' => 'string',
|
||||
'default' => $config->getData(),
|
||||
'required' => $config->getRequired(),
|
||||
'description' => $this->getFormDescription($config, $domain),
|
||||
'readonly' => $config->getDisabled(),
|
||||
], $customInfo);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$typeName = method_exists($type, 'getBlockPrefix') ?
|
||||
$type->getBlockPrefix() : $type->getName();
|
||||
|
||||
$dataType = $this->getDataType($typeName);
|
||||
if (null !== $dataType) {
|
||||
$actualType = $bestType = $dataType;
|
||||
} elseif ('collection' === $typeName) {
|
||||
// BC sf < 2.8
|
||||
$typeOption = $config->hasOption('entry_type') ? $config->getOption('entry_type') : $config->getOption('type');
|
||||
|
||||
if (is_object($typeOption)) {
|
||||
$typeOption = method_exists($typeOption, 'getBlockPrefix') ?
|
||||
$typeOption->getBlockPrefix() : $typeOption->getName();
|
||||
}
|
||||
|
||||
$dataType = $this->getDataType($typeOption);
|
||||
if (null !== $dataType) {
|
||||
$subType = $dataType;
|
||||
$actualType = DataTypes::COLLECTION;
|
||||
$bestType = sprintf('array of %ss', $subType);
|
||||
$bestType = sprintf('array of %ss', $subType);
|
||||
} else {
|
||||
// Embedded form collection
|
||||
$embbededType = $config->getOption('type');
|
||||
$subForm = $this->formFactory->create($embbededType, null, $config->getOption('options', array()));
|
||||
$children = $this->parseForm($subForm);
|
||||
// BC sf < 2.8
|
||||
$embbededType = $config->hasOption('entry_type') ? $config->getOption('entry_type') : $config->getOption('type');
|
||||
$subForm = $this->formFactory->create($embbededType, null, $config->getOption('entry_options', []));
|
||||
$children = $this->parseForm($subForm);
|
||||
$actualType = DataTypes::COLLECTION;
|
||||
$subType = is_object($embbededType) ? get_class($embbededType) : $embbededType;
|
||||
$subType = is_object($embbededType) ? $embbededType::class : $embbededType;
|
||||
|
||||
if (class_exists($subType)) {
|
||||
if ($subType && class_exists($subType)) {
|
||||
$parts = explode('\\', $subType);
|
||||
$bestType = sprintf('array of objects (%s)', end($parts));
|
||||
} else {
|
||||
|
@ -171,27 +308,39 @@ class FormTypeParser implements ParserInterface
|
|||
*/
|
||||
$addDefault = false;
|
||||
try {
|
||||
$subForm = $this->formFactory->create($type);
|
||||
if (isset($subForm)) {
|
||||
unset($subForm);
|
||||
}
|
||||
|
||||
if (LegacyFormHelper::hasBCBreaks()) {
|
||||
try {
|
||||
$subForm = $this->formFactory->create($type::class, null, $options);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
if (!isset($subForm)) {
|
||||
$subForm = $this->formFactory->create($type, null, $options);
|
||||
}
|
||||
|
||||
$subParameters = $this->parseForm($subForm, $name);
|
||||
|
||||
if (!empty($subParameters)) {
|
||||
$children = $subParameters;
|
||||
$config = $subForm->getConfig();
|
||||
$subType = get_class($type);
|
||||
$parts = explode('\\', $subType);
|
||||
$config = $subForm->getConfig();
|
||||
$subType = $type::class;
|
||||
$parts = explode('\\', $subType);
|
||||
$bestType = sprintf('object (%s)', end($parts));
|
||||
|
||||
$parameters[$name] = array(
|
||||
'dataType' => $bestType,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'default' => null,
|
||||
'subType' => $subType,
|
||||
'required' => $config->getRequired(),
|
||||
'description' => ($config->getOption('description')) ? $config->getOption('description'):$config->getOption('label'),
|
||||
'readonly' => $config->getDisabled(),
|
||||
'children' => $children,
|
||||
);
|
||||
|
||||
$parameters[$name] = [
|
||||
'dataType' => $bestType,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'default' => null,
|
||||
'subType' => $subType,
|
||||
'required' => $config->getRequired(),
|
||||
'description' => $this->getFormDescription($config, $domain),
|
||||
'readonly' => $config->getDisabled(),
|
||||
'children' => $children,
|
||||
];
|
||||
} else {
|
||||
$addDefault = true;
|
||||
}
|
||||
|
@ -200,14 +349,14 @@ class FormTypeParser implements ParserInterface
|
|||
}
|
||||
|
||||
if ($addDefault) {
|
||||
$parameters[$name] = array(
|
||||
'dataType' => 'string',
|
||||
'actualType' => 'string',
|
||||
'default' => $config->getData(),
|
||||
'required' => $config->getRequired(),
|
||||
'description' => ($config->getOption('description')) ? $config->getOption('description'):$config->getOption('label'),
|
||||
'readonly' => $config->getDisabled(),
|
||||
);
|
||||
$parameters[$name] = [
|
||||
'dataType' => 'string',
|
||||
'actualType' => 'string',
|
||||
'default' => $config->getData(),
|
||||
'required' => $config->getRequired(),
|
||||
'description' => $this->getFormDescription($config, $domain),
|
||||
'readonly' => $config->getDisabled(),
|
||||
];
|
||||
}
|
||||
|
||||
continue;
|
||||
|
@ -215,15 +364,15 @@ class FormTypeParser implements ParserInterface
|
|||
}
|
||||
}
|
||||
|
||||
$parameters[$name] = array(
|
||||
'dataType' => $bestType,
|
||||
'actualType' => $actualType,
|
||||
'subType' => $subType,
|
||||
'default' => $config->getData(),
|
||||
'required' => $config->getRequired(),
|
||||
'description' => ($config->getOption('description')) ? $config->getOption('description'):$config->getOption('label'),
|
||||
'readonly' => $config->getDisabled(),
|
||||
);
|
||||
$parameters[$name] = [
|
||||
'dataType' => $bestType,
|
||||
'actualType' => $actualType,
|
||||
'subType' => $subType,
|
||||
'default' => $config->getData(),
|
||||
'required' => $config->getRequired(),
|
||||
'description' => $this->getFormDescription($config, $domain),
|
||||
'readonly' => $config->getDisabled(),
|
||||
];
|
||||
|
||||
if (null !== $children) {
|
||||
$parameters[$name]['children'] = $children;
|
||||
|
@ -252,9 +401,24 @@ class FormTypeParser implements ParserInterface
|
|||
}
|
||||
|
||||
if (($choices = $config->getOption('choices')) && is_array($choices) && count($choices)) {
|
||||
$parameters[$name]['format'] = json_encode($choices);
|
||||
} elseif (($choiceList = $config->getOption('choice_list')) && $choiceList instanceof ChoiceListInterface) {
|
||||
$choices = $this->handleChoiceListValues($choiceList);
|
||||
$choices = $config->getOption('choices_as_values') ?
|
||||
array_values($choices) :
|
||||
array_keys($choices);
|
||||
sort($choices);
|
||||
$parameters[$name]['format'] = '[' . implode('|', $choices) . ']';
|
||||
} elseif ($choiceList = $config->getOption('choice_list')) {
|
||||
$choiceListType = $config->getType();
|
||||
$choiceListName = method_exists($choiceListType, 'getBlockPrefix') ?
|
||||
$choiceListType->getBlockPrefix() : $choiceListType->getName();
|
||||
|
||||
if ('entity' === $choiceListName && false === $this->entityToChoice) {
|
||||
$choices = [];
|
||||
} else {
|
||||
// TODO: fixme
|
||||
// does not work since: https://github.com/symfony/symfony/commit/03efce1b568379eac21d880e427090e43035f505
|
||||
$choices = [];
|
||||
}
|
||||
|
||||
if (is_array($choices) && count($choices)) {
|
||||
$parameters[$name]['format'] = json_encode($choices);
|
||||
}
|
||||
|
@ -268,7 +432,7 @@ class FormTypeParser implements ParserInterface
|
|||
|
||||
private function implementsType($item)
|
||||
{
|
||||
if (!class_exists($item)) {
|
||||
if (null === $item || !class_exists($item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -290,27 +454,24 @@ class FormTypeParser implements ParserInterface
|
|||
return $refl->newInstance();
|
||||
}
|
||||
|
||||
private function createForm($item)
|
||||
private function createForm($type, $data = null, array $options = [])
|
||||
{
|
||||
if ($this->implementsType($item)) {
|
||||
$type = $this->getTypeInstance($item);
|
||||
|
||||
return $this->formFactory->create($type);
|
||||
try {
|
||||
return $this->formFactory->create($type, null, $options);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->formFactory->create($item);
|
||||
} catch (UnexpectedTypeException $e) {
|
||||
// nothing
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// nothing
|
||||
if (!LegacyFormHelper::hasBCBreaks() && !isset($form) && $this->implementsType($type)) {
|
||||
$type = $this->getTypeInstance($type);
|
||||
|
||||
return $this->formFactory->create($type, null, $options);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleChoiceListValues(ChoiceListInterface $choiceList)
|
||||
{
|
||||
$choices = array();
|
||||
foreach (array($choiceList->getPreferredViews(), $choiceList->getRemainingViews()) as $viewList) {
|
||||
$choices = [];
|
||||
foreach ([$choiceList->getPreferredViews(), $choiceList->getRemainingViews()] as $viewList) {
|
||||
$choices = array_merge($choices, $this->handleChoiceViewsHierarchy($viewList));
|
||||
}
|
||||
|
||||
|
@ -319,7 +480,7 @@ class FormTypeParser implements ParserInterface
|
|||
|
||||
private function handleChoiceViewsHierarchy(array $choiceViews)
|
||||
{
|
||||
$choices = array();
|
||||
$choices = [];
|
||||
foreach ($choiceViews as $item) {
|
||||
if ($item instanceof ChoiceView) {
|
||||
$choices[$item->value] = $item->label;
|
||||
|
@ -330,4 +491,16 @@ class FormTypeParser implements ParserInterface
|
|||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
private function getFormDescription($config, $domain = null)
|
||||
{
|
||||
$description = ($config->getOption('description'))
|
||||
?: $config->getOption('label');
|
||||
|
||||
if (null != $description) {
|
||||
return $this->translator->trans($description, [], $domain);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
|
||||
use JMS\Serializer\Metadata\PropertyMetadata;
|
||||
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
|
||||
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
|
||||
use JMS\Serializer\SerializationContext;
|
||||
use Metadata\MetadataFactoryInterface;
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
|
||||
use JMS\Serializer\Metadata\PropertyMetadata;
|
||||
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
|
||||
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
|
||||
|
||||
/**
|
||||
* Uses the JMS metadata factory to extract input/output model information
|
||||
|
@ -26,7 +26,7 @@ use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
|
|||
class JmsMetadataParser implements ParserInterface, PostParserInterface
|
||||
{
|
||||
/**
|
||||
* @var \Metadata\MetadataFactoryInterface
|
||||
* @var MetadataFactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
|
@ -36,11 +36,11 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
private $namingStrategy;
|
||||
|
||||
/**
|
||||
* @var \Nelmio\ApiDocBundle\Util\DocCommentExtractor
|
||||
* @var DocCommentExtractor
|
||||
*/
|
||||
private $commentExtractor;
|
||||
|
||||
private $typeMap = array(
|
||||
private $typeMap = [
|
||||
'integer' => DataTypes::INTEGER,
|
||||
'boolean' => DataTypes::BOOLEAN,
|
||||
'string' => DataTypes::STRING,
|
||||
|
@ -48,23 +48,21 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
'double' => DataTypes::FLOAT,
|
||||
'array' => DataTypes::COLLECTION,
|
||||
'DateTime' => DataTypes::DATETIME,
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor, requires JMS Metadata factory
|
||||
*/
|
||||
public function __construct(
|
||||
MetadataFactoryInterface $factory,
|
||||
PropertyNamingStrategyInterface $namingStrategy,
|
||||
DocCommentExtractor $commentExtractor
|
||||
DocCommentExtractor $commentExtractor,
|
||||
) {
|
||||
$this->factory = $factory;
|
||||
$this->namingStrategy = $namingStrategy;
|
||||
$this->commentExtractor = $commentExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supports(array $input)
|
||||
{
|
||||
$className = $input['class'];
|
||||
|
@ -79,47 +77,75 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(array $input)
|
||||
{
|
||||
$className = $input['class'];
|
||||
$groups = $input['groups'];
|
||||
$groups = $input['groups'];
|
||||
|
||||
return $this->doParse($className, array(), $groups);
|
||||
$result = $this->doParse($className, [], $groups);
|
||||
|
||||
if (!isset($input['name']) || empty($input['name'])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($className && class_exists($className)) {
|
||||
$parts = explode('\\', $className);
|
||||
$dataType = sprintf('object (%s)', end($parts));
|
||||
} else {
|
||||
$dataType = sprintf('object (%s)', $className);
|
||||
}
|
||||
|
||||
return [
|
||||
$input['name'] => [
|
||||
'required' => null,
|
||||
'readonly' => null,
|
||||
'default' => null,
|
||||
'dataType' => $dataType,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'subType' => $dataType,
|
||||
'children' => $result,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively parse all metadata for a class
|
||||
*
|
||||
* @param string $className Class to get all metadata for
|
||||
* @param array $visited Classes we've already visited to prevent infinite recursion.
|
||||
* @param array $groups Serialization groups to include.
|
||||
* @return array metadata for given class
|
||||
* @param string $className Class to get all metadata for
|
||||
* @param array $visited Classes we've already visited to prevent infinite recursion.
|
||||
* @param array $groups Serialization groups to include.
|
||||
*
|
||||
* @return array metadata for given class
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function doParse($className, $visited = array(), array $groups = array())
|
||||
protected function doParse($className, $visited = [], array $groups = [])
|
||||
{
|
||||
$meta = $this->factory->getMetadataForClass($className);
|
||||
|
||||
if (null === $meta) {
|
||||
throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $className));
|
||||
throw new \InvalidArgumentException(sprintf('No metadata found for class %s', $className));
|
||||
}
|
||||
|
||||
$exclusionStrategies = array();
|
||||
$exclusionStrategies = [];
|
||||
if ($groups) {
|
||||
$exclusionStrategies[] = new GroupsExclusionStrategy($groups);
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$params = [];
|
||||
|
||||
$reflection = new \ReflectionClass($className);
|
||||
$defaultProperties = $reflection->getDefaultProperties();
|
||||
$defaultProperties = array_map(function ($default) {
|
||||
if (is_array($default) && 0 === count($default)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}, $reflection->getDefaultProperties());
|
||||
|
||||
// iterate over property metadata
|
||||
foreach ($meta->propertyMetadata as $item) {
|
||||
if (!is_null($item->type)) {
|
||||
if (null !== $item->type) {
|
||||
$name = $this->namingStrategy->translateName($item);
|
||||
|
||||
$dataType = $this->processDataType($item);
|
||||
|
@ -132,26 +158,26 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
}
|
||||
|
||||
if (!$dataType['inline']) {
|
||||
$params[$name] = array(
|
||||
'dataType' => $dataType['normalized'],
|
||||
'actualType' => $dataType['actualType'],
|
||||
'subType' => $dataType['class'],
|
||||
'required' => false,
|
||||
'default' => isset($defaultProperties[$item->name]) ? $defaultProperties[$item->name] : null,
|
||||
//TODO: can't think of a good way to specify this one, JMS doesn't have a setting for this
|
||||
'description' => $this->getDescription($item),
|
||||
'readonly' => $item->readOnly,
|
||||
$params[$name] = [
|
||||
'dataType' => $dataType['normalized'],
|
||||
'actualType' => $dataType['actualType'],
|
||||
'subType' => $dataType['class'],
|
||||
'required' => false,
|
||||
'default' => $defaultProperties[$item->name] ?? null,
|
||||
// TODO: can't think of a good way to specify this one, JMS doesn't have a setting for this
|
||||
'description' => $this->getDescription($item),
|
||||
'readonly' => $item->readOnly,
|
||||
'sinceVersion' => $item->sinceVersion,
|
||||
'untilVersion' => $item->untilVersion,
|
||||
);
|
||||
];
|
||||
|
||||
if (!is_null($dataType['class']) && false === $dataType['primitive']) {
|
||||
if (null !== $dataType['class'] && false === $dataType['primitive']) {
|
||||
$params[$name]['class'] = $dataType['class'];
|
||||
}
|
||||
}
|
||||
|
||||
// we can use type property also for custom handlers, then we don't have here real class name
|
||||
if (!class_exists($dataType['class'])) {
|
||||
if (!$dataType['class'] || !class_exists($dataType['class'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -163,7 +189,7 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
// check for nested classes with JMS metadata
|
||||
if ($dataType['class'] && false === $dataType['primitive'] && null !== $this->factory->getMetadataForClass($dataType['class'])) {
|
||||
$visited[] = $dataType['class'];
|
||||
$children = $this->doParse($dataType['class'], $visited, $groups);
|
||||
$children = $this->doParse($dataType['class'], $visited, $groups);
|
||||
|
||||
if ($dataType['inline']) {
|
||||
$params = array_merge($params, $children);
|
||||
|
@ -181,7 +207,6 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
* Figure out a normalized data type (for documentation), and get a
|
||||
* nested class name, if available.
|
||||
*
|
||||
* @param PropertyMetadata $item
|
||||
* @return array
|
||||
*/
|
||||
protected function processDataType(PropertyMetadata $item)
|
||||
|
@ -189,94 +214,89 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
// check for a type inside something that could be treated as an array
|
||||
if ($nestedType = $this->getNestedTypeInArray($item)) {
|
||||
if ($this->isPrimitive($nestedType)) {
|
||||
return array(
|
||||
'normalized' => sprintf("array of %ss", $nestedType),
|
||||
return [
|
||||
'normalized' => sprintf('array of %ss', $nestedType),
|
||||
'actualType' => DataTypes::COLLECTION,
|
||||
'class' => $this->typeMap[$nestedType],
|
||||
'primitive' => true,
|
||||
'inline' => false,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
$exp = explode("\\", $nestedType);
|
||||
$exp = explode('\\', $nestedType);
|
||||
|
||||
return array(
|
||||
'normalized' => sprintf("array of objects (%s)", end($exp)),
|
||||
return [
|
||||
'normalized' => sprintf('array of objects (%s)', end($exp)),
|
||||
'actualType' => DataTypes::COLLECTION,
|
||||
'class' => $nestedType,
|
||||
'primitive' => false,
|
||||
'inline' => false,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
$type = $item->type['name'];
|
||||
|
||||
// could be basic type
|
||||
if ($this->isPrimitive($type)) {
|
||||
return array(
|
||||
return [
|
||||
'normalized' => $type,
|
||||
'actualType' => $this->typeMap[$type],
|
||||
'class' => null,
|
||||
'primitive' => true,
|
||||
'inline' => false,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
// we can use type property also for custom handlers, then we don't have here real class name
|
||||
if (!class_exists($type)) {
|
||||
return array(
|
||||
'normalized' => sprintf("custom handler result for (%s)", $type),
|
||||
if (!$type || !class_exists($type)) {
|
||||
return [
|
||||
'normalized' => sprintf('custom handler result for (%s)', $type),
|
||||
'class' => $type,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'primitive' => false,
|
||||
'inline' => false,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
// if we got this far, it's a general class name
|
||||
$exp = explode("\\", $type);
|
||||
$exp = explode('\\', $type);
|
||||
|
||||
return array(
|
||||
'normalized' => sprintf("object (%s)", end($exp)),
|
||||
return [
|
||||
'normalized' => sprintf('object (%s)', end($exp)),
|
||||
'class' => $type,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'primitive' => false,
|
||||
'inline' => $item->inline,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function isPrimitive($type)
|
||||
{
|
||||
return in_array($type, array('boolean', 'integer', 'string', 'float', 'double', 'array', 'DateTime'));
|
||||
return in_array($type, ['boolean', 'integer', 'string', 'float', 'double', 'array', 'DateTime']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function postParse(array $input, array $parameters)
|
||||
{
|
||||
return $this->doPostParse($parameters);
|
||||
return $this->doPostParse($parameters, [], $input['groups'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive `doPostParse` to avoid circular post parsing.
|
||||
*
|
||||
* @param array $parameters
|
||||
* @param array $visited
|
||||
* @return array
|
||||
*/
|
||||
protected function doPostParse (array $parameters, array $visited = array())
|
||||
protected function doPostParse(array $parameters, array $visited = [], array $groups = [])
|
||||
{
|
||||
foreach ($parameters as $param => $data) {
|
||||
if (isset($data['class']) && isset($data['children']) && !in_array($data['class'], $visited)) {
|
||||
$visited[] = $data['class'];
|
||||
|
||||
$input = array('class' => $data['class'], 'groups' => isset($data['groups']) ? $data['groups'] : array());
|
||||
$input = ['class' => $data['class'], 'groups' => $data['groups'] ?? []];
|
||||
$parameters[$param]['children'] = array_merge(
|
||||
$parameters[$param]['children'], $this->doPostParse($parameters[$param]['children'], $visited)
|
||||
$parameters[$param]['children'], $this->doPostParse($parameters[$param]['children'], $visited, $groups)
|
||||
);
|
||||
$parameters[$param]['children'] = array_merge(
|
||||
$parameters[$param]['children'], $this->doParse($input['class'], $visited, $input['groups'])
|
||||
$parameters[$param]['children'], $this->doParse($input['class'], $visited, $groups)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -288,12 +308,11 @@ class JmsMetadataParser implements ParserInterface, PostParserInterface
|
|||
* Check the various ways JMS describes values in arrays, and
|
||||
* get the value type in the array
|
||||
*
|
||||
* @param PropertyMetadata $item
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getNestedTypeInArray(PropertyMetadata $item)
|
||||
{
|
||||
if (isset($item->type['name']) && in_array($item->type['name'], array('array', 'ArrayCollection'))) {
|
||||
if (isset($item->type['name']) && in_array($item->type['name'], ['array', 'ArrayCollection'])) {
|
||||
if (isset($item->type['params'][1]['name'])) {
|
||||
// E.g. array<string, MyNamespaceMyObject>
|
||||
return $item->type['params'][1]['name'];
|
||||
|
|
90
Parser/JsonSerializableParser.php
Normal file
90
Parser/JsonSerializableParser.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
/**
|
||||
* Created by mcfedr on 30/06/15 21:03
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
class JsonSerializableParser implements ParserInterface
|
||||
{
|
||||
public function supports(array $item)
|
||||
{
|
||||
if (!is_subclass_of($item['class'], 'JsonSerializable')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ref = new \ReflectionClass($item['class']);
|
||||
if ($ref->hasMethod('__construct')) {
|
||||
foreach ($ref->getMethod('__construct')->getParameters() as $parameter) {
|
||||
if (!$parameter->isOptional()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function parse(array $input)
|
||||
{
|
||||
/** @var \JsonSerializable $obj */
|
||||
$obj = new $input['class']();
|
||||
|
||||
$encoded = $obj->jsonSerialize();
|
||||
$parsed = $this->getItemMetaData($encoded);
|
||||
|
||||
if (isset($input['name']) && !empty($input['name'])) {
|
||||
$output = [];
|
||||
$output[$input['name']] = $parsed;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
return $parsed['children'];
|
||||
}
|
||||
|
||||
public function getItemMetaData($item)
|
||||
{
|
||||
$type = gettype($item);
|
||||
|
||||
$meta = [
|
||||
'dataType' => 'NULL' == $type ? null : $type,
|
||||
'actualType' => $type,
|
||||
'subType' => null,
|
||||
'required' => null,
|
||||
'description' => null,
|
||||
'readonly' => null,
|
||||
'default' => is_scalar($item) ? $item : null,
|
||||
];
|
||||
|
||||
if ('object' == $type && $item instanceof \JsonSerializable) {
|
||||
$meta = $this->getItemMetaData($item->jsonSerialize());
|
||||
$meta['class'] = $item::class;
|
||||
} elseif (('object' == $type && $item instanceof \stdClass) || ('array' == $type && !$this->isSequential($item))) {
|
||||
$meta['dataType'] = 'object';
|
||||
$meta['children'] = [];
|
||||
foreach ($item as $key => $value) {
|
||||
$meta['children'][$key] = $this->getItemMetaData($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for numeric sequential keys, just like the json encoder does
|
||||
* Credit: http://stackoverflow.com/a/25206156/859027
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isSequential(array $arr)
|
||||
{
|
||||
for ($i = count($arr) - 1; $i >= 0; --$i) {
|
||||
if (!isset($arr[$i]) && !array_key_exists($i, $arr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -19,8 +19,9 @@ interface ParserInterface
|
|||
/**
|
||||
* Return true/false whether this class supports parsing the given class.
|
||||
*
|
||||
* @param array $item containing the following fields: class, groups. Of which groups is optional
|
||||
* @return boolean
|
||||
* @param array $item containing the following fields: class, groups. Of which groups is optional
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(array $item);
|
||||
|
||||
|
@ -36,7 +37,8 @@ interface ParserInterface
|
|||
* - class (optional) the fully-qualified class name of the item, if
|
||||
* it is represented by an object
|
||||
*
|
||||
* @param string $item The string type of input to parse.
|
||||
* @param array $item The string type of input to parse.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse(array $item);
|
||||
|
|
|
@ -30,8 +30,9 @@ interface PostParserInterface
|
|||
* - children (optional) array of nested property names mapped to arrays
|
||||
* in the format described here
|
||||
*
|
||||
* @param string $item The string type of input to parse.
|
||||
* @param array $parameters The previously-parsed parameters array.
|
||||
* @param string $item The string type of input to parse.
|
||||
* @param array $parameters The previously-parsed parameters array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function postParse(array $item, array $parameters);
|
||||
|
|
|
@ -12,10 +12,11 @@
|
|||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface as LegacyMetadataFactoryInterface;
|
||||
|
||||
/**
|
||||
* Uses the Symfony Validation component to extract information about API objects.
|
||||
|
@ -23,38 +24,38 @@ use Symfony\Component\Validator\Constraints\Type;
|
|||
class ValidationParser implements ParserInterface, PostParserInterface
|
||||
{
|
||||
/**
|
||||
* @var \Symfony\Component\Validator\MetadataFactoryInterface
|
||||
* @var LegacyMetadataFactoryInterface
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
protected $typeMap = array(
|
||||
'integer' => DataTypes::INTEGER,
|
||||
'int' => DataTypes::INTEGER,
|
||||
'scalar' => DataTypes::STRING,
|
||||
'numeric' => DataTypes::INTEGER,
|
||||
'boolean' => DataTypes::BOOLEAN,
|
||||
'string' => DataTypes::STRING,
|
||||
'float' => DataTypes::FLOAT,
|
||||
'double' => DataTypes::FLOAT,
|
||||
'long' => DataTypes::INTEGER,
|
||||
'object' => DataTypes::MODEL,
|
||||
'array' => DataTypes::COLLECTION,
|
||||
protected $typeMap = [
|
||||
'integer' => DataTypes::INTEGER,
|
||||
'int' => DataTypes::INTEGER,
|
||||
'scalar' => DataTypes::STRING,
|
||||
'numeric' => DataTypes::INTEGER,
|
||||
'boolean' => DataTypes::BOOLEAN,
|
||||
'string' => DataTypes::STRING,
|
||||
'float' => DataTypes::FLOAT,
|
||||
'double' => DataTypes::FLOAT,
|
||||
'long' => DataTypes::INTEGER,
|
||||
'object' => DataTypes::MODEL,
|
||||
'array' => DataTypes::COLLECTION,
|
||||
'DateTime' => DataTypes::DATETIME,
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* Requires a validation MetadataFactory.
|
||||
*
|
||||
* @param MetadataFactoryInterface $factory
|
||||
* @param MetadataFactoryInterface|LegacyMetadataFactoryInterface $factory
|
||||
*/
|
||||
public function __construct(MetadataFactoryInterface $factory)
|
||||
public function __construct($factory)
|
||||
{
|
||||
if (!($factory instanceof MetadataFactoryInterface) && !($factory instanceof LegacyMetadataFactoryInterface)) {
|
||||
throw new \InvalidArgumentException('Argument 1 of %s constructor must be either an instance of Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface or Symfony\Component\Validator\MetadataFactoryInterface.');
|
||||
}
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supports(array $input)
|
||||
{
|
||||
$className = $input['class'];
|
||||
|
@ -62,26 +63,50 @@ class ValidationParser implements ParserInterface, PostParserInterface
|
|||
return $this->factory->hasMetadataFor($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(array $input)
|
||||
{
|
||||
$className = $input['class'];
|
||||
|
||||
return $this->doParse($className, array());
|
||||
$groups = [];
|
||||
if (isset($input['groups']) && $input['groups']) {
|
||||
$groups = $input['groups'];
|
||||
}
|
||||
|
||||
$parsed = $this->doParse($className, [], $groups);
|
||||
|
||||
if (!isset($input['name']) || empty($input['name'])) {
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
if ($className && class_exists($className)) {
|
||||
$parts = explode('\\', $className);
|
||||
$dataType = sprintf('object (%s)', end($parts));
|
||||
} else {
|
||||
$dataType = sprintf('object (%s)', $className);
|
||||
}
|
||||
|
||||
return [
|
||||
$input['name'] => [
|
||||
'dataType' => $dataType,
|
||||
'actualType' => DataTypes::MODEL,
|
||||
'class' => $className,
|
||||
'subType' => $dataType,
|
||||
'required' => null,
|
||||
'readonly' => null,
|
||||
'children' => $parsed,
|
||||
'default' => null,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively parse constraints.
|
||||
*
|
||||
* @param $className
|
||||
* @param array $visited
|
||||
* @return array
|
||||
*/
|
||||
protected function doParse($className, array $visited)
|
||||
protected function doParse($className, array $visited, array $groups = [])
|
||||
{
|
||||
$params = array();
|
||||
$params = [];
|
||||
$classdata = $this->factory->getMetadataFor($className);
|
||||
$properties = $classdata->getConstrainedProperties();
|
||||
|
||||
|
@ -89,24 +114,24 @@ class ValidationParser implements ParserInterface, PostParserInterface
|
|||
$defaults = $refl->getDefaultProperties();
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$vparams = array();
|
||||
$vparams = [];
|
||||
|
||||
$vparams['default'] = isset($defaults[$property]) ? $defaults[$property] : null;
|
||||
$vparams['default'] = $defaults[$property] ?? null;
|
||||
|
||||
$pds = $classdata->getPropertyMetadata($property);
|
||||
foreach ($pds as $propdata) {
|
||||
$constraints = $propdata->getConstraints();
|
||||
|
||||
foreach ($constraints as $constraint) {
|
||||
$vparams = $this->parseConstraint($constraint, $vparams, $className, $visited);
|
||||
$vparams = $this->parseConstraint($constraint, $vparams, $className, $visited, $groups);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($vparams['format'])) {
|
||||
$vparams['format'] = join(', ', $vparams['format']);
|
||||
$vparams['format'] = implode(', ', array_unique($vparams['format']));
|
||||
}
|
||||
|
||||
foreach (array('dataType', 'readonly', 'required') as $reqprop) {
|
||||
foreach (['dataType', 'readonly', 'required', 'subType'] as $reqprop) {
|
||||
if (!isset($vparams[$reqprop])) {
|
||||
$vparams[$reqprop] = null;
|
||||
}
|
||||
|
@ -115,23 +140,27 @@ class ValidationParser implements ParserInterface, PostParserInterface
|
|||
// check for nested classes with All constraint
|
||||
if (isset($vparams['class']) && !in_array($vparams['class'], $visited) && null !== $this->factory->getMetadataFor($vparams['class'])) {
|
||||
$visited[] = $vparams['class'];
|
||||
$vparams['children'] = $this->doParse($vparams['class'], $visited);
|
||||
$vparams['children'] = $this->doParse($vparams['class'], $visited, $groups);
|
||||
}
|
||||
|
||||
$vparams['actualType'] = $vparams['actualType'] ?? DataTypes::STRING;
|
||||
|
||||
$params[$property] = $vparams;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function postParse(array $input, array $parameters)
|
||||
{
|
||||
$groups = [];
|
||||
if (isset($input['groups']) && $input['groups']) {
|
||||
$groups = $input['groups'];
|
||||
}
|
||||
|
||||
foreach ($parameters as $param => $data) {
|
||||
if (isset($data['class']) && isset($data['children'])) {
|
||||
$input = array('class' => $data['class']);
|
||||
$input = ['class' => $data['class'], 'groups' => $groups];
|
||||
$parameters[$param]['children'] = array_merge(
|
||||
$parameters[$param]['children'], $this->postParse($input, $parameters[$param]['children'])
|
||||
);
|
||||
|
@ -156,20 +185,41 @@ class ValidationParser implements ParserInterface, PostParserInterface
|
|||
* - Choice (single and multiple, min and max)
|
||||
* - Regex (match and non-match)
|
||||
*
|
||||
* @param Constraint $constraint The constraint metadata object.
|
||||
* @param array $vparams The existing validation parameters.
|
||||
* @return mixed The parsed list of validation parameters.
|
||||
* @param Constraint $constraint The constraint metadata object.
|
||||
* @param array $vparams The existing validation parameters.
|
||||
* @param array $groups Validation groups.
|
||||
*
|
||||
* @return mixed The parsed list of validation parameters.
|
||||
*/
|
||||
protected function parseConstraint(Constraint $constraint, $vparams, $className, &$visited = array())
|
||||
{
|
||||
$class = substr(get_class($constraint), strlen('Symfony\\Component\\Validator\\Constraints\\'));
|
||||
protected function parseConstraint(
|
||||
Constraint $constraint,
|
||||
$vparams,
|
||||
$className,
|
||||
&$visited = [],
|
||||
array $groups = [],
|
||||
) {
|
||||
$class = substr($constraint::class, strlen('Symfony\\Component\\Validator\\Constraints\\'));
|
||||
|
||||
$vparams['actualType'] = DataTypes::STRING;
|
||||
$vparams['subType'] = null;
|
||||
$vparams['groups'] = $constraint->groups;
|
||||
|
||||
if ($groups) {
|
||||
$containGroup = false;
|
||||
foreach ($groups as $group) {
|
||||
if (in_array($group, $vparams['groups'])) {
|
||||
$containGroup = true;
|
||||
}
|
||||
}
|
||||
if (!$containGroup) {
|
||||
return $vparams;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
case 'NotBlank':
|
||||
$vparams['format'][] = '{not blank}';
|
||||
// no break
|
||||
case 'NotNull':
|
||||
$vparams['required'] = true;
|
||||
break;
|
||||
|
@ -200,30 +250,41 @@ class ValidationParser implements ParserInterface, PostParserInterface
|
|||
$vparams['format'][] = '{Time HH:MM:SS}';
|
||||
$vparams['actualType'] = DataTypes::TIME;
|
||||
break;
|
||||
case 'Range':
|
||||
$messages = [];
|
||||
if (isset($constraint->min)) {
|
||||
$messages[] = ">={$constraint->min}";
|
||||
}
|
||||
if (isset($constraint->max)) {
|
||||
$messages[] = "<={$constraint->max}";
|
||||
}
|
||||
$vparams['format'][] = '{range: {' . implode(', ', $messages) . '}}';
|
||||
break;
|
||||
case 'Length':
|
||||
$messages = array();
|
||||
$messages = [];
|
||||
if (isset($constraint->min)) {
|
||||
$messages[] = "min: {$constraint->min}";
|
||||
}
|
||||
if (isset($constraint->max)) {
|
||||
$messages[] = "max: {$constraint->max}";
|
||||
}
|
||||
$vparams['format'][] = '{length: ' . join(', ', $messages) . '}';
|
||||
$vparams['format'][] = '{length: {' . implode(', ', $messages) . '}}';
|
||||
break;
|
||||
case 'Choice':
|
||||
$choices = $this->getChoices($constraint, $className);
|
||||
$format = '[' . join('|', $choices) . ']';
|
||||
sort($choices);
|
||||
$format = '[' . implode('|', $choices) . ']';
|
||||
if ($constraint->multiple) {
|
||||
$vparams['actualType'] = DataTypes::COLLECTION;
|
||||
$vparams['subType'] = DataTypes::ENUM;
|
||||
$messages = array();
|
||||
$messages = [];
|
||||
if (isset($constraint->min)) {
|
||||
$messages[] = "min: {$constraint->min} ";
|
||||
}
|
||||
if (isset($constraint->max)) {
|
||||
$messages[] = "max: {$constraint->max} ";
|
||||
}
|
||||
$vparams['format'][] = '{' . join ('', $messages) . 'choice of ' . $format . '}';
|
||||
$vparams['format'][] = '{' . implode('', $messages) . 'choice of ' . $format . '}';
|
||||
} else {
|
||||
$vparams['actualType'] = DataTypes::ENUM;
|
||||
$vparams['format'][] = $format;
|
||||
|
@ -240,19 +301,19 @@ class ValidationParser implements ParserInterface, PostParserInterface
|
|||
foreach ($constraint->constraints as $childConstraint) {
|
||||
if ($childConstraint instanceof Type) {
|
||||
$nestedType = $childConstraint->type;
|
||||
$exp = explode("\\", $nestedType);
|
||||
if (!class_exists($nestedType)) {
|
||||
$nestedType = substr($className, 0, strrpos($className, '\\') + 1).$nestedType;
|
||||
$exp = explode('\\', $nestedType);
|
||||
if (!$nestedType || !class_exists($nestedType)) {
|
||||
$nestedType = substr($className, 0, strrpos($className, '\\') + 1) . $nestedType;
|
||||
|
||||
if (!class_exists($nestedType)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$vparams['dataType'] = sprintf("array of objects (%s)", end($exp));
|
||||
$vparams['dataType'] = sprintf('array of objects (%s)', end($exp));
|
||||
$vparams['actualType'] = DataTypes::COLLECTION;
|
||||
$vparams['subType'] = $nestedType;
|
||||
$vparams['class'] = $nestedType;
|
||||
$vparams['subType'] = $nestedType;
|
||||
$vparams['class'] = $nestedType;
|
||||
|
||||
if (!in_array($nestedType, $visited)) {
|
||||
$visited[] = $nestedType;
|
||||
|
@ -269,16 +330,15 @@ class ValidationParser implements ParserInterface, PostParserInterface
|
|||
/**
|
||||
* Return Choice constraint choices.
|
||||
*
|
||||
* @param Constraint $constraint
|
||||
* @param $className
|
||||
* @return array
|
||||
* @throws \Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
*
|
||||
* @throws ConstraintDefinitionException
|
||||
*/
|
||||
protected function getChoices(Constraint $constraint, $className)
|
||||
{
|
||||
if ($constraint->callback) {
|
||||
if (is_callable(array($className, $constraint->callback))) {
|
||||
$choices = call_user_func(array($className, $constraint->callback));
|
||||
if (is_callable([$className, $constraint->callback])) {
|
||||
$choices = call_user_func([$className, $constraint->callback]);
|
||||
} elseif (is_callable($constraint->callback)) {
|
||||
$choices = call_user_func($constraint->callback);
|
||||
} else {
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
namespace Nelmio\ApiDocBundle\Parser;
|
||||
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Uses the Symfony Validation component to extract information about API objects. This is a backwards-compatible Validation component for Symfony2.1
|
||||
|
@ -20,7 +19,7 @@ use Symfony\Component\Validator\Constraint;
|
|||
class ValidationParserLegacy extends ValidationParser
|
||||
{
|
||||
/**
|
||||
* @var \Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface
|
||||
* @var ClassMetadataFactoryInterface
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
|
@ -34,9 +33,6 @@ class ValidationParserLegacy extends ValidationParser
|
|||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supports(array $input)
|
||||
{
|
||||
$className = $input['class'];
|
||||
|
@ -44,12 +40,9 @@ class ValidationParserLegacy extends ValidationParser
|
|||
return null !== $this->factory->getClassMetadata($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(array $input)
|
||||
{
|
||||
$params = array();
|
||||
$params = [];
|
||||
$className = $input['class'];
|
||||
|
||||
$classdata = $this->factory->getClassMetadata($className);
|
||||
|
@ -60,9 +53,9 @@ class ValidationParserLegacy extends ValidationParser
|
|||
$defaults = $refl->getDefaultProperties();
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$vparams = array();
|
||||
$vparams = [];
|
||||
|
||||
$vparams['default'] = isset($defaults[$property]) ? $defaults[$property] : null;
|
||||
$vparams['default'] = $defaults[$property] ?? null;
|
||||
|
||||
$pds = $classdata->getMemberMetadatas($property);
|
||||
|
||||
|
@ -75,10 +68,10 @@ class ValidationParserLegacy extends ValidationParser
|
|||
}
|
||||
|
||||
if (isset($vparams['format'])) {
|
||||
$vparams['format'] = join(', ', $vparams['format']);
|
||||
$vparams['format'] = implode(', ', $vparams['format']);
|
||||
}
|
||||
|
||||
foreach (array('dataType', 'readonly', 'required') as $reqprop) {
|
||||
foreach (['dataType', 'readonly', 'required'] as $reqprop) {
|
||||
if (!isset($vparams[$reqprop])) {
|
||||
$vparams[$reqprop] = null;
|
||||
}
|
||||
|
@ -89,5 +82,4 @@ class ValidationParserLegacy extends ValidationParser
|
|||
|
||||
return $params;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
14
README.md
14
README.md
|
@ -1,25 +1,13 @@
|
|||
NelmioApiDocBundle
|
||||
==================
|
||||
|
||||
[](http://travis-ci.org/nelmio/NelmioApiDocBundle)
|
||||
|
||||
The **NelmioApiDocBundle** bundle allows you to generate a decent documentation
|
||||
for your APIs.
|
||||
|
||||
**Important:** This bundle is developed in sync with [symfony's
|
||||
repository](https://github.com/symfony/symfony).
|
||||
For Symfony `2.0.x`, you need to use the `1.*` version of the bundle.
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
For documentation, see:
|
||||
|
||||
Resources/doc/
|
||||
|
||||
[Read the documentation](https://github.com/nelmio/NelmioApiDocBundle/blob/master/Resources/doc/index.md)
|
||||
[Read the documentation on symfony.com](https://symfony.com/doc/current/bundles/NelmioApiDocBundle/index.html)
|
||||
|
||||
|
||||
Contributing
|
||||
|
|
23
Resources/config/autowired.yaml
Normal file
23
Resources/config/autowired.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
services:
|
||||
_defaults:
|
||||
public: false
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Nelmio\ApiDocBundle\Command\DumpCommand:
|
||||
arguments:
|
||||
$simpleFormatter: '@nelmio_api_doc.formatter.simple_formatter'
|
||||
$markdownFormatter: '@nelmio_api_doc.formatter.markdown_formatter'
|
||||
$htmlFormatter: '@nelmio_api_doc.formatter.html_formatter'
|
||||
$apiDocExtractor: '@nelmio_api_doc.extractor.api_doc_extractor'
|
||||
|
||||
Nelmio\ApiDocBundle\Command\SwaggerDumpCommand:
|
||||
arguments:
|
||||
$extractor: '@nelmio_api_doc.extractor.api_doc_extractor'
|
||||
$formatter: '@nelmio_api_doc.formatter.swagger_formatter'
|
||||
|
||||
Nelmio\ApiDocBundle\Controller\ApiDocController:
|
||||
arguments:
|
||||
$extractor: '@nelmio_api_doc.extractor.api_doc_extractor'
|
||||
$htmlFormatter: '@nelmio_api_doc.formatter.html_formatter'
|
||||
$swaggerFormatter: '@nelmio_api_doc.formatter.swagger_formatter'
|
|
@ -8,19 +8,20 @@
|
|||
<parameter key="nelmio_api_doc.formatter.markdown_formatter.class">Nelmio\ApiDocBundle\Formatter\MarkdownFormatter</parameter>
|
||||
<parameter key="nelmio_api_doc.formatter.simple_formatter.class">Nelmio\ApiDocBundle\Formatter\SimpleFormatter</parameter>
|
||||
<parameter key="nelmio_api_doc.formatter.html_formatter.class">Nelmio\ApiDocBundle\Formatter\HtmlFormatter</parameter>
|
||||
<parameter key="nelmio_api_doc.formatter.swagger_formatter.class">Nelmio\ApiDocBundle\Formatter\SwaggerFormatter</parameter>
|
||||
<parameter key="nelmio_api_doc.sandbox.authentication">null</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="nelmio_api_doc.formatter.abstract_formatter" class="%nelmio_api_doc.formatter.abstract_formatter.class%" />
|
||||
<service id="nelmio_api_doc.formatter.abstract_formatter" class="%nelmio_api_doc.formatter.abstract_formatter.class%" abstract="true" />
|
||||
<service id="nelmio_api_doc.formatter.markdown_formatter" class="%nelmio_api_doc.formatter.markdown_formatter.class%"
|
||||
parent="nelmio_api_doc.formatter.abstract_formatter" />
|
||||
<service id="nelmio_api_doc.formatter.simple_formatter" class="%nelmio_api_doc.formatter.simple_formatter.class%"
|
||||
parent="nelmio_api_doc.formatter.abstract_formatter" />
|
||||
<service id="nelmio_api_doc.formatter.html_formatter" class="%nelmio_api_doc.formatter.html_formatter.class%"
|
||||
parent="nelmio_api_doc.formatter.abstract_formatter">
|
||||
parent="nelmio_api_doc.formatter.abstract_formatter" public="true">
|
||||
<call method="setTemplatingEngine">
|
||||
<argument type="service" id="templating" />
|
||||
<argument type="service" id="twig" />
|
||||
</call>
|
||||
<call method="setMotdTemplate">
|
||||
<argument>%nelmio_api_doc.motd.template%</argument>
|
||||
|
@ -55,7 +56,12 @@
|
|||
<call method="setAuthentication">
|
||||
<argument>%nelmio_api_doc.sandbox.authentication%</argument>
|
||||
</call>
|
||||
<call method="setDefaultSectionsOpened">
|
||||
<argument>%nelmio_api_doc.default_sections_opened%</argument>
|
||||
</call>
|
||||
</service>
|
||||
<service id="nelmio_api_doc.formatter.swagger_formatter" class="%nelmio_api_doc.formatter.swagger_formatter.class%"
|
||||
parent="nelmio_api_doc.formatter.abstract_formatter" public="true" />
|
||||
</services>
|
||||
|
||||
</container>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
nelmio_api_doc_index:
|
||||
pattern: /
|
||||
defaults: { _controller: NelmioApiDocBundle:ApiDoc:index }
|
||||
requirements:
|
||||
_method: GET
|
||||
path: /{view}
|
||||
defaults: { _controller: Nelmio\ApiDocBundle\Controller\ApiDocController::index, view: 'default' }
|
||||
methods: [GET]
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
<services>
|
||||
<service id="nelmio_api_doc.parser.form_type_parser" class="%nelmio_api_doc.parser.form_type_parser.class%">
|
||||
<argument type="service" id="form.factory" />
|
||||
<argument type="service" id="translator" />
|
||||
<argument>%nelmio_api_doc.sandbox.entity_to_choice%</argument>
|
||||
<tag name="nelmio_api_doc.extractor.parser" />
|
||||
</service>
|
||||
</services>
|
||||
|
|
|
@ -9,25 +9,29 @@
|
|||
<parameter key="nelmio_api_doc.twig.extension.extra_markdown.class">Nelmio\ApiDocBundle\Twig\Extension\MarkdownExtension</parameter>
|
||||
<parameter key="nelmio_api_doc.doc_comment_extractor.class">Nelmio\ApiDocBundle\Util\DocCommentExtractor</parameter>
|
||||
|
||||
<parameter key="nelmio_api_doc.extractor.handler.fos_rest.class">Nelmio\ApiDocBundle\Extractor\Handler\FosRestHandler</parameter>
|
||||
<parameter key="nelmio_api_doc.extractor.handler.jms_security.class">Nelmio\ApiDocBundle\Extractor\Handler\JmsSecurityExtraHandler</parameter>
|
||||
<parameter key="nelmio_api_doc.extractor.handler.sensio_framework_extra.class">Nelmio\ApiDocBundle\Extractor\Handler\SensioFrameworkExtraHandler</parameter>
|
||||
<parameter key="nelmio_api_doc.extractor.handler.phpdoc.class">Nelmio\ApiDocBundle\Extractor\Handler\PhpDocHandler</parameter>
|
||||
|
||||
<parameter key="nelmio_api_doc.parser.collection_parser.class">Nelmio\ApiDocBundle\Parser\CollectionParser</parameter>
|
||||
<parameter key="nelmio_api_doc.parser.form_errors_parser.class">Nelmio\ApiDocBundle\Parser\FormErrorsParser</parameter>
|
||||
<parameter key="nelmio_api_doc.parser.json_serializable_parser.class">Nelmio\ApiDocBundle\Parser\JsonSerializableParser</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id='nelmio_api_doc.doc_comment_extractor' class="%nelmio_api_doc.doc_comment_extractor.class%" />
|
||||
<service id="nelmio_api_doc.doc_comment_extractor" class="%nelmio_api_doc.doc_comment_extractor.class%" />
|
||||
|
||||
<service id="nelmio_api_doc.extractor.api_doc_extractor" class="%nelmio_api_doc.extractor.api_doc_extractor.class%">
|
||||
<argument type="service" id="service_container"/>
|
||||
<service id="nelmio_api_doc.controller_name_parser" class="Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser" public="false">
|
||||
<argument type="service" id="kernel" />
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.extractor.api_doc_extractor" class="%nelmio_api_doc.extractor.api_doc_extractor.class%" public="true">
|
||||
<argument type="service" id="router" />
|
||||
<argument type="service" id="annotation_reader" />
|
||||
<argument type="service" id="nelmio_api_doc.doc_comment_extractor" />
|
||||
<argument type="collection"/>
|
||||
<argument type="collection" />
|
||||
<argument>%nelmio_api_doc.exclude_sections%</argument>
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.form.extension.description_form_type_extension" class="%nelmio_api_doc.form.extension.description_form_type_extension.class%">
|
||||
<tag name="form.type_extension" alias="form" />
|
||||
<tag name="form.type_extension" alias="form" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" />
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.twig.extension.extra_markdown" class="%nelmio_api_doc.twig.extension.extra_markdown.class%">
|
||||
|
@ -36,22 +40,22 @@
|
|||
|
||||
<!-- Extractor Annotation Handlers -->
|
||||
|
||||
<service id="nelmio_api_doc.extractor.handler.fos_rest" class="%nelmio_api_doc.extractor.handler.fos_rest.class%" public="false">
|
||||
<tag name="nelmio_api_doc.extractor.handler"/>
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.extractor.handler.jms_security" class="%nelmio_api_doc.extractor.handler.jms_security.class%" public="false">
|
||||
<tag name="nelmio_api_doc.extractor.handler"/>
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.extractor.handler.sensio_framework_extra" class="%nelmio_api_doc.extractor.handler.sensio_framework_extra.class%" public="false">
|
||||
<tag name="nelmio_api_doc.extractor.handler"/>
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.extractor.handler.phpdoc" class="%nelmio_api_doc.extractor.handler.phpdoc.class%" public="false">
|
||||
<argument type="service" id="nelmio_api_doc.doc_comment_extractor" />
|
||||
<tag name="nelmio_api_doc.extractor.handler"/>
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.parser.collection_parser" class="%nelmio_api_doc.parser.collection_parser.class%">
|
||||
<tag name="nelmio_api_doc.extractor.parser" />
|
||||
</service>
|
||||
<service id="nelmio_api_doc.parser.form_errors_parser" class="%nelmio_api_doc.parser.form_errors_parser.class%">
|
||||
<tag name="nelmio_api_doc.extractor.parser" />
|
||||
</service>
|
||||
|
||||
<!-- priority=1 means it comes before the validation parser, which can often add better type information -->
|
||||
<service id="nelmio_api_doc.parser.json_serializable_parser" class="%nelmio_api_doc.parser.json_serializable_parser.class%">
|
||||
<tag name="nelmio_api_doc.extractor.parser" priority="1" />
|
||||
</service>
|
||||
</services>
|
||||
|
||||
</container>
|
||||
|
|
9
Resources/config/swagger_routing.yml
Normal file
9
Resources/config/swagger_routing.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
nelmio_api_doc_swagger_resource_list:
|
||||
path: /
|
||||
methods: [GET]
|
||||
defaults: { _controller: Nelmio\ApiDocBundle\Controller\ApiDocController::swagger }
|
||||
|
||||
nelmio_api_doc_swagger_api_declaration:
|
||||
path: /{resource}
|
||||
methods: [GET]
|
||||
defaults: { _controller: Nelmio\ApiDocBundle\Controller\ApiDocController::swagger }
|
21
Resources/doc/commands.rst
Normal file
21
Resources/doc/commands.rst
Normal file
|
@ -0,0 +1,21 @@
|
|||
Commands
|
||||
========
|
||||
|
||||
A command is provided in order to dump the documentation in ``json``, ``markdown``,
|
||||
or ``html``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:doc:dump [--format="..."]
|
||||
|
||||
The ``--format`` option allows to choose the format (default is: ``markdown``).
|
||||
|
||||
For example to generate a static version of your documentation you can use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:doc:dump --format=html > api.html
|
||||
|
||||
By default, the generated HTML will add the sandbox feature if you didn't
|
||||
disable it in the configuration. If you want to generate a static version of
|
||||
your documentation without sandbox, use the ``--no-sandbox`` option.
|
132
Resources/doc/configuration-in-depth.rst
Normal file
132
Resources/doc/configuration-in-depth.rst
Normal file
|
@ -0,0 +1,132 @@
|
|||
Configuration In-Depth
|
||||
======================
|
||||
|
||||
API Name
|
||||
--------
|
||||
|
||||
You can specify your own API name:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
name: My API
|
||||
|
||||
Authentication Methods
|
||||
----------------------
|
||||
|
||||
You can choose between different authentication methods:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
sandbox:
|
||||
authentication:
|
||||
delivery: header
|
||||
name: X-Custom
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
sandbox:
|
||||
authentication:
|
||||
delivery: query
|
||||
name: param
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
sandbox:
|
||||
authentication:
|
||||
delivery: http
|
||||
type: basic # or bearer
|
||||
|
||||
When choosing an ``http`` delivery, ``name`` defaults to ``Authorization``, and
|
||||
the header value will automatically be prefixed by the corresponding type (ie.
|
||||
``Basic`` or ``Bearer``).
|
||||
|
||||
Section Exclusion
|
||||
-----------------
|
||||
|
||||
You can specify which sections to exclude from the documentation generation:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
exclude_sections: ["privateapi", "testapi"]
|
||||
|
||||
Note that ``exclude_sections`` will literally exclude a section from your api
|
||||
documentation. It's possible however to create multiple views by specifying the
|
||||
``views`` parameter within the ``@ApiDoc`` annotations. This allows you to move
|
||||
private or test methods to a complete different view of your documentation
|
||||
instead.
|
||||
|
||||
Parsers
|
||||
-------
|
||||
|
||||
By default, all registered parsers are used, but sometimes you may want to
|
||||
define which parsers you want to use. The ``parsers`` attribute is used to
|
||||
configure a list of parsers that will be used::
|
||||
|
||||
output={
|
||||
"class" = "Acme\Bundle\Entity\User",
|
||||
"parsers" = {
|
||||
"Nelmio\ApiDocBundle\Parser\JmsMetadataParser",
|
||||
"Nelmio\ApiDocBundle\Parser\ValidationParser"
|
||||
}
|
||||
}
|
||||
|
||||
In this case the parsers ``JmsMetadataParser`` and ``ValidationParser`` are used
|
||||
to generate returned data. This feature also works for both the ``input`` and
|
||||
``output`` properties.
|
||||
|
||||
Moreover, the bundle provides a way to register multiple ``input`` parsers. The
|
||||
first parser that can handle the specified input is used, so you can configure
|
||||
their priorities via container tags. Here's an example parser service
|
||||
registration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
services:
|
||||
mybundle.api_doc.extractor.custom_parser:
|
||||
class: MyBundle\Parser\CustomDocParser
|
||||
tags:
|
||||
- { name: nelmio_api_doc.extractor.parser, priority: 2 }
|
||||
|
||||
MOTD
|
||||
----
|
||||
|
||||
You can also define your own motd content (above methods list). All you have to
|
||||
do is add to configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
# ...
|
||||
motd:
|
||||
template: AcmeApiBundle::Components/motd.html.twig
|
||||
|
||||
Caching
|
||||
-------
|
||||
|
||||
It is a good idea to enable the internal caching mechanism on production:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
cache:
|
||||
enabled: true
|
||||
|
||||
You can define an alternate location where the ApiDoc configurations are to be
|
||||
cached:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
cache:
|
||||
enabled: true
|
||||
file: "/tmp/symfony-app/%kernel.environment%/api-doc.cache"
|
55
Resources/doc/configuration-reference.rst
Normal file
55
Resources/doc/configuration-reference.rst
Normal file
|
@ -0,0 +1,55 @@
|
|||
Configuration Reference
|
||||
=======================
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
nelmio_api_doc:
|
||||
name: 'API documentation'
|
||||
exclude_sections: []
|
||||
default_sections_opened: true
|
||||
motd:
|
||||
template: '@NelmioApiDoc/Components/motd.html.twig'
|
||||
request_listener:
|
||||
enabled: true
|
||||
parameter: _doc
|
||||
sandbox:
|
||||
enabled: true
|
||||
endpoint: null
|
||||
accept_type: null
|
||||
body_format:
|
||||
formats:
|
||||
|
||||
# Defaults:
|
||||
- form
|
||||
- json
|
||||
default_format: ~ # One of "form"; "json"
|
||||
request_format:
|
||||
formats:
|
||||
|
||||
# Defaults:
|
||||
json: application/json
|
||||
xml: application/xml
|
||||
method: ~ # One of "format_param"; "accept_header"
|
||||
default_format: json
|
||||
authentication:
|
||||
delivery: ~ # Required
|
||||
name: ~ # Required
|
||||
|
||||
# Required if http delivery is selected.
|
||||
type: ~ # One of "basic"; "bearer"
|
||||
custom_endpoint: false
|
||||
entity_to_choice: true
|
||||
swagger:
|
||||
api_base_path: /api
|
||||
swagger_version: '1.2'
|
||||
api_version: '0.1'
|
||||
info:
|
||||
title: Symfony2
|
||||
description: 'My awesome Symfony2 app!'
|
||||
TermsOfServiceUrl: null
|
||||
contact: null
|
||||
license: null
|
||||
licenseUrl: null
|
||||
cache:
|
||||
enabled: false
|
||||
file: '%kernel.cache_dir%/api-doc.cache'
|
12
Resources/doc/faq.rst
Normal file
12
Resources/doc/faq.rst
Normal file
|
@ -0,0 +1,12 @@
|
|||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
How can I remove the parameter ``_format`` sent in ``POST`` and ``PUT`` request?
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
nelmio_api_doc:
|
||||
sandbox:
|
||||
request_format:
|
||||
method: accept_header
|
|
@ -1,473 +0,0 @@
|
|||
NelmioApiDocBundle
|
||||
==================
|
||||
|
||||
The **NelmioApiDocBundle** bundle allows you to generate a decent documentation
|
||||
for your APIs.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Add this bundle to your `composer.json` file:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"nelmio/api-doc-bundle": "@stable"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
**Protip:** you should browse the
|
||||
[`nelmio/api-doc-bundle`](https://packagist.org/packages/nelmio/api-doc-bundle)
|
||||
page to choose a stable version to use, avoid the `@stable` meta constraint.
|
||||
|
||||
Register the bundle in `app/AppKernel.php`:
|
||||
|
||||
// app/AppKernel.php
|
||||
public function registerBundles()
|
||||
{
|
||||
return array(
|
||||
// ...
|
||||
new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
|
||||
);
|
||||
}
|
||||
|
||||
Import the routing definition in `routing.yml`:
|
||||
|
||||
# app/config/routing.yml
|
||||
NelmioApiDocBundle:
|
||||
resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
|
||||
prefix: /api/doc
|
||||
|
||||
Enable the bundle's configuration in `app/config/config.yml`:
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc: ~
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The main problem with documentation is to keep it up to date. That's why the **NelmioApiDocBundle**
|
||||
uses introspection a lot. Thanks to an annotation, it's really easy to document an API method.
|
||||
|
||||
### The ApiDoc() Annotation
|
||||
|
||||
The bundle provides an `ApiDoc()` annotation for your controllers:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
|
||||
namespace Your\Namespace;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
|
||||
class YourController extends Controller
|
||||
{
|
||||
/**
|
||||
* This is the documentation description of your method, it will appear
|
||||
* on a specific pane. It will read all the text until the first
|
||||
* annotation.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* description="This is a description of your API method",
|
||||
* filters={
|
||||
* {"name"="a-filter", "dataType"="integer"},
|
||||
* {"name"="another-filter", "dataType"="string", "pattern"="(foo|bar) ASC|DESC"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Create a new Object",
|
||||
* input="Your\Namespace\Form\Type\YourType",
|
||||
* output="Your\Namespace\Class"
|
||||
* )
|
||||
*/
|
||||
public function postAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Returns a collection of Object",
|
||||
* requirements={
|
||||
* {
|
||||
* "name"="limit",
|
||||
* "dataType"="integer",
|
||||
* "requirement"="\d+",
|
||||
* "description"="how many objects to return"
|
||||
* }
|
||||
* },
|
||||
* parameters={
|
||||
* {"name"="categoryId", "dataType"="integer", "required"=true, "description"="category id"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function cgetAction($id)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following properties are available:
|
||||
|
||||
* `section`: allow to group resources
|
||||
|
||||
* `resource`: whether the method describes a main resource or not (default: `false`);
|
||||
|
||||
* `description`: a description of the API method;
|
||||
|
||||
* `https`: whether the method described requires the https protocol (default: `false`);
|
||||
|
||||
* `deprecated`: allow to set method as deprecated (default: `false`);
|
||||
|
||||
* `tags`: allow to tag a method (e.g. `beta` or `in-development`). Either a single tag or an array of tags.
|
||||
|
||||
* `filters`: an array of filters;
|
||||
|
||||
* `requirements`: an array of requirements;
|
||||
|
||||
* `parameters`: an array of parameters;
|
||||
|
||||
* `input`: the input type associated to the method (currently this supports Form Types, classes with JMS Serializer
|
||||
metadata, and classes with Validation component metadata) useful for POST|PUT methods, either as FQCN or as form type
|
||||
(if it is registered in the form factory in the container).
|
||||
|
||||
* `output`: the output type associated with the response. Specified and parsed the same way as `input`.
|
||||
|
||||
* `statusCodes`: an array of HTTP status codes and a description of when that status is returned; Example:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
|
||||
class YourController
|
||||
{
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* statusCodes={
|
||||
* 200="Returned when successful",
|
||||
* 403="Returned when the user is not authorized to say hello",
|
||||
* 404={
|
||||
* "Returned when the user is not found",
|
||||
* "Returned when something else is not found"
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function myFunction()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each _filter_ has to define a `name` parameter, but other parameters are free. Filters are often optional
|
||||
parameters, and you can document them as you want, but keep in mind to be consistent for the whole documentation.
|
||||
|
||||
If you set `input`, then the bundle automatically extracts parameters based on the given type,
|
||||
and determines for each parameter its data type, and if it's required or not.
|
||||
|
||||
For classes parsed with JMS metadata, description will be taken from the properties doc comment, if available.
|
||||
|
||||
For Form Types, you can add an extra option named `description` on each field:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
|
||||
class YourType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilder $builder, array $options)
|
||||
{
|
||||
$builder->add('note', null, array(
|
||||
'description' => 'this is a note',
|
||||
));
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The bundle will also get information from the routing definition (`requirements`, `pattern`, etc), so to get the
|
||||
best out of it you should define strict _method requirements etc.
|
||||
|
||||
### Other Bundle Annotations
|
||||
|
||||
Also bundle will get information from the other annotations:
|
||||
|
||||
* @FOS\RestBundle\Controller\Annotations\RequestParam - use as `parameters`
|
||||
|
||||
* @FOS\RestBundle\Controller\Annotations\QueryParam - use as `requirements` (when strict parameter is true), `filters` (when strict is false)
|
||||
|
||||
* @JMS\SecurityExtraBundle\Annotation\Secure - set `authentication` to true, `authenticationRoles` to the given roles
|
||||
|
||||
* @Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache - set `cache`
|
||||
|
||||
### PHPDoc
|
||||
|
||||
Route functions marked as @deprecated will be set method as deprecation in
|
||||
documentation.
|
||||
|
||||
#### JMS Serializer Features
|
||||
|
||||
The bundle has support for some of the JMS Serializer features and use these
|
||||
extra information in the generated documentation.
|
||||
|
||||
##### Group Exclusion Strategy
|
||||
|
||||
If your classes use [JMS Group Exclusion
|
||||
Strategy](http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#creating-different-views-of-your-objects),
|
||||
you can specify which groups to use when generating the documentation by using
|
||||
this syntax :
|
||||
|
||||
```
|
||||
input={
|
||||
"class"="Acme\Bundle\Entity\User",
|
||||
"groups"={"update", "public"}
|
||||
}
|
||||
```
|
||||
|
||||
In this case the groups 'update' and 'public' are used.
|
||||
|
||||
This feature also works for the `output` property.
|
||||
|
||||
##### Versioning Objects
|
||||
|
||||
If your `output` classes use [versioning capabilities of JMS
|
||||
Serializer](http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#versioning-objects),
|
||||
the versioning information will be automatically used when generating the
|
||||
documentation.
|
||||
|
||||
#### Form Types Features
|
||||
|
||||
Even if you use `FormFactoryInterface::createNamed('', 'your_form_type')` the documentation will generate the form type name as the prefix for inputs
|
||||
(`your_form_type[param]` ... instead of just `param`).
|
||||
|
||||
You can specify which prefix to use with the `name` key in the `input` section:
|
||||
|
||||
```
|
||||
input = {
|
||||
"class" = "your_form_type",
|
||||
"name" = ""
|
||||
}
|
||||
```
|
||||
|
||||
#### Used Parsers
|
||||
|
||||
By default, all registered parsers are used, but sometimes you may want to
|
||||
define which parsers you want to use. The `parsers` attribute is used to
|
||||
configure a list of parsers that will be used:
|
||||
|
||||
```
|
||||
output={
|
||||
"class" = "Acme\Bundle\Entity\User",
|
||||
"parsers" = {
|
||||
"Nelmio\ApiDocBundle\Parser\JmsMetadataParser",
|
||||
"Nelmio\ApiDocBundle\Parser\ValidationParser"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this case the parsers `JmsMetadataParser` and `ValidationParser` are used to
|
||||
generate returned data.
|
||||
|
||||
This feature also works for both the `input` and `output` properties.
|
||||
|
||||
### Web Interface
|
||||
|
||||
You can browse the whole documentation at: `http://example.org/api/doc`.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### On-The-Fly Documentation
|
||||
|
||||
By calling an URL with the parameter `?_doc=1`, you will get the corresponding
|
||||
documentation if available.
|
||||
|
||||
### Sandbox
|
||||
|
||||
This bundle provides a sandbox mode in order to test API methods. You can
|
||||
configure this sandbox using the following parameters:
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
sandbox:
|
||||
authentication: # default is `~` (`null`), if set, the sandbox automatically
|
||||
# send authenticated requests using the configured `delivery`
|
||||
|
||||
name: access_token # access token name or query parameter name or header name
|
||||
|
||||
delivery: http # `query`, `http`, and `header` are supported
|
||||
|
||||
# Required if http delivery is selected.
|
||||
type: basic # `basic`, `bearer` are supported
|
||||
|
||||
custom_endpoint: true # default is `false`, if `true`, your user will be able to
|
||||
# specify its own endpoint
|
||||
|
||||
enabled: true # default is `true`, you can set this parameter to `false`
|
||||
# to disable the sandbox
|
||||
|
||||
endpoint: http://sandbox.example.com/ # default is `/app_dev.php`, use this parameter
|
||||
# to define which URL to call through the sandbox
|
||||
|
||||
accept_type: application/json # default is `~` (`null`), if set, the value is
|
||||
# automatically populated as the `Accept` header
|
||||
|
||||
body_format:
|
||||
formats: [ form, json ] # array of enabled body formats,
|
||||
# remove all elements to disable the selectbox
|
||||
default_format: form # default is `form`, determines whether to send
|
||||
# `x-www-form-urlencoded` data or json-encoded
|
||||
# data (by setting this parameter to `json`) in
|
||||
# sandbox requests
|
||||
|
||||
request_format:
|
||||
formats: # default is `json` and `xml`,
|
||||
json: application/json # override to add custom formats or disable
|
||||
xml: application/xml # the default formats
|
||||
|
||||
method: format_param # default is `format_param`, alternately `accept_header`,
|
||||
# decides how to request the response format
|
||||
|
||||
default_format: json # default is `json`,
|
||||
# default content format to request (see formats)
|
||||
|
||||
### Command
|
||||
|
||||
A command is provided in order to dump the documentation in `json`, `markdown`, or `html`.
|
||||
|
||||
php app/console api:doc:dump [--format="..."]
|
||||
|
||||
The `--format` option allows to choose the format (default is: `markdown`).
|
||||
|
||||
For example to generate a static version of your documentation you can use:
|
||||
|
||||
php app/console api:doc:dump --format=html > api.html
|
||||
|
||||
By default, the generated HTML will add the sandbox feature if you didn't disable it in the configuration.
|
||||
If you want to generate a static version of your documentation without sandbox, use the `--no-sandbox` option.
|
||||
|
||||
|
||||
Configuration In-Depth
|
||||
----------------------
|
||||
|
||||
You can specify your own API name:
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
name: My API
|
||||
|
||||
You can choose between different authentication methods:
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
authentication:
|
||||
delivery: header
|
||||
name: X-Custom
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
authentication:
|
||||
delivery: query
|
||||
name: param
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
authentication:
|
||||
delivery: http
|
||||
type: basic # or bearer
|
||||
|
||||
When choosing an `http` delivery, `name` defaults to `Authorization`,
|
||||
and the header value will automatically be prefixed by the corresponding type (ie. `Basic` or `Bearer`).
|
||||
|
||||
You can specify which sections to exclude from the documentation generation:
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
exclude_sections: ["privateapi", "testapi"]
|
||||
|
||||
The bundle provides a way to register multiple `input` parsers. The first parser
|
||||
that can handle the specified input is used, so you can configure their
|
||||
priorities via container tags. Here's an example parser service registration:
|
||||
|
||||
#app/config/config.yml
|
||||
services:
|
||||
mybundle.api_doc.extractor.custom_parser:
|
||||
class: MyBundle\Parser\CustomDocParser
|
||||
tags:
|
||||
- { name: nelmio_api_doc.extractor.parser, priority: 2 }
|
||||
|
||||
You can also define your own motd content (above methods list). All you have to
|
||||
do is add to configuration:
|
||||
|
||||
#app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
# ...
|
||||
motd:
|
||||
template: AcmeApiBundle::Components/motd.html.twig
|
||||
|
||||
### Using Your Own Annotations
|
||||
|
||||
If you have developed your own project-related annotations, and you want to parse them to populate
|
||||
the `ApiDoc`, you can provide custom handlers as services. You just have to implement the
|
||||
`Nelmio\ApiDocBundle\Extractor\HandlerInterface` and tag it as `nelmio_api_doc.extractor.handler`:
|
||||
|
||||
# app/config/config.yml
|
||||
services:
|
||||
mybundle.api_doc.extractor.my_annotation_handler:
|
||||
class: MyBundle\AnnotationHandler\MyAnnotationHandler
|
||||
tags:
|
||||
- { name: nelmio_api_doc.extractor.handler }
|
||||
|
||||
Look at the built-in [Handlers](https://github.com/nelmio/NelmioApiDocBundle/tree/master/Extractor/Handler).
|
||||
|
||||
|
||||
### Reference Configuration
|
||||
|
||||
``` yaml
|
||||
nelmio_api_doc:
|
||||
name: 'API documentation'
|
||||
exclude_sections: []
|
||||
motd:
|
||||
template: 'NelmioApiDocBundle::Components/motd.html.twig'
|
||||
request_listener:
|
||||
enabled: true
|
||||
parameter: _doc
|
||||
sandbox:
|
||||
enabled: true
|
||||
endpoint: null
|
||||
accept_type: null
|
||||
body_format:
|
||||
formats:
|
||||
|
||||
# Defaults:
|
||||
- form
|
||||
- json
|
||||
default_format: ~ # One of "form"; "json"
|
||||
request_format:
|
||||
formats:
|
||||
|
||||
# Defaults:
|
||||
json: application/json
|
||||
xml: application/xml
|
||||
method: ~ # One of "format_param"; "accept_header"
|
||||
default_format: json
|
||||
authentication:
|
||||
delivery: ~ # Required
|
||||
name: ~ # Required
|
||||
|
||||
# Required if http delivery is selected.
|
||||
type: ~ # One of "basic"; "bearer"
|
||||
custom_endpoint: false
|
||||
```
|
143
Resources/doc/index.rst
Normal file
143
Resources/doc/index.rst
Normal file
|
@ -0,0 +1,143 @@
|
|||
NelmioApiDocBundle
|
||||
==================
|
||||
|
||||
The **NelmioApiDocBundle** bundle allows you to generate a decent documentation
|
||||
for your APIs.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Step 1: Download the Bundle
|
||||
---------------------------
|
||||
|
||||
Open a command console, enter your project directory and execute the
|
||||
following command to download the latest stable version of this bundle:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ composer require nelmio/api-doc-bundle
|
||||
|
||||
This command requires you to have Composer installed globally, as explained
|
||||
in the `installation chapter`_ of the Composer documentation.
|
||||
|
||||
Step 2: Enable the Bundle
|
||||
-------------------------
|
||||
|
||||
Then, enable the bundle by adding it to the list of registered bundles
|
||||
in the ``app/AppKernel.php`` file of your project:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// app/AppKernel.php
|
||||
|
||||
// ...
|
||||
class AppKernel extends Kernel
|
||||
{
|
||||
public function registerBundles()
|
||||
{
|
||||
$bundles = array(
|
||||
// ...
|
||||
new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
|
||||
);
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
Step 3: Register the Routes
|
||||
---------------------------
|
||||
|
||||
Import the routing definition in ``routing.yml``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/routing.yml
|
||||
NelmioApiDocBundle:
|
||||
resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
|
||||
prefix: /api/doc
|
||||
|
||||
Step 4: Configure the Bundle
|
||||
----------------------------
|
||||
|
||||
Enable the bundle's configuration in ``app/config/config.yml``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc: ~
|
||||
|
||||
The **NelmioApiDocBundle** requires Twig as a template engine so do not forget
|
||||
to enable it:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
framework:
|
||||
templating:
|
||||
engines: ['twig']
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The main problem with documentation is to keep it up to date. That's why the
|
||||
**NelmioApiDocBundle** uses introspection a lot. Thanks to an annotation, it's
|
||||
really easy to document an API method. The following chapters will help you
|
||||
setup your API documentation:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
the-apidoc-annotation
|
||||
multiple-api-doc
|
||||
other-bundle-annotations
|
||||
swagger-support
|
||||
sandbox
|
||||
commands
|
||||
configuration-in-depth
|
||||
configuration-reference
|
||||
faq
|
||||
|
||||
Web Interface
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
You can browse the whole documentation at: ``http://example.org/api/doc``.
|
||||
|
||||
.. image:: webview.png
|
||||
:align: center
|
||||
|
||||
.. image:: webview2.png
|
||||
:align: center
|
||||
|
||||
On-The-Fly Documentation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By calling an URL with the parameter ``?_doc=1``, you will get the corresponding
|
||||
documentation if available.
|
||||
|
||||
.. _`installation chapter`: https://getcomposer.org/doc/00-intro.md
|
||||
|
||||
Route versions
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You can define version for the API routes:
|
||||
|
||||
.. code-block:: yaml
|
||||
api_v3_products_list:
|
||||
pattern: /api/v3/products.{_format}
|
||||
defaults: { _controller: NelmioApiDocTestBundle:Test:routeVersion, _format: json, _version: "3.0" }
|
||||
requirements:
|
||||
_method: GET
|
||||
api_v1_orders:
|
||||
resource: "@AcmeOrderBundle/Resources/config/routing/orders_v1.yml"
|
||||
defaults: { _version: "1.0" }
|
||||
prefix: /api/v1/orders
|
||||
|
||||
And generate documentation for specific version by the command:
|
||||
|
||||
.. code-block:: bash
|
||||
php app/console api:doc:dump --format=html --api-version=3.0 > api.html
|
||||
|
||||
Or by adding `?_version={version}` to API documentation page URL.
|
59
Resources/doc/multiple-api-doc.rst
Normal file
59
Resources/doc/multiple-api-doc.rst
Normal file
|
@ -0,0 +1,59 @@
|
|||
Multiple API Documentation ("Views")
|
||||
====================================
|
||||
|
||||
With the ``views`` tag in the ``@ApiDoc`` annotation, it is possible to create
|
||||
different views of your API documentation. Without the tag, all methods are
|
||||
located in the ``default`` view, and can be found under the normal API
|
||||
documentation url.
|
||||
|
||||
You can specify one or more *view* names under which the method will be
|
||||
visible.
|
||||
|
||||
An example::
|
||||
|
||||
/**
|
||||
* A resource
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* description="This is a description of your API method",
|
||||
* views = { "default", "premium" }
|
||||
* )
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Another resource
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* description="This is a description of another API method",
|
||||
* views = { "premium" }
|
||||
* )
|
||||
*/
|
||||
public function getAnotherAction()
|
||||
{
|
||||
}
|
||||
|
||||
In this case, only the first resource will be available under the default view,
|
||||
while both methods will be available under the ``premium`` view.
|
||||
|
||||
Accessing Specific API Views
|
||||
----------------------------
|
||||
|
||||
The ``default`` view can be found at the normal location. Other views can be
|
||||
found at ``http://your.documentation/<view name>``.
|
||||
|
||||
For instance, if your documentation is located at:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
http://example.org/doc/api/v1/
|
||||
|
||||
then the ``premium`` view will be located at:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
http://example.org/doc/api/v1/premium
|
81
Resources/doc/other-bundle-annotations.rst
Normal file
81
Resources/doc/other-bundle-annotations.rst
Normal file
|
@ -0,0 +1,81 @@
|
|||
Other Bundle Annotations
|
||||
========================
|
||||
|
||||
PHPDoc
|
||||
------
|
||||
|
||||
Actions marked as ``@deprecated`` will be marked as such in the interface.
|
||||
|
||||
JMS Serializer Features
|
||||
-----------------------
|
||||
|
||||
The bundle has support for some of the JMS Serializer features and uses this
|
||||
extra piece of information in the generated documentation.
|
||||
|
||||
Group Exclusion Strategy
|
||||
------------------------
|
||||
|
||||
If your classes use `JMS Group Exclusion Strategy`_, you can specify which
|
||||
groups to use when generating the documentation by using this syntax::
|
||||
|
||||
input={
|
||||
"class"="Acme\Bundle\Entity\User",
|
||||
"groups"={"update", "public"}
|
||||
}
|
||||
|
||||
In this case the groups ``update`` and ``public`` are used. This feature also
|
||||
works for the ``output`` property.
|
||||
|
||||
Versioning Objects
|
||||
------------------
|
||||
|
||||
If your ``output`` classes use `versioning capabilities of JMS Serializer`_, the
|
||||
versioning information will be automatically used when generating the
|
||||
documentation.
|
||||
|
||||
Form Types Features
|
||||
-------------------
|
||||
|
||||
Even if you use ``FormFactoryInterface::createNamed('', 'your_form_type')`` the
|
||||
documentation will generate the form type name as the prefix for inputs
|
||||
(``your_form_type[param]`` ... instead of just ``param``).
|
||||
|
||||
You can specify which prefix to use with the ``name`` key in the ``input``
|
||||
section::
|
||||
|
||||
input = {
|
||||
"class" = "your_form_type",
|
||||
"name" = ""
|
||||
}
|
||||
|
||||
You can also add some options to pass to the form. You just have to use the
|
||||
``options`` key::
|
||||
|
||||
input = {
|
||||
"class" = "your_form_type",
|
||||
"options" = {"method" = "PUT"},
|
||||
}
|
||||
|
||||
Using Your Own Annotations
|
||||
--------------------------
|
||||
|
||||
If you have developed your own project-related annotations, and you want to
|
||||
parse them to populate the ``ApiDoc``, you can provide custom handlers as
|
||||
services. You just have to implement the
|
||||
``Nelmio\ApiDocBundle\Extractor\HandlerInterface`` and tag it as
|
||||
``nelmio_api_doc.extractor.handler``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
services:
|
||||
mybundle.api_doc.extractor.my_annotation_handler:
|
||||
class: MyBundle\AnnotationHandler\MyAnnotationHandler
|
||||
tags:
|
||||
- { name: nelmio_api_doc.extractor.handler }
|
||||
|
||||
Look at the `built-in Handlers`_.
|
||||
|
||||
.. _`JMS Group Exclusion Strategy`: http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#creating-different-views-of-your-objects
|
||||
.. _`versioning capabilities of JMS Serializer`: http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#versioning-objects
|
||||
.. _`built-in Handlers`: https://github.com/nelmio/NelmioApiDocBundle/tree/master/Extractor/Handler
|
54
Resources/doc/sandbox.rst
Normal file
54
Resources/doc/sandbox.rst
Normal file
|
@ -0,0 +1,54 @@
|
|||
Sandbox
|
||||
=======
|
||||
|
||||
This bundle provides a sandbox mode in order to test API methods. You can
|
||||
configure this sandbox using the following parameters:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# app/config/config.yml
|
||||
nelmio_api_doc:
|
||||
sandbox:
|
||||
authentication: # default is `~` (`null`), if set, the sandbox automatically
|
||||
# send authenticated requests using the configured `delivery`
|
||||
|
||||
name: access_token # access token name or query parameter name or header name
|
||||
|
||||
delivery: http # `query`, `http`, and `header` are supported
|
||||
|
||||
# Required if http delivery is selected.
|
||||
type: basic # `basic`, `bearer` are supported
|
||||
|
||||
custom_endpoint: true # default is `false`, if `true`, your user will be able to
|
||||
# specify its own endpoint
|
||||
|
||||
enabled: true # default is `true`, you can set this parameter to `false`
|
||||
# to disable the sandbox
|
||||
|
||||
endpoint: http://sandbox.example.com/ # default is `/app_dev.php`, use this parameter
|
||||
# to define which URL to call through the sandbox
|
||||
|
||||
accept_type: application/json # default is `~` (`null`), if set, the value is
|
||||
# automatically populated as the `Accept` header
|
||||
|
||||
body_format:
|
||||
formats: [ form, json ] # array of enabled body formats,
|
||||
# remove all elements to disable the selectbox
|
||||
default_format: form # default is `form`, determines whether to send
|
||||
# `x-www-form-urlencoded` data or json-encoded
|
||||
# data (by setting this parameter to `json`) in
|
||||
# sandbox requests
|
||||
|
||||
request_format:
|
||||
formats: # default is `json` and `xml`,
|
||||
json: application/json # override to add custom formats or disable
|
||||
xml: application/xml # the default formats
|
||||
|
||||
method: format_param # default is `format_param`, alternately `accept_header`,
|
||||
# decides how to request the response format
|
||||
|
||||
default_format: json # default is `json`,
|
||||
# default content format to request (see formats)
|
||||
|
||||
entity_to_choice: false # default is `true`, if `false`, entity collection
|
||||
# will not be mapped as choice
|
163
Resources/doc/swagger-support.rst
Normal file
163
Resources/doc/swagger-support.rst
Normal file
|
@ -0,0 +1,163 @@
|
|||
Swagger Support
|
||||
===============
|
||||
|
||||
It is possible to make your application produce Swagger-compliant JSON output
|
||||
based on ``@ApiDoc`` annotations, which can be used for consumption by
|
||||
`swagger-ui`_.
|
||||
|
||||
Annotation options
|
||||
------------------
|
||||
|
||||
A couple of properties has been added to ``@ApiDoc``:
|
||||
|
||||
To define a **resource description**::
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* resourceDescription="Operations on users.",
|
||||
* description="Retrieve list of users."
|
||||
* )
|
||||
*/
|
||||
public function listUsersAction()
|
||||
{
|
||||
/* Stuff */
|
||||
}
|
||||
|
||||
The ``resourceDescription`` is distinct from ``description`` as it applies to the
|
||||
whole resource group and not just the particular API endpoint.
|
||||
|
||||
Defining a form-type as a GET form
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you use forms to capture GET requests, you will have to specify the
|
||||
``paramType`` to ``query`` in the annotation::
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* input = {"class" = "Foo\ContentBundle\Form\SearchType", "paramType" = "query"},
|
||||
* ...
|
||||
* )
|
||||
*/
|
||||
|
||||
public function searchAction(Request $request)
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
Multiple response models
|
||||
------------------------
|
||||
|
||||
Swagger provides you the ability to specify alternate output models for
|
||||
different status codes. Example, ``200`` would return your default resource object
|
||||
in JSON form, but ``400`` may return a custom validation error list object. This
|
||||
can be specified through the ``responseMap`` property::
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Retrieve list of users.",
|
||||
* statusCodes={
|
||||
* 400 = "Validation failed."
|
||||
* },
|
||||
* responseMap={
|
||||
* 200 = "FooBundle\Entity\User",
|
||||
* 400 = {
|
||||
* "class"="CommonBundle\Model\ValidationErrors",
|
||||
* "parsers"={"Nelmio\ApiDocBundle\Parser\JmsMetadataParser"}
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function updateUserAction()
|
||||
{
|
||||
/* Stuff */
|
||||
}
|
||||
|
||||
This will tell Swagger that ``CommonBundle\Model\ValidationErrors`` is returned
|
||||
when this endpoint returns a ``400 Validation failed.`` HTTP response.
|
||||
|
||||
.. note::
|
||||
|
||||
You can omit the ``200`` entry in the ``responseMap`` property and specify
|
||||
the default ``output`` property instead. That will result on the same thing.
|
||||
|
||||
Integration with ``swagger-api/swagger-ui``
|
||||
-------------------------------------------
|
||||
|
||||
You could import the routes for use with `swagger-ui`_.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
#app/config/routing.yml
|
||||
nelmio_api_swagger:
|
||||
resource: "@NelmioApiDocBundle/Resources/config/swagger_routing.yml"
|
||||
prefix: /api-docs
|
||||
|
||||
Et voila!, simply specify http://yourdomain.com/api-docs in your ``swagger-ui``
|
||||
instance and you are good to go.
|
||||
|
||||
.. note::
|
||||
|
||||
If your ``swagger-ui`` instance does not live under the same domain, you
|
||||
will probably encounter some problems related to same-origin policy
|
||||
violations. `NelmioCorsBundle`_ can solve this problem for you. Read through
|
||||
how to allow cross-site requests for the ``/api-docs/*`` pages.
|
||||
|
||||
Dumping the Swagger-compliant JSON API definitions
|
||||
--------------------------------------------------
|
||||
|
||||
To display all JSON definitions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:swagger:dump
|
||||
|
||||
To dump just the resource list:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:swagger:dump --list-only
|
||||
|
||||
To dump just the API definition the ``users`` resource:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:swagger:dump --resource=users
|
||||
|
||||
Specify the ``--pretty`` flag to display a prettified JSON output.
|
||||
|
||||
Dump to files
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
You can specify the destination if you wish to dump the JSON definition to a file:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:swagger:dump --list-only swagger-docs/api-docs.json
|
||||
$ php app/console api:swagger:dump --resource=users swagger-docs/users.json
|
||||
|
||||
Or, you can dump everything into a directory in one command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php app/console api:swagger:dump swagger-docs
|
||||
|
||||
Model naming
|
||||
------------
|
||||
|
||||
By default, the model naming strategy used is the ``dot_notation`` strategy. The
|
||||
model IDs are simply the Fully Qualified Class Name (FQCN) of the class
|
||||
associated to it, with the ``\`` replaced with ``.``:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Vendor\UserBundle\Entity\User => Vendor.UserBundle.Entity.User
|
||||
|
||||
You can also change the ``model_naming_strategy`` in the configuration to
|
||||
``last_segment_only``, if you want model IDs to be just the class name minus the
|
||||
namespaces (``Vendor\UserBundle\Entity\User => User``). This will not afford you
|
||||
the guarantee that model IDs are unique, but that would really just depend on
|
||||
the classes you have in use.
|
||||
|
||||
.. _`swagger-ui`: https://github.com/swagger-api/swagger-ui
|
||||
.. _`NelmioCorsBundle`: https://github.com/nelmio/NelmioCorsBundle
|
181
Resources/doc/the-apidoc-annotation.rst
Normal file
181
Resources/doc/the-apidoc-annotation.rst
Normal file
|
@ -0,0 +1,181 @@
|
|||
The ``ApiDoc()`` Annotation
|
||||
===========================
|
||||
|
||||
The bundle provides an ``ApiDoc()`` annotation for your controllers::
|
||||
|
||||
namespace Your\Namespace;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
|
||||
class YourController extends Controller
|
||||
{
|
||||
/**
|
||||
* This is the documentation description of your method, it will appear
|
||||
* on a specific pane. It will read all the text until the first
|
||||
* annotation.
|
||||
*
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* description="This is a description of your API method",
|
||||
* filters={
|
||||
* {"name"="a-filter", "dataType"="integer"},
|
||||
* {"name"="another-filter", "dataType"="string", "pattern"="(foo|bar) ASC|DESC"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Create a new Object",
|
||||
* input="Your\Namespace\Form\Type\YourType",
|
||||
* output="Your\Namespace\Class"
|
||||
* )
|
||||
*/
|
||||
public function postAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Returns a collection of Object",
|
||||
* requirements={
|
||||
* {
|
||||
* "name"="limit",
|
||||
* "dataType"="integer",
|
||||
* "requirement"="\d+",
|
||||
* "description"="how many objects to return"
|
||||
* }
|
||||
* },
|
||||
* parameters={
|
||||
* {"name"="categoryId", "dataType"="integer", "required"=true, "description"="category id"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function cgetAction($limit)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
The following properties are available:
|
||||
|
||||
* ``section``: allow to group resources
|
||||
* ``resource``: whether the method describes a main resource or not (default:
|
||||
``false``);
|
||||
* ``description``: a description of the API method;
|
||||
* ``deprecated``: allow to set method as deprecated (default: ``false``);
|
||||
* ``tags``: allow to tag a method (e.g. ``beta`` or ``in-development``). Either
|
||||
a single tag or an array of tags. Each tag can have an optional hex colorcode
|
||||
attached.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class YourController
|
||||
{
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* tags={
|
||||
* "stable",
|
||||
* "deprecated" = "#ff0000"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function myFunction()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
* ``filters``: an array of filters;
|
||||
* ``requirements``: an array of requirements;
|
||||
* ``parameters``: an array of parameters;
|
||||
* ``headers``: an array of headers; available properties are: ``name``, ``description``, ``required``, ``default``. Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class YourController
|
||||
{
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* headers={
|
||||
* {
|
||||
* "name"="X-AUTHORIZE-KEY",
|
||||
* "description"="Authorization key"
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function myFunction()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
* ``input``: the input type associated to the method (currently this supports
|
||||
Form Types, classes with JMS Serializer metadata, classes with Validation
|
||||
component metadata and classes that implement JsonSerializable) useful for
|
||||
POST|PUT methods, either as FQCN or as form type (if it is registered in the
|
||||
form factory in the container).
|
||||
* ``output``: the output type associated with the response. Specified and
|
||||
parsed the same way as ``input``.
|
||||
* ``statusCodes``: an array of HTTP status codes and a description of when that
|
||||
status is returned; Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class YourController
|
||||
{
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* statusCodes={
|
||||
* 200="Returned when successful",
|
||||
* 403="Returned when the user is not authorized to say hello",
|
||||
* 404={
|
||||
* "Returned when the user is not found",
|
||||
* "Returned when something else is not found"
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function myFunction()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
* ``views``: the view(s) under which this resource will be shown. Leave empty to
|
||||
specify the default view. Either a single view, or an array of views.
|
||||
|
||||
Each *filter* has to define a ``name`` parameter, but other parameters are free.
|
||||
Filters are often optional parameters, and you can document them as you want,
|
||||
but keep in mind to be consistent for the whole documentation.
|
||||
|
||||
If you set ``input``, then the bundle automatically extracts parameters based on
|
||||
the given type, and determines for each parameter its data type, and if it's
|
||||
required or not.
|
||||
|
||||
For classes parsed with JMS metadata, description will be taken from the
|
||||
properties doc comment, if available.
|
||||
|
||||
For Form Types, you can add an extra option named ``description`` on each field::
|
||||
|
||||
class YourType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilder $builder, array $options)
|
||||
{
|
||||
$builder->add('note', null, array(
|
||||
'description' => 'this is a note',
|
||||
));
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
The bundle will also get information from the routing definition
|
||||
(``requirements``, ``path``, etc), so to get the best out of it you should
|
||||
define strict methods requirements etc.
|
|
@ -99,6 +99,11 @@ em {
|
|||
code, pre {
|
||||
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
||||
background-color: #fcf6db;
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
p code {
|
||||
|
@ -113,6 +118,11 @@ pre {
|
|||
line-height:1.2em;
|
||||
}
|
||||
|
||||
div.content ul, div.content ol {
|
||||
line-height: 1.4em;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
table.fullwidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -135,6 +145,10 @@ table tbody tr td {
|
|||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
table tbody tr td.format {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#header {
|
||||
background-color: #89BF04;
|
||||
padding: 1%;
|
||||
|
@ -172,10 +186,37 @@ table tbody tr td {
|
|||
}
|
||||
|
||||
.section {
|
||||
border: 1px solid #ddd;
|
||||
background: #f8f8f8;
|
||||
padding: 5px 20px;
|
||||
margin-bottom: 15px;
|
||||
padding: 5px 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.section h1 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.section.active {
|
||||
border: 1px solid #ddd;
|
||||
background: #f8f8f8;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.section.active h1 {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.section .actions {
|
||||
text-align: right;
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.section .actions a {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.section .actions a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
li.resource {
|
||||
|
@ -557,7 +598,7 @@ form .parameters {
|
|||
width: 50%;
|
||||
}
|
||||
|
||||
form .parameters .tuple input {
|
||||
form .parameters .tuple input, form .parameters .tuple textarea {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
|
@ -589,3 +630,8 @@ form .request-content {
|
|||
.motd {
|
||||
padding:20px;
|
||||
}
|
||||
|
||||
.json-collapse-section {
|
||||
color: #660;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -24,11 +24,13 @@
|
|||
{% if 'json' in bodyFormats %}<option value="json"{{ defaultBodyFormat == 'json' ? ' selected' : '' }}>JSON</option>{% endif %}
|
||||
</select>
|
||||
{% endif %}
|
||||
{% if requestFormats|length > 0 %}
|
||||
request format:
|
||||
<select id="request_format">
|
||||
{% for format, header in requestFormats %}
|
||||
<option value="{{ header }}"{{ defaultRequestFormat == format ? ' selected' : '' }}>{{ format }}</option>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</select>
|
||||
{% if authentication %}
|
||||
{% if authentication.delivery == 'http' and authentication.type == 'basic' %}
|
||||
|
@ -41,6 +43,8 @@
|
|||
{% if authentication.custom_endpoint %}
|
||||
api endpoint: <input type="text" id="api_endpoint" value=""/>
|
||||
{% endif %}
|
||||
<button id="save_api_auth" type="button">Save</button>
|
||||
<button id="clear_api_auth" type="button">Clear</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -82,14 +86,22 @@
|
|||
};
|
||||
|
||||
$(window).load(function() {
|
||||
var id = getHash().substr(1).replace( /([:\.\[\]\{\}])/g, "\\$1");
|
||||
var id = getHash().substr(1).replace( /([:\.\[\]\{\}|])/g, "\\$1");
|
||||
var elem = $('#' + id);
|
||||
if (elem.length) {
|
||||
setTimeout(function() {
|
||||
$('body,html').scrollTop(elem.position().top);
|
||||
});
|
||||
elem.find('.toggler').click();
|
||||
var section = elem.parents('.section').first();
|
||||
if (section) {
|
||||
section.addClass('active');
|
||||
section.find('.section-list').slideDown('fast');
|
||||
}
|
||||
}
|
||||
{% if enableSandbox %}
|
||||
loadStoredAuthParams();
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
$('.toggler').click(function(event) {
|
||||
|
@ -105,39 +117,104 @@
|
|||
return false;
|
||||
});
|
||||
|
||||
{% if enableSandbox %}
|
||||
var setParameterType = function ($context,setType) {
|
||||
// no 2nd argument, use default from parameters
|
||||
if (typeof setType == "undefined") {
|
||||
setType = $context.parent().attr("data-dataType");
|
||||
$context.val(setType);
|
||||
}
|
||||
|
||||
$context.parent().find('.value').remove();
|
||||
var placeholder = "";
|
||||
if ($context.parent().attr("data-dataType") != "" && typeof $context.parent().attr("data-dataType") != "undefined") {
|
||||
placeholder += "[" + $context.parent().attr("data-dataType") + "] ";
|
||||
}
|
||||
if ($context.parent().attr("data-format") != "" && typeof $context.parent().attr("data-format") != "undefined") {
|
||||
placeholder += $context.parent().attr("data-dataType");
|
||||
}
|
||||
if ($context.parent().attr("data-description") != "" && typeof $context.parent().attr("data-description") != "undefined") {
|
||||
placeholder += $context.parent().attr("data-description");
|
||||
$('.action-show-hide, .section > h1').on('click', function(){
|
||||
var section = $(this).parents('.section').first();
|
||||
if (section.hasClass('active')) {
|
||||
section.removeClass('active');
|
||||
section.find('.section-list').slideUp('fast');
|
||||
} else {
|
||||
placeholder += "Value";
|
||||
section.addClass('active');
|
||||
section.find('.section-list').slideDown('fast');
|
||||
}
|
||||
|
||||
switch(setType) {
|
||||
case "boolean":
|
||||
$('<select class="value"><option value=""></option><option value="1">True</option><option value="0">False</option></select>').insertAfter($context);
|
||||
break;
|
||||
case "file":
|
||||
$('<input type="file" class="value" placeholder="'+ placeholder +'">').insertAfter($context);
|
||||
break;
|
||||
default:
|
||||
$('<input type="text" class="value" placeholder="'+ placeholder +'">').insertAfter($context);
|
||||
});
|
||||
|
||||
$('.action-list').on('click', function(){
|
||||
var section = $(this).parents('.section').first();
|
||||
if (!section.hasClass('active')) {
|
||||
section.addClass('active');
|
||||
}
|
||||
};
|
||||
section.find('.section-list').slideDown('fast');
|
||||
section.find('.operation > .content').slideUp('fast');
|
||||
});
|
||||
|
||||
$('.action-expand').on('click', function(){
|
||||
var section = $(this).parents('.section').first();
|
||||
if (!section.hasClass('active')) {
|
||||
section.addClass('active');
|
||||
}
|
||||
$(section).find('ul').slideDown('fast');
|
||||
$(section).find('.operation > .content').slideDown('fast');
|
||||
});
|
||||
|
||||
{% if enableSandbox %}
|
||||
var getStoredValue, storeValue, deleteStoredValue;
|
||||
var apiAuthKeys = ['api_key', 'api_login', 'api_pass', 'api_endpoint'];
|
||||
|
||||
if ('localStorage' in window) {
|
||||
var buildKey = function (key) {
|
||||
return 'nelmio_' + key;
|
||||
}
|
||||
|
||||
getStoredValue = function (key) {
|
||||
return localStorage.getItem(buildKey(key));
|
||||
}
|
||||
|
||||
storeValue = function (key, value) {
|
||||
localStorage.setItem(buildKey(key), value);
|
||||
}
|
||||
|
||||
deleteStoredValue = function (key) {
|
||||
localStorage.removeItem(buildKey(key));
|
||||
}
|
||||
} else {
|
||||
getStoredValue = storeValue = deleteStoredValue = function (){};
|
||||
}
|
||||
|
||||
var loadStoredAuthParams = function() {
|
||||
$.each(apiAuthKeys, function(_, value) {
|
||||
var elm = $('#' + value);
|
||||
if (elm.length) {
|
||||
elm.val(getStoredValue(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var setParameterType = function ($context,setType) {
|
||||
// no 2nd argument, use default from parameters
|
||||
if (typeof setType == "undefined") {
|
||||
setType = $context.parent().attr("data-dataType");
|
||||
$context.val(setType);
|
||||
}
|
||||
|
||||
$context.parent().find('.value').remove();
|
||||
var placeholder = "";
|
||||
if ($context.parent().attr("data-dataType") != "" && typeof $context.parent().attr("data-dataType") != "undefined") {
|
||||
placeholder += "[" + $context.parent().attr("data-dataType") + "] ";
|
||||
}
|
||||
if ($context.parent().attr("data-format") != "" && typeof $context.parent().attr("data-format") != "undefined") {
|
||||
placeholder += $context.parent().attr("data-format");
|
||||
}
|
||||
if ($context.parent().attr("data-description") != "" && typeof $context.parent().attr("data-description") != "undefined") {
|
||||
placeholder += $context.parent().attr("data-description");
|
||||
} else {
|
||||
placeholder += "Value";
|
||||
}
|
||||
|
||||
switch(setType) {
|
||||
case "boolean":
|
||||
$('<select class="value"><option value=""></option><option value="1">True</option><option value="0">False</option></select>').insertAfter($context);
|
||||
break;
|
||||
case "file":
|
||||
$('<input type="file" class="value" placeholder="'+ placeholder +'">').insertAfter($context);
|
||||
break;
|
||||
case "textarea":
|
||||
$('<textarea class="value" placeholder="'+ placeholder +'" />').insertAfter($context);
|
||||
break;
|
||||
default:
|
||||
$('<input type="text" class="value" placeholder="'+ placeholder +'">').insertAfter($context);
|
||||
}
|
||||
};
|
||||
|
||||
var toggleButtonText = function ($btn) {
|
||||
if ($btn.text() === 'Default') {
|
||||
|
@ -169,7 +246,7 @@
|
|||
$btn = $container.parents('.pane').find('.to-prettify');
|
||||
|
||||
$container.removeClass('prettyprinted');
|
||||
$container.html(prettifyResponse(rawData));
|
||||
$container.html(attachCollapseMarker(prettifyResponse(rawData)));
|
||||
prettyPrint && prettyPrint();
|
||||
|
||||
$btn.removeClass('to-prettify');
|
||||
|
@ -201,7 +278,26 @@
|
|||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
};
|
||||
|
||||
$('#save_api_auth').click(function(event) {
|
||||
$.each(apiAuthKeys, function(_, value) {
|
||||
var elm = $('#' + value);
|
||||
if (elm.length) {
|
||||
storeValue(value, elm.val());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#clear_api_auth').click(function(event) {
|
||||
$.each(apiAuthKeys, function(_, value) {
|
||||
deleteStoredValue(value);
|
||||
var elm = $('#' + value);
|
||||
if (elm.length) {
|
||||
elm.val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.tabs li').click(function() {
|
||||
var contentGroup = $(this).parents('.content');
|
||||
|
@ -213,6 +309,22 @@
|
|||
$(this).addClass('selected');
|
||||
});
|
||||
|
||||
var getJsonCollapseHtml = function(sectionOpenCharacter) {
|
||||
var $toggler = $('<span>').addClass('json-collapse-section').
|
||||
attr('data-section-open-character', sectionOpenCharacter).
|
||||
append($('<span>').addClass('json-collapse-marker')
|
||||
.html('▿')
|
||||
).append(sectionOpenCharacter);
|
||||
return $('<div>').append($toggler).html();
|
||||
};
|
||||
|
||||
var attachCollapseMarker = function (prettifiedJsonString) {
|
||||
prettifiedJsonString = prettifiedJsonString.replace(/(\{|\[)\n/g, function(match, sectionOpenCharacter) {
|
||||
return getJsonCollapseHtml(sectionOpenCharacter) + '<span class="json-collapse-content">\n';
|
||||
});
|
||||
return prettifiedJsonString.replace(/([^\[][\}\]],?)\n/g, '$1</span>\n');
|
||||
};
|
||||
|
||||
var prettifyResponse = function(text) {
|
||||
try {
|
||||
var data = typeof text === 'string' ? JSON.parse(text) : text;
|
||||
|
@ -224,8 +336,23 @@
|
|||
return $('<div>').text(text).html();
|
||||
};
|
||||
|
||||
var displayFinalUrl = function(xhr, method, url, container) {
|
||||
container.text(method + ' ' + url);
|
||||
var displayFinalUrl = function(xhr, method, url, data, container) {
|
||||
container.text(method + ' ' + getFinalUrl(method, url, data));
|
||||
};
|
||||
|
||||
var displayRequestBody = function(method, data, container, header) {
|
||||
if ('GET' != method && !jQuery.isEmptyObject(data) && data !== "" && data !== undefined) {
|
||||
if (jQuery.type(data) !== 'string') {
|
||||
data = decodeURIComponent(jQuery.param(data));
|
||||
}
|
||||
|
||||
container.text(data);
|
||||
container.show();
|
||||
header.show();
|
||||
} else {
|
||||
container.hide();
|
||||
header.hide();
|
||||
}
|
||||
};
|
||||
|
||||
var displayProfilerUrl = function(xhr, link, container) {
|
||||
|
@ -237,7 +364,7 @@
|
|||
link.attr('href', '');
|
||||
container.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var displayResponseData = function(xhr, container) {
|
||||
var data = xhr.responseText;
|
||||
|
@ -257,30 +384,70 @@
|
|||
container.text(text);
|
||||
};
|
||||
|
||||
var displayResponse = function(xhr, method, url, result_container) {
|
||||
displayFinalUrl(xhr, method, url, $('.url', result_container));
|
||||
var displayCurl = function(method, url, headers, data, result_container) {
|
||||
var escapeShell = function(param) {
|
||||
param = "" + param;
|
||||
return '"' + param.replace(/(["\s'$`\\])/g,'\\$1') + '"';
|
||||
};
|
||||
|
||||
url = getFinalUrl(method, url, data);
|
||||
|
||||
var command = "curl";
|
||||
command += " -X " + escapeShell(method);
|
||||
|
||||
if (method != "GET" && !jQuery.isEmptyObject(data) && data !== "" && data !== undefined) {
|
||||
if (jQuery.type(data) !== 'string') {
|
||||
data = decodeURIComponent(jQuery.param(data));
|
||||
}
|
||||
command += " -d " + escapeShell(data);
|
||||
}
|
||||
|
||||
for (headerKey in headers) {
|
||||
if (headers.hasOwnProperty(headerKey)) {
|
||||
command += " -H " + escapeShell(headerKey + ': ' + headers[headerKey]);
|
||||
}
|
||||
}
|
||||
|
||||
command += " " + url;
|
||||
|
||||
result_container.text(command);
|
||||
};
|
||||
|
||||
var getFinalUrl = function(method, url, data) {
|
||||
if ('GET' == method && !jQuery.isEmptyObject(data)) {
|
||||
var separator = url.indexOf('?') >= 0 ? '&' : '?';
|
||||
url = url + separator + decodeURIComponent(jQuery.param(data));
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
var displayResponse = function(xhr, method, url, headers, data, result_container) {
|
||||
displayFinalUrl(xhr, method, url, data, $('.url', result_container));
|
||||
displayRequestBody(method, data, $('.request-body', result_container), $('.request-body-header', result_container));
|
||||
displayProfilerUrl(xhr, $('.profiler-link', result_container), $('.profiler', result_container));
|
||||
displayResponseData(xhr, $('.response', result_container));
|
||||
displayResponseHeaders(xhr, $('.headers', result_container));
|
||||
displayCurl(method, url, headers, data, $('.curl-command', result_container));
|
||||
|
||||
result_container.show();
|
||||
};
|
||||
|
||||
$('.pane.sandbox form').submit(function() {
|
||||
var url = $(this).attr('action'),
|
||||
method = $(this).attr('method'),
|
||||
method = $('[name="header_method"]', this).val(),
|
||||
self = this,
|
||||
params = {},
|
||||
filters = {},
|
||||
formData = new FormData(),
|
||||
doubledParams = {},
|
||||
doubledFilters = {},
|
||||
headers = {},
|
||||
content = $(this).find('textarea.content').val(),
|
||||
result_container = $('.result', $(this).parent());
|
||||
|
||||
if (method === 'ANY') {
|
||||
method = 'POST';
|
||||
} else if (method.indexOf('|') !== -1) {
|
||||
method = method.split('|').sort().pop();
|
||||
}
|
||||
|
||||
// set requestFormat
|
||||
|
@ -315,6 +482,11 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
if (hasFileTypes && bodyFormat != 'form') {
|
||||
alert("Body Format must be set to 'Form Data' when utilizing file upload type parameters.\nYour current bodyFormat is '" + bodyFormat + "'. Change your BodyFormat or do not use file type\nparameters.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasFileTypes) {
|
||||
// retrieve all the parameters to send for file upload
|
||||
$('.parameters .tuple', $(this)).each(function() {
|
||||
|
@ -323,6 +495,9 @@
|
|||
key = $('.key', $(this)).val();
|
||||
if ($('.value', $(this)).attr('type') === 'file' ) {
|
||||
value = $('.value', $(this)).prop('files')[0];
|
||||
if(!value) {
|
||||
value = new File([], '');
|
||||
}
|
||||
} else {
|
||||
value = $('.value', $(this)).val();
|
||||
}
|
||||
|
@ -342,6 +517,11 @@
|
|||
value = $('.value', $(this)).val();
|
||||
|
||||
if (value) {
|
||||
// convert boolean values to boolean
|
||||
if ('json' === bodyFormat && 'boolean' === $('.tuple_type', $(this)).val()) {
|
||||
value = '1' === value;
|
||||
}
|
||||
|
||||
// temporary save all additional/doubled parameters
|
||||
if (key in params) {
|
||||
doubledParams[key] = value;
|
||||
|
@ -354,6 +534,25 @@
|
|||
|
||||
|
||||
|
||||
// retrieve all the filters to send
|
||||
$('.parameters .tuple.filter', $(this)).each(function() {
|
||||
var key, value;
|
||||
|
||||
key = $('.key', $(this)).val();
|
||||
value = $('.value', $(this)).val();
|
||||
|
||||
if (value) {
|
||||
// temporary save all additional/doubled parameters
|
||||
if (key in filters) {
|
||||
doubledFilters[key] = value;
|
||||
} else {
|
||||
filters[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// retrieve the additional headers to send
|
||||
$('.headers .tuple', $(this)).each(function() {
|
||||
|
@ -385,9 +584,10 @@
|
|||
$('input, button', $(this)).attr('disabled', 'disabled');
|
||||
|
||||
// append the query authentication
|
||||
if (authentication_delivery == 'query') {
|
||||
var api_key_val = $('#api_key').val();
|
||||
if (authentication_delivery == 'query' && api_key_val.length>0) {
|
||||
url += url.indexOf('?') > 0 ? '&' : '?';
|
||||
url += api_key_parameter + '=' + $('#api_key').val();
|
||||
url += api_key_parameter + '=' + api_key_val;
|
||||
}
|
||||
|
||||
// prepare the api enpoint
|
||||
|
@ -396,14 +596,22 @@
|
|||
{% else -%}
|
||||
var endpoint = '{{ endpoint }}';
|
||||
{% endif -%}
|
||||
if ($('#api_endpoint') && $('#api_endpoint').val() != null) {
|
||||
{% if authentication and authentication.custom_endpoint %}
|
||||
if ($('#api_endpoint') && typeof($('#api_endpoint').val()) != 'undefined') {
|
||||
endpoint = $('#api_endpoint').val();
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// Workaround for Firefox bug and a thereby resulting nginx incompatibility
|
||||
if (method == "LINK") {
|
||||
method = "POST";
|
||||
params._method = "LINK";
|
||||
//add filters as GET params and remove them from params
|
||||
if(method != 'GET'){
|
||||
for (var filterKey in $.extend({}, filters)){
|
||||
url += url.indexOf('?') > 0 ? '&' : '?';
|
||||
url += filterKey + '=' + filters[filterKey];
|
||||
|
||||
if (params.hasOwnProperty(filterKey)){
|
||||
delete(params[filterKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prepare final parameters
|
||||
|
@ -416,7 +624,8 @@
|
|||
}
|
||||
var data = content.length ? content : body;
|
||||
var ajaxOptions = {
|
||||
url: endpoint + url,
|
||||
url: (url.indexOf('http')!=0?endpoint:'') + url,
|
||||
xhrFields: { withCredentials: true },
|
||||
type: method,
|
||||
data: data,
|
||||
headers: headers,
|
||||
|
@ -439,7 +648,7 @@
|
|||
}
|
||||
},
|
||||
complete: function(xhr) {
|
||||
displayResponse(xhr, method, url, result_container);
|
||||
displayResponse(xhr, method, url, headers, data, result_container);
|
||||
|
||||
// and enable them back
|
||||
$('input:not(.content-type), button', $(self)).removeAttr('disabled');
|
||||
|
@ -451,7 +660,7 @@
|
|||
ajaxOptions.data = formData;
|
||||
ajaxOptions.processData = false;
|
||||
ajaxOptions.contentType = false;
|
||||
delete(ajaxOptions.headers);
|
||||
delete(headers['Content-type']);
|
||||
}
|
||||
|
||||
// and trigger the API call
|
||||
|
@ -467,6 +676,25 @@
|
|||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.json-collapse-section', function() {
|
||||
var openChar = $(this).data('section-open-character'),
|
||||
closingChar = (openChar == '{' ? '}' : ']');
|
||||
if ($(this).next('.json-collapse-content').is(':visible')) {
|
||||
$(this).html('⊕' + openChar + '...' + closingChar);
|
||||
} else {
|
||||
$(this).html('▿' + $(this).data('section-open-character'));
|
||||
}
|
||||
$(this).next('.json-collapse-content').toggle();
|
||||
});
|
||||
|
||||
$(document).on('copy', '.prettyprinted', function () {
|
||||
var $toggleMarkers = $(this).find('.json-collapse-marker');
|
||||
$toggleMarkers.hide();
|
||||
setTimeout(function () {
|
||||
$toggleMarkers.show();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
$('.pane.sandbox').on('click', '.to-raw', function(e) {
|
||||
renderRawBody($(this).parents('.pane').find('.response'));
|
||||
|
||||
|
|
|
@ -11,13 +11,6 @@
|
|||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if data.https %}
|
||||
<span class="icon lock" title="HTTPS"></span>
|
||||
{% endif %}
|
||||
{% if data.authentication %}
|
||||
<span class="icon keys" title="Needs {{ data.authenticationRoles|length > 0 ? data.authenticationRoles|join(', ') : 'authentication' }}"></span>
|
||||
{% endif %}
|
||||
|
||||
<span class="path">
|
||||
{% if data.host is defined -%}
|
||||
{{ data.https ? 'https://' : 'http://' -}}
|
||||
|
@ -26,8 +19,8 @@
|
|||
{{ data.uri }}
|
||||
</span>
|
||||
{% if data.tags is defined %}
|
||||
{% for tag in data.tags %}
|
||||
<span class="tag">{{ tag }}</span>
|
||||
{% for tag, color_code in data.tags %}
|
||||
<span class="tag" {% if color_code is defined and color_code is not empty %}style="background-color:{{ color_code }};"{% endif %}>{{ tag }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
@ -40,8 +33,8 @@
|
|||
|
||||
<div class="content" style="display: {% if displayContent is defined and displayContent == true %}display{% else %}none{% endif %};">
|
||||
<ul class="tabs">
|
||||
<li class="selected" data-pane="content">Documentation</li>
|
||||
{% if enableSandbox %}
|
||||
<li class="selected" data-pane="content">Documentation</li>
|
||||
<li data-pane="sandbox">Sandbox</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@ -58,7 +51,7 @@
|
|||
<div><a href="{{ data.link }}" target="_blank">{{ data.link }}</a></div>
|
||||
{% endif %}
|
||||
|
||||
{% if data.requirements is defined and data.requirements is not empty %}
|
||||
{% if data.requirements is defined and data.requirements is not empty %}
|
||||
<h4>Requirements</h4>
|
||||
<table class="fullwidth">
|
||||
<thead>
|
||||
|
@ -100,7 +93,7 @@
|
|||
{% for key, value in infos %}
|
||||
<tr>
|
||||
<td>{{ key|title }}</td>
|
||||
<td>{{ value|json_encode|replace({'\\\\': '\\'})|trim('"') }}</td>
|
||||
<td>{{ value|json_encode(constant('JSON_UNESCAPED_UNICODE'))|replace({'\\\\': '\\'})|trim('"') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
@ -130,8 +123,8 @@
|
|||
<td>{{ name }}</td>
|
||||
<td>{{ infos.dataType is defined ? infos.dataType : '' }}</td>
|
||||
<td>{{ infos.required ? 'true' : 'false' }}</td>
|
||||
<td>{{ infos.format }}</td>
|
||||
<td>{{ infos.description is defined ? infos.description : '' }}</td>
|
||||
<td class="format">{{ infos.format }}</td>
|
||||
<td>{{ infos.description is defined ? infos.description|trans : '' }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -139,27 +132,62 @@
|
|||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if data.response is defined and data.response is not empty %}
|
||||
|
||||
{% if data.headers is defined and data.headers is not empty %}
|
||||
<h4>Headers</h4>
|
||||
<table class="fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Required?</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for name, infos in data.headers %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ infos.required is defined and infos.required == 'true' ? 'true' : 'false'}}</td>
|
||||
<td>{{ infos.description is defined ? infos.description|trans : ''}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if data.parsedResponseMap is defined and data.parsedResponseMap is not empty %}
|
||||
<h4>Return</h4>
|
||||
<table class='fullwidth'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Type</th>
|
||||
<th>Versions</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Type</th>
|
||||
<th>Versions</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for status_code, response in data.parsedResponseMap %}
|
||||
<tbody>
|
||||
{% for name, infos in data.response %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ infos.dataType }}</td>
|
||||
<td>{% include 'NelmioApiDocBundle:Components:version.html.twig' with {'sinceVersion': infos.sinceVersion, 'untilVersion': infos.untilVersion} only %}</td>
|
||||
<td>{{ infos.description }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td>
|
||||
<h4>
|
||||
{{ status_code }}
|
||||
{% if data.statusCodes is defined and data.statusCodes[status_code] is defined %}
|
||||
- {{ data.statusCodes[status_code]|join(', ') }}
|
||||
{% endif %}
|
||||
</h4>
|
||||
</td>
|
||||
</tr>
|
||||
{% for name, infos in response.model %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ infos.dataType }}</td>
|
||||
<td>{% include '@NelmioApiDoc/Components/version.html.twig' with {'sinceVersion': infos.sinceVersion, 'untilVersion': infos.untilVersion} only %}</td>
|
||||
<td>{{ infos.description }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
|
@ -199,9 +227,9 @@
|
|||
{% if enableSandbox %}
|
||||
<div class="pane sandbox">
|
||||
{% if app.request is not null and data.https and app.request.secure != data.https %}
|
||||
Please reload the documentation using the scheme {% if data.https %}HTTPS{% else %}HTTP{% endif %} if you want to use the sandbox.
|
||||
Please reload the documentation using the scheme HTTP if you want to use the sandbox.
|
||||
{% else %}
|
||||
<form method="{{ data.method|upper }}" action="{% if data.host is defined %}{{ data.https ? 'https://' : 'http://' }}{{ data.host }}{% endif %}{{ data.uri }}">
|
||||
<form method="" action="{% if data.host is defined %}http://{{ data.host }}{% endif %}{{ data.uri }}">
|
||||
<fieldset class="parameters">
|
||||
<legend>Input</legend>
|
||||
{% if data.requirements is defined %}
|
||||
|
@ -217,7 +245,7 @@
|
|||
{% if data.filters is defined %}
|
||||
<h4>Filters</h4>
|
||||
{% for name, infos in data.filters %}
|
||||
<p class="tuple">
|
||||
<p class="tuple filter">
|
||||
<input type="text" class="key" value="{{ name }}" placeholder="Key" />
|
||||
<span>=</span>
|
||||
<input type="text" class="value" placeholder="{% if infos.description is defined %}{{ infos.description }}{% else %}Value{% endif %}" {% if infos.default is defined %} value="{{ infos.default }}" {% endif %}/> <span class="remove">-</span>
|
||||
|
@ -228,7 +256,7 @@
|
|||
<h4>Parameters</h4>
|
||||
{% for name, infos in data.parameters %}
|
||||
{% if not infos.readonly %}
|
||||
<p class="tuple" data-dataType="{% if infos.dataType %}{{ infos.dataType }}{% endif %}" data-format="{% if infos.format %}{{ infos.format }}{% endif %}" data-description="{% if infos.description %}{{ infos.description }}{% endif %}">
|
||||
<p class="tuple" data-dataType="{% if infos.dataType %}{{ infos.dataType }}{% endif %}" data-format="{% if infos.format %}{{ infos.format }} {% endif %}" data-description="{% if infos.description %}{{ infos.description|trans }}{% endif %}">
|
||||
<input type="text" class="key" value="{{ name }}" placeholder="Key" />
|
||||
<span>=</span>
|
||||
<select class="tuple_type">
|
||||
|
@ -236,8 +264,9 @@
|
|||
<option value="string">String</option>
|
||||
<option value="boolean">Boolean</option>
|
||||
<option value="file">File</option>
|
||||
<option value="textarea">Textarea</option>
|
||||
</select>
|
||||
<input type="text" class="value" placeholder="{% if infos.dataType %}[{{ infos.dataType }}] {% endif %}{% if infos.format %}{{ infos.format }}{% endif %}{% if infos.description %}{{ infos.description }}{% else %}Value{% endif %}" {% if infos.default is defined %} value="{{ infos.default }}" {% endif %}/> <span class="remove">-</span>
|
||||
<input type="text" class="value" placeholder="{% if infos.dataType %}[{{ infos.dataType }}] {% endif %}{% if infos.format %}{{ infos.format }}{% endif %}{% if infos.description %}{{ infos.description|trans }}{% else %}Value{% endif %}" {% if infos.default is defined %} value="{{ infos.default }}" {% endif %}/> <span class="remove">-</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -247,6 +276,18 @@
|
|||
</fieldset>
|
||||
|
||||
<fieldset class="headers">
|
||||
{% set methods = data.method|upper|split('|') %}
|
||||
{% if methods|length > 1 %}
|
||||
<legend>Method</legend>
|
||||
<select name="header_method">
|
||||
{% for method in methods %}
|
||||
<option value="{{ method }}">{{ method }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="hidden" name="header_method" value="{{ methods|join }}" />
|
||||
{% endif %}
|
||||
|
||||
<legend>Headers</legend>
|
||||
|
||||
{% if acceptType %}
|
||||
|
@ -257,6 +298,18 @@
|
|||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if data.headers is defined %}
|
||||
|
||||
{% for name, infos in data.headers %}
|
||||
<p class="tuple">
|
||||
<input type="text" class="key" value="{{ name }}" />
|
||||
<span>=</span>
|
||||
<input type="text" class="value" value="{% if infos.default is defined %}{{ infos.default }}{% endif %}" placeholder="Value" /> <span class="remove">-</span>
|
||||
</p>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<p class="tuple">
|
||||
<input type="text" class="key" placeholder="Key" />
|
||||
<span>=</span>
|
||||
|
@ -293,6 +346,7 @@
|
|||
<option value="string">String</option>
|
||||
<option value="boolean">Boolean</option>
|
||||
<option value="file">File</option>
|
||||
<option value="textarea">Textarea</option>
|
||||
</select>
|
||||
<input type="text" class="value" placeholder="Value" /> <span class="remove">-</span>
|
||||
</p>
|
||||
|
@ -311,11 +365,17 @@
|
|||
<h4>Request URL</h4>
|
||||
<pre class="url"></pre>
|
||||
|
||||
<h4 class="request-body-header">Request body</h4>
|
||||
<pre class="request-body"></pre>
|
||||
|
||||
<h4>Response Headers <small>[<a href="" class="to-expand">Expand</a>]</small> <small class="profiler">[<a href="" class="profiler-link" target="_blank">Profiler</a>]</small></h4>
|
||||
<pre class="headers to-expand"></pre>
|
||||
|
||||
<h4>Response Body <small>[<a href="" class="to-raw">Raw</a>]</small></h4>
|
||||
<pre class="response prettyprint"></pre>
|
||||
|
||||
<h4>Curl Command Line</h4>
|
||||
<pre class="curl-command"></pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{% extends "NelmioApiDocBundle::layout.html.twig" %}
|
||||
{% extends "@NelmioApiDoc/layout.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<li class="resource">
|
||||
<ul class="endpoints">
|
||||
<li class="endpoint">
|
||||
<ul class="operations">
|
||||
{% include 'NelmioApiDocBundle::method.html.twig' %}
|
||||
{% include '@NelmioApiDoc/method.html.twig' %}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
{% extends "NelmioApiDocBundle::layout.html.twig" %}
|
||||
{% extends "@NelmioApiDoc/layout.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div id="summary">
|
||||
<ul>
|
||||
{% for section, sections in resources %}
|
||||
<li><a href="#section-{{ section }}">{{ section }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% for section, sections in resources %}
|
||||
{% if section != '_others' %}
|
||||
<li class="section">
|
||||
<li class="section{{ defaultSectionsOpened? ' active':'' }}">
|
||||
<div class="actions">
|
||||
<a class="action-show-hide">Show/hide</a>
|
||||
<a class="action-list">List Operations</a>
|
||||
<a class="action-expand">Expand Operations</a>
|
||||
</div>
|
||||
<h1>{{ section }}</h1>
|
||||
<ul>
|
||||
<ul class="section-list" {% if not defaultSectionsOpened %}style="display: none"{% endif %}>
|
||||
{% endif %}
|
||||
{% for resource, methods in sections %}
|
||||
<a id="section-{{ section }}"></a>
|
||||
<li class="resource">
|
||||
<div class="heading">
|
||||
{% if section == '_others' and resource != 'others' %}
|
||||
|
@ -20,7 +33,7 @@
|
|||
<li class="endpoint">
|
||||
<ul class="operations">
|
||||
{% for data in methods %}
|
||||
{% include 'NelmioApiDocBundle::method.html.twig' %}
|
||||
{% include '@NelmioApiDoc/method.html.twig' %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
|
|
236
Swagger/ModelRegistry.php
Normal file
236
Swagger/ModelRegistry.php
Normal file
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Swagger;
|
||||
|
||||
use Nelmio\ApiDocBundle\DataTypes;
|
||||
|
||||
/**
|
||||
* Class ModelRegistry
|
||||
*
|
||||
* @author Bez Hermoso <bez@activelamp.com>
|
||||
*/
|
||||
class ModelRegistry
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $namingStrategies = [
|
||||
'dot_notation' => 'nameDotNotation',
|
||||
'last_segment_only' => 'nameLastSegmentOnly',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $models = [];
|
||||
|
||||
protected $classes = [];
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $namingStategy;
|
||||
|
||||
protected $typeMap = [
|
||||
DataTypes::INTEGER => 'integer',
|
||||
DataTypes::FLOAT => 'number',
|
||||
DataTypes::STRING => 'string',
|
||||
DataTypes::BOOLEAN => 'boolean',
|
||||
DataTypes::FILE => 'string',
|
||||
DataTypes::DATE => 'string',
|
||||
DataTypes::DATETIME => 'string',
|
||||
];
|
||||
|
||||
protected $formatMap = [
|
||||
DataTypes::INTEGER => 'int32',
|
||||
DataTypes::FLOAT => 'float',
|
||||
DataTypes::FILE => 'byte',
|
||||
DataTypes::DATE => 'date',
|
||||
DataTypes::DATETIME => 'date-time',
|
||||
];
|
||||
|
||||
public function __construct($namingStrategy)
|
||||
{
|
||||
if (!isset($this->namingStrategies[$namingStrategy])) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Invalid naming strategy. Choose from: %s',
|
||||
json_encode(array_keys($this->namingStrategies))
|
||||
));
|
||||
}
|
||||
|
||||
$this->namingStategy = [$this, $this->namingStrategies[$namingStrategy]];
|
||||
}
|
||||
|
||||
public function register($className, ?array $parameters = null, $description = '')
|
||||
{
|
||||
if (!isset($this->classes[$className])) {
|
||||
$this->classes[$className] = [];
|
||||
}
|
||||
|
||||
$id = call_user_func_array($this->namingStategy, [$className]);
|
||||
|
||||
if (isset($this->models[$id])) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
$this->classes[$className][] = $id;
|
||||
|
||||
$model = [
|
||||
'id' => $id,
|
||||
'description' => $description,
|
||||
];
|
||||
|
||||
if (is_array($parameters)) {
|
||||
$required = [];
|
||||
$properties = [];
|
||||
|
||||
foreach ($parameters as $name => $prop) {
|
||||
$subParam = [];
|
||||
|
||||
if (DataTypes::MODEL === $prop['actualType']) {
|
||||
$subParam['$ref'] = $this->register(
|
||||
$prop['subType'],
|
||||
$prop['children'] ?? null,
|
||||
$prop['description'] ?: $prop['dataType']
|
||||
);
|
||||
} else {
|
||||
$type = null;
|
||||
$format = null;
|
||||
$items = null;
|
||||
$enum = null;
|
||||
$ref = null;
|
||||
|
||||
if (isset($this->typeMap[$prop['actualType']])) {
|
||||
$type = $this->typeMap[$prop['actualType']];
|
||||
} else {
|
||||
switch ($prop['actualType']) {
|
||||
case DataTypes::ENUM:
|
||||
$type = 'string';
|
||||
if (isset($prop['format'])) {
|
||||
$enum = array_keys(json_decode($prop['format'], true));
|
||||
}
|
||||
break;
|
||||
|
||||
case DataTypes::COLLECTION:
|
||||
$type = 'array';
|
||||
|
||||
if (null === $prop['subType']) {
|
||||
$items = [
|
||||
'type' => 'string',
|
||||
];
|
||||
} elseif (isset($this->typeMap[$prop['subType']])) {
|
||||
$items = [
|
||||
'type' => $this->typeMap[$prop['subType']],
|
||||
];
|
||||
} elseif (!isset($this->typeMap[$prop['subType']])) {
|
||||
$items = [
|
||||
'$ref' => $this->register(
|
||||
$prop['subType'],
|
||||
$prop['children'] ?? null,
|
||||
$prop['description'] ?: $prop['dataType']
|
||||
),
|
||||
];
|
||||
}
|
||||
/* @TODO: Handle recursion if subtype is a model. */
|
||||
break;
|
||||
|
||||
case DataTypes::MODEL:
|
||||
$ref = $this->register(
|
||||
$prop['subType'],
|
||||
$prop['children'] ?? null,
|
||||
$prop['description'] ?: $prop['dataType']
|
||||
);
|
||||
|
||||
$type = $ref;
|
||||
/* @TODO: Handle recursion. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->formatMap[$prop['actualType']])) {
|
||||
$format = $this->formatMap[$prop['actualType']];
|
||||
}
|
||||
|
||||
$subParam = [
|
||||
'type' => $type,
|
||||
'description' => false === empty($prop['description']) ? (string) $prop['description'] : $prop['dataType'],
|
||||
];
|
||||
|
||||
if (null !== $format) {
|
||||
$subParam['format'] = $format;
|
||||
}
|
||||
|
||||
if (null !== $enum) {
|
||||
$subParam['enum'] = $enum;
|
||||
}
|
||||
|
||||
if (null !== $ref) {
|
||||
$subParam['$ref'] = $ref;
|
||||
}
|
||||
|
||||
if (null !== $items) {
|
||||
$subParam['items'] = $items;
|
||||
}
|
||||
|
||||
if ($prop['required']) {
|
||||
$required[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$properties[$name] = $subParam;
|
||||
}
|
||||
|
||||
$model['properties'] = $properties;
|
||||
$model['required'] = $required;
|
||||
$this->models[$id] = $model;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function nameDotNotation($className)
|
||||
{
|
||||
/*
|
||||
* Converts \Fully\Qualified\Class\Name to Fully.Qualified.Class.Name
|
||||
* "[...]" in aliased and non-aliased collections preserved.
|
||||
*/
|
||||
$id = preg_replace('#(\\\|[^A-Za-z0-9\[\]])#', '.', $className);
|
||||
// Replace duplicate dots.
|
||||
$id = preg_replace('/\.+/', '.', $id);
|
||||
// Replace trailing dots.
|
||||
$id = preg_replace('/^\./', '', $id);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function nameLastSegmentOnly($className)
|
||||
{
|
||||
/*
|
||||
* Converts \Fully\Qualified\ClassName to ClassName
|
||||
*/
|
||||
$segments = explode('\\', $className);
|
||||
$id = end($segments);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function getModels()
|
||||
{
|
||||
return $this->models;
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->models = [];
|
||||
$this->classes = [];
|
||||
}
|
||||
}
|
|
@ -11,38 +11,32 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Annotation;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Tests\TestCase;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
class ApiDocTest extends TestCase
|
||||
{
|
||||
public function testConstructWithoutData()
|
||||
public function testConstructWithoutData(): void
|
||||
{
|
||||
$data = array();
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc();
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
$this->assertFalse(isset($array['filters']));
|
||||
$this->assertFalse($annot->isResource());
|
||||
$this->assertEmpty($annot->getViews());
|
||||
$this->assertFalse($annot->getDeprecated());
|
||||
$this->assertFalse(isset($array['description']));
|
||||
$this->assertFalse(isset($array['requirements']));
|
||||
$this->assertFalse(isset($array['parameters']));
|
||||
$this->assertNull($annot->getInput());
|
||||
$this->assertFalse($array['authentication']);
|
||||
$this->assertTrue(is_array($array['authenticationRoles']));
|
||||
$this->assertFalse(isset($array['headers']));
|
||||
}
|
||||
|
||||
public function testConstructWithInvalidData()
|
||||
public function testConstructWithInvalidData(): void
|
||||
{
|
||||
$data = array(
|
||||
'unknown' => 'foo',
|
||||
'array' => array('bar' => 'bar'),
|
||||
);
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc();
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
|
@ -55,13 +49,13 @@ class ApiDocTest extends TestCase
|
|||
$this->assertNull($annot->getInput());
|
||||
}
|
||||
|
||||
public function testConstruct()
|
||||
public function testConstruct(): void
|
||||
{
|
||||
$data = array(
|
||||
$data = [
|
||||
'description' => 'Heya',
|
||||
);
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(description: $data['description']);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
|
@ -74,14 +68,17 @@ class ApiDocTest extends TestCase
|
|||
$this->assertNull($annot->getInput());
|
||||
}
|
||||
|
||||
public function testConstructDefinesAFormType()
|
||||
public function testConstructDefinesAFormType(): void
|
||||
{
|
||||
$data = array(
|
||||
'description' => 'Heya',
|
||||
'input' => 'My\Form\Type',
|
||||
);
|
||||
$data = [
|
||||
'description' => 'Heya',
|
||||
'input' => 'My\Form\Type',
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
description: $data['description'],
|
||||
input: $data['input']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
|
@ -94,16 +91,21 @@ class ApiDocTest extends TestCase
|
|||
$this->assertEquals($data['input'], $annot->getInput());
|
||||
}
|
||||
|
||||
public function testConstructMethodIsResource()
|
||||
public function testConstructMethodIsResource(): void
|
||||
{
|
||||
$data = array(
|
||||
'resource' => true,
|
||||
'description' => 'Heya',
|
||||
'deprecated' => true,
|
||||
'input' => 'My\Form\Type',
|
||||
);
|
||||
$data = [
|
||||
'resource' => true,
|
||||
'description' => 'Heya',
|
||||
'deprecated' => true,
|
||||
'input' => 'My\Form\Type',
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
resource: $data['resource'],
|
||||
description: $data['description'],
|
||||
deprecated: $data['deprecated'],
|
||||
input: $data['input']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
|
@ -116,16 +118,21 @@ class ApiDocTest extends TestCase
|
|||
$this->assertEquals($data['input'], $annot->getInput());
|
||||
}
|
||||
|
||||
public function testConstructMethodResourceIsFalse()
|
||||
public function testConstructMethodResourceIsFalse(): void
|
||||
{
|
||||
$data = array(
|
||||
'resource' => false,
|
||||
'description' => 'Heya',
|
||||
'deprecated' => false,
|
||||
'input' => 'My\Form\Type',
|
||||
);
|
||||
$data = [
|
||||
'resource' => false,
|
||||
'description' => 'Heya',
|
||||
'deprecated' => false,
|
||||
'input' => 'My\Form\Type',
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
resource: $data['resource'],
|
||||
description: $data['description'],
|
||||
deprecated: $data['deprecated'],
|
||||
input: $data['input']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
|
@ -138,24 +145,29 @@ class ApiDocTest extends TestCase
|
|||
$this->assertEquals($data['input'], $annot->getInput());
|
||||
}
|
||||
|
||||
public function testConstructMethodHasFilters()
|
||||
public function testConstructMethodHasFilters(): void
|
||||
{
|
||||
$data = array(
|
||||
'resource' => true,
|
||||
'deprecated' => false,
|
||||
'description' => 'Heya',
|
||||
'filters' => array(
|
||||
array('name' => 'a-filter'),
|
||||
),
|
||||
);
|
||||
$data = [
|
||||
'resource' => true,
|
||||
'deprecated' => false,
|
||||
'description' => 'Heya',
|
||||
'filters' => [
|
||||
['name' => 'a-filter'],
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
resource: $data['resource'],
|
||||
description: $data['description'],
|
||||
deprecated: $data['deprecated'],
|
||||
filters: $data['filters']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
$this->assertTrue(is_array($array['filters']));
|
||||
$this->assertCount(1, $array['filters']);
|
||||
$this->assertEquals(array('a-filter' => array()), $array['filters']);
|
||||
$this->assertEquals(['a-filter' => []], $array['filters']);
|
||||
$this->assertTrue($annot->isResource());
|
||||
$this->assertEquals($data['description'], $array['description']);
|
||||
$this->assertFalse(isset($array['requirements']));
|
||||
|
@ -164,104 +176,66 @@ class ApiDocTest extends TestCase
|
|||
$this->assertNull($annot->getInput());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \InvalidArgumentException
|
||||
*/
|
||||
public function testConstructMethodHasFiltersWithoutName()
|
||||
public function testConstructMethodHasFiltersWithoutName(): void
|
||||
{
|
||||
$data = array(
|
||||
'description' => 'Heya',
|
||||
'filters' => array(
|
||||
array('parameter' => 'foo'),
|
||||
),
|
||||
);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
}
|
||||
|
||||
public function testConstructNoFiltersIfFormTypeDefined()
|
||||
{
|
||||
$data = array(
|
||||
'resource' => true,
|
||||
'description' => 'Heya',
|
||||
'input' => 'My\Form\Type',
|
||||
'filters' => array(
|
||||
array('name' => 'a-filter'),
|
||||
),
|
||||
);
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
$this->assertFalse(isset($array['filters']));
|
||||
$this->assertTrue($annot->isResource());
|
||||
$this->assertEquals($data['description'], $array['description']);
|
||||
$this->assertEquals($data['input'], $annot->getInput());
|
||||
}
|
||||
|
||||
public function testConstructWithStatusCodes()
|
||||
{
|
||||
$data = array(
|
||||
$data = [
|
||||
'description' => 'Heya',
|
||||
'statusCodes' => array(
|
||||
200 => "Returned when successful",
|
||||
403 => "Returned when the user is not authorized",
|
||||
404 => array(
|
||||
"Returned when the user is not found",
|
||||
"Returned when when something else is not found"
|
||||
)
|
||||
)
|
||||
);
|
||||
'filters' => [
|
||||
['parameter' => 'foo'],
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
description: $data['description'],
|
||||
filters: $data['filters']
|
||||
);
|
||||
}
|
||||
|
||||
public function testConstructWithStatusCodes(): void
|
||||
{
|
||||
$data = [
|
||||
'description' => 'Heya',
|
||||
'statusCodes' => [
|
||||
200 => 'Returned when successful',
|
||||
403 => 'Returned when the user is not authorized',
|
||||
404 => [
|
||||
'Returned when the user is not found',
|
||||
'Returned when when something else is not found',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc(
|
||||
description: $data['description'],
|
||||
statusCodes: $data['statusCodes']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
$this->assertTrue(is_array($array['statusCodes']));
|
||||
foreach ($data['statusCodes'] as $code => $message) {
|
||||
$this->assertEquals($array['statusCodes'][$code], !is_array($message) ? array($message) : $message);
|
||||
$this->assertEquals($array['statusCodes'][$code], !is_array($message) ? [$message] : $message);
|
||||
}
|
||||
}
|
||||
|
||||
public function testConstructWithAuthentication()
|
||||
public function testConstructWithRequirements(): void
|
||||
{
|
||||
$data = array(
|
||||
'authentication' => true
|
||||
);
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue($array['authentication']);
|
||||
}
|
||||
|
||||
public function testConstructWithCache()
|
||||
{
|
||||
$data = array(
|
||||
'cache' => '60'
|
||||
);
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertEquals($data['cache'], $array['cache']);
|
||||
}
|
||||
|
||||
public function testConstructWithRequirements()
|
||||
{
|
||||
$data = array(
|
||||
'requirements' => array(
|
||||
array(
|
||||
$data = [
|
||||
'requirements' => [
|
||||
[
|
||||
'name' => 'fooId',
|
||||
'requirement' => '\d+',
|
||||
'dataType' => 'integer',
|
||||
'description' => 'This requirement might be used withing action method directly from Request object'
|
||||
)
|
||||
)
|
||||
);
|
||||
'description' => 'This requirement might be used withing action method directly from Request object',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
requirements: $data['requirements']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
|
@ -269,19 +243,21 @@ class ApiDocTest extends TestCase
|
|||
$this->assertTrue(isset($array['requirements']['fooId']['dataType']));
|
||||
}
|
||||
|
||||
public function testConstructWithParameters()
|
||||
public function testConstructWithParameters(): void
|
||||
{
|
||||
$data = array(
|
||||
'parameters' => array(
|
||||
array(
|
||||
$data = [
|
||||
'parameters' => [
|
||||
[
|
||||
'name' => 'fooId',
|
||||
'dataType' => 'integer',
|
||||
'description' => 'Some description'
|
||||
)
|
||||
)
|
||||
);
|
||||
'description' => 'Some description',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
parameters: $data['parameters']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
|
@ -289,34 +265,146 @@ class ApiDocTest extends TestCase
|
|||
$this->assertTrue(isset($array['parameters']['fooId']['dataType']));
|
||||
}
|
||||
|
||||
public function testConstructWithOneTag()
|
||||
public function testConstructWithHeaders(): void
|
||||
{
|
||||
$data = array(
|
||||
'tags' => 'beta'
|
||||
);
|
||||
$data = [
|
||||
'headers' => [
|
||||
[
|
||||
'name' => 'headerName',
|
||||
'description' => 'Some description',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
headers: $data['headers']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertArrayHasKey('headerName', $array['headers']);
|
||||
$this->assertNotEmpty($array['headers']['headerName']);
|
||||
|
||||
$keys = array_keys($array['headers']);
|
||||
$this->assertEquals($data['headers'][0]['name'], $keys[0]);
|
||||
$this->assertEquals($data['headers'][0]['description'], $array['headers']['headerName']['description']);
|
||||
}
|
||||
|
||||
public function testConstructWithOneTag(): void
|
||||
{
|
||||
$data = [
|
||||
'tags' => 'beta',
|
||||
];
|
||||
|
||||
$annot = new ApiDoc(
|
||||
tags: $data['tags']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
$this->assertTrue(is_array($array['tags']), 'Single tag should be put in array');
|
||||
$this->assertEquals(array('beta'), $array['tags']);
|
||||
$this->assertEquals(['beta'], $array['tags']);
|
||||
}
|
||||
|
||||
public function testConstructWithMultipleTags()
|
||||
public function testConstructWithOneTagAndColorCode(): void
|
||||
{
|
||||
$data = array(
|
||||
'tags' => array(
|
||||
'experimental',
|
||||
'alpha'
|
||||
)
|
||||
);
|
||||
$data = [
|
||||
'tags' => [
|
||||
'beta' => '#ff0000',
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc($data);
|
||||
$annot = new ApiDoc(
|
||||
tags: $data['tags']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
$this->assertTrue(is_array($array['tags']), 'Single tag should be put in array');
|
||||
$this->assertEquals(['beta' => '#ff0000'], $array['tags']);
|
||||
}
|
||||
|
||||
public function testConstructWithMultipleTags(): void
|
||||
{
|
||||
$data = [
|
||||
'tags' => [
|
||||
'experimental' => '#0000ff',
|
||||
'beta' => '#0000ff',
|
||||
],
|
||||
];
|
||||
|
||||
$annot = new ApiDoc(
|
||||
tags: $data['tags']
|
||||
);
|
||||
$array = $annot->toArray();
|
||||
|
||||
$this->assertTrue(is_array($array));
|
||||
$this->assertTrue(is_array($array['tags']), 'Tags should be in array');
|
||||
$this->assertEquals($data['tags'], $array['tags']);
|
||||
}
|
||||
|
||||
public function testAlignmentOfOutputAndResponseModels(): void
|
||||
{
|
||||
$data = [
|
||||
'output' => 'FooBar',
|
||||
'responseMap' => [
|
||||
400 => 'Foo\\ValidationErrorCollection',
|
||||
],
|
||||
];
|
||||
|
||||
$apiDoc = new ApiDoc(
|
||||
output: $data['output'],
|
||||
responseMap: $data['responseMap']
|
||||
);
|
||||
|
||||
$map = $apiDoc->getResponseMap();
|
||||
|
||||
$this->assertCount(2, $map);
|
||||
$this->assertArrayHasKey(200, $map);
|
||||
$this->assertArrayHasKey(400, $map);
|
||||
$this->assertEquals($data['output'], $map[200]);
|
||||
}
|
||||
|
||||
public function testAlignmentOfOutputAndResponseModels2(): void
|
||||
{
|
||||
$data = [
|
||||
'responseMap' => [
|
||||
200 => 'FooBar',
|
||||
400 => 'Foo\\ValidationErrorCollection',
|
||||
],
|
||||
];
|
||||
|
||||
$apiDoc = new ApiDoc(
|
||||
responseMap: $data['responseMap']
|
||||
);
|
||||
$map = $apiDoc->getResponseMap();
|
||||
|
||||
$this->assertCount(2, $map);
|
||||
$this->assertArrayHasKey(200, $map);
|
||||
$this->assertArrayHasKey(400, $map);
|
||||
$this->assertEquals($apiDoc->getOutput(), $map[200]);
|
||||
}
|
||||
|
||||
public function testSetRoute(): void
|
||||
{
|
||||
$route = new Route(
|
||||
'/path/{foo}',
|
||||
[
|
||||
'foo' => 'bar',
|
||||
'nested' => [
|
||||
'key1' => 'value1',
|
||||
'key2' => 'value2',
|
||||
],
|
||||
],
|
||||
[],
|
||||
[],
|
||||
'{foo}.awesome_host.com'
|
||||
);
|
||||
|
||||
$apiDoc = new ApiDoc();
|
||||
$apiDoc->setRoute($route);
|
||||
|
||||
$this->assertSame($route, $apiDoc->getRoute());
|
||||
$this->assertEquals('bar.awesome_host.com', $apiDoc->getHost());
|
||||
$this->assertEquals('ANY', $apiDoc->getMethod());
|
||||
}
|
||||
}
|
||||
|
|
108
Tests/Command/DumpCommandTest.php
Normal file
108
Tests/Command/DumpCommandTest.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace NelmioApiDocBundle\Tests\Command;
|
||||
|
||||
use Nelmio\ApiDocBundle\Tests\WebTestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Tester\ApplicationTester;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
class DumpCommandTest extends WebTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider viewProvider
|
||||
*
|
||||
* @param string $view Command view option value
|
||||
* @param array $expectedMethodsCount Expected resource methods count
|
||||
* @param array $expectedMethodValues Expected resource method values
|
||||
*/
|
||||
public function testDumpWithViewOption($view, array $expectedMethodsCount, array $expectedMethodValues): void
|
||||
{
|
||||
$this->getContainer();
|
||||
|
||||
$application = new Application(static::$kernel);
|
||||
$application->setCatchExceptions(false);
|
||||
$application->setAutoExit(false);
|
||||
|
||||
$tester = new ApplicationTester($application);
|
||||
|
||||
$input = [
|
||||
'command' => 'api:doc:dump',
|
||||
'--view' => $view,
|
||||
'--format' => 'json',
|
||||
];
|
||||
$tester->run($input);
|
||||
|
||||
$display = $tester->getDisplay();
|
||||
|
||||
$this->assertJson($display);
|
||||
|
||||
$json = json_decode($display);
|
||||
|
||||
$accessor = PropertyAccess::createPropertyAccessor();
|
||||
|
||||
foreach ($expectedMethodsCount as $propertyPath => $expectedCount) {
|
||||
$this->assertCount($expectedCount, $accessor->getValue($json, $propertyPath));
|
||||
}
|
||||
|
||||
foreach ($expectedMethodValues as $propertyPath => $expectedValue) {
|
||||
$this->assertEquals($expectedValue, $accessor->getValue($json, $propertyPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function viewProvider()
|
||||
{
|
||||
return [
|
||||
'test' => [
|
||||
'test',
|
||||
[
|
||||
'/api/resources' => 1,
|
||||
],
|
||||
[
|
||||
'/api/resources[0].method' => 'GET',
|
||||
'/api/resources[0].uri' => '/api/resources.{_format}',
|
||||
],
|
||||
],
|
||||
'premium' => [
|
||||
'premium',
|
||||
[
|
||||
'/api/resources' => 2,
|
||||
],
|
||||
[
|
||||
'/api/resources[0].method' => 'GET',
|
||||
'/api/resources[0].uri' => '/api/resources.{_format}',
|
||||
'/api/resources[1].method' => 'POST',
|
||||
'/api/resources[1].uri' => '/api/resources.{_format}',
|
||||
],
|
||||
],
|
||||
'default' => [
|
||||
'default',
|
||||
[
|
||||
'/api/resources' => 4,
|
||||
],
|
||||
[
|
||||
'/api/resources[0].method' => 'GET',
|
||||
'/api/resources[0].uri' => '/api/resources.{_format}',
|
||||
'/api/resources[1].method' => 'POST',
|
||||
'/api/resources[1].uri' => '/api/resources.{_format}',
|
||||
'/api/resources[2].method' => 'DELETE',
|
||||
'/api/resources[2].uri' => '/api/resources/{id}.{_format}',
|
||||
'/api/resources[3].method' => 'GET',
|
||||
'/api/resources[3].uri' => '/api/resources/{id}.{_format}',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
66
Tests/Controller/ApiDocControllerTest.php
Normal file
66
Tests/Controller/ApiDocControllerTest.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace NelmioApiDocBundle\Tests\Controller;
|
||||
|
||||
use Nelmio\ApiDocBundle\Tests\WebTestCase;
|
||||
|
||||
/**
|
||||
* Class ApiDocControllerTest
|
||||
*
|
||||
* @author Bez Hermoso <bez@activelamp.com>
|
||||
*/
|
||||
class ApiDocControllerTest extends WebTestCase
|
||||
{
|
||||
public function testSwaggerDocResourceListRoute(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/api-docs');
|
||||
|
||||
$response = $client->getResponse();
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('text/html; charset=UTF-8', $response->headers->get('Content-type'));
|
||||
}
|
||||
|
||||
public function dataTestApiDeclarations()
|
||||
{
|
||||
return [
|
||||
['resources'],
|
||||
['tests'],
|
||||
['tests2'],
|
||||
['TestResource'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataTestApiDeclarations
|
||||
*/
|
||||
public function testApiDeclarationRoutes($resource): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/api-docs/' . $resource);
|
||||
|
||||
$response = $client->getResponse();
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('application/json', $response->headers->get('Content-type'));
|
||||
}
|
||||
|
||||
public function testNonExistingApiDeclaration(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/api-docs/santa');
|
||||
|
||||
$response = $client->getResponse();
|
||||
$this->assertEquals(404, $response->getStatusCode());
|
||||
}
|
||||
}
|
|
@ -15,14 +15,14 @@ use Nelmio\ApiDocBundle\Tests\WebTestCase;
|
|||
|
||||
class RequestListenerTest extends WebTestCase
|
||||
{
|
||||
public function testDocQueryArg()
|
||||
public function testDocQueryArg(): void
|
||||
{
|
||||
$client = $this->createClient();
|
||||
|
||||
$client->request('GET', '/tests?_doc=1');
|
||||
$content = $client->getResponse()->getContent();
|
||||
$this->assertTrue(0 !== strpos($content, '<h1>API documentation</h1>'), 'Event listener should capture ?_doc=1 requests');
|
||||
$this->assertTrue(0 !== strpos($content, '/tests.{_format}'), 'Event listener should capture ?_doc=1 requests');
|
||||
$this->assertTrue(!str_starts_with($content, '<h1>API documentation</h1>'), 'Event listener should capture ?_doc=1 requests');
|
||||
$this->assertTrue(!str_starts_with($content, '/tests.{_format}'), 'Event listener should capture ?_doc=1 requests');
|
||||
|
||||
$client->request('GET', '/tests');
|
||||
$this->assertEquals('tests', $client->getResponse()->getContent(), 'Event listener should let normal requests through');
|
||||
|
|
|
@ -11,77 +11,61 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Extractor;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
|
||||
use Nelmio\ApiDocBundle\Tests\WebTestCase;
|
||||
|
||||
class ApiDocExtractorTest extends WebTestCase
|
||||
{
|
||||
const ROUTES_QUANTITY = 25;
|
||||
private static $ROUTES_QUANTITY_DEFAULT = 26; // Routes in the default view
|
||||
private static $ROUTES_QUANTITY_PREMIUM = 5; // Routes in the premium view
|
||||
private static $ROUTES_QUANTITY_TEST = 2; // Routes in the test view
|
||||
|
||||
public function testAll()
|
||||
public function testAll(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
set_error_handler(array($this, 'handleDeprecation'));
|
||||
set_error_handler([$this, 'handleDeprecation']);
|
||||
$data = $extractor->all();
|
||||
restore_error_handler();
|
||||
|
||||
$this->assertTrue(is_array($data));
|
||||
$this->assertCount(self::ROUTES_QUANTITY, $data);
|
||||
$this->assertCount(self::$ROUTES_QUANTITY_DEFAULT, $data);
|
||||
|
||||
foreach ($data as $d) {
|
||||
$cacheFile = $container->getParameter('kernel.cache_dir') . '/api-doc.cache.' . ApiDoc::DEFAULT_VIEW;
|
||||
$this->assertFileExists($cacheFile);
|
||||
$this->assertStringEqualsFile($cacheFile, serialize($data));
|
||||
|
||||
foreach ($data as $key => $d) {
|
||||
$this->assertTrue(is_array($d));
|
||||
$this->assertArrayHasKey('annotation', $d);
|
||||
$this->assertArrayHasKey('resource', $d);
|
||||
|
||||
$this->assertInstanceOf('Nelmio\ApiDocBundle\Annotation\ApiDoc', $d['annotation']);
|
||||
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $d['annotation']);
|
||||
$this->assertInstanceOf('Symfony\Component\Routing\Route', $d['annotation']->getRoute());
|
||||
$this->assertNotNull($d['resource']);
|
||||
}
|
||||
|
||||
$a1 = $data[0]['annotation'];
|
||||
$array1 = $a1->toArray();
|
||||
$this->assertTrue($a1->isResource());
|
||||
$this->assertEquals('index action', $a1->getDescription());
|
||||
$this->assertTrue(is_array($array1['filters']));
|
||||
$this->assertNull($a1->getInput());
|
||||
|
||||
$a1 = $data[1]['annotation'];
|
||||
$array1 = $a1->toArray();
|
||||
$this->assertTrue($a1->isResource());
|
||||
$this->assertEquals('index action', $a1->getDescription());
|
||||
$this->assertTrue(is_array($array1['filters']));
|
||||
$this->assertNull($a1->getInput());
|
||||
|
||||
$a2 = $data[2]['annotation'];
|
||||
$array2 = $a2->toArray();
|
||||
$this->assertFalse($a2->isResource());
|
||||
$this->assertEquals('create test', $a2->getDescription());
|
||||
$this->assertFalse(isset($array2['filters']));
|
||||
$this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getInput());
|
||||
|
||||
$a2 = $data[3]['annotation'];
|
||||
$array2 = $a2->toArray();
|
||||
$this->assertFalse($a2->isResource());
|
||||
$this->assertEquals('create test', $a2->getDescription());
|
||||
$this->assertFalse(isset($array2['filters']));
|
||||
$this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getInput());
|
||||
|
||||
$a4 = $data[5]['annotation'];
|
||||
$this->assertTrue($a4->isResource());
|
||||
$this->assertEquals('TestResource', $a4->getResource());
|
||||
|
||||
$a3 = $data['14']['annotation'];
|
||||
$this->assertTrue($a3->getHttps());
|
||||
|
||||
}
|
||||
|
||||
public function testGet()
|
||||
public function testRouteVersionChecking(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$data = $extractor->allForVersion('1.5');
|
||||
$this->assertTrue(is_array($data));
|
||||
$this->assertCount(self::$ROUTES_QUANTITY_DEFAULT, $data);
|
||||
$data = $extractor->allForVersion('1.4');
|
||||
$this->assertTrue(is_array($data));
|
||||
$this->assertCount(self::$ROUTES_QUANTITY_DEFAULT - 1, $data);
|
||||
}
|
||||
|
||||
public function testGet(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::indexAction', 'test_route_1');
|
||||
|
||||
$this->assertInstanceOf('Nelmio\ApiDocBundle\Annotation\ApiDoc', $annotation);
|
||||
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $annotation);
|
||||
|
||||
$this->assertTrue($annotation->isResource());
|
||||
$this->assertEquals('index action', $annotation->getDescription());
|
||||
|
@ -90,14 +74,15 @@ class ApiDocExtractorTest extends WebTestCase
|
|||
$this->assertTrue(is_array($array['filters']));
|
||||
$this->assertNull($annotation->getInput());
|
||||
|
||||
$annotation2 = $extractor->get('nelmio.test.controller:indexAction', 'test_service_route_1');
|
||||
$annotation2 = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::indexAction', 'test_service_route_1');
|
||||
$annotation2->getRoute()
|
||||
->setDefault('_controller', $annotation->getRoute()->getDefault('_controller'))
|
||||
->compile(); // compile as we changed a default value
|
||||
->compile() // compile as we changed a default value
|
||||
;
|
||||
$this->assertEquals($annotation, $annotation2);
|
||||
}
|
||||
|
||||
public function testGetWithBadController()
|
||||
public function testGetWithBadController(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
|
@ -110,7 +95,7 @@ class ApiDocExtractorTest extends WebTestCase
|
|||
$this->assertNull($data);
|
||||
}
|
||||
|
||||
public function testGetWithBadRoute()
|
||||
public function testGetWithBadRoute(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
|
@ -123,7 +108,7 @@ class ApiDocExtractorTest extends WebTestCase
|
|||
$this->assertNull($data);
|
||||
}
|
||||
|
||||
public function testGetWithInvalidPattern()
|
||||
public function testGetWithInvalidPath(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
|
@ -136,7 +121,7 @@ class ApiDocExtractorTest extends WebTestCase
|
|||
$this->assertNull($data);
|
||||
}
|
||||
|
||||
public function testGetWithMethodWithoutApiDocAnnotation()
|
||||
public function testGetWithMethodWithoutApiDocAnnotation(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
|
@ -149,15 +134,15 @@ class ApiDocExtractorTest extends WebTestCase
|
|||
$this->assertNull($data);
|
||||
}
|
||||
|
||||
public function testGetWithDocComment()
|
||||
public function testGetWithDocComment(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::myCommentedAction', 'test_route_5');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
$this->assertEquals(
|
||||
"This method is useful to test if the getDocComment works.",
|
||||
'This method is useful to test if the getDocComment works.',
|
||||
$annotation->getDescription()
|
||||
);
|
||||
|
||||
|
@ -176,38 +161,10 @@ class ApiDocExtractorTest extends WebTestCase
|
|||
);
|
||||
}
|
||||
|
||||
public function testGetWithAuthentication()
|
||||
public function testGetWithDeprecated(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::AuthenticatedAction', 'test_route_13');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
$this->assertTrue(
|
||||
$annotation->getAuthentication()
|
||||
);
|
||||
$this->assertContains('ROLE_USER', $annotation->getAuthenticationRoles());
|
||||
$this->assertContains('ROLE_FOOBAR', $annotation->getAuthenticationRoles());
|
||||
$this->assertCount(2, $annotation->getAuthenticationRoles());
|
||||
}
|
||||
|
||||
public function testGetWithCache()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::CachedAction', 'test_route_14');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
$this->assertEquals(
|
||||
60,
|
||||
$annotation->getCache()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetWithDeprecated()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::DeprecatedAction', 'test_route_14');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
|
@ -216,39 +173,233 @@ class ApiDocExtractorTest extends WebTestCase
|
|||
);
|
||||
}
|
||||
|
||||
public function testOutputWithSelectedParsers()
|
||||
public function testOutputWithSelectedParsers(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zReturnSelectedParsersOutputAction', 'test_route_19');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
$output = $annotation->getOutput();
|
||||
|
||||
$parsers = $output['parsers'];
|
||||
$this->assertEquals(
|
||||
"Nelmio\\ApiDocBundle\\Parser\\JmsMetadataParser",
|
||||
'Nelmio\\ApiDocBundle\\Parser\\JmsMetadataParser',
|
||||
$parsers[0]
|
||||
);
|
||||
$this->assertEquals(
|
||||
"Nelmio\\ApiDocBundle\\Parser\\ValidationParser",
|
||||
'Nelmio\\ApiDocBundle\\Parser\\ValidationParser',
|
||||
$parsers[1]
|
||||
);
|
||||
$this->assertCount(2, $parsers);
|
||||
}
|
||||
|
||||
public function testInputWithSelectedParsers()
|
||||
public function testInputWithSelectedParsers(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zReturnSelectedParsersInputAction', 'test_route_20');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
$input = $annotation->getInput();
|
||||
$parsers = $input['parsers'];
|
||||
$this->assertEquals(
|
||||
"Nelmio\\ApiDocBundle\\Parser\\FormTypeParser",
|
||||
'Nelmio\\ApiDocBundle\\Parser\\FormTypeParser',
|
||||
$parsers[0]
|
||||
);
|
||||
$this->assertCount(1, $parsers);
|
||||
}
|
||||
|
||||
public function testPostRequestDoesRequireParametersWhenMarkedAsSuch(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
/** @var ApiDocExtractor $extractor */
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
/** @var ApiDoc $annotation */
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::requiredParametersAction', 'test_required_parameters');
|
||||
|
||||
$parameters = $annotation->getParameters();
|
||||
$this->assertTrue($parameters['required_field']['required']);
|
||||
}
|
||||
|
||||
public function testPatchRequestDoesNeverRequireParameters(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
/** @var ApiDocExtractor $extractor */
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
/** @var ApiDoc $annotation */
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::requiredParametersAction', 'test_patch_disables_required_parameters');
|
||||
|
||||
$parameters = $annotation->getParameters();
|
||||
$this->assertFalse($parameters['required_field']['required']);
|
||||
}
|
||||
|
||||
public static function dataProviderForViews(): array
|
||||
{
|
||||
$offset = 0;
|
||||
|
||||
return [
|
||||
['default', self::$ROUTES_QUANTITY_DEFAULT + $offset],
|
||||
['premium', self::$ROUTES_QUANTITY_PREMIUM + $offset],
|
||||
['test', self::$ROUTES_QUANTITY_TEST + $offset],
|
||||
['foobar', $offset],
|
||||
['', $offset],
|
||||
[null, $offset],
|
||||
];
|
||||
}
|
||||
|
||||
public function testViewNamedTest(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
set_error_handler([$this, 'handleDeprecation']);
|
||||
$data = $extractor->all('test');
|
||||
restore_error_handler();
|
||||
|
||||
$this->assertTrue(is_array($data));
|
||||
$this->assertCount(self::$ROUTES_QUANTITY_TEST, $data);
|
||||
|
||||
$a1 = $data[0]['annotation'];
|
||||
$this->assertCount(3, $a1->getViews());
|
||||
$this->assertEquals('List resources.', $a1->getDescription());
|
||||
|
||||
$a2 = $data[1]['annotation'];
|
||||
$this->assertCount(2, $a2->getViews());
|
||||
$this->assertEquals('create another test', $a2->getDescription());
|
||||
}
|
||||
|
||||
public function testViewNamedPremium(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
set_error_handler([$this, 'handleDeprecation']);
|
||||
$data = $extractor->all('premium');
|
||||
restore_error_handler();
|
||||
|
||||
$this->assertTrue(is_array($data));
|
||||
$this->assertCount(self::$ROUTES_QUANTITY_PREMIUM, $data);
|
||||
|
||||
$a1 = $data[0]['annotation'];
|
||||
$this->assertCount(2, $a1->getViews());
|
||||
$this->assertEquals('List another resource.', $a1->getDescription());
|
||||
|
||||
$a2 = $data[1]['annotation'];
|
||||
$this->assertCount(3, $a2->getViews());
|
||||
$this->assertEquals('List resources.', $a2->getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderForViews
|
||||
*/
|
||||
public function testForViews($view, $count): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
set_error_handler([$this, 'handleDeprecation']);
|
||||
$data = $extractor->all($view);
|
||||
restore_error_handler();
|
||||
|
||||
$this->assertTrue(is_array($data));
|
||||
$this->assertCount($count, $data);
|
||||
}
|
||||
|
||||
public function testOverrideJmsAnnotationWithApiDocParameters(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get(
|
||||
'Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::overrideJmsAnnotationWithApiDocParametersAction',
|
||||
'test_route_27'
|
||||
);
|
||||
|
||||
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $annotation);
|
||||
|
||||
$array = $annotation->toArray();
|
||||
$this->assertTrue(is_array($array['parameters']));
|
||||
|
||||
$this->assertEquals('string', $array['parameters']['foo']['dataType']);
|
||||
$this->assertEquals('DateTime', $array['parameters']['bar']['dataType']);
|
||||
|
||||
$this->assertEquals('integer', $array['parameters']['number']['dataType']);
|
||||
$this->assertEquals('string', $array['parameters']['number']['actualType']);
|
||||
$this->assertNull($array['parameters']['number']['subType']);
|
||||
$this->assertTrue($array['parameters']['number']['required']);
|
||||
$this->assertEquals('This is the new description', $array['parameters']['number']['description']);
|
||||
$this->assertFalse($array['parameters']['number']['readonly']);
|
||||
$this->assertEquals('v3.0', $array['parameters']['number']['sinceVersion']);
|
||||
$this->assertEquals('v4.0', $array['parameters']['number']['untilVersion']);
|
||||
|
||||
$this->assertEquals('object (ArrayCollection)', $array['parameters']['arr']['dataType']);
|
||||
|
||||
$this->assertEquals('object (JmsNested)', $array['parameters']['nested']['dataType']);
|
||||
$this->assertEquals('integer', $array['parameters']['nested']['children']['bar']['dataType']);
|
||||
$this->assertEquals('d+', $array['parameters']['nested']['children']['bar']['format']);
|
||||
}
|
||||
|
||||
public function testJmsAnnotation(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get(
|
||||
'Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::defaultJmsAnnotations',
|
||||
'test_route_27'
|
||||
);
|
||||
|
||||
$this->assertInstanceOf('Nelmio\ApiDocBundle\Attribute\ApiDoc', $annotation);
|
||||
|
||||
$array = $annotation->toArray();
|
||||
$this->assertTrue(is_array($array['parameters']));
|
||||
|
||||
$this->assertEquals('string', $array['parameters']['foo']['dataType']);
|
||||
$this->assertEquals('DateTime', $array['parameters']['bar']['dataType']);
|
||||
|
||||
$this->assertEquals('double', $array['parameters']['number']['dataType']);
|
||||
$this->assertEquals('float', $array['parameters']['number']['actualType']);
|
||||
$this->assertNull($array['parameters']['number']['subType']);
|
||||
$this->assertFalse($array['parameters']['number']['required']);
|
||||
$this->assertEquals('', $array['parameters']['number']['description']);
|
||||
$this->assertFalse($array['parameters']['number']['readonly']);
|
||||
$this->assertNull($array['parameters']['number']['sinceVersion']);
|
||||
$this->assertNull($array['parameters']['number']['untilVersion']);
|
||||
|
||||
$this->assertEquals('array', $array['parameters']['arr']['dataType']);
|
||||
|
||||
$this->assertEquals('object (JmsNested)', $array['parameters']['nested']['dataType']);
|
||||
$this->assertEquals('string', $array['parameters']['nested']['children']['bar']['dataType']);
|
||||
}
|
||||
|
||||
public function testMergeParametersDefaultKeyNotExistingInFirstArray(): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
|
||||
$mergeMethod = new \ReflectionMethod('Nelmio\ApiDocBundle\Extractor\ApiDocExtractor', 'mergeParameters');
|
||||
$mergeMethod->setAccessible(true);
|
||||
|
||||
$p1 = [
|
||||
'myPropName' => [
|
||||
'dataType' => 'string',
|
||||
'actualType' => 'string',
|
||||
'subType' => null,
|
||||
'required' => null,
|
||||
'description' => null,
|
||||
'readonly' => null,
|
||||
],
|
||||
];
|
||||
|
||||
$p2 = [
|
||||
'myPropName' => [
|
||||
'dataType' => 'string',
|
||||
'actualType' => 'string',
|
||||
'subType' => null,
|
||||
'required' => null,
|
||||
'description' => null,
|
||||
'readonly' => null,
|
||||
'default' => '',
|
||||
],
|
||||
];
|
||||
|
||||
$mergedResult = $mergeMethod->invokeArgs($extractor, [$p1, $p2]);
|
||||
$this->assertEquals($p2, $mergedResult);
|
||||
}
|
||||
}
|
||||
|
|
94
Tests/Extractor/CachingApiDocExtractorTest.php
Normal file
94
Tests/Extractor/CachingApiDocExtractorTest.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Extractor;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Extractor\CachingApiDocExtractor;
|
||||
use Nelmio\ApiDocBundle\Tests\WebTestCase;
|
||||
|
||||
class CachingApiDocExtractorTest extends WebTestCase
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function viewsWithoutDefaultProvider()
|
||||
{
|
||||
$data = ApiDocExtractorTest::dataProviderForViews();
|
||||
// remove default view data from provider
|
||||
array_shift($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that every view cache is saved in its own cache file
|
||||
*
|
||||
* @dataProvider viewsWithoutDefaultProvider
|
||||
*
|
||||
* @param string $view View name
|
||||
*/
|
||||
public function testDifferentCacheFilesAreCreatedForDifferentViews($view): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
/* @var CachingApiDocExtractor $extractor */
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$this->assertInstanceOf('\Nelmio\ApiDocBundle\Extractor\CachingApiDocExtractor', $extractor);
|
||||
|
||||
set_error_handler([$this, 'handleDeprecation']);
|
||||
$defaultData = $extractor->all(ApiDoc::DEFAULT_VIEW);
|
||||
$data = $extractor->all($view);
|
||||
restore_error_handler();
|
||||
|
||||
$this->assertIsArray($data);
|
||||
$this->assertNotSameSize($defaultData, $data);
|
||||
$this->assertNotEquals($defaultData, $data);
|
||||
|
||||
$cacheFile = $container->getParameter('kernel.cache_dir') . '/api-doc.cache';
|
||||
|
||||
$expectedDefaultViewCacheFile = $cacheFile . '.' . ApiDoc::DEFAULT_VIEW;
|
||||
$expectedViewCacheFile = $cacheFile . '.' . $view;
|
||||
|
||||
$this->assertFileExists($expectedDefaultViewCacheFile);
|
||||
$this->assertFileExists($expectedViewCacheFile);
|
||||
$this->assertFileNotEquals($expectedDefaultViewCacheFile, $expectedViewCacheFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider \Nelmio\ApiDocBundle\Tests\Extractor\ApiDocExtractorTest::dataProviderForViews
|
||||
*
|
||||
* @param string $view View name to test
|
||||
*/
|
||||
public function testCachedResultSameAsGenerated($view): void
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
/* @var CachingApiDocExtractor $extractor */
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$this->assertInstanceOf('\Nelmio\ApiDocBundle\Extractor\CachingApiDocExtractor', $extractor);
|
||||
|
||||
$cacheFile = $container->getParameter('kernel.cache_dir') . '/api-doc.cache';
|
||||
|
||||
$expectedViewCacheFile = $cacheFile . '.' . $view;
|
||||
|
||||
set_error_handler([$this, 'handleDeprecation']);
|
||||
$data = $extractor->all($view);
|
||||
|
||||
$this->assertFileExists($expectedViewCacheFile);
|
||||
|
||||
$cachedData = $extractor->all($view);
|
||||
restore_error_handler();
|
||||
|
||||
$this->assertIsArray($data);
|
||||
$this->assertIsArray($cachedData);
|
||||
$this->assertSameSize($data, $cachedData);
|
||||
$this->assertEquals($data, $cachedData);
|
||||
}
|
||||
}
|
116
Tests/Extractor/CollectionDirectiveTest.php
Normal file
116
Tests/Extractor/CollectionDirectiveTest.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Extractor;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CollectionDirectiveTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var TestExtractor
|
||||
*/
|
||||
private $testExtractor;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->testExtractor = new TestExtractor();
|
||||
}
|
||||
|
||||
private function normalize($input)
|
||||
{
|
||||
return $this->testExtractor->getNormalization($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataNormalizationTests
|
||||
*/
|
||||
public function testNormalizations($input, callable $callable): void
|
||||
{
|
||||
call_user_func($callable, $this->normalize($input), $this);
|
||||
}
|
||||
|
||||
public function dataNormalizationTests()
|
||||
{
|
||||
return [
|
||||
'test_simple_notation' => [
|
||||
'array<User>',
|
||||
function ($actual, TestCase $case): void {
|
||||
$case->assertArrayHasKey('collection', $actual);
|
||||
$case->assertArrayHasKey('collectionName', $actual);
|
||||
$case->assertArrayHasKey('class', $actual);
|
||||
|
||||
$case->assertTrue($actual['collection']);
|
||||
$case->assertEquals('', $actual['collectionName']);
|
||||
$case->assertEquals('User', $actual['class']);
|
||||
},
|
||||
],
|
||||
'test_simple_notation_with_namespaces' => [
|
||||
'array<Vendor0_2\\_Namespace1\\Namespace_2\\User>',
|
||||
function ($actual, TestCase $case): void {
|
||||
$case->assertArrayHasKey('collection', $actual);
|
||||
$case->assertArrayHasKey('collectionName', $actual);
|
||||
$case->assertArrayHasKey('class', $actual);
|
||||
|
||||
$case->assertTrue($actual['collection']);
|
||||
$case->assertEquals('', $actual['collectionName']);
|
||||
$case->assertEquals('Vendor0_2\\_Namespace1\\Namespace_2\\User', $actual['class']);
|
||||
},
|
||||
],
|
||||
'test_simple_named_collections' => [
|
||||
'array<Group> as groups',
|
||||
function ($actual, TestCase $case): void {
|
||||
$case->assertArrayHasKey('collection', $actual);
|
||||
$case->assertArrayHasKey('collectionName', $actual);
|
||||
$case->assertArrayHasKey('class', $actual);
|
||||
|
||||
$case->assertTrue($actual['collection']);
|
||||
$case->assertEquals('groups', $actual['collectionName']);
|
||||
$case->assertEquals('Group', $actual['class']);
|
||||
},
|
||||
],
|
||||
'test_namespaced_named_collections' => [
|
||||
'array<_Vendor\\Namespace0\\Namespace_2F3\\Group> as groups',
|
||||
function ($actual, TestCase $case): void {
|
||||
$case->assertArrayHasKey('collection', $actual);
|
||||
$case->assertArrayHasKey('collectionName', $actual);
|
||||
$case->assertArrayHasKey('class', $actual);
|
||||
|
||||
$case->assertTrue($actual['collection']);
|
||||
$case->assertEquals('groups', $actual['collectionName']);
|
||||
$case->assertEquals('_Vendor\\Namespace0\\Namespace_2F3\\Group', $actual['class']);
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataInvalidDirectives
|
||||
*/
|
||||
public function testInvalidDirectives($input): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->normalize($input);
|
||||
}
|
||||
|
||||
public function dataInvalidDirectives()
|
||||
{
|
||||
return [
|
||||
['array<>'],
|
||||
['array<Vendor\\>'],
|
||||
['array<2Vendor\\>'],
|
||||
['array<Vendor\\2Class>'],
|
||||
['array<User> as'],
|
||||
['array<User> as '],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Extractor;
|
||||
|
||||
use Nelmio\ApiDocBundle\Tests\WebTestCase;
|
||||
|
||||
class FosRestHandlerTest extends WebTestCase
|
||||
{
|
||||
|
||||
public function testGetWithQueryParamStrict()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zActionWithQueryParamStrictAction', 'test_route_15');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
|
||||
$requirements = $annotation->getRequirements();
|
||||
$this->assertCount(1, $requirements);
|
||||
$this->assertArrayHasKey('page', $requirements);
|
||||
|
||||
$requirement = $requirements['page'];
|
||||
|
||||
$this->assertArrayHasKey('requirement', $requirement);
|
||||
$this->assertEquals($requirement['requirement'], '\d+');
|
||||
|
||||
$this->assertArrayHasKey('description', $requirement);
|
||||
$this->assertEquals($requirement['description'], 'Page of the overview.');
|
||||
|
||||
$this->assertArrayHasKey('dataType', $requirement);
|
||||
|
||||
$this->assertArrayNotHasKey('default', $requirement);
|
||||
}
|
||||
|
||||
public function testGetWithQueryParam()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zActionWithQueryParamAction', 'test_route_8');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
|
||||
$filters = $annotation->getFilters();
|
||||
$this->assertCount(1, $filters);
|
||||
$this->assertArrayHasKey('page', $filters);
|
||||
|
||||
$filter = $filters['page'];
|
||||
|
||||
$this->assertArrayHasKey('requirement', $filter);
|
||||
$this->assertEquals($filter['requirement'], '\d+');
|
||||
|
||||
$this->assertArrayHasKey('description', $filter);
|
||||
$this->assertEquals($filter['description'], 'Page of the overview.');
|
||||
|
||||
$this->assertArrayHasKey('default', $filter);
|
||||
$this->assertEquals($filter['default'], '1');
|
||||
}
|
||||
|
||||
public function testGetWithQueryParamNoDefault()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zActionWithQueryParamNoDefaultAction', 'test_route_16');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
|
||||
$filters = $annotation->getFilters();
|
||||
$this->assertCount(1, $filters);
|
||||
$this->assertArrayHasKey('page', $filters);
|
||||
|
||||
$filter = $filters['page'];
|
||||
|
||||
$this->assertArrayHasKey('requirement', $filter);
|
||||
$this->assertEquals($filter['requirement'], '\d+');
|
||||
|
||||
$this->assertArrayHasKey('description', $filter);
|
||||
$this->assertEquals($filter['description'], 'Page of the overview.');
|
||||
|
||||
$this->assertArrayNotHasKey('default', $filter);
|
||||
}
|
||||
|
||||
public function testGetWithConstraintAsRequirements()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zActionWithConstraintAsRequirements', 'test_route_21');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
|
||||
$filters = $annotation->getFilters();
|
||||
$this->assertCount(1, $filters);
|
||||
$this->assertArrayHasKey('mail', $filters);
|
||||
|
||||
$filter = $filters['mail'];
|
||||
|
||||
$this->assertArrayHasKey('requirement', $filter);
|
||||
$this->assertEquals($filter['requirement'], 'Email');
|
||||
}
|
||||
|
||||
public function testGetWithRequestParam()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zActionWithRequestParamAction', 'test_route_11');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
|
||||
$parameters = $annotation->getParameters();
|
||||
$this->assertCount(1, $parameters);
|
||||
$this->assertArrayHasKey('param1', $parameters);
|
||||
|
||||
$parameter = $parameters['param1'];
|
||||
|
||||
$this->assertArrayHasKey('dataType', $parameter);
|
||||
$this->assertEquals($parameter['dataType'], 'string');
|
||||
|
||||
$this->assertArrayHasKey('description', $parameter);
|
||||
$this->assertEquals($parameter['description'], 'Param1 description.');
|
||||
|
||||
$this->assertArrayHasKey('required', $parameter);
|
||||
$this->assertEquals($parameter['required'], true);
|
||||
|
||||
$this->assertArrayNotHasKey('default', $parameter);
|
||||
}
|
||||
|
||||
public function testGetWithRequestParamNullable()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
|
||||
$annotation = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::zActionWithNullableRequestParamAction', 'test_route_22');
|
||||
|
||||
$this->assertNotNull($annotation);
|
||||
|
||||
$parameters = $annotation->getParameters();
|
||||
$this->assertCount(1, $parameters);
|
||||
$this->assertArrayHasKey('param1', $parameters);
|
||||
|
||||
$parameter = $parameters['param1'];
|
||||
|
||||
$this->assertArrayHasKey('dataType', $parameter);
|
||||
$this->assertEquals($parameter['dataType'], 'string');
|
||||
|
||||
$this->assertArrayHasKey('description', $parameter);
|
||||
$this->assertEquals($parameter['description'], 'Param1 description.');
|
||||
|
||||
$this->assertArrayHasKey('required', $parameter);
|
||||
$this->assertEquals($parameter['required'], false);
|
||||
|
||||
$this->assertArrayNotHasKey('default', $parameter);
|
||||
}
|
||||
}
|
26
Tests/Extractor/TestExtractor.php
Normal file
26
Tests/Extractor/TestExtractor.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Extractor;
|
||||
|
||||
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
|
||||
|
||||
class TestExtractor extends ApiDocExtractor
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function getNormalization($input)
|
||||
{
|
||||
return $this->normalizeClassParameter($input);
|
||||
}
|
||||
}
|
73
Tests/Fixtures/Controller/ResourceController.php
Normal file
73
Tests/Fixtures/Controller/ResourceController.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Controller;
|
||||
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
|
||||
class ResourceController
|
||||
{
|
||||
#[ApiDoc(
|
||||
resource: true,
|
||||
views: ['test', 'premium', 'default'],
|
||||
resourceDescription: 'Operations on resource.',
|
||||
description: 'List resources.',
|
||||
output: "array<Nelmio\ApiDocBundle\Tests\Fixtures\Model\Test> as tests",
|
||||
statusCodes: [200 => 'Returned on success.', 404 => 'Returned if resource cannot be found.']
|
||||
)]
|
||||
public function listResourcesAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(description: 'Retrieve a resource by ID.')]
|
||||
public function getResourceAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(description: 'Delete a resource by ID.')]
|
||||
public function deleteResourceAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
description: 'Create a new resource.',
|
||||
views: ['default', 'premium'],
|
||||
input: ['class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", 'name' => ''],
|
||||
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsNested",
|
||||
responseMap: [
|
||||
400 => ['class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Form\SimpleType", 'form_errors' => true],
|
||||
]
|
||||
)]
|
||||
public function createResourceAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
resource: true,
|
||||
views: ['default', 'premium'],
|
||||
description: 'List another resource.',
|
||||
resourceDescription: 'Operations on another resource.',
|
||||
output: "array<Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest>"
|
||||
)]
|
||||
public function listAnotherResourcesAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(description: 'Retrieve another resource by ID.')]
|
||||
public function getAnotherResourceAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(description: 'Update a resource bu ID.')]
|
||||
public function updateAnotherResourceAction(): void
|
||||
{
|
||||
}
|
||||
}
|
|
@ -11,67 +11,72 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Controller;
|
||||
|
||||
use FOS\RestBundle\Controller\Annotations\QueryParam;
|
||||
use FOS\RestBundle\Controller\Annotations\RequestParam;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
|
||||
use Nelmio\ApiDocBundle\Tests\Fixtures\DependencyTypePath;
|
||||
use Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Validator\Constraints\Email;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
||||
|
||||
class TestController
|
||||
{
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* resource="TestResource"
|
||||
* )
|
||||
*/
|
||||
public function namedResourceAction()
|
||||
#[ApiDoc(
|
||||
resource: 'TestResource',
|
||||
views: 'default'
|
||||
)]
|
||||
public function namedResourceAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* resource=true,
|
||||
* description="index action",
|
||||
* filters={
|
||||
* {"name"="a", "dataType"="integer"},
|
||||
* {"name"="b", "dataType"="string", "arbitrary"={"arg1", "arg2"}}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
#[ApiDoc(
|
||||
resource: true,
|
||||
description: 'index action',
|
||||
filters: [
|
||||
['name' => 'a', 'dataType' => 'integer'],
|
||||
['name' => 'b', 'dataType' => 'string', 'arbitrary' => ['arg1', 'arg2']],
|
||||
],
|
||||
)]
|
||||
public function indexAction()
|
||||
{
|
||||
return new Response('tests');
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="create test",
|
||||
* input="Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType"
|
||||
* )
|
||||
*/
|
||||
public function postTestAction()
|
||||
#[ApiDoc(
|
||||
resource: true,
|
||||
description: 'create test',
|
||||
views: ['default', 'premium'],
|
||||
input: TestType::class
|
||||
)]
|
||||
public function postTestAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="post test 2",
|
||||
* resource=true
|
||||
* )
|
||||
*/
|
||||
public function postTest2Action()
|
||||
#[ApiDoc(
|
||||
description: 'post test 2',
|
||||
views: ['default', 'premium'],
|
||||
resource: true
|
||||
)]
|
||||
public function postTest2Action(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function anotherAction()
|
||||
#[ApiDoc(
|
||||
description: 'Action with required parameters',
|
||||
input: "Nelmio\ApiDocBundle\Tests\Fixtures\Form\RequiredType"
|
||||
)]
|
||||
public function requiredParametersAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(description="Action without HTTP verb")
|
||||
*/
|
||||
public function anyAction()
|
||||
public function anotherAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc]
|
||||
public function routeVersionAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(description: 'Action without HTTP verb')]
|
||||
public function anyAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -79,221 +84,193 @@ class TestController
|
|||
* This method is useful to test if the getDocComment works.
|
||||
* And, it supports multilines until the first '@' char.
|
||||
*
|
||||
* @ApiDoc()
|
||||
*
|
||||
* @param int $id A nice comment
|
||||
* @param int $page
|
||||
* @param int $paramType The param type
|
||||
* @param int $param The param id
|
||||
*/
|
||||
public function myCommentedAction()
|
||||
#[ApiDoc]
|
||||
public function myCommentedAction($id, $page, int $paramType, int $param): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc]
|
||||
public function yetAnotherAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
views: ['default', 'test'],
|
||||
description: 'create another test',
|
||||
input: DependencyTypePath::TYPE
|
||||
)]
|
||||
public function anotherPostAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
description: 'Testing JMS',
|
||||
input: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
|
||||
)]
|
||||
public function jmsInputTestAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
description: 'Testing return',
|
||||
output: DependencyTypePath::TYPE
|
||||
)]
|
||||
public function jmsReturnTestAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc]
|
||||
public function zCachedAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc]
|
||||
public function zSecuredAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
*/
|
||||
public function yetAnotherAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="create another test",
|
||||
* input="dependency_type"
|
||||
* )
|
||||
*/
|
||||
public function anotherPostAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @QueryParam(strict=true, name="page", requirements="\d+", description="Page of the overview.")
|
||||
*/
|
||||
public function zActionWithQueryParamStrictAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @QueryParam(name="page", requirements="\d+", default="1", description="Page of the overview.")
|
||||
*/
|
||||
public function zActionWithQueryParamAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @QueryParam(name="page", requirements="\d+", description="Page of the overview.")
|
||||
*/
|
||||
public function zActionWithQueryParamNoDefaultAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @QueryParam(name="mail", requirements=@Email, description="Email of someone.")
|
||||
*/
|
||||
public function zActionWithConstraintAsRequirements()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Testing JMS",
|
||||
* input="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
|
||||
* )
|
||||
*/
|
||||
public function jmsInputTestAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Testing return",
|
||||
* output="dependency_type"
|
||||
* )
|
||||
*/
|
||||
public function jmsReturnTestAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @RequestParam(name="param1", requirements="string", description="Param1 description.")
|
||||
*/
|
||||
public function zActionWithRequestParamAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @RequestParam(name="param1", requirements="string", description="Param1 description.", nullable=true)
|
||||
*/
|
||||
public function zActionWithNullableRequestParamAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
*/
|
||||
public function secureRouteAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* authentication=true,
|
||||
* authenticationRoles={"ROLE_USER","ROLE_FOOBAR"}
|
||||
* )
|
||||
*/
|
||||
public function authenticatedAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @Cache(maxage=60, public=1)
|
||||
*/
|
||||
public function cachedAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc()
|
||||
* @deprecated
|
||||
*/
|
||||
public function deprecatedAction()
|
||||
#[ApiDoc]
|
||||
public function deprecatedAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
|
||||
)]
|
||||
public function jmsReturnNestedOutputAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsChild"
|
||||
)]
|
||||
public function jmsReturnNestedExtendedOutputAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest"
|
||||
)]
|
||||
public function zReturnJmsAndValidationOutputAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
description: 'Returns a collection of Object',
|
||||
requirements: [
|
||||
['name' => 'limit', 'dataType' => 'integer', 'requirement' => "\d+", 'description' => 'how many objects to return'],
|
||||
],
|
||||
parameters: [
|
||||
['name' => 'categoryId', 'dataType' => 'integer', 'required' => true, 'description' => 'category id'],
|
||||
]
|
||||
)]
|
||||
public function cgetAction($id): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
input: [
|
||||
'class' => TestType::class,
|
||||
'parsers' => ["Nelmio\ApiDocBundle\Parser\FormTypeParser"],
|
||||
]
|
||||
)]
|
||||
public function zReturnSelectedParsersInputAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
output: [
|
||||
'class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest",
|
||||
'parsers' => [
|
||||
"Nelmio\ApiDocBundle\Parser\JmsMetadataParser",
|
||||
"Nelmio\ApiDocBundle\Parser\ValidationParser",
|
||||
],
|
||||
]
|
||||
)]
|
||||
public function zReturnSelectedParsersOutputAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
section: 'private'
|
||||
)]
|
||||
public function privateAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[ApiDoc(
|
||||
section: 'exclusive'
|
||||
)]
|
||||
public function exclusiveAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
|
||||
* )
|
||||
* @see http://symfony.com
|
||||
*/
|
||||
public function jmsReturnNestedOutputAction()
|
||||
#[ApiDoc]
|
||||
public function withLinkAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsChild"
|
||||
* )
|
||||
*/
|
||||
public function jmsReturnNestedExtendedOutputAction()
|
||||
#[ApiDoc(
|
||||
input: ['class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"],
|
||||
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
|
||||
parameters: [
|
||||
[
|
||||
'name' => 'number',
|
||||
'dataType' => 'integer',
|
||||
'actualType' => 'string',
|
||||
'subType' => null,
|
||||
'required' => true,
|
||||
'description' => 'This is the new description',
|
||||
'readonly' => false,
|
||||
'sinceVersion' => 'v3.0',
|
||||
'untilVersion' => 'v4.0',
|
||||
],
|
||||
[
|
||||
'name' => 'arr',
|
||||
'dataType' => 'object (ArrayCollection)',
|
||||
],
|
||||
[
|
||||
'name' => 'nested',
|
||||
'dataType' => 'object (JmsNested)',
|
||||
'children' => [
|
||||
'bar' => [
|
||||
'dataType' => 'integer',
|
||||
'format' => 'd+',
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
)]
|
||||
public function overrideJmsAnnotationWithApiDocParametersAction(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest"
|
||||
* )
|
||||
*/
|
||||
public function zReturnJmsAndValidationOutputAction()
|
||||
#[ApiDoc(
|
||||
output: "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
|
||||
input: [
|
||||
'class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
|
||||
],
|
||||
)]
|
||||
public function defaultJmsAnnotations(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* description="Returns a collection of Object",
|
||||
* requirements={
|
||||
* {"name"="limit", "dataType"="integer", "requirement"="\d+", "description"="how many objects to return"}
|
||||
* },
|
||||
* parameters={
|
||||
* {"name"="categoryId", "dataType"="integer", "required"=true, "description"="category id"}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function cgetAction($id)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* input={
|
||||
* "class"="Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType",
|
||||
* "parsers"={
|
||||
* "Nelmio\ApiDocBundle\Parser\FormTypeParser",
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function zReturnSelectedParsersInputAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* output={
|
||||
* "class"="Nelmio\ApiDocBundle\Tests\Fixtures\Model\MultipleTest",
|
||||
* "parsers"={
|
||||
* "Nelmio\ApiDocBundle\Parser\JmsMetadataParser",
|
||||
* "Nelmio\ApiDocBundle\Parser\ValidationParser"
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
public function zReturnSelectedParsersOutputAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* section="private"
|
||||
* )
|
||||
*/
|
||||
public function privateAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ApiDoc(
|
||||
* section="exclusive"
|
||||
* )
|
||||
*/
|
||||
public function exclusiveAction()
|
||||
#[ApiDoc(
|
||||
description: 'Route with host placeholder',
|
||||
views: ['default']
|
||||
)]
|
||||
public function routeWithHostAction(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
32
Tests/Fixtures/DependencyTypePath.php
Normal file
32
Tests/Fixtures/DependencyTypePath.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
|
||||
/*
|
||||
* This class is used to have dynamic annotations for BC.
|
||||
* {@see Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController}
|
||||
*
|
||||
* @author Ener-Getick <egetick@gmail.com>
|
||||
*/
|
||||
if (LegacyFormHelper::isLegacy()) {
|
||||
class DependencyTypePath
|
||||
{
|
||||
public const TYPE = 'dependency_type';
|
||||
}
|
||||
} else {
|
||||
class DependencyTypePath
|
||||
{
|
||||
public const TYPE = 'Nelmio\ApiDocBundle\Tests\Fixtures\Form\DependencyType';
|
||||
}
|
||||
}
|
|
@ -11,24 +11,35 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class CollectionType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$collectionType = 'Symfony\Component\Form\Extension\Core\Type\CollectionType';
|
||||
$builder
|
||||
->add('a', 'collection', array('type' => 'text'))
|
||||
->add('b', 'collection', array('type' => new TestType()))
|
||||
->add('a', LegacyFormHelper::getType($collectionType), [
|
||||
LegacyFormHelper::hasBCBreaks() ? 'entry_type' : 'type' => LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'),
|
||||
])
|
||||
->add('b', LegacyFormHelper::getType($collectionType), [
|
||||
LegacyFormHelper::hasBCBreaks() ? 'entry_type' : 'type' => LegacyFormHelper::isLegacy() ? new TestType() : __NAMESPACE__ . '\TestType',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'collection_type';
|
||||
}
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
|
@ -12,20 +22,24 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
*/
|
||||
class CompoundType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('sub_form', new SimpleType())
|
||||
->add('a', 'number')
|
||||
;
|
||||
->add('sub_form', LegacyFormHelper::isLegacy() ? new SimpleType() : __NAMESPACE__ . '\SimpleType')
|
||||
->add('a', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\NumberType'))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this type.
|
||||
*
|
||||
* @return string The name of this type
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
|||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class DependencyType extends AbstractType
|
||||
|
@ -21,29 +22,40 @@ class DependencyType extends AbstractType
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('a', null, array('description' => 'A nice description'))
|
||||
->add('a', null, ['description' => 'A nice description'])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @deprecated Remove it when bumping requirements to Symfony 2.7+
|
||||
*/
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(array(
|
||||
$this->configureOptions($resolver);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\Test',
|
||||
));
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'dependency_type';
|
||||
}
|
||||
|
|
56
Tests/Fixtures/Form/EntityType.php
Normal file
56
Tests/Fixtures/Form/EntityType.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class EntityType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @deprecated Remove it when bumping requirements to Symfony 2.7+
|
||||
*/
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver): void
|
||||
{
|
||||
$this->configureOptions($resolver);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\EntityTest',
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\ChoiceType');
|
||||
}
|
||||
|
||||
/**
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'entity';
|
||||
}
|
||||
}
|
|
@ -1,47 +1,78 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class ImprovedTestType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$choiceType = LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\ChoiceType');
|
||||
$datetimeType = LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\DateTimeType');
|
||||
$dateType = LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\DateType');
|
||||
|
||||
$builder
|
||||
->add('dt1', 'datetime', array('widget' => 'single_text', 'description' => 'A nice description'))
|
||||
->add('dt2', 'datetime', array('date_format' => 'M/d/y'))
|
||||
->add('dt3', 'datetime', array('widget' => 'single_text', 'format' => 'M/d/y H:i:s'))
|
||||
->add('dt4', 'datetime', array('date_format' => \IntlDateFormatter::MEDIUM))
|
||||
->add('dt5', 'datetime', array('format' => 'M/d/y H:i:s'))
|
||||
->add('d1', 'date', array('format' => \IntlDateFormatter::MEDIUM))
|
||||
->add('d2', 'date', array('format' => 'd-M-y'))
|
||||
->add('c1', 'choice', array('choices' => array('m' => 'Male', 'f' => 'Female')))
|
||||
->add('c2', 'choice', array('choices' => array('m' => 'Male', 'f' => 'Female'), 'multiple' => true))
|
||||
->add('c3', 'choice', array('choices' => array()))
|
||||
->add('c4', 'choice', array('choice_list' => new SimpleChoiceList(array('foo' => 'bar', 'bazgroup' => array('baz' => 'Buzz')))))
|
||||
->add('dt1', $datetimeType, ['widget' => 'single_text', 'description' => 'A nice description'])
|
||||
->add('dt2', $datetimeType, ['date_format' => 'M/d/y', 'html5' => false])
|
||||
->add('dt3', $datetimeType, ['widget' => 'single_text', 'format' => 'M/d/y H:i:s', 'html5' => false])
|
||||
->add('dt4', $datetimeType, ['date_format' => \IntlDateFormatter::MEDIUM])
|
||||
->add('dt5', $datetimeType, ['format' => 'M/d/y H:i:s', 'html5' => false])
|
||||
->add('d1', $dateType, ['format' => \IntlDateFormatter::MEDIUM])
|
||||
->add('d2', $dateType, ['format' => 'd-M-y'])
|
||||
->add('c1', $choiceType, ['choices' => ['Male' => 'm', 'Female' => 'f']])
|
||||
->add('c2', $choiceType, ['choices' => ['Male' => 'm', 'Female' => 'f'], 'multiple' => true])
|
||||
->add('c3', $choiceType, ['choices' => []])
|
||||
->add('c4', $choiceType, ['choices' => ['bar' => 'foo', 'bazgroup' => ['Buzz' => 'baz']]])
|
||||
->add('e1', LegacyFormHelper::isLegacy() ? new EntityType() : __NAMESPACE__ . '\EntityType',
|
||||
LegacyFormHelper::isLegacy()
|
||||
? ['choice_list' => new SimpleChoiceList(['bar' => 'foo', 'bazgroup' => ['Buzz' => 'baz']])]
|
||||
: ['choices' => ['bar' => 'foo', 'bazgroup' => ['Buzz' => 'baz']]]
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @deprecated Remove it when bumping requirements to Symfony 2.7+
|
||||
*/
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(array(
|
||||
$this->configureOptions($resolver);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\ImprovedTest',
|
||||
));
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
|||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class RequireConstructionType extends AbstractType
|
||||
|
@ -24,32 +25,44 @@ class RequireConstructionType extends AbstractType
|
|||
$this->noThrow = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if ($this->noThrow !== true)
|
||||
throw new \RuntimeException(__CLASS__ . " require contruction");
|
||||
if (true !== $this->noThrow) {
|
||||
throw new \RuntimeException(__CLASS__ . ' require contruction');
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('a', null, array('description' => 'A nice description'))
|
||||
->add('a', null, ['description' => 'A nice description'])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @deprecated Remove it when bumping requirements to Symfony 2.7+
|
||||
*/
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(array(
|
||||
$this->configureOptions($resolver);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\Test',
|
||||
));
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'require_construction_type';
|
||||
}
|
||||
|
|
43
Tests/Fixtures/Form/RequiredType.php
Normal file
43
Tests/Fixtures/Form/RequiredType.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class SimpleType
|
||||
*
|
||||
* @author Lucas van Lierop <lucas@vanlierop.org>
|
||||
*/
|
||||
class RequiredType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('required_field', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'), ['required' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
|
@ -1,6 +1,17 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
|
@ -11,27 +22,31 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
*/
|
||||
class SimpleType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('a', 'text', array(
|
||||
$builder->add('a', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'), [
|
||||
'description' => 'Something that describes A.',
|
||||
))
|
||||
->add('b', 'number')
|
||||
->add('c', 'choice', array(
|
||||
'choices' => array('x' => 'X', 'y' => 'Y', 'z' => 'Z'),
|
||||
))
|
||||
->add('d', 'datetime')
|
||||
->add('e', 'date')
|
||||
->add('g', 'textarea')
|
||||
])
|
||||
->add('b', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\NumberType'))
|
||||
->add('c', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\ChoiceType'),
|
||||
['choices' => ['X' => 'x', 'Y' => 'y', 'Z' => 'z']]
|
||||
)
|
||||
->add('d', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\DateTimeType'))
|
||||
->add('e', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\DateType'))
|
||||
->add('g', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextareaType'))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this type.
|
||||
*
|
||||
* @return string The name of this type
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'simple';
|
||||
}
|
||||
|
|
|
@ -11,38 +11,51 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
|
||||
|
||||
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class TestType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('a', null, array('description' => 'A nice description'))
|
||||
->add('a', null, ['description' => 'A nice description'])
|
||||
->add('b')
|
||||
->add($builder->create('c', 'checkbox'))
|
||||
->add('d','text',array( 'data' => 'DefaultTest'))
|
||||
->add($builder->create('c', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\CheckboxType')))
|
||||
->add('d', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'), ['data' => 'DefaultTest'])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @deprecated Remove it when bumping requirements to Symfony 2.7+
|
||||
*/
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(array(
|
||||
$this->configureOptions($resolver);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\Test',
|
||||
));
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* BC SF < 2.8
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
|
16
Tests/Fixtures/Model/EntityTest.php
Normal file
16
Tests/Fixtures/Model/EntityTest.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
class EntityTest
|
||||
{
|
||||
}
|
|
@ -11,8 +11,6 @@
|
|||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class ImprovedTest
|
||||
{
|
||||
public $dt1;
|
||||
|
@ -26,4 +24,5 @@ class ImprovedTest
|
|||
public $c2;
|
||||
public $c3;
|
||||
public $c4;
|
||||
public $e1;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
use JMS\Serializer\Annotation as JMS;
|
||||
|
@ -9,5 +19,4 @@ class JmsChild extends JmsTest
|
|||
* @JMS\Type("string");
|
||||
*/
|
||||
public $child;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
use JMS\Serializer\Annotation as JMS;
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
use JMS\Serializer\Annotation as JMS;
|
||||
|
||||
class JmsNested
|
||||
{
|
||||
|
||||
/**
|
||||
* @JMS\Type("DateTime");
|
||||
* @JMS\ReadOnly
|
||||
*
|
||||
* @JMS\ReadOnlyProperty
|
||||
*/
|
||||
public $foo;
|
||||
|
||||
|
@ -38,19 +48,23 @@ class JmsNested
|
|||
|
||||
/**
|
||||
* @Jms\Type("string")
|
||||
*
|
||||
* @Jms\Since("0.2")
|
||||
*/
|
||||
public $since;
|
||||
|
||||
/**
|
||||
* @Jms\Type("string")
|
||||
*
|
||||
* @Jms\Until("0.3")
|
||||
*/
|
||||
public $until;
|
||||
|
||||
/**
|
||||
* @Jms\Type("string")
|
||||
*
|
||||
* @Jms\Since("0.4")
|
||||
*
|
||||
* @Jms\Until("0.5")
|
||||
*/
|
||||
public $sinceAndUntil;
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
use JMS\Serializer\Annotation as JMS;
|
||||
|
@ -15,12 +24,14 @@ class JmsTest
|
|||
|
||||
/**
|
||||
* @JMS\Type("DateTime");
|
||||
* @JMS\ReadOnly
|
||||
*
|
||||
* @JMS\ReadOnlyProperty
|
||||
*/
|
||||
public $bar;
|
||||
|
||||
/**
|
||||
* @JMS\Type("double");
|
||||
*
|
||||
* @JMS\SerializedName("number");
|
||||
*/
|
||||
public $baz;
|
||||
|
@ -40,4 +51,8 @@ class JmsTest
|
|||
*/
|
||||
public $nestedArray;
|
||||
|
||||
/**
|
||||
* @JMS\Groups("hidden")
|
||||
*/
|
||||
public $hidden;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
class JsonSerializableOptionalConstructorTest implements \JsonSerializable
|
||||
{
|
||||
public function __construct($optional = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
class JsonSerializableRequiredConstructorTest implements \JsonSerializable
|
||||
{
|
||||
public function __construct($required)
|
||||
{
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
27
Tests/Fixtures/Model/JsonSerializableTest.php
Normal file
27
Tests/Fixtures/Model/JsonSerializableTest.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle.
|
||||
*
|
||||
* (c) Nelmio <hello@nelm.io>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
|
||||
|
||||
class JsonSerializableTest implements \JsonSerializable
|
||||
{
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return [
|
||||
'id' => 123,
|
||||
'name' => 'My name',
|
||||
'child' => [
|
||||
'value' => [1, 2, 3],
|
||||
],
|
||||
'another' => new JsonSerializableOptionalConstructorTest(),
|
||||
];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue