Compare commits

...

971 commits

Author SHA1 Message Date
Ilyas Salikhov
10ee31bdaa
Merge pull request #24 from retailcrm/twig-deprecates
fix twig deprecates
2024-10-10 23:14:28 +03:00
Ilyas Salikhov
6946e8bdc9 fix twig deprecates 2024-10-10 23:07:49 +03:00
Ilyas Salikhov
a896abb3e2
Merge pull request #23 from retailcrm/attribute
Migrate annotation ApiDoc to attribute
2024-10-01 23:02:33 +03:00
Ilyas Salikhov
a9fceca8df Migrate annotation ApiDoc to attribute 2024-10-01 23:00:23 +03:00
Ilyas Salikhov
8135dec035
Merge pull request #22 from retailcrm/remove-unused-params
Remove unused params
2024-10-01 18:41:22 +03:00
Ilyas Salikhov
5db963666a remove unused cache annotation param 2024-10-01 18:38:58 +03:00
Ilyas Salikhov
990b781322 remove unused https annotation param 2024-10-01 18:33:15 +03:00
Ilyas Salikhov
e7c9fd4f56 remove unused authentication annotation param 2024-10-01 18:26:30 +03:00
Ilyas Salikhov
abbf032483 Remove unused argument 2024-10-01 17:43:18 +03:00
Ilyas Salikhov
49f4161b23
Merge pull request #21 from retailcrm/fos-rest-remove
Remove the support of FosRestBundle, DunglasApiBundle and JmsSecurityExtra
2024-10-01 17:35:38 +03:00
Ilyas Salikhov
3ef60f2926 remove JmsSecurityExtraHandler 2024-10-01 17:30:02 +03:00
Ilyas Salikhov
636eeb7cae Remove dunglasapibundle support 2024-10-01 17:26:49 +03:00
Ilyas Salikhov
e97eba7c1b Remove the support of fos-rest 2024-10-01 17:18:04 +03:00
Ilyas Salikhov
73f9b9e8a2
Merge pull request #20 from retailcrm/duglas-remove
remove the support of duglas
2024-10-01 16:42:22 +03:00
Ilyas Salikhov
4b6137f618 remove the support of duglas 2024-10-01 16:39:44 +03:00
Ilyas Salikhov
eba661dcb6
Merge pull request #19 from retailcrm/scope
add scope field to ApiDoc
2024-10-01 16:12:45 +03:00
Ilyas Salikhov
d761d890a0 add scope field to ApiDoc 2024-10-01 16:11:23 +03:00
Ilyas Salikhov
551366412e
Merge pull request #18 from retailcrm/php-cs
Add php-cs-fixer to project
2024-10-01 15:57:19 +03:00
Ilyas Salikhov
e06fb926f5 Fix code by php-cs-fixer 2024-10-01 15:54:04 +03:00
Ilyas Salikhov
cb69478c78 Add php-cs-fixer 2024-10-01 15:53:49 +03:00
Ilyas Salikhov
5bad727da1
Merge pull request #17 from retailcrm/sensio-support-remove
Removed support of sensio/framework-extra-bundle
2024-09-30 21:34:55 +03:00
Ilyas Salikhov
45d1ed1735 add doctrine/annotations as dev dependency 2024-09-30 21:33:21 +03:00
Ilyas Salikhov
4d4758cb40 fix jms/serializer dependencies 2024-09-30 21:30:38 +03:00
Ilyas Salikhov
6334d2e40f Removed support of sensio/framework-extra-bundle 2024-09-30 21:23:38 +03:00
Ilyas Salikhov
b72dfea35c
Merge pull request #16 from retailcrm/php83
PHP 8.3 in CI
2024-09-24 15:23:20 +03:00
Ilyas Salikhov
7de616a283 PHP 8.3 in CI 2024-09-24 15:20:04 +03:00
Ilyas Salikhov
0f7ecac668
Merge pull request #15 from retailcrm/container-remove
Add explicit dependencies instead of container
2024-07-02 16:26:10 +03:00
Ilyas Salikhov
0808e8421a Add explicit dependencies instead of container 2024-07-02 16:24:26 +03:00
Ilyas Salikhov
ed2e185fe2
Merge pull request #14 from retailcrm/deprecates
sf 6 deprecation types
2024-06-18 20:02:23 +03:00
Ilyas Salikhov
56dd45e0d1 sf 6 deprecation types 2024-06-18 20:01:11 +03:00
Ilyas Salikhov
1cd20df360
Merge pull request #13 from retailcrm/tests
Actualize the code
2024-06-18 19:14:51 +03:00
Ilyas Salikhov
c8e33918a2 Support of PHP 8.2 2024-06-18 13:29:17 +03:00
Ilyas Salikhov
5cbcba78df Symfony 6 compability 2024-06-18 12:54:58 +03:00
Ilyas Salikhov
72441d6bf3 Run phpunit tests in CI 2024-06-18 12:27:20 +03:00
Ilyas Salikhov
b603381139 1. Up min PHP version to 8.1
2. Up min Symfony verion to 5.0. Adopt code to sf 5.0+
3. Local env to run tests
4. Repare tests
2024-06-18 12:24:32 +03:00
Ilyas Salikhov
0765d2b453 Compability with PHP 8.1 (string functions calling) 2024-01-30 18:39:44 +03:00
Ilyas Salikhov
c764717de4 Compability with PHP 8.1 (class_exists calling) 2024-01-30 18:31:12 +03:00
Alexey Chelnakov
66bcfd82e3 fix twig 2023-01-09 16:28:18 +03:00
Alexey
b49a0eb8e7
Sf54 comp (#12)
* symfony 5.4 compatibility
2023-01-09 15:59:54 +03:00
Alexey Chelnakov
6abd901696 fix null $controller 2022-10-07 19:08:17 +03:00
Alexey
d8b5ab9f71
Fix deprecations (#8)
* fix symfony deprecations
2022-10-07 15:32:38 +03:00
Alexey
8d699084aa
Merge pull request #7 from retailcrm/upgrade
Upgrade
2022-07-12 10:14:30 +03:00
mantis
056087cbbf Fix: Deprecation: A tree builder without a root node is deprecated since symfony 4.2 (#1457)
* Fix: Deprecation: A tree builder without a root node is deprecated since Symfony 4.2 and will not be supported anymore in 5.0.

* Fix: Deprecation: A tree builder without a root node is deprecated since Symfony 4.2 and will not be supported anymore in 5.0.
2022-04-26 16:59:07 +03:00
Alan Poulain
b9718f3400 Compatibility with Symfony 4.3 (#1524)
* Use twig instead of templating

* Fix at least one suite test in Travis

* Use Twig namespace (Twig > 1.10 and TwigBundle > 2.2)
2022-04-26 16:58:48 +03:00
Sergey Linnik
33964a4bfc
Merge pull request #6 from devalex86/form_info_parser
ability to change information for types of forms
2020-07-06 20:59:51 +03:00
Alexander Kulinich
d43396c930 ability to change information for types of forms 2020-07-06 14:53:58 +03:00
Vitaliy Chesnokov
99b9d4f486
Fix generateHumanReadableTypes 2019-04-29 12:39:48 +03:00
Vitaliy Chesnokov
8a6213cbd2
Removed duplicated constraints in Format field. Improved format for field type of form 2019-04-29 12:39:48 +03:00
Vitaliy Chesnokov
39d640cce8
Validation with considering groups 2019-04-29 12:39:48 +03:00
Vitaliy Chesnokov
d78f8887a9
Pass the data about field name and field class to template 2019-04-29 12:39:45 +03:00
Vitaliy Chesnokov
befd12e3e5
Form labels translation support. 2019-04-25 21:55:45 +03:00
Vitaliy Chesnokov
81274509da
Allow to define the input name for the JmsMetadataParser 2019-04-25 21:55:45 +03:00
Vitaliy Chesnokov
b7464a93b9
* Range Constraint parsing in the ValidationParser
* Better formatting form Length constraint description
* Allow to define the input name for the ValidationParser
2019-04-25 21:55:41 +03:00
Vitaliy Chesnokov
cd87930d29
Check the version in methods response 2019-04-25 21:50:32 +03:00
Vitaliy Chesnokov
c6fa1e893e
Locale setting for the Translator 2019-04-25 21:50:32 +03:00
Vitaliy Chesnokov
1c4f003e76
Doc generation for the specific API version 2019-04-25 21:50:28 +03:00
Guilhem N
f0a606b636
Merge pull request #1134 from dunglas/fix-34-40-compat
[2.x] Fix compatibility with Symfony 3.4 and 4.0
2017-12-05 07:14:09 +01:00
Kévin Dunglas
f4b599f6d0
[2.x] Fix compatibility with Symfony 3.4 and 4.0 2017-12-04 23:11:29 +01:00
Guilhem Niot
8886e6eff8 Merge pull request #1043 from xabbuh/patch-1
fix section title markup
2017-07-25 11:28:44 +02:00
Christian Flothmann
83ed0a3407 fix section title markup 2017-07-25 11:26:50 +02:00
Guilhem Niot
17b6c3aeba Update branch alias 2017-05-31 14:17:35 +02:00
Guilhem Niot
adcdd91950 Update the documentation link 2017-05-13 16:53:58 +02:00
Lukas Kahwe Smith
ae18441763 Merge pull request #989 from lsmith77/submit-boolean-as-json-boolean
ensure that boolean fields are submited as boolean values when using json
2017-04-28 17:27:09 +02:00
Lukas Kahwe Smith
e5268e3f7f
ensure that boolean fields are submited as boolean values when using json 2017-04-28 16:56:15 +02:00
Guilhem Niot
572c69a509 Merge pull request #988 from lsmith77/fix-filters-always-as-query-parameters
Fix filters always as query parameters
2017-04-28 14:36:48 +00:00
Lukas Kahwe Smith
33307d750e
fix typo to force filters as query parameters 2017-04-28 15:58:38 +02:00
Soltész Balázs
e2c3d7ce94
Make filters always be passed as query string parameters in sandbox requests. 2017-04-28 12:36:27 +02:00
Guilhem Niot
3727d043cb Merge pull request #963 from shadowjs/patch-1
Update swagger-support.rst
2017-03-15 13:54:04 +01:00
shadowjs
ad1b9eec31 Update swagger-support.rst 2017-02-23 22:51:49 +01:00
Javier Spagnoletti
eab2d5d8fc Bumping to PHP 5.4 since the current codebase is already using features introduced in that version 2017-02-19 02:14:31 -03:00
Guilhem N
b01128dc13 Merge pull request #841 from flip111/patch-1
Update ApiDocExtractor.php
2016-12-29 12:21:15 +01:00
Guilhem N
7a7d743ff6 Merge pull request #915 from mhor/patch-1
docs: fix rst typo
2016-12-29 12:16:30 +01:00
Guilhem N
ff3bf88bed Merge pull request #895 from wouterj/patch-1
Fixed little Markdown syntax
2016-12-29 12:14:59 +01:00
Guilhem N
2d70b08021 Merge pull request #924 from tael/patch-1
swagger-ui repository changed
2016-12-29 12:10:31 +01:00
William Durand
95017bf780 Merge pull request #926 from kustov-an/dump_command_description
Add description for the dump command
2016-10-26 09:33:33 +02:00
Alexander Kustov
8f9d096448 Add description for the dump command 2016-10-26 10:15:13 +05:00
TaeL Kim
66e8e15ff1 swagger-ui repository changed 2016-10-21 14:55:56 +09:00
William Durand
958e697981 Merge pull request #922 from javiereguiluz/fix_doc_errors
Fixed minor RST syntax issues in docs
2016-10-03 21:10:35 +02:00
Javier Eguiluz
c531fcc037 Fixed minor RST syntax issues in docs 2016-10-03 15:42:40 +02:00
Maxime Horcholle
13c25eb4c4 docs: fix rst typo 2016-09-22 10:29:54 +02:00
William Durand
73d4db51e1 Merge pull request #912 from ruscon/patch-1
Update .travis.yml
2016-09-10 08:38:33 +02:00
Coroliov Oleg
9387638079 Update .travis.yml 2016-09-10 00:38:59 +03:00
Wouter J
b5d371d984 Fixed little Markdown syntax 2016-08-10 10:37:07 +02:00
William Durand
e6e424687e Merge pull request #890 from Invis1ble/fix-889
Fixed placeholder translations #889
2016-08-04 11:03:43 +02:00
Invis1ble
97c6b1e857 Fixed placeholder translations #889 2016-07-29 09:20:35 +03:00
William Durand
463dea9af6 Merge pull request #874 from rmzamora/master
Fix error: Key "statusCodes" for array with keys...
2016-06-16 17:04:18 +02:00
mellzamora
f5e38a283a Fix error:
Key "statusCodes" for array with keys "method, uri, description, documentation, filters, requirements, parsedResponseMap, https, authentication, authenticationRoles, deprecated, id" does not exist in NelmioApiDocBundle::method.html.twig at line 182.

Sample:

 /**
     * Retrieves the list of categories (paginated) based on criteria.
     *
     * @ApiDoc(
     *  resource=true,
     *  output={"class"="Sonata\DatagridBundle\Pager\PagerInterface", "groups"={"sonata_api_read"}}
     * )
     *
     *
     * @QueryParam(name="page", requirements="\d+", default="1", description="Page for category list pagination")
     * @QueryParam(name="count", requirements="\d+", default="10", description="Number of categories by page")
     * @QueryParam(name="enabled", requirements="0|1", nullable=true, strict=true, description="Enabled/Disabled categories filter")
     * @QueryParam(name="context", requirements="\S+", nullable=true, strict=true, description="Context of categories")
     *
     * @View(serializerGroups="sonata_api_read", serializerEnableMaxDepthChecks=true)
     *
     * @param ParamFetcherInterface $paramFetcher
     *
     * @return PagerInterface
     */
2016-06-16 22:44:56 +08:00
William Durand
a3a9bb3b70
Prepare 2.13.0 release 2016-06-13 11:12:09 +02:00
William Durand
79716d49f5
Remove a test since #811 changes the behavior 2016-06-13 11:04:16 +02:00
William Durand
4b16f4e068
Do not fail on Symfony 4.0 deprecation messages 2016-06-13 10:53:00 +02:00
William Durand
7aeea0871c Merge pull request #811 from InputOutput/allow-input-and-filters
Allow filter descriptions to be used in conjunction with POST/PUT inp…
2016-06-13 10:01:34 +02:00
William Durand
9444172abc Merge pull request #857 from zanardigit/master
Add navigation index for resources
2016-06-13 10:01:12 +02:00
William Durand
e6ea85157e Merge pull request #868 from dpcat237/502_hide-requirement-when-empty
#502 hide requirement when empty
2016-06-13 10:00:49 +02:00
Denys Pasishnyi
1256275185 #502 Test for the improvement 2016-06-13 00:30:40 +02:00
Denys Pasishnyi
725e4d9dda #502 Hide Requirement when not set 2016-06-13 00:29:59 +02:00
William Durand
a009e97382 Merge pull request #866 from Gregoire-M/master
Usage of OUTPUT_RAW to avoid javascript syntax error when dumping HTML
2016-06-10 13:30:55 +02:00
gmarchal
05d3a51259 Usage of OUTPUT_RAW to avoid javascript syntax error when dumping HTML API doc. Fixes issue #864. 2016-06-10 10:47:43 +02:00
William Durand
6d16486abb Merge pull request #865 from javiereguiluz/fix_doc_links
Fixed the links to some doc articles
2016-06-09 16:50:28 +02:00
Javier Eguiluz
4a37d638f8 Fixed the links to some doc articles 2016-06-09 15:56:07 +02:00
William Durand
d0eaafadcb Merge pull request #861 from Uplink03/master
Links from index.rst to the other doc files
2016-06-03 17:50:40 +02:00
Uplink03
8eee7fec49 Added links form index.rst to the other doc files 2016-06-02 16:30:54 +01:00
William Durand
970a78c614 Merge pull request #860 from jupeter/format-wrapping
Fix wrapping of format table row
2016-06-02 11:29:59 +02:00
Piotr Plenik
12c067154c fix format wrapping 2016-06-02 10:33:39 +02:00
Francesco Abeni
df04b5c871 Add navigation index for resources 2016-05-24 13:13:39 +02:00
William Durand
7288ccad07 Merge pull request #853 from miholeus/master
Headers support
2016-05-19 21:43:45 +02:00
miholeus
5123b49bf7 added tests for headers support 2016-05-19 18:20:41 +03:00
miholeus
d5c1c57cc9 added headers documentation example 2016-05-19 14:35:55 +03:00
miholeus
e141ffd291 added headers support 2016-05-19 14:28:32 +03:00
William Durand
a7ad7ff144 Merge pull request #851 from blitzr/master
Allow custom ApiDoc annotation
2016-05-16 21:08:35 +02:00
Martin Le Guillou
aada3151aa enable ApiDocExtractor overriding 2016-05-16 15:59:41 +02:00
William Durand
b494d8e1ab Merge pull request #821 from tcz/sandbox_output_improvements
Adding request body and curl command to sandbox
2016-05-11 17:32:04 +02:00
William Durand
6c77f6f422 Merge pull request #824 from fstr/master
Change visibility of markdownParser property to protected
2016-05-11 17:18:13 +02:00
William Durand
9dfd984c68 Merge pull request #832 from ismailbaskin/vich-compat
Allow sending empty file data
2016-05-11 17:16:17 +02:00
William Durand
4660805291 Merge pull request #823 from ImmRanneft/master
Little template fixes for sandbox
2016-05-11 17:15:43 +02:00
flip111
8921c48b32 Update ApiDocExtractor.php 2016-04-29 18:29:39 +02:00
William Durand
2d214fc934 Merge pull request #775 from DonPrus/master
Fix problem with cyrillic chars in docs
2016-04-21 17:38:04 +02:00
William Durand
9caa2816e7 Merge pull request #837 from timhovius/master
Added translation to description
2016-04-20 13:41:39 +02:00
Tim Hovius
96cfab973c Added translation to description
The labels of a field are usually always translated. But this does not happen in the documentation.
2016-04-19 21:37:22 +02:00
İsmail BASKIN
246babf209 Allow sending empty file data 2016-04-08 15:26:31 +03:00
Jelte Werkhoven
fcd4d8fa2a Allow filter descriptions to be used in conjunction with POST/PUT input descriptions 2016-03-31 10:16:50 +02:00
Florian Strübe
b510dd2c70 Change visibility of markdownParser property to protected, to improve extendability 2016-03-23 10:25:51 +01:00
William Durand
718dfc6e9c Merge pull request #827 from TannerPO/patch-1
Rename other-bundle-annotations.Rst to other-bundle-annotations.rst
2016-03-22 08:28:24 +01:00
Tanner Newton
8cb2847af4 Rename other-bundle-annotations.Rst to other-bundle-annotations.rst 2016-03-21 19:30:50 -07:00
William Durand
4ed06b40f8 Merge pull request #826 from crazyball/patch-1
Fixed link to documentation
2016-03-21 15:01:15 +01:00
Ambrosini Loïc
f6ec8ba6b1 Fixed link to documentation
Link to ApiDoc documentation is now with ReST format instead of Markdown
2016-03-21 14:47:18 +01:00
William Durand
6793b70157
Prepare 2.12.0 release 2016-03-21 12:19:12 +01:00
William Durand
4a95c81914 Merge pull request #814 from javiereguiluz/fix_813
Transformed documentation to RST format
2016-03-21 11:31:18 +01:00
William Durand
476348de54 Merge pull request #825 from nelmio/conflict
Mark symfony ^2.7.7 as conflict
2016-03-21 11:30:06 +01:00
William Durand
4986e02ec1
Mark symfony ~2.7.8 as conflict 2016-03-21 11:18:46 +01:00
ImmortaL
df10a4769c Little template fixes for sandbox 2016-03-18 21:23:22 +03:00
tcz
aef6a89329 Adding request body and curl command to sandbox 2016-03-17 16:09:47 +01:00
Javier Eguiluz
db5303fcd7 Transformed all files 2016-03-05 19:49:21 +01:00
William Durand
2a0f95eac0 Merge pull request #792 from rodrigm/master
Fix #565
2016-02-24 19:00:28 +01:00
Rodrigo Gómez
89f459dc07 Fix #565
Change PUT by PATCH

Refactor tests for #565
2016-02-24 17:57:22 +01:00
William Durand
4381f065cb Merge pull request #794 from mcfedr/patch-1
Remove incorrect usage of hasOption
2016-02-24 17:51:24 +01:00
William Durand
cec5108133 Merge pull request #781 from AlmogBaku/feature/textareaField
Add textarea type for sandbox
2016-02-24 17:50:13 +01:00
William Durand
364ef73680 Merge pull request #791 from Padam87/response-map
Use the response map in the html view
2016-02-24 17:49:33 +01:00
William Durand
65a5159ce9 Merge pull request #796 from zhukovra/master-array-requirements
Fix Array to string conversion error on array requirements
2016-02-24 17:47:14 +01:00
William Durand
dd6a5e32c3 Merge pull request #799 from debesha/master
Fixed bug with wrong form parsing when several custom inner types exist
2016-02-24 17:46:38 +01:00
William Durand
4e65cd3bc3 Merge pull request #805 from obense/master
Fix Collection Type options - Symfony 2.8 Support
2016-02-24 17:45:32 +01:00
William Durand
f6e952ffe1 Merge pull request #810 from teohhanhui/fix-678
Handle circular references in DunglasApiParser
2016-02-24 17:44:50 +01:00
William Durand
028b09e90a Merge pull request #772 from richaas/master
Added xhrFields: { withCredentials: true } to ajaxOptions
2016-02-24 17:43:14 +01:00
Teoh Han Hui
c1c711bc26 Handle circular references in DunglasApiParser
Fixes #678, reverts #800
2016-02-18 23:18:43 +08:00
William Durand
07545629aa Merge pull request #800 from dunglas/fix_678
Handle circular references in DunglasApiParser
2016-02-14 17:45:10 +01:00
Olivier Bense
18c9fb8c29 Fix Collection Type options - Symfony 2.8 Support 2016-02-09 14:32:49 +01:00
Kévin Dunglas
abb100b29b Handle circular references in DunglasApiParser. Close #678. 2016-02-04 08:34:34 +01:00
Dmitry Malyshenko
0ed53788d2 Fixed bug with wrong form parsing when several custom inner types exist 2016-02-03 10:02:33 +00:00
Zhukov Roman
f2fc6b47c0 Fix Array to string conversion error on array requirements
Fixes #564
2016-02-01 14:33:17 +03:00
Fred Cox
9b734b22bd Remove incorrect usage of hasOption
`hasOption` is confusingly named, it actually checks that the option has been created, not if the user has passed the option. So in this case its always true.
Also changed a weird usage of `in_array`.
2016-01-29 10:56:25 +02:00
Adam Prager
7919b24971 Use the response map in the html view 2016-01-26 04:36:38 +01:00
AlmogBaku
3bc98723fe Add textarea type for sandbox 2016-01-08 20:34:15 +02:00
William Durand
5d9c47bbad Merge pull request #780 from damienalexandre/fixArrayToString
Fix Array to string conversion error on array requirements
2016-01-08 13:33:10 +01:00
Damien Alexandre
306f2c47cf Fix Array to string conversion error on array requirements
Fix https://github.com/nelmio/NelmioApiDocBundle/issues/773
See https://github.com/FriendsOfSymfony/FOSRestBundle/pull/1015
2016-01-08 13:04:25 +01:00
Richard
4744de98fb Added xhrFields: { withCredentials: true } to ajaxOptions to enable
browser authentication for cross origin API requests.
2016-01-06 10:00:21 +01:00
Сомов Игорь Андреевич
8aa5e479d1 JSON UNICODE 2015-12-28 17:07:20 +03:00
William Durand
1ae2cfa9a5 Merge pull request #768 from SHyx0rmZ/fix-sf-30-dump-command-enter-scope
Check for deprecated method enterScope() in DumpCommand
2015-12-16 16:17:51 +01:00
Patrick Pokatilo
8c336487e7 Check for deprecated method enterScope() in DumpCommand 2015-12-15 17:04:17 +01:00
William Durand
93b189b16a Merge pull request #766 from odolbeau/avoid-array-to-string-conversion
Avoid "array to string conversion" error
2015-12-11 09:12:17 +01:00
Olivier Dolbeau
d3bc49c3d1 Avoid "array to string conversion" error 2015-12-10 09:23:57 +01:00
William Durand
19d4e37365 Merge pull request #765 from Ener-Getick/DEPRECATION
Fix a deprecation
2015-12-08 14:19:42 +01:00
Ener-Getick
24fa5dfbc0 Fix a deprecation 2015-12-08 13:06:38 +01:00
William Durand
49238f44aa
Prepare 2.11.0 release 2015-12-04 09:15:49 +01:00
William Durand
d5395cc0ea
Fix CS 2015-12-04 09:15:21 +01:00
William Durand
1df3ddf49f Merge pull request #752 from Ener-Getick/SF3
Add symfony 3.0 support
2015-12-04 09:09:28 +01:00
Ener-Getick
0461f5ce1d Optimize travis 2015-12-04 08:19:54 +01:00
Ener-Getick
5541412e69 Fix FOSRestBundle compatibility 2015-12-02 16:19:24 +01:00
Ener-Getick
67b7b46627 Add symfony 3.0 support 2015-12-02 16:19:12 +01:00
William Durand
97707ea5f4 Merge pull request #750 from piotrantosik/host-placeholder
Fix route host with placeholder
2015-11-25 10:13:47 +01:00
Piotr Antosik
dd8bdd2070 Fix route host with placeholder 2015-11-23 00:18:50 +01:00
William Durand
4fad200d7b Merge pull request #758 from dunglas/master
Fix tests related to DunglasApiBundle
2015-11-22 08:22:33 +01:00
Kévin Dunglas
224af02f7f Fix tests related to DunglasApiBundle 2015-11-21 18:36:13 +01:00
William Durand
eafa2ade0b Merge pull request #749 from fstr/ISSUE-747-JSP
Introduced default-value parameter to JsonSerializableParser
2015-11-10 14:39:07 +01:00
Florian Strübe
aa10f9a2da Introduced default-value parameter to JsonSerializableParser
JSP is now able to set a default value for scalar types
This makes merging the parser result with other parser results easier
and gives some additional info when creating docs

Removed PHP doc comment block
2015-11-10 14:34:53 +01:00
William Durand
a24d81f766 Merge pull request #748 from fstr/ISSUE-747
Empty string is now a valid default parameter value
2015-11-10 09:18:44 +01:00
William Durand
ac07a59ed4 Merge pull request #751 from piotrantosik/test-doc
Fix comment block in tests
2015-11-10 09:06:43 +01:00
Piotr Antosik
3dea122b98 fix comment in tests 2015-11-10 01:28:40 +01:00
Florian Strübe
e8a2f690ab Empty string is now a valid default parameter value that is successfully
merged with other parameters
Preventing access of uninitialized array key 'default'
Added functional test case for mergeParameters that covers this issue
2015-11-09 22:22:00 +01:00
William Durand
be90e8aad6 Merge pull request #740 from fstr/master
[ISSUE-739] ApiDoc parameters setting will override lower parameter definitions in the hierarchy
2015-10-28 10:30:40 +01:00
Florian Strübe
e2d2e41f7b [ISSUE-739] ApiDoc parameters setting will override lower parameter
definitions in the hierarchy
2015-10-28 10:13:24 +01:00
Jonathan Chan
8b7dfdd913
Let the js library set the proper Content-type header when sending files in sandbox - fixes #501 2015-10-27 17:29:36 +01:00
William Durand
0b7c5d6749 Merge pull request #742 from nelmio/sf-2.8
Support Symfony 2.8+
2015-10-27 17:28:11 +01:00
William Durand
9df40af264
Support Symfony 2.8+
Fix #738
2015-10-27 17:15:32 +01:00
William Durand
d25dd30453
Refactor doc 2015-10-27 10:35:58 +01:00
William Durand
d49ac45866 Add FAQ section
Add question/answer from #727
2015-10-27 10:00:23 +01:00
William Durand
09bb83e149
Restore 5.3 compat (implicit)
Implicit means: it's a best effort approach
but there is no guarantee that this bundle
will continue to work well with PHP < 5.4
2015-10-27 09:23:44 +01:00
William Durand
329e226426 Merge pull request #736 from BenjaminPaap/pass_options
Options for form were not passed through
2015-10-26 11:40:51 +01:00
Benjamin Paap
490a9081ec Options for form were not passed through 2015-10-26 11:11:28 +01:00
William Durand
f8b038faf2 Merge pull request #734 from TomasVotruba/patch-2
composer: allow min PHP 5.4
2015-10-23 15:28:30 +02:00
Tomáš Votruba
e0a001081a composer: allow min PHP 5.4
According to Travis, the min supported version is 5.4
2015-10-23 15:21:03 +02:00
William Durand
fb863954d8
Prepare 2.10.0 release 2015-10-23 11:30:55 +02:00
William Durand
7f5f8bd258 Merge pull request #694 from tonivdv/revert_fix_link
revert fix "LINK workaround for firefox"
2015-10-23 11:00:49 +02:00
William Durand
57b9aa659e
skip tests related to DunglasApiBundle 2015-10-23 10:53:47 +02:00
William Durand
beaa25b21c
Fix tests (introduce result files to make things a bit more readable) 2015-10-23 10:36:53 +02:00
William Durand
0dc1bc4469
Revert "Fix test related to DungleApiBundle. Remove some of them."
This reverts commit e66fb209b5.
2015-10-23 10:11:52 +02:00
William Durand
4b72190f4d
Speedup tests 2015-10-23 10:07:45 +02:00
William Durand
e66fb209b5
Fix test related to DungleApiBundle. Remove some of them. 2015-10-23 09:29:55 +02:00
William Durand
b37924d4b1
switch to stable version of DunglasApiBundle 2015-10-22 23:06:01 +02:00
William Durand
716f3f19de
fix test suite by disabling a broken feature 2015-10-22 22:46:25 +02:00
William Durand
96e7d1a201
remove useless file 2015-10-22 17:41:48 +02:00
William Durand
c8cea500fa
Merge pull request #719 from maisoui/patch-1
Update MarkdownExtension.php
2015-10-22 16:20:38 +02:00
William Durand
ce71bf0629
Fix CS 2015-10-22 14:42:59 +02:00
William Durand
3c47357a0e
Merge branch 'Soullivaneuh-deprecated-routes' 2015-10-22 14:42:25 +02:00
Sullivan SENECHAL
32de80f97f
Fix deprecated route options 2015-10-22 14:42:16 +02:00
William Durand
189197dc88
Merge pull request #698 from Soullivaneuh/twig-deprecate
Fix deprecated twig stuff usage
2015-10-22 14:41:15 +02:00
William Durand
f08b957670 Merge pull request #713 from dice4x4/master
Twig 2 compatibility
2015-10-22 14:37:56 +02:00
William Durand
c3092d5e36 Merge pull request #730 from OskarStark/patch-1
wrong parameter??
2015-10-22 14:37:38 +02:00
William Durand
b39b7df0d8 Merge pull request #731 from teohhanhui/fix-720-mistake
Fix mistake in #720
2015-10-22 14:37:08 +02:00
Toni Van de Voorde
d60da7a734 remove 1 empty line to force new tests on travis 2015-10-22 12:10:09 +02:00
Teoh Han Hui
5f61d5faf4 Fix mistake in #720 2015-10-19 11:22:39 +08:00
Oskar Stark
ca0fbe38d7 wrong parameter?? 2015-10-14 18:17:25 +02:00
Ilya Cherepanov
f815162ba7 Using old array syntax 2015-10-01 09:55:48 +03:00
William Durand
9af37448fa Merge pull request #720 from maisoui/patch-2
Update routing.yml
2015-09-30 23:35:45 +02:00
Jonathan
082b2fa86f Update routing.yml
Replace deprecated "pattern" and "_method" by "path" and "methods"
2015-09-22 14:16:09 +02:00
Jonathan
6dc6e7c55a Update MarkdownExtension.php
Replace deprecated 'Twig_Filter_Method' by 'Twig_SimpleFilter'
2015-09-22 14:12:25 +02:00
Ilya Cherepanov
2cbd828973 Twig 2 compatibility 2015-09-15 19:15:08 +03:00
Sullivan SENECHAL
9c0c533b4b Fix deprecated twig stuff usage 2015-08-28 10:50:28 +02:00
William Durand
874c8752e6 Merge pull request #695 from ogizanagi/fix_callable_controllers
Fix ApiDocExtractor to accept callable classes as controllers
2015-08-13 09:46:00 +02:00
maxime.steinhausser
6f4373cda4 Fix ApiDocExtractor to accept callable classes as controllers 2015-08-11 17:12:43 +02:00
Toni Van de Voorde
f57a8bf7ae revert fix "Added workaround for Firefox not sending LINK in uppercase what then" 2015-08-11 11:32:48 +02:00
William Durand
4351ca66d3 Merge pull request #681 from munkie/fix-view-cache
Fix CachingApiDocExtractor caches only first accessed view
2015-07-29 11:00:30 +02:00
William Durand
f77f52c041 Merge pull request #685 from dunglas/patch-1
Prevent BC break in DunglasApiBundle
2015-07-29 08:55:16 +02:00
Kévin Dunglas
840b96f8f3 Prevent BC break in DunglasApiBundle 2015-07-29 00:56:26 +02:00
Mikhail Shamin
be682f7d18 Change getViewCache method visibility to private 2015-07-22 11:20:20 +07:00
Mikhail Shamin
02007e957d Fix extractor view data caching 2015-07-21 17:38:44 +07:00
William Durand
0699c45dcc Merge pull request #656 from soyuka/patch-1
Prevent error "Undefined index" when no subtype provided
2015-07-10 11:15:36 +02:00
William Durand
787f69561b Merge pull request #665 from mcfedr/json-serializable
Add support for JsonSerializable classes
2015-07-04 13:52:22 +02:00
Fred Cox
406a4e1b5b Add support for using name in the input and output options for JsonSerializable and validation parsers 2015-07-01 14:05:10 +03:00
Fred Cox
37c6465700 Add a parser for jsonSerializable classes 2015-07-01 14:04:38 +03:00
William Durand
a343bb7c45 Merge pull request #663 from phansys/doc_typo
[minor] Small doc fix in index.md
2015-06-30 00:05:07 +02:00
Javier Spagnoletti
7eccee0a36 [minor] Small doc fix in index.md
| Q             | A
| ------------- | ---
| Doc fix?      | yess
| Fixed tickets |
| License       | MIT
| Doc PR        |

Small fix in ```index.md```.
2015-06-29 13:30:59 -03:00
Antoine Bluchet
20f4cc673f Prevent error "Undefined index" when no subtype provided 2015-06-22 15:47:46 +02:00
William Durand
7c8b010215 Merge pull request #654 from mRoca/master
DunglasApiBundle : Add doc sections
2015-06-18 15:30:13 +02:00
mRoca
65345912a9 Add DunglasApiBundle doc sections 2015-06-18 12:23:07 +02:00
William Durand
a05ed38501 Merge pull request #653 from staurand/master
Added pipe to escaped symbols of jQuery selector
2015-06-17 17:46:56 +02:00
staurand
a967fce6c7 Added pipe to escaped symbols of jQuery selector
Pipe symbol needs to be escaped in jQuery selector.
This symbol is added when multiple methods are allowed for a route.
e.g /api/doc#get|post--...
2015-06-17 11:48:31 +02:00
William Durand
c7909c576e Merge pull request #641 from Soullivaneuh/configure-options
Fix deprecated FormType::setDefaultOptions usage
2015-06-11 13:51:25 +02:00
William Durand
b07db95eaf Merge pull request #648 from Soullivaneuh/patch-1
Bump Symfony requirements to 2.3
2015-06-11 13:50:23 +02:00
Sullivan SENECHAL
b7dbccbc96 Bump Symfony requirements to 2.3 2015-06-10 12:19:47 +02:00
William Durand
a679eb8089 Merge pull request #645 from ogizanagi/dunglas/filters_collection_only
Only display filters on collections GET services
2015-06-08 14:24:59 +02:00
William Durand
b672df0aca Merge pull request #637 from magnetik/fosrestbundle-array
Handle "array" parameter in FOSRestBundle QueryParam or RequestParam
2015-06-08 14:23:58 +02:00
maxime.steinhausser
12372cca00 Only display filters on collections GET services 2015-06-08 11:42:36 +02:00
Baptiste Lafontaine
213dbdfd1c Handle "array" parameter in FOSRestBundle QueryParam or RequestParam 2015-06-08 08:43:38 +02:00
William Durand
a68dcfe829 Merge pull request #643 from ogizanagi/dunglas/fix_filters_doc
Fix DunglasApiBundle filters documentation support.
2015-06-08 08:37:13 +02:00
William Durand
6be99b53ca Merge pull request #644 from Soullivaneuh/config-improve
Improve true/false/null default values on configuration
2015-06-07 18:02:34 +02:00
Sullivan SENECHAL
839e9ed7e0 Improve true/false/null default values on configuration 2015-06-07 11:34:26 +02:00
maxime.steinhausser
a080692961 Fix DunglasApiBundle filters documentation support. 2015-06-05 16:27:29 +02:00
Sullivan SENECHAL
f5b45607c6 Fix deprecated FormType::setDefaultOptions usage 2015-06-05 09:34:11 +02:00
William Durand
f305905928 Merge pull request #640 from Soullivaneuh/travis-update
Include Symfony 2.7 stable on Travis and improve configuration
2015-06-05 09:04:17 +02:00
Sullivan SENECHAL
3072cc2917 Include Symfony 2.7 stable on Travis and improve configuration 2015-06-04 12:20:14 +02:00
William Durand
36c9f8b7fc Merge pull request #638 from piotrantosik/patch-1
Improve doc format
2015-06-03 20:55:46 +02:00
Piotr Antosik
38a3137465 Improve doc format 2015-06-02 18:15:49 +02:00
William Durand
5de5d530dd Merge pull request #630 from munkie/dump-command-views
Add view option to api:doc:dump command
2015-05-26 17:36:26 +02:00
William Durand
4bee3afe9a Merge pull request #629 from ifdattic/patch-1
Update index.md
2015-05-26 16:21:03 +02:00
Mikhail Shamin
1a92a112bc Added view option to api:doc:dump command 2015-05-26 18:09:29 +07:00
Andrew Marcinkevičius
2d1c9693a1 Update index.md 2015-05-26 13:19:18 +03:00
William Durand
e505139f98 Merge pull request #627 from csuarez/css-fix
Styles for lists inside .content
2015-05-22 11:49:13 +02:00
César Suárez
b801ddbf48 Styles for lists inside .content 2015-05-22 11:33:26 +02:00
William Durand
de31760fd8
Prepare 2.9.0 release 2015-05-16 19:16:14 +02:00
William Durand
cc93ddb306 Merge pull request #550 from thenetexperts/master
better readability when using text inside <pre> tag in motd html
2015-05-16 13:19:05 +02:00
William Durand
647131ab8e Merge pull request #586 from PedroTroller/fix/subform-options
Pass children options to subform
2015-05-16 13:07:10 +02:00
William Durand
a0c0398ca9 Merge pull request #540 from rodrigorigotti/master
Showing 'Documentation' tab makes no sense if the sandbox is disabled.
2015-05-16 12:59:29 +02:00
William Durand
4398a8861c
Test against PHP 5.6 2015-05-16 12:54:31 +02:00
William Durand
14aba75938
remove hhvm-nightly in travis-ci config 2015-05-16 12:53:16 +02:00
William Durand
1e33a8821c
Refactor tests 2015-05-16 12:48:11 +02:00
William Durand
e04981356b
Fix doc: Default => default (views) 2015-05-16 12:22:11 +02:00
William Durand
e46bd73a32
Fix routing definition 2015-05-16 12:21:32 +02:00
William Durand
c71fa155d5
Introduce the concept of 'views'
Rewrite #619
2015-05-16 12:17:59 +02:00
William Durand
94ba751848
fix CS 2015-05-15 17:42:03 +02:00
William Durand
f601dc17fb
Merge branch 'Nyholm-issue-404' 2015-05-15 17:41:37 +02:00
Joshua Thijssen
6052643b9f
Initial setup on a multi-api documentation 2015-05-15 17:41:22 +02:00
William Durand
b3e7c1498d Merge pull request #622 from dunglas/fix_filters
Fix DunglasApi filters support. Fix tests.
2015-05-15 02:28:38 +02:00
Kévin Dunglas
9c465e37ca Fix DunglasApi filters supports. Fix tests. 2015-05-14 23:23:41 +02:00
William Durand
1d7b45e1e7 Merge pull request #543 from mojoLyon/fix/issue-398
Fix/issue 398
2015-05-13 22:57:45 +02:00
Stéphane Reuille
dc20b249e7 fix memory limit issue wirh entity list
Allow to disable mapping of entity list to avoid memory limit error on long list
2015-05-13 14:51:15 +02:00
William Durand
a8f047c78a Merge pull request #617 from Soullivaneuh/deprecated-form-options
Fix deprecated forms setDefaultOptions method
2015-05-13 11:06:52 +02:00
William Durand
54be9c3bf1 Merge pull request #615 from matks/twig-doc
Add documentation statement about twig dependency
2015-05-13 11:06:35 +02:00
Sullivan SENECHAL
c1d7edd162 Fix deprecated forms setDefaultOptions method 2015-05-01 02:16:35 +02:00
Mathieu Ferment
a1ea71e1c3 Add doc statement about twig dependency 2015-04-30 17:40:12 +02:00
William Durand
f7611613af Merge pull request #612 from dunglas/patch-2
Fix broken image in README.md
2015-04-30 09:56:37 +02:00
Kévin Dunglas
a53b061baa Fix broken image in README.md 2015-04-30 09:05:17 +02:00
William Durand
768f45641e Merge pull request #610 from dunglas/enhanced_dngls_api
Enhanced DunglasApiBundle support
2015-04-29 22:42:26 +02:00
William Durand
7336771f87 Merge pull request #611 from dunglas/patch-1
Use new Travis infrastructure. Test with PHP7.
2015-04-29 22:41:19 +02:00
Kévin Dunglas
79c1c4e4f4 Enhanced DunglasApiBundle support 2015-04-29 09:11:54 +02:00
Kévin Dunglas
8ee26388c0 Use new Travis infrastructure. Test with PHP7. 2015-04-28 23:55:57 +02:00
William Durand
ad70ff96fa Merge pull request #602 from snamor/patch-1
Add the commas to the prettified JSON
2015-04-14 17:21:49 +02:00
Pol
226e38851f Optimization on the regex 2015-04-14 17:10:09 +02:00
Pol
186d673517 Add the commas to the prettified JSON
I recently came across that when copying the prettified response there were no comma separating the elements. This solves this problem.
2015-04-14 17:01:01 +02:00
William Durand
8b6186500a Merge pull request #599 from dunglas/fix_dunglas
Fix DunglasJsonLdApiBundle support
2015-04-08 09:07:32 +02:00
Kévin Dunglas
c47265bb71 Fix DunglasJsonLdApiBundle support 2015-04-07 22:21:36 +02:00
William Durand
ef917f257d Merge pull request #597 from leberknecht/feature-collapsible-json-response
[Feature] collapsible sections in json-response
2015-03-31 17:13:44 +02:00
dtonder
01f4efc4e0 -fixed binding of on-click handler for json-toggler, - fixed css-class naming 2015-03-29 15:06:58 +02:00
dtonder
d73531aeb5 - added javascript for collapsing sections in json-response, - fixed usage of private service-alias (that will be removed by RemovePrivateAliases-compiler pass) 2015-03-29 14:37:06 +02:00
William Durand
6a9ca36bb4 Merge pull request #560 from wodka/patch-1
fix sandbox with host
2015-03-25 23:17:02 +01:00
William Durand
d516de98f9 Merge pull request #593 from AlmogBaku/patch-1
fixes: not attaching `api_key` to query if empty
2015-03-25 23:16:28 +01:00
Almog Baku
752b953a07 fixes: not attaching api_key to query if empty 2015-03-25 14:53:31 +02:00
William Durand
26fe302a5a Merge pull request #585 from dunglas/dunglasjsonldapibundle
DunglasJsonLdApiBundle support
2015-03-20 10:51:03 +01:00
Kévin Dunglas
adc03ba26d DunglasJsonLdApiBundle support 2015-03-20 09:53:48 +01:00
pedro
ff38cd4568 Pass children options to subform 2015-03-16 11:12:26 +01:00
William Durand
d8c05dbf57 Merge pull request #584 from PedroTroller/feature/form-options
Add form options to ApiDoc input
2015-03-13 17:22:24 +01:00
pedro
d121e28d69 Add form options to ApiDoc input
Update index.md

Remove unused variable. $arguments is now named $options
2015-03-13 16:15:29 +01:00
William Durand
f23351b132
update branch-alias (composer) 2015-03-06 11:47:58 +01:00
William Durand
0a1cef77ad
cs 2015-03-06 11:19:08 +01:00
William Durand
e23a7d7d66 Merge pull request #573 from jcrombez/patch-1
[doc] missing "sandbox:" in the authentication yaml examples
2015-02-06 16:04:01 +01:00
Jérémy CROMBEZ
6990a11e3a [doc] missing "sandbox:" in the authentication yaml examples 2015-02-06 15:54:35 +01:00
William Durand
d641bbf32f Merge pull request #562 from lmammino/patch-1
Fixed type annotations
2015-01-01 18:25:04 +01:00
Luciano Mammino
e0ff981653 Fixed type annotations
(avoid triggering warning on IDEs like PhpStorm
2014-12-31 17:08:41 +01:00
Michael Schramm
10d9172d26 fix sandbox with host
suggested change in https://github.com/nelmio/NelmioApiDocBundle/pull/353 is not working
2014-12-30 00:00:27 +01:00
William Durand
a84c53bf6a Merge pull request #555 from damienalexandre/fixLinkParser
Fix the PhpDoc Handler for `@link` annotation and add tests
2014-12-12 16:27:51 +01:00
Damien Alexandre
a939fef59b Fix the PhpDoc Handler for @link annotation and add tests 2014-12-12 16:13:55 +01:00
William Durand
8ff30b7dec Merge pull request #554 from greg0ire/documentation
proofread the index
2014-12-11 14:13:04 +01:00
Grégoire Paris
95b99a1129 proofread the index 2014-12-11 12:11:51 +01:00
thenetexperts
c6b57a22d1 better readability 2014-12-08 15:04:40 +01:00
Rodrigo Rigotti
fa4b8f3805 Showing 'Documentation' tab makes no sense if the sandbox is disabled. 2014-11-05 10:32:48 -02:00
William Durand
847b1fe757 Merge pull request #536 from devster/master
Display select to choose http method in sandbox
2014-10-27 00:51:54 +01:00
Jeremy Perret
e7d3c803bb Display select to choose http method in sandbox 2014-10-26 12:28:50 +01:00
Jordi Boggiano
4beb08e587 Update install instructions 2014-10-21 21:24:58 +01:00
Jordi Boggiano
cb6df68d8a Remove outdated note 2014-10-21 21:24:09 +01:00
William Durand
888705cd23 Merge pull request #512 from bezhermoso/form_errors
FOSRest integration: Form errors format
2014-10-12 15:38:01 +02:00
Bez Hermoso
859421df9a Form errors parser. Mirrored actual form-errors response by FOSRest. Made sure that FieldErrors is not duplicated. 2014-10-10 11:59:19 -07:00
William Durand
6d50c200ba Merge pull request #498 from jonmchan/honorBodyFormat
Honor body format before uploading file type parameters
2014-10-08 11:36:12 +02:00
William Durand
c69ab200d0 Merge pull request #524 from lucasvanlierop/fix-no-required-parameters-for-put-requests
Fix no required parameters for PUT requests
2014-10-08 11:34:06 +02:00
William Durand
5950acd73c Merge pull request #490 from ricardclau/store_api_key
Add support for LocalStorage
2014-10-08 11:32:56 +02:00
William Durand
ff5c201880 Merge pull request #519 from mjanser/fix-swagger-formatter
Fix parsing of filters, default values and base path in SwaggerFormatter
2014-10-06 11:47:14 +02:00
Martin Janser
b8cc4d9264 Fix parsing of filters, default values, descriptions and base path in SwaggerFormatter 2014-10-06 10:50:14 +02:00
lucasvanlierop
b4a6825db7 Fixed tests by adding separate form type 2014-09-30 13:54:30 +02:00
William Durand
3889e7ff24 Merge pull request #523 from jeskew/master
Update parser to convert empty array defaults to null
2014-09-29 17:12:56 +02:00
lucasvanlierop
f625d9671c Fixed disabling required for HTTP PUT requests 2014-09-29 16:49:25 +02:00
lucasvanlierop
eaaa54bf11 Fixed checking HTTP method type 2014-09-29 16:17:36 +02:00
Jonathan Eskew
37ae52ba0a Update parser to convert empty array defaults to null
Don't let empty arrays get passed through as defaults.
2014-09-26 14:19:55 -04:00
William Durand
6bcd5e8d81 Merge pull request #518 from AveVlad/patch-1
Add highlight index.md
2014-09-22 10:19:00 +02:00
Vlad
59ea615b7b Update index.md 2014-09-21 05:04:16 +04:00
William Durand
3f3580f9c3 Merge pull request #514 from stof/patch-2
Remove EOLed Symfony versions from Travis
2014-09-11 10:34:36 +02:00
Christophe Coevoet
8861fb9d95 Remove EOLed Symfony versions from Travis
Currently, the testsuite relies on SensioFrameworkExtraBundle 3.x which requires Symfony 2.3+. Given that 2.1 and 2.2 are EOLed, removing them from Travis is simpler than updating the testsuite to support them.
2014-09-10 14:29:24 +02:00
William Durand
4b163e1b80 Merge pull request #469 from bezhermoso/collections
Support collections on output (including named collections)
2014-09-06 11:33:45 +02:00
William Durand
f503b73dc4 Merge pull request #507 from jaugustin/fix-side-effect-list-expand-btn
fix side effect introduce with list/expand buttons
2014-09-06 11:32:20 +02:00
Jérémie Augustin
01fb243751 fix side effect introduce with list/expand buttons 2014-09-05 11:40:06 +02:00
Bez Hermoso
0d17c10b70 Collection handling fix. 2014-09-04 11:29:31 -07:00
Bez Hermoso
5fa69a0504 Tests for aliased collections; Swagger formatting for wrapped collections. 2014-09-04 11:19:54 -07:00
Bez Hermoso
c56aceaef5 Updated regex pattern to base on http://fr2.php.net/manual/en/language.oop5.basic.php 2014-09-04 10:47:58 -07:00
Bez Hermoso
4b7dbcd478 Improved directive parsing, and separate test class for parsing directives. 2014-09-04 10:47:57 -07:00
Bez Hermoso
928a23e2c8 Updated regex pattern matching and added tests for parsing array<..> directives. 2014-09-04 10:47:57 -07:00
Bez Hermoso
f5c1b06807 Support for collections. 2014-09-04 10:47:57 -07:00
Bez Hermoso
06cfe9d48b Allow parsers to remove/replace root parameters. 2014-09-04 10:47:57 -07:00
William Durand
8fe99a9c45 Merge pull request #504 from EmmanuelVella/patch-2
Replace incorrect jQuery.size() method
2014-09-04 10:12:53 +02:00
Emmanuel Vella
2d87ad0fe1 Replace incorrect jQuery.size() method 2014-09-03 15:49:16 +02:00
William Durand
ca0dd69752 Merge pull request #497 from pyrech/security-annotation-support
Added support for Security annotation
2014-09-02 08:55:54 +02:00
Ricard Clau
08153a3071 support for localstorage 2014-08-28 23:02:05 +01:00
Jonathan Chan
493e6066b3 Honor body format before uploading file type parameters 2014-08-28 01:48:10 -04:00
Loick Piera
16b104edec Added support for Security annotation 2014-08-28 00:12:34 +02:00
William Durand
72a1418b7d Merge pull request #456 from bezhermoso/swagger-doc
Updated the docs. Added sections on caching and Swagger support.
2014-08-27 08:59:42 +02:00
Bez Hermoso
4be579f7a2 Same-origin policy violation notes 2014-08-26 17:27:41 -07:00
Bez Hermoso
9b544ef535 Updated the docs. Added sections on caching and Swagger support. 2014-08-26 16:26:39 -07:00
William Durand
d6777b881b Merge pull request #493 from EmmanuelVella/query-parameters
Add query parameters in sandbox request URL
2014-08-26 15:39:14 +02:00
Emmanuel Vella
f9d713f9b4 Add query parameters in sandbox request URL 2014-08-26 12:44:11 +02:00
William Durand
0d45e7f186 Merge pull request #467 from bezhermoso/model_naming_strategy
Swagger: Alternate model naming strategy.
2014-08-26 09:05:18 +02:00
Bez Hermoso
480fcc5ecd Added clear() 2014-08-25 10:14:04 -07:00
Bez Hermoso
a5a13501e2 Alternate model naming strategy. 2014-08-25 10:14:04 -07:00
William Durand
fad6f576ee Merge pull request #455 from bezhermoso/swagger-dump-command
api:swagger:dump update
2014-08-25 12:00:30 +02:00
William DURAND
b2a996e047 Merge pull request #440 from thenetexperts/tags-with-colors 2014-08-25 11:28:31 +02:00
thenetexperts
e1c7e8a5bd adding optional color codes for tags annotation 2014-08-25 11:27:49 +02:00
William Durand
f8793d2439 Merge pull request #478 from spolischook/patch-1
Disallow PhpDocHandler rewrite already existing/parsed "requirements" doc point
2014-08-25 10:46:49 +02:00
William Durand
87d269fedb Merge pull request #491 from jaugustin/feat-section-hide-show
[DX] Sections enhancement #489
2014-08-25 10:45:25 +02:00
jaugustin
ae2c62fad1 add show/hide button and list/expand operations buttons on sections
add a new parameter default_sections_opened: false (default)
To start with sections opened or closed
2014-08-23 17:17:46 +02:00
William Durand
6952c4b32c Fix #486 2014-08-21 22:44:19 +02:00
William Durand
ce54848c31 Merge pull request #486 from JeroenDeDauw/patch-1
Only show request format dropdown when there are multiple formats
2014-08-21 10:40:20 +02:00
Jeroen De Dauw
83fc7d08bc Only show request format dropdown when there are multiple formats 2014-08-21 04:43:44 +02:00
William Durand
a1ec98376b Merge pull request #485 from bezhermoso/cache-param-handling-patch
Caching: Pass in parameters for late resolving
2014-08-20 22:09:17 +02:00
Bez Hermoso
313f0af195 Pass in parameter for late resolving. 2014-08-20 10:26:43 -07:00
William Durand
068d2f1a32 Merge pull request #484 from bezhermoso/fix_items_for_collection_parameters
[Fix] 'items' parameter for collections in parameters.
2014-08-19 22:08:24 +02:00
Bez Hermoso
d99c209f7d Fix: 'items' parameter for collections in parameters. 2014-08-19 12:32:06 -07:00
William Durand
fd0d78e0a8 Merge pull request #472 from sroze/recursive-groups
Added nested JMS groups exclusion
2014-08-18 17:28:42 +02:00
William Durand
57ef437d25 Merge pull request #482 from mathielen/patch-1
Impossible to access an attribute (\"custom_endpoint\") on a NULL variab...
2014-08-18 17:28:31 +02:00
Markus Thielen
b6f7179b59 Impossible to access an attribute (\"custom_endpoint\") on a NULL variable
My setup obviously does not have a authentication property set. This change fixes the error.
2014-08-18 11:55:01 +02:00
Samuel ROZE
3e3ef87b79 Added nested JMS groups exclusion 2014-08-18 11:32:36 +02:00
William Durand
d32381d18d Merge pull request #476 from bezhermoso/swagger-collection-parameter
[Fix] Missing handling for DataTypes::COLLECTION in parameters (input)
2014-08-16 14:29:11 +02:00
Sergey Polischook
3e6a47818e Update PhpDocHandler.php
https://github.com/nelmio/NelmioApiDocBundle/issues/477
2014-08-16 01:59:39 +03:00
Bez Hermoso
b289a6e846 Missing handling for DataTypes::COLLECTION in parameters (input) 2014-08-15 09:47:35 -07:00
William Durand
bc1d3f6f7f Merge pull request #468 from bezhermoso/param_type_options
Swagger: Ability to specify "paramType" for input
2014-08-14 21:44:53 +02:00
Bez Hermoso
18004189b3 Ability to specify param-type of input class. 2014-08-14 12:41:27 -07:00
William Durand
40c76339b6 Merge pull request #471 from yoshz/bugfix/undefined-endpoint
Fixed endpoint is undefined in sandbox when custom_endpoint is disabled
2014-08-14 21:31:58 +02:00
Yosh de Vos
d53028098f Fixed endpoint is undefined in sandbox when custom_endpoint is disabled 2014-08-08 17:55:36 +02:00
William Durand
38f0bec705 Merge pull request #466 from djlemmings/master
Fixed the wrong endpoint value check when no endpoint is set in config.
2014-08-08 17:46:19 +02:00
Bruce HELLER
5d43f463cf Fixed the wrong endpoint value check when no endpoint is set in config. 2014-08-07 16:45:46 +02:00
William Durand
eb08b7af27 Merge pull request #461 from bezhermoso/validation_parser_patch
Default to DataTypes::STRING when no constraints are found
2014-08-06 22:41:22 +02:00
William Durand
80d1c266f6 Merge pull request #462 from bezhermoso/swagger-auth-definitions
Added 'authorizations` definitions when sandbox authentication is configured
2014-08-06 03:45:37 +02:00
Bez Hermoso
b7f5fb58d4 Added definition when is provided. 2014-08-04 10:58:41 -07:00
Bez Hermoso
bcaaf28d61 Default to DataTypes::STRING 2014-08-04 10:02:18 -07:00
William Durand
e3103c073a Merge pull request #460 from lucasdealmeida/master
fix enpoint bug
2014-08-04 15:52:55 +02:00
Lucas Almeida
b4ca14618a fix enpoint bug 2014-08-04 09:58:02 -03:00
Bez Hermoso
0e01a00aaf Behavior and usage updates. 2014-08-01 12:56:46 -07:00
William Durand
4306a1a4a3 Merge pull request #457 from deegital/patch-1
No header deletion for forms with files
2014-08-01 15:47:52 +02:00
Jan Behrens
df7e97a941 No header deletion for forms with files
Fixes #453
2014-08-01 14:38:00 +02:00
Bez Hermoso
ea41c41c7c Swagger command updates. 2014-07-31 12:12:23 -07:00
William Durand
7a364db571 Merge pull request #444 from bezhermoso/caching
Added caching layer
2014-07-31 09:44:46 +02:00
Bez Hermoso
cc0d445601 Added caching layer with the controllers and routing files as resources. 2014-07-31 00:43:59 -07:00
William Durand
310a1f8cfd Merge pull request #438 from nikita2206/patch-1
Add abstract="true" to abstract_formatter
2014-07-30 11:25:40 +02:00
William Durand
75eb7ca356 Merge pull request #418 from bezhermoso/swagger
Swagger support
2014-07-30 11:23:46 +02:00
William DURAND
fb35704d5f Add poser badges 2014-07-30 11:17:22 +02:00
William DURAND
3fdb2d4a81 Prepare 2.7.0 release 2014-07-30 11:11:08 +02:00
William DURAND
9ad7e68703 update config reference 2014-07-30 11:10:39 +02:00
William DURAND
6c7c53e78d Fix CS & file permissions 2014-07-30 10:50:23 +02:00
Bez Hermoso
07c6557fc5 Test fixes. 2014-07-29 10:25:06 -07:00
Bez Hermoso
a8221d4515 Post-parser support for response map models. 2014-07-29 10:25:06 -07:00
Bez Hermoso
9824a6ba3c Added default value handling. 2014-07-29 10:25:06 -07:00
Bez Hermoso
abaeb374e8 Added 'type' to API item if applicable. 2014-07-29 10:25:06 -07:00
Bez Hermoso
9d3c0a8c29 Swagger support:
Unified data types [actualType and subType]
Updated tests.
JMS parsing fixes; updated {Validator,FormType}Parser, FOSRestHandler, and AbstractFormatter, and updated DataTypes enum.
Modified dataType checking.
Updated tests.
Updated DataTypes enum.
Quick fix and added doc comments.
CS fixes.
Refactored FormTypeParser to produce nested parameters. Updated tests accordingly.
Logical and CS fixes.
Sub-forms and more tests.
Logical and CS fixes.
Swagger support: created formatter.
Configuration and resourcePath logic update.
ApiDoc annotation update. Updated formatter and added tests.
Parameter formatting.
Added tests for SwaggerFormatter.
Added  option in annotation, and the corresponding logic for parsing the supplied values and processing them in the formatter.
Routing update.
Updated tests.
Removed unused dependency and updated doc comments.
Renamed 'responseModels' to 'responseMap'
Update the resource filtering and formatting of response messages.
Updated check for 200 response model.
Ignore data_class and always use form-type to avoid conflicts.
Fix: add 'type' even if '' is specified.
Refactored responseMap; added parsedResponseMap. Added tests and updated some.
Fix: add 'type' even if '' is specified.
Initial commit of command.
Finished logic for dumping files.
Updated doc comment; added license and added more meaningful class comment.
Array of models support.
2014-07-29 10:25:06 -07:00
Bez Hermoso
bb723bdb40 Added new tests for Swagger doc controllers. Also some CS fixes. 2014-07-29 10:25:06 -07:00
Bez Hermoso
ee0496af65 Update swagger-support.md 2014-07-29 10:25:05 -07:00
Bez Hermoso
af5cb3dd76 Removed ambiguity 2014-07-29 10:25:05 -07:00
Bez Hermoso
04818b00e5 Added configuration reference. 2014-07-29 10:25:05 -07:00
Bez Hermoso
cfe6cbc134 Create swagger-support.md 2014-07-29 10:25:05 -07:00
Bez Hermoso
6f85aed33c Swagger support:
Unified data types [actualType and subType]
Updated tests.
JMS parsing fixes; updated {Validator,FormType}Parser, FOSRestHandler, and AbstractFormatter, and updated DataTypes enum.
Modified dataType checking.
Updated tests.
Updated DataTypes enum.
Quick fix and added doc comments.
CS fixes.
Refactored FormTypeParser to produce nested parameters. Updated tests accordingly.
Logical and CS fixes.
Sub-forms and more tests.
Logical and CS fixes.
Swagger support: created formatter.
Configuration and resourcePath logic update.
ApiDoc annotation update. Updated formatter and added tests.
Parameter formatting.
Added tests for SwaggerFormatter.
Added  option in annotation, and the corresponding logic for parsing the supplied values and processing them in the formatter.
Routing update.
Updated tests.
Removed unused dependency and updated doc comments.
Renamed 'responseModels' to 'responseMap'
Update the resource filtering and formatting of response messages.
Updated check for 200 response model.
Ignore data_class and always use form-type to avoid conflicts.
Fix: add 'type' even if '' is specified.
Refactored responseMap; added parsedResponseMap. Added tests and updated some.
Fix: add 'type' even if '' is specified.
Initial commit of command.
Finished logic for dumping files.
Updated doc comment; added license and added more meaningful class comment.

Array of models support.
2014-07-29 10:25:05 -07:00
William Durand
6b18d88517 Merge pull request #446 from EmmanuelVella/bearer
Add Bearer authentication
2014-07-29 16:18:39 +02:00
William Durand
79e84c9867 Merge pull request #439 from thenetexperts/tags-fix
fixed css for tags output
2014-07-29 16:06:14 +02:00
William Durand
c30197f63f Merge pull request #445 from ogizanagi/patch-1
Fix embedded collection of custom FormType Error
2014-07-29 15:59:33 +02:00
William Durand
dcf8cdfe72 Merge pull request #447 from EmmanuelVella/toggler
Fix toggler click event
2014-07-29 15:56:48 +02:00
William Durand
f6df97b9d7 Merge pull request #449 from dbu/patch-1
Update branch-alias
2014-07-29 15:54:22 +02:00
David Buchmann
67a1b380ae Update composer.json 2014-07-28 11:08:21 +02:00
Emmanuel Vella
6bc971c50a Refactored authentication config 2014-07-25 13:40:26 +02:00
Emmanuel Vella
abaf38adc3 Fix toggler click event 2014-07-23 13:21:33 +02:00
ogizanagi
4939d116e0 Fix embedded collection of custom FormType Error
Fix an error when trying to use embedded form collections.

Referenced issue: #442
2014-07-21 22:27:09 +02:00
thenetexperts
492bb230a8 fixed css for tags output 2014-07-18 11:12:58 +02:00
Nikita Nefedov
afb8536b41 Add abstract="true" to abstract_formatter
Add abstract="true" to abstract_formatter in container definition
2014-07-16 12:20:49 +04:00
William Durand
c03d35bee4 Merge pull request #434 from ahilles107/patch-2
Don't parse custom properties as classes.
2014-07-14 21:24:21 +02:00
Paweł Mikołajczuk
55f26508ab Don't parse custom properties as classes.
Sometimes instead real class name we can use custom handler name. Then this class can't be initialized and we will get this exception ```ReflectionException: Class custom_handler_name does not exist```
2014-07-10 11:46:30 +02:00
William Durand
b9b453c857 Merge pull request #432 from Prezent/jms-inline
Parse JSM\Inline, fixes #372
2014-07-09 10:29:17 +02:00
Sander Marechal
b66e5c4449 Parse JSM\Inline, fixes #372 2014-07-08 13:20:13 +02:00
William Durand
ae877d74a0 Merge pull request #390 from frastel/params-fix
Added fix for doubled parameters
2014-07-07 11:38:47 +02:00
Frank Stelzer
54e5fae3de added fix for doubled parameters 2014-07-07 09:08:21 +00:00
William Durand
94243f0a3e Merge pull request #428 from gnat42/patch-1
If a description is not provided use form label
2014-07-04 09:38:41 +02:00
gnat42
48e7bd2616 If a description is not provided use form label
It would be nice if there was no description the form label was used instead. In the future I think it would be even better to have the label as an header, and the description as 'additional' instructions.
2014-07-03 13:34:07 -06:00
William Durand
54819590f2 Merge pull request #425 from dmishh/patch-1
Added more clearance to docs
2014-07-02 14:37:52 +02:00
Dmitriy
1c05399261 Added more clearance to docs
Specifying form inputs' names prefix in the Form Types Features documentation section
2014-07-01 12:29:58 +03:00
William DURAND
c92789e00f remove php 5.3 from travis-ci 2014-06-27 14:43:22 +02:00
William DURAND
0151624773 update config reference 2014-06-27 14:13:36 +02:00
William DURAND
94ec568237 Merge pull request #387 from giosh94mhz/form_type_parser_should_use_type_constructor 2014-06-27 10:59:06 +02:00
Giorgio Premi
e2c2d00075 FormTypeParser: FormType constructor should be called when possible 2014-06-27 10:58:53 +02:00
Simon Schick
4fa50ebe53 Added workaround for Firefox not sending LINK in uppercase what then is denied by nginx.
More info: http://stackoverflow.com/questions/9645037/nginx-rejects-custom-http-methods-if-not-all-upper-case
2014-06-27 10:32:32 +02:00
William Durand
c6741d5710 Merge pull request #369 from emmanuelballery/fixes_for_ff36
Fixes HTML structures for FF36 (and maybe other old browsers)
2014-06-27 10:24:27 +02:00
William DURAND
0030ce6825 Merge pull request #358 from pborreli/typos 2014-06-27 10:22:56 +02:00
Pascal Borreli
dbc3fcbb73 Fixed typos 2014-06-27 10:22:36 +02:00
Bez Hermoso
882f658599 Added 'default' parameters in {JmsMetadata,Validator}Parser, and FOSRestHandler. 2014-06-27 10:19:28 +02:00
William Durand
0d1bde9f8a Merge pull request #352 from sroze/output-post-parser
Add PostParserInterface to JmsMetadataParser to get ValidatorParser found children parsed
2014-06-27 10:14:51 +02:00
William DURAND
a817081ab2 Add missing array key checks
Fixes #423
2014-06-27 10:07:03 +02:00
William Durand
03f6142a84 Merge pull request #422 from nelmio/bezhermoso-expand_form_type_parser
Unified data types [actualType and subType]
2014-06-27 09:54:14 +02:00
Bez Hermoso
3a31c93c94 Unified data types [actualType and subType]
Updated tests.

JMS parsing fixes; updated {Validator,FormType}Parser, FOSRestHandler, and AbstractFormatter, and updated DataTypes enum.

Modified dataType checking.

Updated tests.

Updated DataTypes enum.

Quick fix and added doc comments.

CS fixes.

Refactored FormTypeParser to produce nested parameters. Updated tests accordingly.

Logical and CS fixes.

Sub-forms and more tests.

Ignore data_class and always use form-type to avoid conflicts.

Quick fix.
2014-06-27 09:35:18 +02:00
William Durand
a2a4782af5 Merge pull request #408 from jonmchan/ParamDefaults
Add default values support for form types
2014-06-26 17:54:22 +02:00
Jonathan Chan
d4e12d66f2 Add default values support for form types
Adding displaying default value in Markdown

updating tests to work with new default value
2014-06-26 11:20:49 -04:00
William Durand
df7387aec9 Merge pull request #419 from jonmchan/addParameterBugFix
Fixed small bug introduced when adding 'new parameter'
2014-06-26 00:04:36 +02:00
Jonathan Chan
ba5e2d1454 Fixed small bug introduced when adding 'new parameter' 2014-06-25 13:29:59 -04:00
William Durand
ea2201762d Merge pull request #345 from AlexeyKupershtokh/web-profiler
Symfony web profiler integration
2014-06-25 10:56:43 +02:00
William DURAND
39dd41e285 Merge pull request #388 from yoshz/feature/body_format
Added configuration to disable body formats
2014-06-25 10:30:56 +02:00
William DURAND
8ddade0e30 Merge pull request #407 from jonmchan/ParameterTypeSupport 2014-06-25 10:25:59 +02:00
Jonathan Chan
1cd77e2f14 added support to properly handle file upload POST 2014-06-25 10:25:45 +02:00
Jonathan Chan
210596eae9 adding support for different parameter types 2014-06-25 10:25:45 +02:00
Jonathan Chan
b124824a8d added file type to FormType Parser 2014-06-25 10:25:45 +02:00
William DURAND
96e5d15f1b Merge pull request #412 from grEvenX/nullable_request_support 2014-06-25 10:20:00 +02:00
Even André Fiskvik
b4e874e2dc Add support for nullable option in RequestParam 2014-06-25 10:19:30 +02:00
William DURAND
607d031051 Merge pull request #413 from bezhermoso/unified_data_types 2014-06-25 09:06:32 +02:00
Bez Hermoso
14d1021c8b Unified data types [actualType and subType]
This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410.

This PR aims to provide a uniform way of declaring data-types of parameters for
parsers and handlers to follow. In turn, this would allow formatters to
determine data-types in a cleaner and less volatile manner. (See use-case that
can be improved with this PR:
https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103)

This is possible by the addition two properties to each property item in
`response`, and `parameters` fields in each API endpoint produced by the
`ApiDocExtractor`:

* `actualType` Contains a value from one of the `DataTypes` class constants.

* `subType` Can contain either `null`, or any other `DataTypes` class constant.
This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein
`subType` would specify the type of the collection items. It is also relevant
when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an
identifier of the model (the FQCN or anything the parser would wish to specify)

Examples:

```php

array(
    'id' => array(
        'dataType' => 'integer',
        'actualType' => DataTypes::INTEGER,
        'subType' => null,
    ),
    'profile' => array(
        'dataType' => 'object (Profile)',
        'actualType' => DataTypes::MODEL,
        'subType' => 'Foo\Entity\Profile',
        'children' => array(
            'name' => array(
                'dataType' => 'string',
                'actualType' => DataTypes::STRING,
                'subType' => null,
             ),
            'birthDate' => array(
                'dataType' => 'date',
                'actualType' => DataTypes::DATE,
                'subType' => null,
            ),
        )
    ),
    'languages' => array(
        'dataType' => 'array of strings',
        'actualType' => DataTypes::COLLECTION,
        'subType' => DataTypes::STRING,
    ),
    'roles' => array(
        'dataType' => 'array of choices',
        'actualType' => DataTypes::COLLECTION,
        'subType' => DataTypes::ENUM,
    ),
    'groups' => array(
        'dataType' => 'array of objects (Group)',
        'actualType' => DataTypes::COLLECTION,
        'subType' => 'Foo\Entity\Group',
    ),
    'profileRevisions' => array(
         'dataType' => 'array of objects (Profile)',
         'actualType' => DataTypes::COLLECTION,
         'subType' => 'Foo\Entity\Profile',
    ),
    'address' => array(
        'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)',
        'actualType' => DataTypes::MODEL,
        'subType' => 'a_type_a_custom_JMS_serializer_handler_handles',
    ),
);
```

When a formatter omits the `dataType` property or leaves it blank, it is
inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-25 09:05:48 +02:00
William DURAND
b48650a9e0 Fix CS 2014-06-25 08:52:01 +02:00
William Durand
87b690d5e1 Merge pull request #409 from stof/patch-2
Simplified the Travis configuration
2014-06-25 08:51:55 +02:00
Christophe Coevoet
8d3fd662bf Fixed the retrieval of the validation MetadataFactory
The service is private so getting it from the container get() method is invalid and it does not work anymore in Symfony 2.5 because the service gets inlined.
2014-06-18 09:38:16 +02:00
Christophe Coevoet
df1c85ae5e Simplified the Travis configuration
The tests against specific Symfony versions are now running only for a single PHP version to limit the number of jobs in the build matrix. They are also installing the full Symfony repo to be sure that all components are actually at the specified version without the need to require each of them explicitly.
2014-06-17 19:01:48 +02:00
William Durand
56124e7c40 Merge pull request #395 from fechu/master
Implement tags in ApiDoc annotation.
2014-06-01 15:56:45 +02:00
Sandro Meier
106b42530c Add documentation for tag property 2014-05-27 19:33:52 +02:00
Sandro Meier
dfd094371d Implement Tags for functions. 2014-05-27 13:33:50 +02:00
William Durand
dfb089f993 Merge pull request #392 from marco-jantke/submit-on-enter-sandbox-mode
Pressing enter in the sandbox mode will now lead to submit the form.
2014-05-26 10:51:28 +02:00
Marco Jantke
d7c70720b0 Pressing enter in the sandbox mode will now lead to submit the form. 2014-05-23 15:30:21 +02:00
Yosh de Vos
624802b57a Added configuration to disable body formats 2014-05-21 15:59:55 +02:00
William Durand
221f109ad6 Merge pull request #386 from marco-jantke/window-location-hash-fix
Remove hash when closing a method container again. Added small hash help...
2014-05-19 12:03:46 +02:00
William Durand
3771e2d834 Merge pull request #385 from yoshz/master
Added request formats configuration
2014-05-19 10:45:49 +02:00
Yosh de Vos
8402c748ee Added request formats configuration 2014-05-18 21:25:30 +02:00
Marco Jantke
df7be182cd Remove hash when closing a method container again. Added small hash helper functions. 2014-05-18 01:24:52 +02:00
William Durand
968a162544 Merge pull request #365 from pyrech/master
Fix #357 - doc broken with Validator Constraints in FOSRestBundle requirements
2014-05-16 23:52:41 +02:00
Loick Piera
d0149c65ab fix #357 2014-05-16 22:04:24 +02:00
William DURAND
fa92011126 fix cs 2014-05-16 11:33:58 +02:00
William Durand
a88e7d8278 Merge pull request #381 from mvdbos/master
Only render sandbox config if  the sandbox is enabled
2014-05-15 19:01:35 +02:00
Matthijs van den Bos
86d9c31340 Only show sandbox config if enabled 2014-05-14 22:05:03 +02:00
William Durand
493963f0c0 Merge pull request #375 from merk/2.5-fix
Fix ValidatorParser in symfony 2.5
2014-05-12 12:01:33 -04:00
William Durand
36c223ab3a Merge pull request #329 from maxromanovsky/form-type-parser-improvements
Form Parser improvements for date, datetime & choice form types (format & types)
2014-05-12 11:56:00 -04:00
Tim Nagel
c366ffba84 Fix ValidatorParser in symfony 2.5
Fixes #373
2014-04-30 22:18:06 +10:00
Emmanuel BALLERY
0a42d1773c Fix wrong HTML structure a>div 2014-04-24 23:02:56 +02:00
Emmanuel BALLERY
7150ac17de Remove duplicate HTML ID 2014-04-24 23:00:13 +02:00
Emmanuel BALLERY
f2b606fc23 Fix wrong HTML structure ul>li 2014-04-24 22:59:12 +02:00
Emmanuel BALLERY
c78466bbb9 Fix wrong HTML tag 2014-04-24 22:58:34 +02:00
William Durand
1c553e93ec Merge pull request #363 from cedriclombardot/fix-model-type
Fix parse of input forms with options required
2014-04-23 12:13:35 +02:00
Cedric LOMBARDOT
4181079d8b Fix parse of input forms with options required
When form like model type was parsed this pass the required 'class' option to enable the build of the class
2014-04-11 09:45:35 +00:00
Max Romanovsky
b4fa6013cd Form Parser improvements for date, datetime & choice form types (format & types) 2014-04-04 13:52:12 +03:00
William Durand
fd126c1a01 Merge pull request #359 from aybbou/patch-1
Added a missing 'is'.
2014-03-28 13:48:20 +01:00
Ayyoub BOUMYA
e6cf2ff0d2 Added a missing 'is'. 2014-03-28 10:46:38 +00:00
Samuel ROZE
3f66888f00 Add PostParserInterface to JmsMetadataParser to get ValidatorParser found children parsed. 2014-03-20 16:39:36 +01:00
William Durand
eddba56c66 Merge pull request #348 from tonivdv/fix_https_reload_scheme
Sandbox should not be disabled in https whenever a method is not marked as https explictly
2014-03-12 14:46:04 +01:00
Toni Van de Voorde
04c03e3e06 Sandbox should not be disabled in https whenever a method is not marked as
https explictly
2014-03-11 10:24:56 +01:00
Alexey Kupershtokh
11f163d589 Show web profiler link in sandbox 2014-03-11 07:07:35 +07:00
William Durand
855f95afd1 Merge pull request #347 from h4cc/master
Testing against HHVM and PHP 5.6
2014-03-10 10:04:00 +01:00
Julius Beckmann
56f8c43849 Added PHP 5.6 and HHVM to Travis.
Modified RequestListenerTest so it does not use the Crawler, that currently does not work with HHVM.
2014-03-03 18:44:37 +01:00
William Durand
eca9d25ca7 Merge pull request #339 from mvrhov/michelfMarkdown
Replace deprecated fork of dflydev's mardown library with the original o...
2014-02-19 22:34:43 +01:00
Miha Vrhovnik
457df811e2 Replace deprecated fork of dflydev's mardown library with the original one provided by Michel Fortin 2014-02-19 12:55:16 +01:00
William DURAND
ee68eabe0c Prepare 2.5.0 release 2014-02-11 16:44:12 +01:00
William Durand
5bd455f955 Merge pull request #333 from richardfullmer/https
Fix and properly document the https option
2014-02-07 01:05:59 +01:00
Richard Fullmer
dc8494bbdf Fix an properly document https option 2014-02-06 09:27:30 -08:00
William Durand
14571944b4 Merge pull request #332 from Pyrech/master
Fix twig errors when generating html doc
2014-02-06 15:20:37 +01:00
Loick Piera
b198e240ce Fix null host value in layout.html.twig 2014-02-06 15:12:26 +01:00
Loick Piera
414e87b58b Fix null secure value in method.html.twig 2014-02-06 15:06:43 +01:00
William Durand
e8a1e639ef Merge pull request #331 from ahilles107/patch-1
Don't try to call class with custom handlers.
2014-02-05 16:39:04 +01:00
Paweł Mikołajczuk
32e37dbd8a Don't try to call class with custom handlers.
With JMS Serializer you can set up own type and use it later with custom handler for this property - Parser allways try to call that custom property type like a class. This fix that.
2014-02-03 16:57:46 +01:00
William Durand
d177862b4d Merge pull request #321 from Ninir/patch-1
Fixed typo in index.md documentation
2014-01-21 08:15:19 -08:00
Ninir
4a9e99bddd Fixed typo in index.md documentation 2014-01-21 10:41:55 +01:00
William DURAND
667044863c Fix HTTPS detection
Fix #281
2014-01-20 14:18:02 +01:00
William Durand
347ac74648 Merge pull request #318 from antwebes/master
Added exclude_envs to ApiDoc annotation to exclude the documentation from the specified environments
2014-01-20 04:50:49 -08:00
jdeveloper
dea78901a0 Added exclude_envs to ApiDoc annotation to exclude the documentation from the specified environments 2014-01-20 13:24:41 +01:00
William Durand
f45cc2565e Merge pull request #316 from maxromanovsky/patch-cg-proxy
Fixed ApiDoc for controllers enhanced with JMS CG
2014-01-13 13:35:37 -08:00
Max Romanovsky
e4ec8e79f3 Fixed ApiDoc for controllers enhanced with JMS CG with Doctrine Common 2014-01-13 19:10:07 +03:00
William Durand
f2a80511a7 Merge pull request #313 from maxromanovsky/patch-1
Misspelled authentication config parameter in docs
2014-01-13 06:26:52 -08:00
Max Romanovsky
dc4b4066a7 Misspelled authentication config parameter in docs 2014-01-13 14:53:28 +03:00
William DURAND
5e1bf1fbda [doc] fix sandbox doc 2014-01-10 11:29:52 +01:00
William DURAND
eca2eb6d80 Improve documentation (authentication/sandbox) 2014-01-10 11:20:22 +01:00
William DURAND
6038fe842f Merge PR #299 from SimonSimCity/https-fixes
Fix #266
Close #283
Close #299
2013-12-28 18:55:06 +01:00
Simon Schick
912178dc88 Hide “_scheme” in the list of requirements per URL
+ disable sandbox if the scheme of the URL doesn’t match the scheme, the
documentation is opened in
2013-12-28 18:52:32 +01:00
William Durand
393dbaf6ed Merge pull request #282 from sroze/patch-1
Fix PHPDoc
2013-12-28 09:43:50 -08:00
William DURAND
627130637d Merge PR #288 from piotrantosik/feature/selectparsers 2013-12-28 18:42:51 +01:00
Piotr Antosik
3fdbfbed1c select used parsers 2013-12-28 18:41:59 +01:00
William DURAND
af7c04cdfc Merge PR #298 from 'skler/extended_class 2013-12-28 18:28:07 +01:00
William DURAND
dc9c706b51 [Test] Extended Class test case 2013-12-28 18:27:48 +01:00
William Durand
f65a9c9965 Merge pull request #304 from h4cc/patch-2-squashed
Use only JMS/GroupExclusion if groups are there
2013-12-28 02:30:51 -08:00
Julius Beckmann
0845377300 Use only JMS/GroupExclusion if groups are there
When no groups are given in the ApiDoc, there should be no GroupExclusion and the Entity should be parsed wihtout exclusions.

Otherwise only the "Default" group of the Entity would be parsed, which may not be used.

The ApiDoc should not enforce the Entity to be grouped with "Default", to generate a "full-view" of it.
2013-12-20 15:08:26 +01:00
William DURAND
4b6efc6631 Fix undefined index in AbstractFormatter 2013-12-16 16:47:21 +01:00
William Durand
d21833daea Merge pull request #290 from rajasaur/dumpcommand_bugfix
Fix DumpCommand to generate html with/out sandbox
2013-12-16 06:30:50 -08:00
William DURAND
c3097c7439 cs 2013-12-11 01:59:59 +01:00
William Durand
cb8b0c6f90 Merge pull request #294 from marcj/outsource-classic-phpdoc
Outsourced the parsing of the classic phpDoc into a extra handler.
2013-12-10 16:58:18 -08:00
Marc J. Schmidt
ead8174192 Outsourced the parsing of the classic phpDoc into a extra handler.
This makes it possible to overwrite route-requirements/description through a own handler.
Otherwise it's impossible e.g. to overwrite a `@param string $page` annotation via a own handler.
2013-12-11 01:40:16 +01:00
William Durand
7380f95fa5 Merge pull request #293 from marcj/patch-1
Remove the annoying scroll-jumping on method-click.
2013-12-10 04:18:30 -08:00
Marc J. Schmidt
d7ef725613 Suppressed the auto-scrolling on click and fixed initial-jump issues.
For the initial auto-scrolling to the selected route in location.hash:
 - Fixed typo in the calculation of the offset-top position.
 - Fixed requesting the selected route that has special chars (e.g. "/content/{page}")
 - Fixed for refreshes with F5. It needs a setTimeout workaround to get that working in Webkit.
    It needs also in Firefox a other dom element to fire scrollTop at.

Fixed also some old .delegation calls, which are deprecated in jQuery 1.7.

Why suppressing auto-scrolling on click:
When clicking through all methods it's very annoying when the browser always jumps to the clicked method,
especially when you go through all methods from bottom to up. This jump is unexpected and disturbing.
2013-12-10 03:35:06 +01:00
rajasaur
9eee2e263f Fix DumpCommand to generate html with/out sandbox
api:doc:dump --format=html always disables the Sandbox since the check in
DumpCommand seems to be wrong. Fixed that as well as introduce a dummy
Request Object as the layout.html.twig expects an app.request object.
2013-12-06 15:28:19 +05:30
William DURAND
21a0774265 Fix travis-ci config 2013-12-05 21:36:37 +01:00
William DURAND
3166640c5a Rework documentation, add reference config 2013-12-05 17:10:38 +01:00
Samuel ROZE
95e8c9d200 Fix typo
$route parameter of ApiDocExtractor::get is a string. Passing a Route object throw an error.
2013-11-21 15:05:45 +01:00
William Durand
2ceea4871a Merge pull request #274 from sroze/patch-2
Add support of `All` constraint
2013-11-18 02:06:15 -08:00
Samuel ROZE
1112cca784 Add support of All constraint 2013-11-18 10:24:56 +01:00
William Durand
6c01424a95 Merge pull request #272 from sroze/patch-1
Merge output parameters
2013-11-17 09:28:45 -08:00
Samuel ROZE
184a364fa4 Merge output parameters 2013-11-16 17:29:23 +01:00
William Durand
eaee55e44a Merge pull request #279 from nord-ua/patch-1
Typo fix
2013-11-15 02:31:53 -08:00
Сергей Рябенко
aae9af23c2 Typo fix 2013-11-15 13:29:32 +03:00
William DURAND
4ff018b111 Fix composer again 2013-11-14 17:01:56 +01:00
William DURAND
fb75ceb318 update branch-alias 2013-11-14 16:28:08 +01:00
William DURAND
28e192290c Fix composer 2013-11-14 16:25:39 +01:00
William DURAND
e59ae1e1ef Fix tests for Symfony2 2.1 2013-11-14 14:20:51 +01:00
William DURAND
a2d8629f68 Composer minimum stability is not 'dev' anymore 2013-11-14 13:31:38 +01:00
William DURAND
3ce5dca429 Fix a failing test 2013-11-14 13:26:10 +01:00
William DURAND
88d56cb445 Fix travis-ci config 2013-11-14 12:09:06 +01:00
William DURAND
4edc91ea46 Add a note about PR desc in CONTRIBUTING file 2013-11-14 12:07:19 +01:00
William DURAND
e9439de94e Move doc to its own folder 2013-11-14 11:43:09 +01:00
William DURAND
e7b7c87113 Update README, move LICENSE to meta folder 2013-11-14 11:39:29 +01:00
William DURAND
291f2e8efc Add CONTRIBUTING file 2013-11-14 11:35:51 +01:00
William DURAND
87328e27f5 Merge pull request #206 from dothiv/master
Show authorized roles in key icon tooltip
2013-11-14 11:17:06 +01:00
William DURAND
7fdcd65286 Add not blank format validation
All credits go to @spolischook

See: https://github.com/nelmio/NelmioApiDocBundle/pull/234

Closes #234
2013-11-14 11:12:58 +01:00
William DURAND
32183f2618 Better travis-ci config 2013-11-14 11:09:27 +01:00
William DURAND
46275a51be Merge PR #235 2013-11-14 11:05:12 +01:00
Martin Westergaard Lassen
83315fcc80 Set format according to Date, DateTime and Time annotations 2013-11-14 11:05:05 +01:00
William DURAND
be130a2d10 Fix incompatibility with Symfony2 2.1
All credits go to @restyler

See: https://github.com/nelmio/NelmioApiDocBundle/pull/237

Closes: #231
Replaces: #237
2013-11-14 11:00:44 +01:00
William Durand
f91a0868a8 Merge pull request #247 from domnikl/xsd
add XML namespace and XSD
2013-11-14 01:51:01 -08:00
William Durand
7ac9c1c1b6 Merge pull request #251 from Peekmo/patch-1
Showing default values in sandbox
2013-11-14 01:50:29 -08:00
William Durand
357d1ff967 Merge pull request #262 from skler/issue-259-2
[FIX] Issue 259
2013-11-14 01:47:29 -08:00
William Durand
29965ec7bb Merge pull request #264 from driebit/embedded-forms
Add support for embedded forms
2013-11-14 01:42:43 -08:00
William Durand
8e41122ca7 Merge pull request #268 from konradpodgorski/feature/optionalRequirementsFields
Optional requirements fields: requirement, dataType and description
2013-11-14 01:41:14 -08:00
William DURAND
d7fd929379 Fix CS 2013-11-14 10:33:57 +01:00
William Durand
f94d6403f5 Merge pull request #277 from adriensamson/sandbox-host
Add support for host in sandbox
2013-11-14 00:25:50 -08:00
Adrien SAMSON
f71ba12d48 Add support for host in sandbox 2013-11-13 17:04:49 +01:00
William Durand
8728e825ea Merge pull request #270 from sroze/patch-1
Support "callback" on Choice Validator
2013-11-06 17:53:53 -08:00
Samuel ROZE
f02a6729eb Support "callback" on Choice Validator 2013-11-06 15:25:10 +01:00
Konrad Podgórski
4a831c3a20 Requirements fields: requirement, dataType and description can be optional 2013-10-30 16:38:32 +01:00
Jordi Boggiano
769b435cf3 Test suite cleanups 2013-10-29 14:41:13 +01:00
Jordi Boggiano
ecc4fb9897 Keep order of loaders, fixes #265 build 2013-10-29 14:41:03 +01:00
Jordi Boggiano
6c2fd53edc Update travis config 2013-10-29 14:40:30 +01:00
Jordi Boggiano
7336197536 Merge remote-tracking branch 'Tobion/optional_form' 2013-10-29 14:02:30 +01:00
Tobias Schultze
deaf69dc87 add missing dependencies 2013-10-29 09:54:57 +01:00
Tobias Schultze
09e82fa6d3 make form and validation extractors optional 2013-10-28 19:12:43 +01:00
David de Boer
fbe9488963 Add support for embedded forms 2013-10-23 15:45:30 +02:00
William Durand
bef7da0ef9 Merge pull request #218 from konradpodgorski/feature/extendedAnnotation
Option to set requirements and parameters directly from ApiDoc annotation
2013-10-15 15:39:44 -07:00
Konrad Podgórski
74d30d9e39 Option to set requirements and parameters directly from ApiDoc annotation
Sometimes required parameters are not used through routing but still they are mandatory. I wanted to have API with

resource.json?foo=bar&something=else

format, that was possible through QueryParam annotation or requirements in routing BUT!

There was no way to set dataType or description

This PR solves problem for me.

Side note: if you want to declare e.g. _format requirement through Annotation or any other param that is used in url ({foo} format) then it won't work. Because Bundle still overrides requirements and parameters after the constuctor in ApiDoc is called. This might be solved in separate PR by adding check if given requirements or parameters was already defined.
2013-10-15 15:28:54 +02:00
Jordi Boggiano
22cd63f9bd Merge pull request #258 from fdubost/master
Allow to named resources
2013-10-11 09:47:55 -07:00
Mauro Foti
018860205c [FIX] Issue 259 2013-10-11 18:29:10 +02:00
Florent DUBOST
f16aa64cf0 more readable 2013-10-11 16:40:26 +02:00
Florent DUBOST
8f6ac59c97 Adding test for named resource 2013-10-11 16:18:02 +02:00
Florent DUBOST
56fdd4e64c Adding possibilty to name resource 2013-10-11 15:44:31 +02:00
William Durand
7317c7aa81 Merge pull request #255 from adrienbrault/form-type-name
Can now specifiy form name
2013-10-09 16:18:34 -07:00
Adrien Brault
083ae3aef1 Can now specifiy form name
If you create you forms using FormFactoryInterface::createNamed, then you should now be able to tell the ApiDocBundle the correct form prefix.
2013-10-08 12:09:46 -07:00
Axel Anceau
ebbbebfab6 Showing default values in sandbox
When a parameter "default" is set for a filter, or a parameter, the field will be fill with this value.
2013-10-03 14:17:48 +02:00
Dominik Liebler
7ba7ea4036 changed XSD elements to attributes 2013-09-23 13:46:32 +02:00
Dominik Liebler
7f8a4d4b59 added XML namespace and XSD 2013-09-21 23:35:53 +02:00
William Durand
c2d36d9ef0 Merge pull request #221 from kmfk/documentation-in-handler
Modify the `documentation` property inside annotation handlers
2013-09-12 01:51:49 -07:00
William Durand
9dc3f10828 Merge pull request #240 from kbsali/ids
Make it possible to refer to methods through a URI
2013-09-11 05:13:05 -07:00
Kevin Saliou
23bc2b6f47 make api method headers anchor links so a speicif method can be refered to through its unique id 2013-09-06 13:42:19 +02:00
William Durand
f690529fc6 Merge pull request #233 from ckdarby/patch-2
Fixing border to not be applied to children
2013-08-29 16:16:09 -07:00
Cory
912906578a Fixing border to not be applied to children 2013-08-29 12:00:18 -04:00
William Durand
a2dc56d6e5 Merge pull request #230 from eillarra/patch-1
Not all "strict" params should be "Requirements" in the documentation, closes #229
2013-08-18 13:44:37 -07:00
Eneko Illarramendi
fef656cd13 Not all "strict" params should be "Requirements" in the documentation
View https://github.com/nelmio/NelmioApiDocBundle/issues/229
2013-08-18 22:33:04 +02:00
William Durand
6455a8669d Merge pull request #228 from vincentchalamon/master
Fix #226
2013-08-16 17:10:44 -07:00
Vincent CHALAMON
a1517543e8 Enable PHPDoc @link feature 2013-08-16 19:18:05 +02:00
Vincent CHALAMON
4fb050175a fix #226 2013-08-16 17:02:18 +02:00
William Durand
96b40b8a8c Merge pull request #210 from jhallbachner/validation2
Added Support for Validation Component (refactored)
2013-08-13 03:28:09 -07:00
Jordi Boggiano
475ecf349b Speed up toggling 2013-08-07 12:56:32 +02:00
Jordi Boggiano
d9607477ba Merge pull request #219 from jsampedro77/header-auth
Adds custom header Authentication method
2013-07-31 03:28:43 -07:00
Jordi Boggiano
44ec2c257b Merge pull request #222 from Bladrak/improvedFormRendering
Increased input field width for sandbox forms to improve readability wit...
2013-07-31 03:27:14 -07:00
Hugo Briand
c90bd7cce4 Increased input field width for sandbox forms to improve readability with long keys 2013-07-31 10:38:09 +02:00
keith kirk
f67dfb05db adds the ability to modify the documentation property inside custom extractor handlers 2013-07-25 13:47:54 -07:00
William Durand
3b6223feb2 Merge pull request #220 from qpleple/patch-1
Clarify config snippet
2013-07-23 03:03:30 -07:00
Quentin Pleplé
e3f1f55d27 Clarify config snippet 2013-07-22 12:54:08 -07:00
jsampedro
7f2cff2ac5 Adds custom header Authentication method 2013-07-19 10:05:57 +02:00
Jordi Boggiano
a8957a5915 Merge pull request #216 from lioshi/master
Add missed border-top's pane for sandbox form
2013-07-12 02:15:36 -07:00
lioshi
112f027b20 Add missed border-top pane for Sandbox form 2013-07-12 10:23:13 +02:00
William Durand
9e02563860 Merge pull request #205 from fterrier/escape-input
html encode the url when it is written to the page
2013-07-08 00:43:16 -07:00
Jordi Boggiano
2a87244869 Fix build 2013-07-05 00:27:42 +02:00
Jordi Boggiano
65c52577d8 Merge pull request #212 from stof/patch-1
Fixed the branch alias for master
2013-07-04 04:22:18 -07:00
Christophe Coevoet
b0022d78a2 Fixed the branch alias for master
2.3.0 has already been released
2013-07-04 13:21:13 +02:00
Josh Hall-Bachner
54a6ad566d Updated postParse logic to utilize an interface and to avoid unnecessary "supports" checks.
Expanded documentation on new classes and methods.
2013-07-02 21:57:09 -07:00
Josh Hall-Bachner
23f64b84f6 Fixed multi-level validation nesting.
Removed "class" parameters from results after processed.

Updated README.
2013-06-30 23:46:43 -07:00
Josh Hall-Bachner
5e1549a29d Built parse-merging into the ApiDocExtractor.
Wired up a "post-parse" pass to allow recursive parsing across multiple parsers.
2013-06-30 23:46:41 -07:00
Josh Hall-Bachner
0913157399 Added the initial structure for a Symfony Validation handler that is injected into the parsers. 2013-06-30 23:46:41 -07:00
Nils Wisiol
b2a7dde6f4 improved unit tests by using more specific asserts 2013-06-24 15:57:54 +02:00
Nils Wisiol
f764773c89 authenticationRoles can be set to appear in the tooltip of the key icon for API calls that require authentication. 2013-06-24 14:27:22 +02:00
François Terrier
9236e9471d using a simpler version 2013-06-20 15:05:02 +02:00
François Terrier
b4bb454518 html encode the url when it is written to the page 2013-06-20 14:29:58 +02:00
William Durand
0b17291084 Merge pull request #203 from choomz/patch-1
Update FormTypeParser.php
2013-06-19 01:25:17 -07:00
Valentin Ferriere
475c92f17f Update FormTypeParser.php
This catch is missing, because it can break here https://github.com/symfony/Form/blob/master/FormRegistry.php#L89
2013-06-19 10:17:37 +02:00
William Durand
01044bb7ac Merge pull request #198 from jhallbachner/json
Add BodyFormat Selector and Support Json-Encoded Api Requests In Sandbox
2013-06-18 06:46:45 -07:00
William Durand
0eb7ec27ec Merge pull request #152 from adriensamson/issue-147
Fix Illegal offset warning in FormTypeParser (closes #147)
2013-06-18 06:44:06 -07:00
Josh Hall-Bachner
c5beab777f Fixed parameter encoding for GET requests. 2013-06-14 16:15:43 -07:00
Jordi Boggiano
7857921186 Merge pull request #200 from lsmith77/add-missing-link
add missing link to the api doc overview
2013-06-13 03:33:30 -07:00
Lukas Kahwe Smith
bcecc03139 add missing link to the api doc overview 2013-06-13 12:05:01 +02:00
Josh Hall-Bachner
6ba548e21e Removed stray console.log 2013-06-05 19:20:12 -07:00
Josh Hall-Bachner
c237d65bad Added a helper method to enable deep encoding of nested parameters when encoding requests into JSON. 2013-06-05 18:51:01 -07:00
Josh Hall-Bachner
defe9c0b36 Updated README to reflect additional parameter. 2013-06-04 15:52:27 -07:00
Josh Hall-Bachner
a9f0613cee Added a configuration parameter to determine the default body format. 2013-06-04 15:30:07 -07:00
Josh Hall-Bachner
705e01625e Added a method to send JSON-encoded data. 2013-06-03 19:11:42 -07:00
William Durand
ce1b40eac3 Merge pull request #156 from fieg/jms-group-support
added support for JMS Serializer GroupsExclusionStrategy
2013-05-12 09:17:18 -07:00
Pierre-Yves LEBECQ
13efea8975 Added support for the jms version annotations in formatters 2013-05-12 14:54:01 +02:00
fieg
06271f824a added support for JMS Serializer GroupsExclusionStrategy 2013-05-12 14:29:36 +02:00
Jordi Boggiano
806e009391 Merge pull request #192 from Tobion/patch-1
fix jms metadata parser for hashmaps: array<K, V>
2013-05-11 03:33:45 -07:00
Tobias Schultze
d2cd56dafc fix jms metadata parser for hashmaps: array<K, V> 2013-05-10 19:09:57 +02:00
Jordi Boggiano
00bcdc927e Remove scope hack since 2.3 has been fixed 2013-05-10 10:34:21 +02:00
William Durand
3e8b896d00 Merge pull request #191 from iambrosi/patch-1
Added check for deprecated setting
2013-05-10 01:30:50 -07:00
Ismael Ambrosi
0c416c1788 Fixed tests for deprecated indicator 2013-05-09 23:59:03 -03:00
William Durand
e264bcd8b9 Merge pull request #187 from marapper/docs
Add annotation reference to docs
2013-05-09 05:16:10 -07:00
William Durand
48b92527bb Merge pull request #188 from marapper/post_color
Clearly distiguishable color for POST methods
2013-05-09 05:15:37 -07:00
William Durand
6ec959a6aa Merge pull request #189 from marapper/data_uri
Icons can be embedded with data URI
2013-05-09 05:15:15 -07:00
William Durand
26be89bc53 Merge pull request #190 from marapper/no_description
Make parameters description conform to others
2013-05-09 05:14:43 -07:00
Ismael Ambrosi
0c2cad6f0a Added check for deprecated setting
Avoids adding the method as deprecated if the value of the deprecated setting is false
2013-05-08 11:00:44 -03:00
marapper
a97c65da70 Make parameters description conform to others 2013-05-07 23:11:32 +04:00
marapper
fd860b43cd Icons as data URI 2013-05-07 22:52:18 +04:00
marapper
9d77dad94d Clearly distiguishable color for POST methods 2013-05-07 22:16:01 +04:00
marapper
3a4c9cbc12 Add annotation reference to docs 2013-05-07 21:49:23 +04:00
marapper
80b5162c83 Add @QueryParam default support 2013-05-06 10:04:13 +02:00
William Durand
5567f74692 Merge pull request #179 from lightglitch/fix-178
Fix parameter name boundary in regex
2013-05-06 01:00:51 -07:00
William Durand
1780b09387 Merge pull request #185 from blaugueux/patch-form
Handle the prefix form when parseForm.
2013-05-06 00:57:43 -07:00
Jordi Boggiano
255d42830d Allow sf2.3, fix scope issue 2013-05-03 16:26:16 +02:00
Mario Franco
e6b6987141 Fix formatter tests 2013-05-03 14:49:17 +01:00
Mario Franco
1dc3380e8e Refactor tests 2013-05-03 14:37:52 +01:00
Benjamin Laugueux
6859384983 Handle the prefix form when parseForm.
Cf. #166 and thanks to @madesst.
2013-04-30 16:19:00 +02:00
Jordi Boggiano
867f82d97d Merge pull request #183 from ccapndave/master
The @PreAuthorize annotation should also show the little key icon in the generated docs
2013-04-30 01:52:19 -07:00
Dave Keen
22508bf058 Update JmsSecurityExtraHandler.php
Having a JmsSecurity @PreAuthorize annotation should also count as setting a method as requiring authentication.
2013-04-29 12:10:42 +03:00
William Durand
42f0b65a4a Merge pull request #182 from blaugueux/patch-1
Fixed branch alias
2013-04-29 00:36:00 -07:00
Benjamin Laugueux
c8d7c348e5 Fixed branch alias 2013-04-29 09:48:44 +03:00
William Durand
3c43cc5d07 Update README.md 2013-04-18 01:29:56 +03:00
William Durand
08eebf0fa0 Merge pull request #177 from fvilpoix/annotation_handlers
Moving annotation extraction into tagged Handlers
2013-04-17 15:21:31 -07:00
fvilpoix
3e04cbfdf1 [ExtractorHandler] code cleaning 2013-04-17 14:24:45 +02:00
fvilpoix
7f79ddc065 [ExtractorHandler] cleaning code 2013-04-17 13:41:28 +02:00
Mario Franco
48c65fefc8 Merge branch 'master' of https://github.com/nelmio/NelmioApiDocBundle 2013-04-17 01:43:57 +01:00
William Durand
c5971321cc Merge pull request #180 from lightglitch/deprecated
Added support for deprecated phpdoc tag
2013-04-16 12:30:26 -07:00
Mario Franco
fb154280f9 Fix route 2013-04-16 19:59:54 +01:00
Mario Franco
93e83e610c Added test case 2013-04-16 19:40:29 +01:00
Mario Franco
b31ca81d9e Merge branch 'deprecated' 2013-04-16 16:40:34 +01:00
Mario Franco
bb9fd6d756 Added support for deprecated phpdoc tag 2013-04-16 16:19:19 +01:00
Mario Franco
32840bfe75 Fix parameter name boundary in regex 2013-04-16 16:16:30 +01:00
fvilpoix
76b85938c6 implementing all stof comments :) 2013-04-16 16:00:46 +02:00
Jordi Boggiano
629ee251da Merge pull request #175 from blaugueux/patch-1
Fixed configuration
2013-04-14 02:06:19 -07:00
fvilpoix
63b0f8e4da Moving annotation extraction into tagged Handlers 2013-04-12 17:43:27 +02:00
Jordi Boggiano
f5a409fd43 Merge pull request #176 from iniweb/master
Fix: BC with symfony 2.1
2013-04-12 00:49:55 -07:00
Anton Kasperovich
344ce0d3b7 Fix: BC with symfony 2.1
Commit #5cc7cf212d break compatibility with Symfony 2.1 (or lower),
because only in 2.2 added host support in routing.
2013-04-12 10:45:20 +03:00
Benjamin Laugueux
8f37d7ba79 Fixed configuration 2013-04-12 08:49:38 +03:00
William Durand
1fac0e0cd3 Default MOTD should be empty 2013-04-12 00:03:47 +03:00
Jordi Boggiano
8caa4f6c2b Merge pull request #174 from blaugueux/new-host
Added support for host parameter
2013-04-11 13:21:19 -07:00
Benjamin Laugueux
5cc7cf212d Added support for host parameter 2013-04-11 22:03:40 +02:00
Jordi Boggiano
a30e5744e7 Merge pull request #172 from blaugueux/http-basic
Added support for HTTP Basic authentication and custom api endpoint.
2013-04-11 06:20:33 -07:00
Benjamin Laugueux
0b7e734594 Added http basic authentication and custom api endpoint.
Fixed test
2013-04-11 13:57:31 +02:00
William Durand
d93388797c Merge pull request #173 from blaugueux/patch-1
Added section documentation
2013-04-10 14:50:55 -07:00
Benjamin Laugueux
d109c140be Added section documentation
Cf. #154
2013-04-11 00:34:24 +03:00
Jordi Boggiano
0a865fd449 Merge pull request #171 from abdulklarapl/master
scheme-relative URI
2013-04-09 01:29:47 -07:00
Patryk Szlagowski
ec956ec24c #170 sexy scheme for fonts 2013-04-09 10:19:41 +02:00
Jordi Boggiano
8af10934fe Merge pull request #169 from gordalina/fix-response-headers
Reponse headers are inserted into the DOM as HTML instead of Text
2013-04-09 00:20:24 -07:00
Jordi Boggiano
af8dd12da4 Merge pull request #168 from abdulklarapl/master
motd component
2013-04-09 00:18:59 -07:00
Samuel Gordalina
042b7a203c Use text() instead of html() when inserting headers in the DOM 2013-04-08 21:19:24 +01:00
Patryk Szlagowski
abf591f4fd fixed readme file 2013-04-08 12:21:50 +02:00
Patryk Szlagowski
e92ad37cc2 motd style 2013-04-08 12:20:43 +02:00
Patryk Szlagowski
80d31da774 updated readme with information about motd configuration 2013-04-08 11:54:09 +02:00
Patryk Szlagowski
0eb538083a motd configuration 2013-04-08 11:44:43 +02:00
Patryk Kala
3b6e9da91a Ability to mark function as deprecated 2013-03-31 20:35:46 +02:00
William Durand
d6491e77bc Merge pull request #157 from fdubost/feature-cache
Add cache annotation extractor
2013-03-31 11:09:43 -07:00
William Durand
2c1386b153 Merge pull request #165 from benbender/recursion-on-routecollections
Recursion on routecollections
2013-03-27 16:08:54 -07:00
Benjamin Bender
11a56251a4 Remove extra braces 2013-03-27 23:21:04 +01:00
Benjamin Bender
fd17706118 Cleanup docbook 2013-03-27 23:19:16 +01:00
Benjamin Bender
28b7fa7d0e Fixes typehint for Route 2013-03-27 22:28:26 +01:00
Benjamin Bender
8d771a925f Fix for #163. Makes Reflection on Route-objects more robust. 2013-03-27 19:36:42 +01:00
Jordi Boggiano
905ac99eb5 Fix handling of symfony 2.1 nested routes, fixes #163 2013-03-27 16:27:25 +01:00
Florent DUBOST
e581432d20 Oups, cleaning up 2013-03-27 16:23:13 +01:00
Florent DUBOST
772184cfa1 Add some tests for the cache annotation 2013-03-27 16:20:42 +01:00
Florent DUBOST
5ec931185f Merge branch 'master' of https://github.com/nelmio/NelmioApiDocBundle into feature-cache 2013-03-27 15:46:28 +01:00
Jordi Boggiano
109161e985 Merge pull request #155 from benbender/flexbile-doc-extractor
[ApiDocExtractor] Add a little bit more flexibility
2013-03-27 04:14:19 -07:00
William Durand
86e47fe785 Merge pull request #158 from j/test-fixes
fixed failing tests
2013-03-26 13:38:28 -07:00
Jordan Stout
afd07dc570 fixed failing tests 2013-03-26 11:29:50 -07:00
Florent DUBOST
53b2537aef Add cache annotation extractor 2013-03-26 11:49:12 +01:00
William Durand
5d50a8d49e Merge pull request #139 from j/serializer-parser
Use serializer naming strategy instead of serialized name or property name
2013-03-26 02:59:04 -07:00
Benjamin Bender
c5e12e3c4f Fix typo 2013-03-26 08:57:49 +01:00
Jordan Stout
d099ffa40f use serializer naming strategy for parameter names 2013-03-25 14:40:00 -07:00
William Durand
a9087994da Merge pull request #153 from pylebecq/fix-markdown-formatter
[Formatter] Fixed MarkdownFormatter.
2013-03-22 17:12:31 -07:00
Benjamin Bender
89aec4bfd3 Add a little bit more flexibility to ApiDocExtractor 2013-03-22 14:05:14 +01:00
Pierre-Yves LEBECQ
67db76990e [Formatter] Fixed MarkdownFormatter. 2013-03-21 14:36:52 +01:00
Adrien SAMSON
8fa11944b8 Update FormTypeParser service definition 2013-03-21 10:11:26 +01:00
William Durand
5daf8ac5f6 Merge pull request #149 from pylebecq/fix-float-issue
[Parser] Added float in JmsMetadataParser::isPrimitive().
2013-03-20 11:07:11 -07:00
William Durand
573d564e9a Merge pull request #151 from drgomesp/section-grouping
Fixed condition to display resource url (fixes #146)
2013-03-20 11:05:27 -07:00
Daniel Ribeiro
32e80af4dc Fixed condition to display resource url (fixes #146)
Signed-off-by: Daniel Ribeiro <drgomesp@gmail.com>
2013-03-20 10:02:20 -03:00
Pierre-Yves LEBECQ
7c6c1d5ce0 [Parser] Added float in JmsMetadataParser::isPrimitive(). 2013-03-20 11:16:20 +01:00
Adrien SAMSON
eef1b7db0a Add tests for FormTypeParser 2013-03-19 15:43:30 +01:00
Adrien SAMSON
dee44ee2e1 Fix #147 2013-03-19 15:42:32 +01:00
William DURAND
c8a0115040 Merge pull request #122 from fvilpoix/80_default_string_field
#80 - unrecognized parameters type from form are defaultly set to 'string'
2013-03-17 16:45:23 +01:00
William DURAND
2fd3f33ad8 Fix 8c9b8331d0
* tests
* CS
2013-03-17 16:40:39 +01:00
William DURAND
8c9b8331d0 Merge pull request #138 from drgomesp/section-grouping
Section grouping
2013-03-17 16:13:56 +01:00
William DURAND
04a6292eca Merge pull request #129 from adriensamson/collection-form
Add support for collection type in FormType Parser
2013-03-16 18:54:48 +01:00
William DURAND
026fcc4761 Merge pull request #117 from gregholland/master
Add a config option for a default Accept header
2013-03-16 18:53:19 +01:00
William DURAND
3fe7e15f58 Merge pull request #121 from relaxnow/feature/issue-109-pr2
Issue-109: Fixed infinite recursion on JMS Types that reference themselves or their parents
2013-03-16 18:50:45 +01:00
William DURAND
91a2fc2159 Fix CS 2013-03-16 18:48:34 +01:00
William DURAND
a7a2d8d1bb Fix tests
Related to 43b8f89845
2013-03-16 18:45:41 +01:00
William DURAND
43b8f89845 Merge pull request #123 from fvilpoix/authentication_attribute
adding 'authentication' attribute
2013-03-16 18:22:28 +01:00
drgomesp
718ce472eb Changed constraints back to what it was before the PR 2013-03-08 14:55:46 -03:00
Jordi Boggiano
bfcea36689 Restrict framework-bundle until FOSRest deps are fixed, fixes #144 2013-03-01 22:04:51 +01:00
Jordi Boggiano
abac2a03df Use proper base url by default instead of hardcoded app_dev controller 2013-03-01 21:24:54 +01:00
Jordi Boggiano
b50f3ca185 Merge pull request #143 from stewe/feature-array-collection
[JmsMetadataParser] added support for ArrayCollection
2013-02-27 06:17:00 -08:00
Stefano Sala
f5c938dc02 [JmsMetadataParser] added support for ArrayCollection 2013-02-26 19:24:14 +01:00
drgomesp
381476a2eb Changed html structure and css for sections 2013-02-25 17:27:09 -03:00
drgomesp
5d9accfdb5 #138 Fixed section grouping issue (wrong loop level) 2013-02-21 15:56:10 -03:00
drgomesp
fe96e262ec #138 Changed template to handle the section grouping correctly 2013-02-21 15:47:20 -03:00
drgomesp
9c601664d1 #138 Added section grouping as a higher level key to the resources array 2013-02-21 15:46:59 -03:00
Jordi Boggiano
8522020697 Merge pull request #142 from tonivdv/master
README update with the multiple description for status code.
2013-02-20 06:02:49 -08:00
tonivdv
562e80ccd0 Update README.md
Update with the multiple description for status code.
2013-02-20 14:04:03 +01:00
Jordi Boggiano
6ac6e5b4c4 Merge pull request #141 from tonivdv/master
Support multiple descriptions per status code
2013-02-20 03:47:40 -08:00
Toni Van de Voorde
e0fb5c3afe Support multiple descriptions for a Status Code 2013-02-18 21:07:59 +01:00
Jordi Boggiano
986e1a6174 Fix dev requirements 2013-02-15 13:41:09 +01:00
Jordi Boggiano
9797a16b4c Clarify dependencies to avoid using 2.1+ of this bundle with older jms serializer bundle 2013-02-15 13:33:37 +01:00
Jordi Boggiano
b3d917c9f4 Do not require sf2.2 2013-02-15 13:33:05 +01:00
Jordi Boggiano
f162311a16 CS fix 2013-02-15 13:32:51 +01:00
Jordi Boggiano
efb4bb29dd Merge remote-tracking branch 'stewe/jms_serlializer_1_0_upgrade' 2013-02-15 13:16:59 +01:00
Jordi Boggiano
e3a8fc6df0 Update for latest FOSRestBundle 2013-02-15 11:37:02 +01:00
Stefano Sala
5213b7db71 Fixed version of rest-bundle
Removed check of type string
2013-02-15 10:48:29 +01:00
Stefano Sala
5acf1adced Just fixed some docblock 2013-02-15 09:24:09 +01:00
drgomesp
b4792d1cd5 Changed conditional to work with default value as null 2013-02-14 14:16:54 -02:00
drgomesp
1f6b7b4788 Set the default section to null and added conditional back 2013-02-14 13:52:20 -02:00
drgomesp
301abdc1e7 Removed composer.phar file 2013-02-14 13:43:05 -02:00
drgomesp
3aa6544159 Removed useless conditional 2013-02-14 13:42:23 -02:00
drgomesp
8314471be1 Fixed JMSSerializerBundle version for BC 2013-02-14 13:42:04 -02:00
drgomesp
397b1b9f90 Changed version of JMSSerializerBundle from 0.9.x to 1.0.x 2013-02-14 10:37:21 -02:00
drgomesp
ad4eae7dd7 #25 Added handling of sections on the processCollection method of the AbstractFormatter 2013-02-13 17:08:35 -02:00
drgomesp
b7aa5dca72 #25 Added section property to the ApiDoc class 2013-02-13 17:08:00 -02:00
Stefano Sala
07bb37ac76 Finish upgrade to jms serializer 1.0
Updated deprecated MinLength assertion to Length
Updated array of object parsing
Handled deprecated calls because of using
AbstractType (not sure if it is the best way, though)
2013-02-11 14:42:17 +01:00
Adrien SAMSON
89276b09b7 Add support for collection type in FormType Parser 2013-01-18 15:21:51 +01:00
Greg Holland
826fd35841 update doc to reflect new accept header config param 2013-01-08 16:45:51 -08:00
Greg Holland
62b30fdb87 Fix spacing & indentation 2013-01-08 16:38:52 -08:00
fvilpoix
066da73b05 change icon for authentication parameter 2013-01-03 09:52:49 +01:00
fvilpoix
fdc814e264 Set automaticly the Authentication params when JMS Secure annotation is set 2012-12-27 10:18:15 +01:00
fvilpoix
0aa1619c5f adding 'authentication' attribute 2012-12-26 12:23:28 +01:00
fvilpoix
c1ee7c6705 #80 - unrecognized parameters type from form are defaultly set to 'string' 2012-12-26 11:21:30 +01:00
Boy Baukema
fe76b6df67 Fixed infinite recursion on JMS Types that reference themselves or their parents. 2012-12-20 10:12:50 +01:00
Greg Holland
fef40329a4 Add a config option for a default sandbox accept header 2012-12-11 17:59:03 -08:00
Jordi Boggiano
c946fbcf4f Add glyphicons attribution 2012-12-11 10:42:04 +01:00
William Durand
3eccca509b Merge pull request #116 from baldurrensch/issue-115
Added padlock icon for secure routes (#115)
2012-12-10 11:24:36 -08:00
Baldur Rensch
ef5cbd9b73 Added padlock icon for secure routes
This fixes #115
- Added an https property to the annotation
- Added glyphicon padlock icon
- Updated Templates
- Updated Unit tests
2012-12-10 10:21:04 -08:00
Lukas Kahwe Smith
befeeb3e3f JMSSerializerBundle 1.0 compatibility 2012-12-04 10:27:33 +01:00
Jordi Boggiano
63c19758aa Merge pull request #105 from dalanhurst/master
Fix for JMSSerializerBundle @Type change
2012-11-27 01:50:20 -08:00
Doug Hurst
dad48bba3b Removed IntelliJ stuff from gitignore 2012-11-26 15:41:54 -06:00
Doug Hurst
b42db62be2 #102 - Doh, PHP is not Python 2012-11-26 12:37:44 -06:00
Doug Hurst
035ebfc965 #102 - Fix for JMS @Type change 2012-11-26 12:27:27 -06:00
Jordi Boggiano
5610c032ae Merge pull request #101 from willdurand/fix
Improve doc, update README, "return" param becomes "output" to be more consistent
2012-11-20 06:24:41 -08:00
William DURAND
923043cb2a Improve doc, update README, "return" param becomes "output" to be more
consistent
2012-11-19 18:21:12 +01:00
Jordi Boggiano
588e69ffd7 Merge pull request #100 from alex88/patch-1
Exception handling on unsupported fields types
2012-11-19 02:23:22 -08:00
Alessandro Tagliapietra
2acf49e9ee Added TODO comment to the workaround 2012-11-19 11:15:28 +01:00
Alessandro Tagliapietra
b3d628a31b Exception handling on unsupported fields types
Since the entity (and maybe others) form field type breaks the doc generations, I've added a try{} catch{} exception handler when you try to a compatible field type in the formParser().

It's a workaround for issue https://github.com/nelmio/NelmioApiDocBundle/issues/94.

Later we can add a better support for other types of fields.
2012-11-19 11:09:26 +01:00
Jordi Boggiano
f1e28bb7bb Merge pull request #98 from willdurand/fix
Fixes
2012-11-17 09:26:40 -08:00
William DURAND
b31d439dac Fix tests 2012-11-17 18:09:38 +01:00
William DURAND
1d72183856 Fix CS 2012-11-17 17:53:45 +01:00
William DURAND
2f62db82a9 'type' becomes 'dataType' for consistency purpose 2012-11-17 17:41:12 +01:00
William DURAND
84c82f3b94 Allow HtmlFormatter to be overrided 2012-11-17 17:41:12 +01:00
William DURAND
47112613e8 Refactor formatter, avoid code duplication 2012-11-17 17:41:12 +01:00
William Durand
aee8108413 Merge pull request #97 from FlorianLB/request-param-annotation
RequestParam annotation create a parameter item
2012-11-17 06:07:34 -08:00
FlorianLB
e4e9f62230 CS fix 2012-11-16 12:14:46 +01:00
FlorianLB
6b66edbcda RequestParam annotation create a parameter item 2012-11-14 23:59:06 +01:00
William Durand
74f0778908 Merge pull request #93 from baldurrensch/status_codes
Added status codes
2012-11-13 08:07:33 -08:00
Baldur Rensch
cd45f4b146 Removed unused function 2012-11-13 08:04:04 -08:00
Baldur Rensch
742e53b99b Added status codes 2012-11-13 04:45:07 +00:00
Jordi Boggiano
71f9ec138a Work around _method requirements allowing multiple methods, fixes #87 2012-11-07 22:34:08 +01:00
Jordi Boggiano
35b8b9ca40 Merge pull request #92 from leevigraham/master
A couple of little fixes
2012-11-02 07:58:51 -07:00
Leevi Graham
f4bc5bc5ab Post background should be green not blue 2012-11-03 01:32:00 +11:00
Leevi Graham
55ef512b4d Fixed Syntax in README 2012-11-03 00:59:53 +11:00
Jordi Boggiano
9c7577f7c2 Merge remote-tracking branch 'docteurklein/virtual_property_support' 2012-10-24 15:38:42 +02:00
Klein Florian
197cdfc989 fix cs 2012-10-24 15:36:41 +02:00
Klein Florian
d129db9a53 add virtual property support for jms parser 2012-10-24 15:09:36 +02:00
Jordi Boggiano
1ba258e624 Merge pull request #88 from docteurklein/recursive_parsing
avoid infinite recursion when parsing jms metadata
2012-10-24 02:18:12 -07:00
Klein Florian
db4a375992 avoid infinite recursion when parsing jms metadata 2012-10-24 10:29:08 +02:00
Jordi Boggiano
28f059fff0 Merge pull request #86 from vslinko/allow-to-disable-inline-doc
Make request listener configurable
2012-10-19 01:05:37 -07:00
Jordi Boggiano
bd3e63c479 Merge pull request #85 from vslinko/json-declaration-method
Make request format configurable
2012-10-19 01:04:09 -07:00
Vyacheslav Slinko
ffd9feb797 Improved request_listener configuration 2012-10-17 18:44:14 +04:00
Vyacheslav Slinko
aa70018abe Make documentation on demand configurable 2012-10-17 17:12:36 +04:00
Vyacheslav Slinko
52df5ebf47 Allow to configure default request format 2012-10-17 15:27:20 +04:00
Vyacheslav Slinko
3540bcfcd8 Make response format more configurable 2012-10-17 15:15:35 +04:00
Vyacheslav Slinko
f25c2f1eeb Simplify enum nodes 2012-10-17 05:12:47 +04:00
Vyacheslav Slinko
8e03ef99fa Make JSON declaration method configurable 2012-10-16 23:41:16 +04:00
Jordi Boggiano
6aac4a001e Merge pull request #83 from lsmith77/patch-2
also support symfony 2.2
2012-10-15 05:04:47 -07:00
Lukas Kahwe Smith
766363edec still allow 2.1 2012-10-14 01:06:56 +03:00
Lukas Kahwe Smith
8ab02ecf01 also support symfony 2.2
haven't tested this, but assume it should work fine
2012-10-14 00:43:28 +03:00
William Durand
bd625aebed Merge pull request #81 from docteurklein/fix-supports-formtype-parser
Fix supports formtype parser
2012-10-11 10:02:35 -07:00
Klein Florian
df29283a7f fix cs 2012-10-05 09:36:43 +02:00
Klein Florian
011c59b4d7 do not support form types with required options 2012-10-04 10:37:12 +02:00
Klein Florian
734dd0da7e fix bad variable name 2012-10-03 17:06:56 +02:00
Klein Florian
f9d4fb90cd allow to pass classes wich have constructor arguments 2012-10-03 16:39:07 +02:00
Klein Florian
5c52352eb5 allow to pass classes wich have constructor arguments 2012-10-03 16:35:56 +02:00
William Durand
4cb71512f9 Merge pull request #78 from evillemez/sandbox_update
Updated sandbox template
2012-09-12 11:58:58 -07:00
Evan Villemez
41b8622d8b updated sandbox to not show readonly properties, visually separated filters, requirements and parameters 2012-09-12 14:20:31 -04:00
William Durand
be07de7ffe Merge pull request #76 from evillemez/nested_formatters
Implemented nested parameter handling in formatters
2012-09-11 13:54:42 -07:00
Evan Villemez
a8b656f3a0 fixed cs 2012-09-10 09:46:52 -04:00
Evan Villemez
29c3798124 implemented nested parameter handling in AbstractFormatter, updated MarkdownFormatter and HtmlFormatter to use it 2012-09-10 09:46:02 -04:00
William DURAND
6a7f7b2b3b Merge remote-tracking branch 'evillemez/html_fix' 2012-09-10 13:16:11 +02:00
William Durand
e1a58fd7f7 Merge pull request #75 from evillemez/param_descriptions
implemented param descriptions with JmsMetadataParser
2012-09-10 04:08:11 -07:00
Jordi Boggiano
ee19d631cd Merge pull request #77 from damienalexandre/patch-1
Add a getRoutes method and change property privacy
2012-09-07 02:31:49 -07:00
Damien Alexandre
d2757b6c50 Add a getRoutes method and change property privacy
Overwriting this class for route filtering is not possible / easy 
at the moment. This commit allow to use our own getRoutes method.
2012-09-06 18:45:12 +03:00
Evan Villemez
8c3466f6ed abstracted docblock comment extraction, implemented in JmsMetadataParser to get parameter descriptions 2012-08-31 14:57:42 -04:00
Evan Villemez
5dc7a417c5 fixed table header in method template 2012-08-31 09:54:34 -04:00
William Durand
65c634d32b Merge pull request #72 from evillemez/apidoc_return
Updated README, added `return` to html output
2012-08-31 01:38:10 -07:00
Evan Villemez
da1116a43a stopped displaying readonly input parameters in html template 2012-08-30 15:57:43 -04:00
Evan Villemez
94a3d5858f removed required and readonly properties from html output when documenting return 2012-08-29 14:12:12 -04:00
Evan Villemez
ebba238f1c fixed method template 2012-08-29 13:30:08 -04:00
Evan Villemez
0d7c2272bc updated documentation to reflect return property, updated markdown formatter tests to reflect parameter readonly property 2012-08-28 16:43:47 -04:00
William Durand
d6c50427aa Merge pull request #67 from evillemez/nested_jms
updated JMSMetadataParser to support nested models
2012-08-28 12:29:02 -07:00
Evan Villemez
0af506f151 resolved conflicts 2012-08-28 14:50:40 -04:00
William Durand
cbdddfebb5 Merge pull request #70 from evillemez/apidoc_return
Implemented @ApiDoc `return` property
2012-08-27 23:41:42 -07:00
Evan Villemez
504d5125f9 finished up, tests passing, fixed cs 2012-08-27 13:25:03 -04:00
Evan Villemez
2902ac3cbb working on apidoc return property 2012-08-27 12:56:19 -04:00
Evan Villemez
ea3fb69627 fixed cs 2012-08-27 12:25:18 -04:00
Evan Villemez
01fce41a0e more accurate reporting of arrays and their types 2012-08-27 12:23:23 -04:00
Evan Villemez
d4fe982300 rebased, fixed cs 2012-08-27 12:23:23 -04:00
Evan Villemez
9b94ae4b2c fixed cs 2012-08-27 12:23:23 -04:00
Evan Villemez
185d0e588b updated JMSMetadataParser to support nested models 2012-08-27 12:23:22 -04:00
William Durand
b085338166 Merge pull request #69 from evillemez/jms_fix
fixed JmsMetadataParser::supports
2012-08-27 09:17:09 -07:00
Evan Villemez
be3cbb5ba3 fixed potential error in JmsMetadataParser::supports 2012-08-27 12:11:43 -04:00
William Durand
ee36e599c9 Merge pull request #68 from stof/update_symfony
Fixed the ApiDocExtractor for a change in Symfony 2.1
2012-08-24 09:57:15 -07:00
Christophe Coevoet
5f0d36f3d8 Fixed the ApiDocExtractor for a change in Symfony 2.1
Side note: the new code would also work for the Symfony 2.0 Routing
component, and in a more efficient way by avoiding to compile each route 3
times.
2012-08-24 18:42:54 +02:00
William Durand
6795e118ae Merge pull request #64 from asm89/sandbox-api-key
Add optional api_key parameter to sandbox
2012-08-23 08:24:33 -07:00
Alexander
fc5b8c4e63 Add optional api_key parameter to sandbox 2012-08-23 16:53:08 +02:00
William Durand
594c2faf26 Merge pull request #61 from evillemez/jms_parser
Implemented JmsMetadataParser
2012-08-23 06:19:33 -07:00
William Durand
3028cb63d8 Merge pull request #66 from tonivdv/master
Fixed rendering issue when used with FOSRestBundle
2012-08-23 06:19:20 -07:00
Toni Van de Voorde
2d0fc7fc61 Fixed rendering issue when used with FOSRestBundle and configured to not
accept html (e.g. only accept json & xml)
2012-08-23 15:09:56 +02:00
William Durand
ed39180c6b Merge pull request #63 from asm89/fix-grouping
Remove .{_format} from routes used for resource grouping
2012-08-10 08:52:57 -07:00
Alexander
911aaae0fd Remove .{_format} from routes used for resource grouping 2012-08-10 11:38:01 +02:00
Evan Villemez
1cf7e18d61 fixed README, other minor things 2012-08-08 10:21:56 -04:00
Evan Villemez
034e13a20e restored fosrestbundle to composer.json 2012-08-07 22:09:23 -04:00
Evan Villemez
68767f6c72 fixed cs 2012-08-07 21:57:36 -04:00
Evan Villemez
149f282481 tests passing with JmsMetadataParser, but work still to do on required and description properties 2012-08-07 21:47:33 -04:00
Evan Villemez
b71bc8bf3f started on implementing JmsMetadataParser and tests 2012-08-07 17:50:58 -04:00
William Durand
4b69e1149a Merge pull request #53 from evillemez/parser_interface
Implemented `ParserInterface`
2012-08-06 03:12:17 -07:00
William DURAND
42b9d10306 Fix travis-ci config 2012-08-06 12:04:42 +02:00
William DURAND
2e338d08bc Fix composer.json 2012-08-06 12:02:19 +02:00
William DURAND
a643773902 fix css 2012-07-27 10:33:15 +02:00
William DURAND
83c91a3d9d Add support for nested form types 2012-07-27 10:33:06 +02:00
William DURAND
15a4558277 Add country type 2012-07-27 10:33:06 +02:00
William Durand
6ee4d9014d Merge pull request #60 from gordalina/master
Add requirement text input fields
2012-07-26 04:38:58 -07:00
Samuel Gordalina
87475f5b3a Add requirement input text fields 2012-07-26 12:34:43 +01:00
William Durand
c03624f265 Merge pull request #58 from gordalina/link-unlink-methods
Add LINK and UNLINK method colors
2012-07-25 05:20:17 -07:00
William Durand
593b70ec04 Merge pull request #57 from gordalina/css-method-fixes
CSS Fixes
2012-07-25 05:19:48 -07:00
Samuel Gordalina
5613750bda Add LINK and UNLINK method colors 2012-07-25 13:00:12 +01:00
Samuel Gordalina
65631c23c7 Remove double border in content 2012-07-25 12:59:21 +01:00
Samuel Gordalina
ba05346804 Default color for http methods is softer and pleasent 2012-07-25 12:34:48 +01:00
Evan Villemez
11efee0f65 tests passing 2012-07-24 16:14:11 -04:00
Evan Villemez
89f2044581 fixed compiler pass, updated README 2012-07-24 14:39:43 -04:00
William Durand
f9b9c818e8 Merge pull request #52 from Bladrak/patch-1
Fixed BC Break from master
2012-07-24 08:58:52 -07:00
Evan Villemez
b9e8d61082 changed inputClass to input, refactored method and class names accordingly, fixed cs in several places 2012-07-23 15:44:37 -04:00
Evan Villemez
b2a2426f76 fixed check for wrong class in FormTypeParser, fixed CS in ParserInterface 2012-07-23 13:27:58 -04:00
Evan Villemez
06e3a2256b added a ParserInterface, refactored how Parsers are registered in the ApiDocExtractor, changed formType to inputClass in ApiDocExtractor 2012-07-23 12:54:56 -04:00
Hugo Briand
fa4a31e75b Update composer.json 2012-07-23 13:03:54 +03:00
Hugo Briand
d5de1148bf Update composer.json 2012-07-23 13:02:55 +03:00
Hugo Briand
5f6c9362a7 Fixed BC Break from master (commit d072f35ea0 ) 2012-07-23 13:01:23 +03:00
William Durand
a18bbb0013 Merge pull request #51 from lsmith77/patch-1
form master BC break
2012-07-22 08:44:05 -07:00
Lukas Kahwe Smith
8d7d81b5be form master BC break 2012-07-22 00:27:51 +03:00
William DURAND
ec757e3790 Change service name to fit previous commit 2012-07-20 17:40:33 +02:00
William DURAND
e35e977479 Fix Twig filter name 2012-07-20 17:38:39 +02:00
William DURAND
ff817a567c Update README 2012-07-20 17:35:38 +02:00
Jordi Boggiano
48d46d67eb Merge pull request #50 from willdurand/markdown
Remove hard dependency with KnpMarkdownBundle
2012-07-20 08:09:16 -07:00
William DURAND
64988d00c1 Remove hard dependency with KnpMarkdownBundle 2012-07-20 17:07:20 +02:00
William Durand
09fbfee03b Update README 2012-07-20 12:58:14 +03:00
William DURAND
974cd9f706 Fix travis-ci config due to a Composer issue 2012-07-20 11:07:19 +02:00
Jordi Boggiano
b49ea21c36 Merge pull request #49 from willdurand/refactoring
Refactoring
2012-07-20 01:19:52 -07:00
William DURAND
de079f0a91 Fix CSS 2012-07-20 09:42:24 +02:00
William DURAND
a8e0b24b95 Fix tests, fix markdown/html requirements output 2012-07-20 02:02:45 +02:00
William DURAND
cca97cf6af Refactoring
Move logic to extract data in the Extractor
Remove logic in the AbstractFormatter
Use ApiDoc class as data container
Update tests
Add test to prove the bug with FOSRestBundle annotations (\\d+ instead of \d+)
2012-07-20 01:32:16 +02:00
Jordi Boggiano
8f3327b376 Merge pull request #46 from willdurand/no-sandbox-cmd
Add new option to the command line: --no-sandbox
2012-07-18 07:50:22 -07:00
William DURAND
374ee9a878 Add new option to the command line: --no-sandbox 2012-07-18 16:51:53 +02:00
William DURAND
bee518f92e Allow to disable the sandbox, refactor the configuration. Fix #37 2012-07-18 13:21:31 +02:00
Jordi Boggiano
636c4cab75 Merge pull request #45 from willdurand/fix-37
Allow to disable the sandbox, refactor the configuration. Fix #37
2012-07-18 04:19:26 -07:00
Jordi Boggiano
9e37388bb2 Merge pull request #44 from willdurand/fix-42
Dump all assets in order to correctly render the generated HTML page, ev...
2012-07-18 04:04:27 -07:00
William DURAND
208d25f825 Dump all assets in order to correctly render the generated HTML page, even in offline mode. Fix #42 2012-07-18 12:56:11 +02:00
William DURAND
e7152678a7 Fix tests 2012-07-18 12:46:22 +02:00
Jordi Boggiano
0868d39e9a Merge pull request #43 from willdurand/fix-13
Add support for FOSRest annotations. Fix #13
2012-07-18 03:29:41 -07:00
William DURAND
163d96a295 Add support for FOSRest annotations. Fix #13 2012-07-18 12:23:57 +02:00
William DURAND
f8ab93a167 Fix FormTypeParser to reflect last Symfony2 changes
See: cd7835d8d2 (L5L97)
2012-07-14 14:12:12 +02:00
William DURAND
b19822dde5 Merge branch '1.0.x' 2012-07-14 13:58:37 +02:00
William DURAND
59a6b87f71 Add div instead of p for documentation content 2012-07-14 13:57:58 +02:00
Samuel Gordalina
1c4ed7902d Incremented line-height on <pre> elements 2012-07-14 13:57:58 +02:00
William DURAND
0ab2278667 Merge branch '1.0.x' 2012-07-13 16:08:29 +02:00
William DURAND
da12c85ddc Update README 2012-07-13 16:06:01 +02:00
William DURAND
c4911f57d2 Fix tests 2012-07-13 16:04:29 +02:00
William DURAND
dfcf41b0f8 Merge branch '1.0.x' 2012-07-13 15:54:40 +02:00
William DURAND
6c3aeff227 Fix presentation of documentation 2012-07-13 15:53:24 +02:00
William DURAND
e2e647bb03 Merge branch '1.0.x' 2012-07-13 15:32:51 +02:00
William DURAND
fce0f5d5a6 Fix extractor, markdown formatter, and tests 2012-07-13 15:31:53 +02:00
William DURAND
4b1990b688 Fix stuff 2012-07-13 15:31:53 +02:00
William DURAND
3ec87522b2 Add markdown transformation in HTML output, fix Markdown formatter 2012-07-13 15:31:53 +02:00
William DURAND
a0a9170d1c Add knplabs/knp-markdown-bundle 2012-07-13 15:31:53 +02:00
Samuel Gordalina
dccebe0439 Add documentation in README 2012-07-13 15:31:53 +02:00
Samuel Gordalina
07e6a19dd4 Updated code to pass the tests 2012-07-13 15:31:53 +02:00
William Durand
f247d6af92 Merge pull request #39 from gordalina/1.0.x
Added ability to extract inline documentation from docblock
2012-07-13 05:48:05 -07:00
Samuel Gordalina
2b02733ee9 Added ability to extract inline documentation from docblock 2012-07-13 13:44:11 +01:00
William DURAND
298b75805d Fix tests 2012-07-12 12:13:24 +02:00
William DURAND
f678fd4fe9 Merge branch '1.0.x' 2012-07-10 14:30:59 +02:00
William Durand
736cf22bfb Merge pull request #36 from gordalina/1.0.x
Add content textarea to sandbox
2012-07-06 00:20:42 -07:00
Samuel Gordalina
4fdc526b42 Change content class name to request-content 2012-07-06 00:23:38 +02:00
Samuel Gordalina
557e4523de Add content textarea to sandbox
Problem: Its not possible to POST content in the sandbox.
With this change it becomes possible to, e.g.: POST JSON data
to the api.

Note: The content will take precedence over parameters if set.
2012-07-05 19:44:35 +02:00
William DURAND
23451fe192 Merge branch '1.0.x' 2012-07-05 11:13:15 +02:00
William Durand
ca18f9b4d3 Merge pull request #34 from stof/clean_css
Clean css
2012-07-05 00:59:47 -07:00
William DURAND
f9012ba76b Update composer, fix tests 2012-07-05 09:58:25 +02:00
Christophe Coevoet
9d2021458f Removed more unused rules 2012-07-05 01:59:18 +02:00
Christophe Coevoet
560d24152d Removed more useless code 2012-07-05 01:50:23 +02:00
Christophe Coevoet
adb0021660 Removed useless rules 2012-07-05 01:46:26 +02:00
Christophe Coevoet
f874f2517d Moved the header link styles with the other header rules 2012-07-05 01:44:06 +02:00
Christophe Coevoet
3a9a2849e3 Removed the duplication for the different methods 2012-07-05 01:43:32 +02:00
Christophe Coevoet
5a36d673e8 Simplified the heading selectors and removed unused rules 2012-07-05 00:56:48 +02:00
Christophe Coevoet
77c5ab1336 Simplified some selectors 2012-07-05 00:56:35 +02:00
Christophe Coevoet
0b38a5f761 Removed unused CSS 2012-07-05 00:39:53 +02:00
Samuel Gordalina
ba8d1ae8fc Corrected indentation 2012-07-04 23:42:40 +02:00
Samuel Gordalina
5b78bee151 Moved response headers above response body with a click to expand/shrink 2012-07-04 23:42:38 +02:00
William Durand
ceb9fc021f Merge pull request #31 from K-Phoen/fix-sandbox-default-params
Fix: only filters were used to build the sandbox form
2012-07-02 13:57:36 -07:00
kphoen
c8792f8b53 Fix: only filters were used to build the sandbox form 2012-07-02 21:52:03 +01:00
William DURAND
b74273b72d Merge branch '1.0.x' 2012-06-28 10:40:57 +02:00
William DURAND
d5ab75fd9e Added button to get raw response in the sandbox 2012-06-28 10:40:33 +02:00
William DURAND
519b356374 Merge branch '1.0.x'
Conflicts:
	Form/Extension/DescriptionFormTypeExtension.php
	Parser/FormTypeParser.php
	Tests/Fixtures/Form/TestType.php
2012-06-27 10:15:57 +02:00
kphoen
e6438d657f Add: sandbox, to easily try API methods 2012-06-27 09:45:06 +02:00
William Durand
5bcdbcb808 Merge pull request #28 from stof/service_form_types
Added the support of form types defined as services
2012-06-21 00:19:49 -07:00
William Durand
9590048b3e Merge pull request #29 from stof/patch_style
Added the CSS for the patch method
2012-06-21 00:18:14 -07:00
Christophe Coevoet
f7c2425265 Added the CSS for the patch method
Closes #18
2012-06-21 00:43:28 +02:00
Christophe Coevoet
5b70c775c0 Added a test for types defined as services 2012-06-21 00:12:21 +02:00
Christophe Coevoet
fd8ee2679a Added the support of form types defined as services 2012-06-21 00:11:32 +02:00
William Durand
c127a1c4bf Merge pull request #27 from stof/fix_testsuite
Fixed the bootstrap of the testsuite
2012-06-20 14:24:41 -07:00
Christophe Coevoet
32b40deda8 Fixed the testsuite 2012-06-20 23:19:34 +02:00
Christophe Coevoet
cf36b414b1 Fixed the bootstrap of the testsuite 2012-06-20 22:53:27 +02:00
William Durand
1077e710e1 Merge pull request #24 from vicb/patch-1
fix indentation
2012-05-28 02:13:54 -07:00
Victor Berchet
383529a607 fix indentation 2012-05-28 10:56:37 +03:00
William Durand
82813e1ae1 Merge pull request #23 from vicb/form2.1
[Form] Updated to sf2.1
2012-05-27 14:36:52 -07:00
Victor Berchet
fd00844dc6 [Form] Update to sf2.1 2012-05-27 22:56:01 +02:00
William Durand
88646a23f8 Merge pull request #22 from K-Phoen/fix-buildForm-buildView-tests
Updated tests
2012-05-26 09:56:56 -07:00
kphoen
c8bc72fae1 Updated tests
I forgot to update the tests in the PR #21.
2012-05-26 18:43:11 +02:00
William Durand
5a5d5ff836 Merge pull request #21 from K-Phoen/fix-buildForm-buildView
Fix: buildForm() and buildView signatures
2012-05-26 09:34:26 -07:00
kphoen
6c75b13e36 Fix: buildForm() and buildView signatures
Their signatures changed with the recent modifications done in symfony's
form component.
2012-05-26 18:20:01 +02:00
William DURAND
43033152b7 Aliased master to 2.* 2012-05-23 09:44:34 +02:00
William Durand
40d3750b20 Merge pull request #15 from stof/fix_form_parser
Fix form parser
2012-05-22 23:39:06 -07:00
Jordi Boggiano
16497845be Remove 5.3.2 from travis builds 2012-05-23 00:22:51 +03:00
132 changed files with 15933 additions and 2410 deletions

47
.github/workflows/ci.yml vendored Normal file
View 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
View file

@ -1,3 +1,5 @@
vendor/
composer.lock
phpunit.xml
.idea
.phpunit.result.cache

11
.php-cs-fixer.dist.php Normal file
View 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');

View file

@ -1,11 +0,0 @@
language: php
php:
- 5.3
- 5.4
before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --dev
script: phpunit

View file

@ -1,102 +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;
/**
* @Annotation
*/
class ApiDoc
{
/**
* @var array
*/
private $filters = array();
/**
* @var string
*/
private $formType = null;
/**
* @var string
*/
private $description = null;
/**
* @var Boolean
*/
private $isResource = false;
public function __construct(array $data)
{
if (isset($data['formType'])) {
$this->formType = $data['formType'];
} 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->filters[$name] = $filter;
}
}
if (isset($data['description'])) {
$this->description = $data['description'];
}
$this->isResource = isset($data['resource']) && $data['resource'];
}
/**
* @return array
*/
public function getFilters()
{
return $this->filters;
}
/**
* @return string|null
*/
public function getFormType()
{
return $this->formType;
}
/**
* @return string|null
*/
public function getDescription()
{
return $this->description;
}
/**
* @param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* @return Boolean
*/
public function isResource()
{
return $this->isResource;
}
}

500
Attribute/ApiDoc.php Normal file
View 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;
}
}
}

33
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,33 @@
Contributing
============
First of all, **thank you** for contributing, **you are awesome**!
Here are a few rules to follow in order to ease code reviews, and discussions before
maintainers accept and merge your work.
You MUST follow the [PSR-1](http://www.php-fig.org/psr/1/) and
[PSR-2](http://www.php-fig.org/psr/2/). If you don't know about any of them, you
should really read the recommendations. Can't wait? Use the [PHP-CS-Fixer
tool](http://cs.sensiolabs.org/).
You MUST run the test suite.
You MUST write (or update) unit tests.
You SHOULD write documentation.
Please, write [commit messages that make
sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html),
and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing)
before submitting your Pull Request.
One may ask you to [squash your
commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)
too. This is used to "clean" your Pull Request before merging it (we don't want
commits such as `fix tests`, `fix 2`, `fix 3`, etc.).
Also, while creating your Pull Request on GitHub, you MUST write a description
which gives the context and/or explains why you are creating it.
Thank you!

View file

@ -11,53 +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\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]
)
->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') ?? '');
}
$extractedDoc = $this->getContainer()->get('nelmio_api_doc.extractor.api_doc_extractor')->all();
if ($input->hasOption('api-version')) {
$formatter->setVersion($input->getOption('api-version'));
}
if ($formatter instanceof HtmlFormatter && $input->getOption('no-sandbox')) {
$formatter->setEnableSandbox(false);
}
$extractedDoc = $input->hasOption('api-version') ?
$this->apiDocExtractor->allForVersion($input->getOption('api-version'), $view) :
$this->apiDocExtractor->all($view);
$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;
}
}

View 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);
}
}

View file

@ -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);
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);
}
}

64
DataTypes.php Normal file
View file

@ -0,0 +1,64 @@
<?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;
/**
* All the supported data-types which will be specified in the `actualType` properties in parameters.
*
* @author Bez Hermoso <bez@activelamp.com>
*/
class DataTypes
{
public const INTEGER = 'integer';
public const FLOAT = 'float';
public const STRING = 'string';
public const BOOLEAN = 'boolean';
public const FILE = 'file';
public const ENUM = 'choice';
public const COLLECTION = 'collection';
public const MODEL = 'model';
public const DATE = 'date';
public const DATETIME = 'datetime';
public const TIME = 'time';
/**
* Returns true if the supplied `actualType` value is considered a primitive type. Returns false, otherwise.
*
* @param string $type
*
* @return bool
*/
public static function isPrimitive($type)
{
return in_array(strtolower($type), [
static::INTEGER,
static::FLOAT,
static::STRING,
static::BOOLEAN,
static::FILE,
static::DATE,
static::DATETIME,
static::TIME,
static::ENUM,
]);
}
}

View file

@ -16,14 +16,161 @@ 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('@NelmioApiDoc/Components/motd.html.twig')->end()
->end()
->end()
->arrayNode('request_listener')
->beforeNormalization()
->ifTrue(function ($a) { return is_bool($a); })
->then(function ($a) { return ['enabled' => $a]; })
->end()
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultTrue()->end()
->scalarNode('parameter')->defaultValue('_doc')->end()
->end()
->end()
->arrayNode('sandbox')
->addDefaultsIfNotSet()
->children()
->scalarNode('enabled')->defaultTrue()->end()
->scalarNode('endpoint')->defaultNull()->end()
->scalarNode('accept_type')->defaultNull()->end()
->arrayNode('body_format')
->addDefaultsIfNotSet()
->beforeNormalization()
->ifString()
->then(function ($v) { return ['default_format' => $v]; })
->end()
->children()
->arrayNode('formats')
->defaultValue(['form', 'json'])
->prototype('scalar')->end()
->end()
->enumNode('default_format')
->values(['form', 'json'])
->defaultValue('form')
->end()
->end()
->end()
->arrayNode('request_format')
->addDefaultsIfNotSet()
->children()
->arrayNode('formats')
->defaultValue([
'json' => 'application/json',
'xml' => 'application/xml',
])
->prototype('scalar')->end()
->end()
->enumNode('method')
->values(['format_param', 'accept_header'])
->defaultValue('format_param')
->end()
->scalarNode('default_format')->defaultValue('json')->end()
->end()
->end()
->arrayNode('authentication')
->children()
->scalarNode('delivery')
->isRequired()
->validate()
->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(['basic', 'bearer'])
->end()
->booleanNode('custom_endpoint')->defaultFalse()->end()
->end()
->validate()
->ifTrue(function ($v) {
return 'http' === $v['delivery'] && !$v['type'];
})
->thenInvalid('"type" is required when using http delivery.')
->end()
// http_basic BC
->beforeNormalization()
->ifTrue(function ($v) {
return 'http_basic' === $v['delivery'];
})
->then(function ($v) {
$v['delivery'] = 'http';
$v['type'] = 'basic';
return $v;
})
->end()
->beforeNormalization()
->ifTrue(function ($v) {
return 'http' === $v['delivery'];
})
->then(function ($v) {
if ('http' === $v['delivery'] && !isset($v['name'])) {
$v['name'] = 'Authorization';
}
return $v;
})
->end()
->end()
->booleanNode('entity_to_choice')->defaultTrue()->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;
}

View 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\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class ExtractorHandlerCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$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(2, $handlers)
;
}
}

View 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)]);
}
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Nelmio\ApiDocBundle\DependencyInjection;
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.
*
* They are only loaded when the corresponding library is installed and enabled.
*/
class LoadExtractorParsersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$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')) {
$loader->load('services.form.xml');
}
// validation may not be installed/enabled, if it is, load that config as well
if ($container->has('validator.mapping.class_metadata_factory')) {
$loader->load('services.validation.xml');
}
// JMS may or may not be installed, if it is, load that config as well
if ($container->hasDefinition('jms_serializer.serializer')) {
$loader->load('services.jms.xml');
}
}
}

View file

@ -11,28 +11,94 @@
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();
$config = $processor->processConfiguration($configuration, $configs);
$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.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']);
$container->setParameter('nelmio_api_doc.sandbox.body_format.default_format', $config['sandbox']['body_format']['default_format']);
$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('request_listener.xml');
$loader->load('services.xml');
if ($config['request_listener']['enabled']) {
$container->setParameter('nelmio_api_doc.request_listener.parameter', $config['request_listener']['parameter']);
$loader->load('request_listener.xml');
}
if (isset($config['sandbox']['authentication'])) {
$container->setParameter('nelmio_api_doc.sandbox.authentication', $config['sandbox']['authentication']);
}
// backwards compatibility for Symfony2.1 https://github.com/nelmio/NelmioApiDocBundle/issues/231
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');
}
/**
* @return string
*/
public function getNamespace()
{
return 'http://nelmio.github.io/schema/dic/nelmio_api_doc';
}
/**
* @return string
*/
public function getXsdValidationBasePath()
{
return __DIR__ . '/../Resources/config/schema';
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Nelmio\ApiDocBundle\DependencyInjection;
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): void
{
if (false === $container->hasDefinition('nelmio_api_doc.extractor.api_doc_extractor')) {
return;
}
$definition = $container->getDefinition('nelmio_api_doc.extractor.api_doc_extractor');
// find registered parsers and sort by priority
$sortedParsers = [];
foreach ($container->findTaggedServiceIds('nelmio_api_doc.extractor.parser') as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
$priority = $attributes['priority'] ?? 0;
$sortedParsers[$priority][] = $id;
}
}
// add parsers if any
if (!empty($sortedParsers)) {
krsort($sortedParsers);
$sortedParsers = call_user_func_array('array_merge', $sortedParsers);
// add method call for each registered parsers
foreach ($sortedParsers as $id) {
$definition->addMethodCall('addParser', [new Reference($id)]);
}
}
}
}

View 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
View 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

View file

@ -14,52 +14,54 @@ namespace Nelmio\ApiDocBundle\EventListener;
use Nelmio\ApiDocBundle\Extractor\ApiDocExtractor;
use Nelmio\ApiDocBundle\Formatter\FormatterInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
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;
public function __construct(ApiDocExtractor $extractor, FormatterInterface $formatter)
/**
* @var string
*/
protected $parameter;
public function __construct(ApiDocExtractor $extractor, FormatterInterface $formatter, $parameter)
{
$this->extractor = $extractor;
$this->formatter = $formatter;
$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;
}
$request = $event->getRequest();
if (!$request->query->get('_doc')) {
if (!$request->query->has($this->parameter)) {
return;
}
$controller = $request->attributes->get('_controller');
$route = $request->attributes->get('_route');
$route = $request->attributes->get('_route');
if (null !== $array = $this->extractor->get($controller, $route)) {
$result = $this->formatter->formatOne($array['annotation'], $array['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',
]));
}
}
}

View file

@ -11,60 +11,108 @@
namespace Nelmio\ApiDocBundle\Extractor;
use Doctrine\Common\Annotations\Reader;
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;
class ApiDocExtractor
{
const ANNOTATION_CLASS = 'Nelmio\\ApiDocBundle\\Annotation\\ApiDoc';
/**
* @var ParserInterface[]
*/
protected array $parsers = [];
/**
* @var \Symfony\Component\DependencyInjection\ContainerInterface
* @param HandlerInterface[] $handlers
* @param string[] $excludeSections
*/
private $container;
public function __construct(
protected RouterInterface $router,
protected DocCommentExtractor $commentExtractor,
protected array $handlers,
protected array $excludeSections,
) {
}
/**
* @var \Symfony\Component\Routing\RouterInterface
* Return a list of route to inspect for ApiDoc annotation
* You can extend this method if you don't want all the routes
* to be included.
*
* @return Route[] An array of routes
*/
private $router;
/**
* @var \Doctrine\Common\Annotations\Reader
*/
private $reader;
public function __construct(ContainerInterface $container, RouterInterface $router, Reader $reader)
public function getRoutes(): array
{
$this->container = $container;
$this->router = $router;
$this->reader = $reader;
return $this->router->getRouteCollection()->all();
}
/*
* Extracts annotations from all known routes
*/
public function all($view = ApiDoc::DEFAULT_VIEW): array
{
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;
}
/**
* Returns an array of data where each data is an array with the following keys:
* - annotation
* - route
* - resource
*
* @return array
* @param array $routes array of Route-objects for which the annotations should be extracted
*/
public function all()
public function extractAnnotations(array $routes, $view = ApiDoc::DEFAULT_VIEW): array
{
$array = array();
$resources = array();
$array = [];
$resources = [];
foreach ($routes as $route) {
if (!$route instanceof Route) {
throw new \InvalidArgumentException(sprintf('All elements of $routes must be instances of Route. "%s" given', gettype($route)));
}
foreach ($this->router->getRouteCollection()->all() as $route) {
if ($method = $this->getReflectionMethod($route->getDefault('_controller'))) {
if ($annot = $this->reader->getMethodAnnotation($method, self::ANNOTATION_CLASS)) {
if ($annot->isResource()) {
$resources[] = $route->getPattern();
$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->getPath() ?: '');
}
}
$array[] = $this->getData($annot, $route, $method);
$array[] = ['annotation' => $this->extractData($annotation, $route, $method)];
}
}
}
@ -72,10 +120,10 @@ class ApiDocExtractor
rsort($resources);
foreach ($array as $index => $element) {
$hasResource = false;
$pattern = $element['route']->getPattern();
$path = $element['annotation']->getRoute()->getPath() ?: '';
foreach ($resources as $resource) {
if (0 === strpos($pattern, $resource)) {
if (str_starts_with($path, $resource) || $resource === $element['annotation']->getResource()) {
$array[$index]['resource'] = $resource;
$hasResource = true;
@ -88,22 +136,27 @@ class ApiDocExtractor
}
}
$methodOrder = array('GET', 'POST', 'PUT', 'DELETE');
usort($array, function($a, $b) use ($methodOrder) {
$methodOrder = ['GET', 'POST', 'PUT', 'DELETE'];
usort($array, function ($a, $b) use ($methodOrder) {
if ($a['resource'] === $b['resource']) {
if ($a['route']->getPattern() === $b['route']->getPattern()) {
$methodA = array_search($a['route']->getRequirement('_method'), $methodOrder);
$methodB = array_search($b['route']->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['route']->getRequirement('_method'), $b['route']->getRequirement('_method'));
return strcmp(
implode('|', $a['annotation']->getRoute()->getMethods()),
implode('|', $b['annotation']->getRoute()->getMethods())
);
}
return $methodA > $methodB ? 1 : -1;
}
return strcmp($a['route']->getPattern(), $b['route']->getPattern());
return strcmp(
$a['annotation']->getRoute()->getPath(),
$b['annotation']->getRoute()->getPath()
);
}
return strcmp($a['resource'], $b['resource']);
@ -113,25 +166,21 @@ class ApiDocExtractor
}
/**
* Returns the ReflectionMethod for the given controller string
* 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);
$class = get_class($this->container->get($controller));
$this->container->leaveScope('request');
}
}
if (isset($class) && isset($method)) {
@ -145,20 +194,19 @@ class ApiDocExtractor
}
/**
* Returns an array containing two values with the following keys:
* - annotation
* - route
* Returns an ApiDoc annotation.
*
* @param string $controller
* @param Route $route
* @return array|null
* @param string $route
*
* @return ApiDoc|null
*/
public function get($controller, $route)
{
if ($method = $this->getReflectionMethod($controller)) {
if ($annot = $this->reader->getMethodAnnotation($method, self::ANNOTATION_CLASS)) {
if ($annotation = $this->getMethodApiDoc($method)) {
if ($route = $this->router->getRouteCollection()->get($route)) {
return $this->getData($annot, $route, $method);
return $this->extractData($annotation, $route, $method);
}
}
}
@ -166,58 +214,390 @@ 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();
}
/**
* Allows to add more data to the ApiDoc object, and
* returns an array containing the following keys:
* - annotation
* - route
* Registers a class parser to use for parsing input class metadata
*/
public function addParser(ParserInterface $parser): void
{
$this->parsers[] = $parser;
}
/**
* Returns a new ApiDoc instance with more data.
*
* @return ApiDoc
*/
protected function extractData(ApiDoc $annotation, Route $route, \ReflectionMethod $method)
{
// create a new annotation
$annotation = clone $annotation;
// doc
$annotation->setDocumentation($this->commentExtractor->getDocCommentText($method));
// parse annotations
$this->parseAnnotations($annotation, $route, $method);
// 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 (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));
}
}
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 ('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 = [];
$supportedParsers = [];
$normalizedOutput = $this->normalizeClassParameter($output);
foreach ($this->getParsers($normalizedOutput) as $parser) {
if ($parser->supports($normalizedOutput)) {
$supportedParsers[] = $parser;
$response = $this->mergeParameters($response, $parser->parse($normalizedOutput));
}
}
foreach ($supportedParsers as $parser) {
if ($parser instanceof PostParserInterface) {
$mp = $parser->postParse($normalizedOutput, $response);
$response = $this->mergeParameters($response, $mp);
}
}
$response = $this->clearClasses($response);
$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;
}
protected function normalizeClassParameter($input)
{
$defaults = [
'class' => '',
'groups' => [],
'options' => [],
];
// normalize strings
if (is_string($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
if (isset($input['groups']) && is_string($input['groups'])) {
$input['groups'] = array_map('trim', explode(',', $input['groups']));
}
return array_merge($defaults, $input);
}
/**
* Merges two parameter arrays together. This logic allows documentation to be built
* from multiple parser passes, with later passes extending previous passes:
* - Boolean values are true if any parser returns true.
* - Requirement parameters are concatenated.
* - Other string values are overridden by later parsers when present.
* - Array parameters are recursively merged.
* - Non-null default values prevail over null default values. Later values overrides previous defaults.
*
* 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)
{
$params = $p1;
foreach ($p2 as $propname => $propvalue) {
if (null === $propvalue) {
unset($params[$propname]);
continue;
}
if (!isset($p1[$propname])) {
$params[$propname] = $propvalue;
} elseif (is_array($propvalue)) {
$v1 = $p1[$propname];
foreach ($propvalue as $name => $value) {
if (is_array($value)) {
if (isset($v1[$name]) && is_array($v1[$name])) {
$v1[$name] = $this->mergeParameters($v1[$name], $value);
} else {
$v1[$name] = $value;
}
} elseif (null !== $value) {
if (in_array($name, ['required', 'readonly'])) {
$v1[$name] = $v1[$name] || $value;
} elseif ('requirement' === $name) {
if (isset($v1[$name])) {
$v1[$name] .= ', ' . $value;
} else {
$v1[$name] = $value;
}
} elseif ('default' === $name) {
if (isset($v1[$name])) {
$v1[$name] = $value ?? $v1[$name];
} else {
$v1[$name] = $value ?? null;
}
} else {
$v1[$name] = $value;
}
}
}
$params[$propname] = $v1;
}
}
return $params;
}
/**
* Parses annotations for a given method, and adds new information to the given ApiDoc
* annotation. Useful to extract information from the FOSRestBundle annotations.
*/
protected function parseAnnotations(ApiDoc $annotation, Route $route, \ReflectionMethod $method): void
{
foreach ($this->handlers as $handler) {
$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.
*
* @return array The cleared array.
*/
protected function clearClasses($array)
{
if (is_array($array)) {
unset($array['class']);
foreach ($array as $name => $item) {
$array[$name] = $this->clearClasses($item);
}
}
return $array;
}
/**
* Populates the `dataType` properties in the parameter array if empty. Recurses through children when necessary.
*
* @param ApiDoc $annotation
* @param Route $route
* @param \ReflectionMethod $method
* @return array
*/
protected function getData(ApiDoc $annotation, Route $route, \ReflectionMethod $method)
protected function generateHumanReadableTypes(array $array)
{
$docblock = $this->getDocComment($method);
foreach ($array as $name => $info) {
if (empty($info['dataType']) && array_key_exists('subType', $info)) {
$array[$name]['dataType'] = $this->generateHumanReadableType($info['actualType'], $info['subType']);
}
if (null === $annotation->getDescription()) {
$comments = explode("\n @", $docblock);
// just set the first line
$comment = trim($comments[0]);
$comment = preg_replace("#[\n]+#", ' ', $comment);
$comment = preg_replace('#[ ]+#', ' ', $comment);
if ('@' !== substr($comment, 0, 1)) {
$annotation->setDescription($comment);
if (isset($info['children'])) {
$array[$name]['children'] = $this->generateHumanReadableTypes($info['children']);
}
}
$paramDocs = array();
foreach (explode("\n", $docblock) as $line) {
if (preg_match('{^@param (.+)}', trim($line), $matches)) {
$paramDocs[] = $matches[1];
}
}
$route->setOptions(array_merge($route->getOptions(), array('_paramDocs' => $paramDocs)));
return array('annotation' => $annotation, 'route' => $route);
return $array;
}
protected function getDocComment(\Reflector $reflected)
/**
* Creates a human-readable version of the `actualType`. `subType` is taken into account.
*
* @param string $actualType
* @param string $subType
*
* @return string
*/
protected function generateHumanReadableType($actualType, $subType)
{
$comment = $reflected->getDocComment();
if (DataTypes::MODEL == $actualType) {
if ($subType && class_exists($subType)) {
$parts = explode('\\', $subType);
// let's clean the doc block
$comment = str_replace('/**', '', $comment);
$comment = str_replace('*', '', $comment);
$comment = str_replace('*/', '', $comment);
$comment = str_replace("\r", '', trim($comment));
$comment = preg_replace("#^\n[ \t]+[*]?#i", "\n", trim($comment));
$comment = preg_replace("#[\t ]+#i", ' ', trim($comment));
$comment = str_replace("\"", "\\\"", $comment);
return sprintf('object (%s)', end($parts));
}
return $comment;
return sprintf('object (%s)', $subType);
}
if (DataTypes::COLLECTION == $actualType) {
if (DataTypes::isPrimitive($subType)) {
return sprintf('array of %ss', $subType);
}
if ($subType && class_exists($subType)) {
$parts = explode('\\', $subType);
return sprintf('array of objects (%s)', end($parts));
}
return sprintf('array of objects (%s)', $subType);
}
return $actualType;
}
private function getParsers(array $parameters)
{
if (isset($parameters['parsers'])) {
$parsers = [];
foreach ($this->parsers as $parser) {
if (in_array($parser::class, $parameters['parsers'])) {
$parsers[] = $parser;
}
}
} else {
$parsers = $this->parsers;
}
return $parsers;
}
}

View 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);
}
}

View file

@ -0,0 +1,103 @@
<?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\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\Extractor\HandlerInterface;
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
use Symfony\Component\Routing\Route;
class PhpDocHandler implements HandlerInterface
{
/**
* @var DocCommentExtractor
*/
protected $commentExtractor;
public function __construct(DocCommentExtractor $commentExtractor)
{
$this->commentExtractor = $commentExtractor;
}
public function handle(ApiDoc $annotation, Route $route, \ReflectionMethod $method): void
{
// description
if (null === $annotation->getDescription()) {
$comments = explode("\n", $annotation->getDocumentation());
// just set the first line
$comment = trim($comments[0]);
$comment = preg_replace("#\n+#", ' ', $comment);
$comment = preg_replace('#\s+#', ' ', $comment);
$comment = preg_replace('#[_`*]+#', '', $comment);
if ('@' !== substr($comment, 0, 1)) {
$annotation->setDescription($comment);
}
}
// requirements
$requirements = $annotation->getRequirements();
foreach ($route->getRequirements() as $name => $value) {
if (!isset($requirements[$name]) && '_method' !== $name && '_scheme' !== $name) {
$requirements[$name] = [
'requirement' => $value,
'dataType' => '',
'description' => '',
];
}
}
$paramDocs = [];
foreach (explode("\n", $this->commentExtractor->getDocComment($method)) as $line) {
if (preg_match('{^@param (.+)}', trim($line), $matches)) {
$paramDocs[] = $matches[1];
}
if (preg_match('{^@deprecated}', trim($line))) {
$annotation->setDeprecated(true);
}
if (preg_match('{^@(link|see) (.+)}', trim($line), $matches)) {
$annotation->setLink($matches[2]);
}
}
$regexp = '{(\w*) *\$%s\b *(.*)}i';
foreach ($route->compile()->getVariables() as $var) {
$found = false;
foreach ($paramDocs as $paramDoc) {
if (preg_match(sprintf($regexp, preg_quote($var)), $paramDoc, $matches)) {
$annotationRequirements = $annotation->getRequirements();
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'] = '';
}
$found = true;
break;
}
}
if (!isset($requirements[$var]) && false === $found) {
$requirements[$var] = ['requirement' => '', 'dataType' => '', 'description' => ''];
}
}
$annotation->setRequirements($requirements);
}
}

View file

@ -0,0 +1,23 @@
<?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 Symfony\Component\Routing\Route;
interface HandlerInterface
{
/**
* Parse route parameters in order to populate ApiDoc.
*/
public function handle(ApiDoc $annotation, Route $route, \ReflectionMethod $method);
}

View file

@ -1,54 +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\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class DescriptionFieldTypeExtension extends AbstractTypeExtension
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilder $builder, array $options)
{
$builder->setAttribute('description', $options['description']);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form)
{
$view->set('description', $form->getAttribute('description'));
}
/**
* {@inheritdoc}
*/
public function getDefaultOptions(array $options)
{
return array(
'description' => '',
);
}
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
return 'field';
}
}

View file

@ -0,0 +1,53 @@
<?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\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
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->setAttribute('description', $options['description']);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['description'] = $options['description'];
}
/**
* @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([
'description' => '',
]);
}
public static function getExtendedTypes(): iterable
{
return [LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\FormType')];
}
}

View file

@ -11,52 +11,35 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Parser\FormTypeParser;
use Symfony\Component\Routing\Route;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
use Nelmio\ApiDocBundle\DataTypes;
abstract class AbstractFormatter implements FormatterInterface
{
/**
* @var \Nelmio\ApiDocBundle\Parser\FormTypeParser
*/
protected $parser;
protected $version;
public function __construct(FormTypeParser $parser)
public function setVersion($version): void
{
$this->parser = $parser;
$this->version = $version;
}
/**
* {@inheritdoc}
*/
public function formatOne(ApiDoc $apiDoc, Route $route)
public function formatOne(ApiDoc $annotation)
{
return $this->renderOne($this->getData($apiDoc, $route));
return $this->renderOne(
$this->processAnnotation($annotation->toArray())
);
}
/**
* {@inheritdoc}
*/
public function format(array $collection)
{
$array = array();
foreach ($collection as $coll) {
$resource = $coll['resource'];
if (!isset($array[$resource])) {
$array[$resource] = array();
}
$array[$resource][] = $this->getData($coll['annotation'], $coll['route']);
}
return $this->render($array);
return $this->render(
$this->processCollection($collection)
);
}
/**
* Format a single array of data
*
* @param array $data
* @return string|array
*/
abstract protected function renderOne(array $data);
@ -64,80 +47,158 @@ abstract class AbstractFormatter implements FormatterInterface
/**
* Format a set of resource sections.
*
* @param array $collection
* @return string|array
*/
abstract protected function render(array $collection);
/**
* @param ApiDoc $apiDoc
* @param Route $route
* 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
*
* @return array
*/
protected function getData(ApiDoc $apiDoc, Route $route)
protected function compressNestedParameters(array $data, $parentName = null, $ignoreNestedReadOnly = false)
{
$method = $route->getRequirement('_method');
$data = array(
'method' => $method ?: 'ANY',
'uri' => $route->compile()->getPattern(),
);
$newParams = [];
foreach ($data as $name => $info) {
if ($this->version && !$this->rangeIncludesVersion(
$info['sinceVersion'] ?? null,
$info['untilVersion'] ?? null
)) {
continue;
}
$requirements = array();
foreach ($route->compile()->getRequirements() as $name => $value) {
if ('_method' !== $name) {
$requirements[$name] = array(
'value' => $value,
'type' => '',
'description' => '',
);
$newName = $this->getNewName($name, $info, $parentName);
$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,
'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) {
$newParams[$nestedItemName] = $nestedItemData;
}
}
}
if (null !== $paramDocs = $route->getOption('_paramDocs')) {
$regexp = '{(\w*) *\$%s *(.*)}i';
foreach ($route->compile()->getVariables() as $var) {
$found = false;
foreach ($paramDocs as $paramDoc) {
if (preg_match(sprintf($regexp, preg_quote($var)), $paramDoc, $matches)) {
$requirements[$var]['type'] = isset($matches[1]) ? $matches[1] : '';
$requirements[$var]['description'] = $matches[2];
return $newParams;
}
if (!isset($requirements[$var]['value'])) {
$requirements[$var]['value'] = '';
}
/**
* 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
*
* @return string
*/
protected function getNewName($name, $data, $parentName = null)
{
$array = '';
$newName = ($parentName) ? sprintf('%s[%s]', $parentName, $name) : $name;
$found = true;
break;
if (isset($data['actualType']) && DataTypes::COLLECTION == $data['actualType']
&& isset($data['subType']) && null !== $data['subType']
) {
$array = '[]';
}
return sprintf('%s%s', $newName, $array);
}
/**
* @param array $annotation
*
* @return array
*/
protected function processAnnotation($annotation)
{
if (isset($annotation['parameters'])) {
$annotation['parameters'] = $this->compressNestedParameters($annotation['parameters'], null, true);
}
if (isset($annotation['response'])) {
$annotation['response'] = $this->compressNestedParameters($annotation['response']);
}
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 = [];
foreach ($collection as $coll) {
$array[$coll['annotation']->getSection()][$coll['resource']][] = $coll['annotation']->toArray();
}
$processedCollection = [];
foreach ($array as $section => $resources) {
foreach ($resources as $path => $annotations) {
foreach ($annotations as $annotation) {
if ($section) {
$processedCollection[$section][$path][] = $this->processAnnotation($annotation);
} else {
$processedCollection['_others'][$path][] = $this->processAnnotation($annotation);
}
}
if (!isset($requirements[$var]) && false === $found) {
$requirements[$var] = array('value' => '', 'type' => '', 'description' => '');
}
}
}
$data['requirements'] = $requirements;
ksort($processedCollection);
if (null !== $formType = $apiDoc->getFormType()) {
$data['parameters'] = $this->parser->parse(new $formType());
if ('PUT' === $method) {
// All parameters are optional with PUT (update)
array_walk($data['parameters'], function($val, $key) use (&$data) {
$data['parameters'][$key]['required'] = false;
});
}
}
if ($filters = $apiDoc->getFilters()) {
$data['filters'] = $filters;
}
if ($description = $apiDoc->getDescription()) {
$data['description'] = $description;
}
return $data;
return $processedCollection;
}
}

View file

@ -11,15 +11,15 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\Routing\Route;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
interface FormatterInterface
{
/**
* Format a collection of documentation data.
*
* @param array $collection
* @param array[ApiDoc] $collection
*
* @return string|array
*/
public function format(array $collection);
@ -27,9 +27,8 @@ interface FormatterInterface
/**
* Format documentation data for one route.
*
* @param ApiDoc $apiDoc
* @param Route $route
* return string|array
* @param ApiDoc $annotation
* return string|array
*/
public function formatOne(ApiDoc $apiDoc, Route $route);
public function formatOne(ApiDoc $annotation);
}

View file

@ -12,53 +12,195 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Symfony\Component\Templating\EngineInterface;
use Twig\Environment as TwigEnvironment;
class HtmlFormatter extends AbstractFormatter
{
/**
* @var string
*/
private $apiName;
protected $apiName;
/**
* @var \Symfony\Component\Templating\EngineInterface
* @var string
*/
private $engine;
protected $endpoint;
/**
* @var string
*/
protected $defaultRequestFormat;
/**
* @var EngineInterface|TwigEnvironment
*/
protected $engine;
/**
* @var bool
*/
private $enableSandbox;
/**
* @var array
*/
private $requestFormats;
/**
* @var string
*/
private $requestFormatMethod;
/**
* @var string
*/
private $acceptType;
/**
* @var array
*/
private $bodyFormats;
/**
* @var string
*/
private $defaultBodyFormat;
/**
* @var array
*/
private $authentication;
/**
* @var string
*/
private $motdTemplate;
/**
* @var bool
*/
private $defaultSectionsOpened;
public function setAuthentication(?array $authentication = null): void
{
$this->authentication = $authentication;
}
/**
* @param string $apiName
*/
public function setApiName($apiName)
public function setApiName($apiName): void
{
$this->apiName = $apiName;
}
/**
* @param EngineInterface $engine
* @param string $endpoint
*/
public function setTemplatingEngine(EngineInterface $engine)
public function setEndpoint($endpoint): void
{
$this->endpoint = $endpoint;
}
/**
* @param bool $enableSandbox
*/
public function setEnableSandbox($enableSandbox): void
{
$this->enableSandbox = $enableSandbox;
}
/**
* @param EngineInterface|TwigEnvironment $engine
*/
public function setTemplatingEngine($engine): void
{
$this->engine = $engine;
}
/**
* {@inheritdoc}
* @param string $acceptType
*/
public function setAcceptType($acceptType): void
{
$this->acceptType = $acceptType;
}
public function setBodyFormats(array $bodyFormats): void
{
$this->bodyFormats = $bodyFormats;
}
/**
* @param string $defaultBodyFormat
*/
public function setDefaultBodyFormat($defaultBodyFormat): void
{
$this->defaultBodyFormat = $defaultBodyFormat;
}
/**
* @param string $method
*/
public function setRequestFormatMethod($method): void
{
$this->requestFormatMethod = $method;
}
public function setRequestFormats(array $formats): void
{
$this->requestFormats = $formats;
}
/**
* @param string $format
*/
public function setDefaultRequestFormat($format): void
{
$this->defaultRequestFormat = $format;
}
/**
* @param string $motdTemplate
*/
public function setMotdTemplate($motdTemplate): void
{
$this->motdTemplate = $motdTemplate;
}
/**
* @return string
*/
public function getMotdTemplate()
{
return $this->motdTemplate;
}
/**
* @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, 'displayContent' => true),
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('resources' => $collection),
return $this->engine->render('@NelmioApiDoc/resources.html.twig', array_merge(
[
'resources' => $collection,
],
$this->getGlobalVars()
));
}
@ -68,10 +210,22 @@ class HtmlFormatter extends AbstractFormatter
*/
private function getGlobalVars()
{
return array(
'apiName' => $this->apiName,
'date' => date(DATE_RFC822),
'css' => file_get_contents(__DIR__ . '/../Resources/public/css/screen.css'),
);
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,
'defaultSectionsOpened' => $this->defaultSectionsOpened,
];
}
}

View file

@ -13,32 +13,42 @@ 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 .= "\n\n";
}
if (isset($data['description'])) {
$markdown .= sprintf("\n_%s_", $data['description']);
}
$markdown .= "\n\n";
if (isset($data['documentation']) && !empty($data['documentation'])) {
if (isset($data['description']) && 0 === strcmp($data['description'], $data['documentation'])) {
$markdown .= $data['documentation'];
$markdown .= "\n\n";
}
}
if (isset($data['requirements']) && !empty($data['requirements'])) {
$markdown .= "#### Requirements ####\n\n";
foreach ($data['requirements'] as $name => $infos) {
$markdown .= sprintf("**%s**\n\n", $name);
if (!empty($infos['value'])) {
$markdown .= sprintf(" - Value: %s\n", $infos['value']);
if (!empty($infos['requirement'])) {
$markdown .= sprintf(" - Requirement: %s\n", $infos['requirement']);
}
if (!empty($infos['type'])) {
$markdown .= sprintf(" - Type: %s\n", $infos['type']);
if (!empty($infos['dataType'])) {
$markdown .= sprintf(" - Type: %s\n", $infos['dataType']);
}
if (!empty($infos['description'])) {
$markdown .= sprintf(" - Description: %s\n", $infos['description']);
}
@ -54,7 +64,7 @@ class MarkdownFormatter extends AbstractFormatter
$markdown .= sprintf("%s:\n\n", $name);
foreach ($filter as $key => $value) {
$markdown .= sprintf(" * %s: %s\n", $key, trim(json_encode($value), '"'));
$markdown .= sprintf(" * %s: %s\n", ucwords($key), trim(str_replace('\\\\', '\\', json_encode($value)), '"'));
}
$markdown .= "\n";
@ -65,14 +75,48 @@ class MarkdownFormatter extends AbstractFormatter
$markdown .= "#### Parameters ####\n\n";
foreach ($data['parameters'] as $name => $parameter) {
if (!$parameter['readonly']) {
$markdown .= sprintf("%s:\n\n", $name);
$markdown .= sprintf(" * type: %s\n", $parameter['dataType']);
$markdown .= sprintf(" * required: %s\n", $parameter['required'] ? 'true' : 'false');
if (isset($parameter['description']) && !empty($parameter['description'])) {
$markdown .= sprintf(" * description: %s\n", $parameter['description']);
}
if (isset($parameter['default']) && !empty($parameter['default'])) {
$markdown .= sprintf(" * default value: %s\n", $parameter['default']);
}
$markdown .= "\n";
}
}
}
if (isset($data['response'])) {
$markdown .= "#### Response ####\n\n";
foreach ($data['response'] as $name => $parameter) {
$markdown .= sprintf("%s:\n\n", $name);
$markdown .= sprintf(" * type: %s\n", $parameter['dataType']);
$markdown .= sprintf(" * required: %s\n", $parameter['required'] ? 'true' : 'false');
if (isset($parameter['description']) && !empty($parameter['description'])) {
$markdown .= sprintf(" * description: %s\n", $parameter['description']);
}
if (null !== $parameter['sinceVersion'] || null !== $parameter['untilVersion']) {
$markdown .= ' * versions: ';
if ($parameter['sinceVersion']) {
$markdown .= '>=' . $parameter['sinceVersion'];
}
if ($parameter['untilVersion']) {
if ($parameter['sinceVersion']) {
$markdown .= ',';
}
$markdown .= '<=' . $parameter['untilVersion'];
}
$markdown .= "\n";
}
$markdown .= "\n";
}
}
@ -80,27 +124,36 @@ class MarkdownFormatter extends AbstractFormatter
return $markdown;
}
/**
* {@inheritdoc}
*/
protected function render(array $collection)
{
$markdown = '';
foreach ($collection as $resource => $arrayOfData) {
$markdown .= $this->renderResourceSection($resource, $arrayOfData);
foreach ($collection as $section => $resources) {
$markdown .= $this->renderResourceSection($section, $resources);
$markdown .= "\n";
}
return trim($markdown);
}
private function renderResourceSection($resource, array $arrayOfData)
private function renderResourceSection($section, array $resources)
{
$markdown = sprintf("# %s #\n\n", $resource);
if ('_others' !== $section) {
$markdown = sprintf("# %s #\n\n", $section);
} else {
$markdown = '';
}
foreach ($arrayOfData as $data) {
$markdown .= $this->renderOne($data);
$markdown .= "\n";
foreach ($resources as $resource => $methods) {
if ('_others' === $section && 'others' !== $resource) {
$markdown .= sprintf("## %s ##\n\n", $resource);
} elseif ('others' !== $resource) {
$markdown .= sprintf("## %s ##\n\n", $resource);
}
foreach ($methods as $method) {
$markdown .= $this->renderOne($method);
$markdown .= "\n";
}
}
return $markdown;

View 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);
}
}

View file

@ -11,21 +11,33 @@
namespace Nelmio\ApiDocBundle\Formatter;
use Nelmio\ApiDocBundle\Attribute\ApiDoc;
class SimpleFormatter extends AbstractFormatter
{
/**
* {@inheritdoc}
*/
protected function renderOne(array $data)
public function formatOne(ApiDoc $annotation)
{
return $data;
return $annotation->toArray();
}
/**
* {@inheritdoc}
*/
protected function render(array $collection)
public function format(array $collection)
{
$array = [];
foreach ($collection as $coll) {
$annotationArray = $coll['annotation']->toArray();
unset($annotationArray['parsedResponseMap']);
$array[$coll['resource']][] = $annotationArray;
}
return $array;
}
protected function renderOne(array $data): void
{
}
protected function render(array $collection): void
{
return $collection;
}
}

View 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
View 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

View file

@ -2,8 +2,24 @@
namespace Nelmio\ApiDocBundle;
use Nelmio\ApiDocBundle\DependencyInjection\ExtractorHandlerCompilerPass;
use Nelmio\ApiDocBundle\DependencyInjection\FormInfoParserCompilerPass;
use Nelmio\ApiDocBundle\DependencyInjection\LoadExtractorParsersPass;
use Nelmio\ApiDocBundle\DependencyInjection\RegisterExtractorParsersPass;
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): void
{
parent::build($container);
$container->addCompilerPass(new LoadExtractorParsersPass());
$container->addCompilerPass(new RegisterExtractorParsersPass());
$container->addCompilerPass(new ExtractorHandlerCompilerPass());
$container->addCompilerPass(new SwaggerConfigCompilerPass());
$container->addCompilerPass(new FormInfoParserCompilerPass());
}
}

View 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
View 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
View 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;
}

View file

@ -11,75 +11,496 @@
namespace Nelmio\ApiDocBundle\Parser;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Nelmio\ApiDocBundle\DataTypes;
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\View\ChoiceView;
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\Contracts\Translation\TranslatorInterface;
class FormTypeParser
class FormTypeParser implements ParserInterface
{
/**
* @var \Symfony\Component\Form\FormFactoryInterface
* @var FormFactoryInterface
*/
protected $formFactory;
/**
* @var array
* @var \Symfony\Component\Form\FormRegistry
*/
protected $mapTypes = array(
'text' => 'string',
'date' => 'date',
'datetime' => 'datetime',
'checkbox' => 'boolean',
'time' => 'time',
'number' => 'float',
'integer' => 'int',
'textarea' => 'string',
);
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
protected $formRegistry;
/**
* Returns an array of data where each data is an array with the following keys:
* - dataType
* - required
* - description
*
* @param AbstractType $type
* @return array
* @var \Symfony\Component\Translation\TranslatorInterface
*/
public function parse(AbstractType $type)
protected $translator;
/**
* @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)
{
$builder = $this->formFactory->createBuilder($type);
$this->formFactory = $formFactory;
$this->translator = $translator;
$this->entityToChoice = (bool) $entityToChoice;
}
$refl = new \ReflectionProperty('Symfony\Component\Form\FormBuilder', 'children');
$refl->setAccessible(true);
$children = $refl->getValue($builder);
public function supports(array $item)
{
$className = $item['class'];
$options = $item['options'];
$parameters = array();
foreach ($children as $name => $child) {
if ($child instanceof FormBuilder) {
$childBuilder = $child;
} else {
$childBuilder = $builder->create($name, $child['type'] ?: 'text', $child['options']);
try {
if ($this->createForm($className, null, $options)) {
return true;
}
} catch (FormException $e) {
return false;
} catch (MissingOptionsException $e) {
return false;
}
return false;
}
public function parse(array $item)
{
$type = $item['class'];
$options = $item['options'];
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);
}
}
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']
: (method_exists($form, 'getBlockPrefix') ? $form->getBlockPrefix() : $form->getName());
if (empty($name)) {
return $this->parseForm($form);
}
$subType = is_object($type) ? $type::class : $type;
if ($subType && class_exists($subType)) {
$parts = explode('\\', $subType);
$dataType = sprintf('object (%s)', end($parts));
} else {
$dataType = sprintf('object (%s)', $subType);
}
return [
$name => [
'required' => true,
'readonly' => false,
'description' => '',
'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 = [];
$domain = $form->getConfig()->getOption('translation_domain');
foreach ($form as $name => $child) {
$config = $child->getConfig();
$options = $config->getOptions();
$bestType = '';
foreach ($childBuilder->getTypes() as $type) {
if (isset($this->mapTypes[$type->getName()])) {
$bestType = $this->mapTypes[$type->getName()];
$actualType = null;
$subType = null;
$children = null;
for ($type = $config->getType();
$type instanceof FormInterface || $type instanceof ResolvedFormTypeInterface;
$type = $type->getParent()
) {
$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);
} else {
// Embedded form collection
// 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) ? $embbededType::class : $embbededType;
if ($subType && class_exists($subType)) {
$parts = explode('\\', $subType);
$bestType = sprintf('array of objects (%s)', end($parts));
} else {
$bestType = sprintf('array of objects (%s)', $subType);
}
}
}
}
$parameters[$name] = array(
'dataType' => $bestType,
'required' => $childBuilder->getRequired(),
'description' => $childBuilder->getAttribute('description'),
);
if ('' === $bestType) {
if ($type = $config->getType()) {
if ($type = $type->getInnerType()) {
/**
* TODO: Implement a better handling of unsupported types
* This is just a temporary workaround for don't breaking docs page in case of unsupported types
* like the entity type https://github.com/nelmio/NelmioApiDocBundle/issues/94
*/
$addDefault = false;
try {
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 = $type::class;
$parts = explode('\\', $subType);
$bestType = sprintf('object (%s)', end($parts));
$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;
}
} catch (\Exception $e) {
$addDefault = true;
}
if ($addDefault) {
$parameters[$name] = [
'dataType' => 'string',
'actualType' => 'string',
'default' => $config->getData(),
'required' => $config->getRequired(),
'description' => $this->getFormDescription($config, $domain),
'readonly' => $config->getDisabled(),
];
}
continue;
}
}
}
$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;
}
switch ($bestType) {
case 'datetime':
if (($format = $config->getOption('date_format')) && is_string($format)) {
$parameters[$name]['format'] = $format;
} elseif ('single_text' == $config->getOption('widget') && $format = $config->getOption('format')) {
$parameters[$name]['format'] = $format;
}
break;
case 'date':
if (($format = $config->getOption('format')) && is_string($format)) {
$parameters[$name]['format'] = $format;
}
break;
case 'choice':
if ($config->getOption('multiple')) {
$parameters[$name]['dataType'] = sprintf('array of %ss', $parameters[$name]['dataType']);
$parameters[$name]['actualType'] = DataTypes::COLLECTION;
$parameters[$name]['subType'] = DataTypes::ENUM;
}
if (($choices = $config->getOption('choices')) && is_array($choices) && count($choices)) {
$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);
}
}
break;
}
}
return $parameters;
}
private function implementsType($item)
{
if (null === $item || !class_exists($item)) {
return false;
}
$refl = new \ReflectionClass($item);
return $refl->implementsInterface('Symfony\Component\Form\FormTypeInterface') || $refl->implementsInterface('Symfony\Component\Form\ResolvedFormTypeInterface');
}
private function getTypeInstance($type)
{
$refl = new \ReflectionClass($type);
$constructor = $refl->getConstructor();
// this fallback may lead to runtime exception, but try hard to generate the docs
if ($constructor && $constructor->getNumberOfRequiredParameters() > 0) {
return $refl->newInstanceWithoutConstructor();
}
return $refl->newInstance();
}
private function createForm($type, $data = null, array $options = [])
{
try {
return $this->formFactory->create($type, null, $options);
} catch (InvalidArgumentException $exception) {
}
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 = [];
foreach ([$choiceList->getPreferredViews(), $choiceList->getRemainingViews()] as $viewList) {
$choices = array_merge($choices, $this->handleChoiceViewsHierarchy($viewList));
}
return $choices;
}
private function handleChoiceViewsHierarchy(array $choiceViews)
{
$choices = [];
foreach ($choiceViews as $item) {
if ($item instanceof ChoiceView) {
$choices[$item->value] = $item->label;
} elseif (is_array($item)) {
$choices = array_merge($choices, $this->handleChoiceViewsHierarchy($item));
}
}
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;
}
}

View file

@ -0,0 +1,340 @@
<?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 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;
/**
* Uses the JMS metadata factory to extract input/output model information
*/
class JmsMetadataParser implements ParserInterface, PostParserInterface
{
/**
* @var MetadataFactoryInterface
*/
private $factory;
/**
* @var PropertyNamingStrategyInterface
*/
private $namingStrategy;
/**
* @var DocCommentExtractor
*/
private $commentExtractor;
private $typeMap = [
'integer' => DataTypes::INTEGER,
'boolean' => DataTypes::BOOLEAN,
'string' => DataTypes::STRING,
'float' => DataTypes::FLOAT,
'double' => DataTypes::FLOAT,
'array' => DataTypes::COLLECTION,
'DateTime' => DataTypes::DATETIME,
];
/**
* Constructor, requires JMS Metadata factory
*/
public function __construct(
MetadataFactoryInterface $factory,
PropertyNamingStrategyInterface $namingStrategy,
DocCommentExtractor $commentExtractor,
) {
$this->factory = $factory;
$this->namingStrategy = $namingStrategy;
$this->commentExtractor = $commentExtractor;
}
public function supports(array $input)
{
$className = $input['class'];
try {
if ($meta = $this->factory->getMetadataForClass($className)) {
return true;
}
} catch (\ReflectionException $e) {
}
return false;
}
public function parse(array $input)
{
$className = $input['class'];
$groups = $input['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
*
* @throws \InvalidArgumentException
*/
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));
}
$exclusionStrategies = [];
if ($groups) {
$exclusionStrategies[] = new GroupsExclusionStrategy($groups);
}
$params = [];
$reflection = new \ReflectionClass($className);
$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 (null !== $item->type) {
$name = $this->namingStrategy->translateName($item);
$dataType = $this->processDataType($item);
// apply exclusion strategies
foreach ($exclusionStrategies as $strategy) {
if (true === $strategy->shouldSkipProperty($item, SerializationContext::create())) {
continue 2;
}
}
if (!$dataType['inline']) {
$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 (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 (!$dataType['class'] || !class_exists($dataType['class'])) {
continue;
}
// if class already parsed, continue, to avoid infinite recursion
if (in_array($dataType['class'], $visited)) {
continue;
}
// 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);
if ($dataType['inline']) {
$params = array_merge($params, $children);
} else {
$params[$name]['children'] = $children;
}
}
}
}
return $params;
}
/**
* Figure out a normalized data type (for documentation), and get a
* nested class name, if available.
*
* @return array
*/
protected function processDataType(PropertyMetadata $item)
{
// check for a type inside something that could be treated as an array
if ($nestedType = $this->getNestedTypeInArray($item)) {
if ($this->isPrimitive($nestedType)) {
return [
'normalized' => sprintf('array of %ss', $nestedType),
'actualType' => DataTypes::COLLECTION,
'class' => $this->typeMap[$nestedType],
'primitive' => true,
'inline' => false,
];
}
$exp = explode('\\', $nestedType);
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 [
'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 (!$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);
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, ['boolean', 'integer', 'string', 'float', 'double', 'array', 'DateTime']);
}
public function postParse(array $input, array $parameters)
{
return $this->doPostParse($parameters, [], $input['groups'] ?? []);
}
/**
* Recursive `doPostParse` to avoid circular post parsing.
*
* @return 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 = ['class' => $data['class'], 'groups' => $data['groups'] ?? []];
$parameters[$param]['children'] = array_merge(
$parameters[$param]['children'], $this->doPostParse($parameters[$param]['children'], $visited, $groups)
);
$parameters[$param]['children'] = array_merge(
$parameters[$param]['children'], $this->doParse($input['class'], $visited, $groups)
);
}
}
return $parameters;
}
/**
* Check the various ways JMS describes values in arrays, and
* get the value type in the array
*
* @return string|null
*/
protected function getNestedTypeInArray(PropertyMetadata $item)
{
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'];
}
if (isset($item->type['params'][0]['name'])) {
// E.g. array<MyNamespaceMyObject>
return $item->type['params'][0]['name'];
}
}
return null;
}
protected function getDescription(PropertyMetadata $item)
{
$ref = new \ReflectionClass($item->class);
if ($item instanceof VirtualPropertyMetadata) {
$extracted = $this->commentExtractor->getDocCommentText($ref->getMethod($item->getter));
} else {
$extracted = $this->commentExtractor->getDocCommentText($ref->getProperty($item->name));
}
return $extracted;
}
}

View 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;
}
}

View file

@ -0,0 +1,45 @@
<?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;
/**
* This is the interface parsers must implement in order to be registered in the ApiDocExtractor.
*/
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 bool
*/
public function supports(array $item);
/**
* Returns an array of class property metadata where each item is a key (the property name) and
* an array of data with the following keys:
* - dataType string
* - required boolean
* - description string
* - readonly boolean
* - children (optional) array of nested property names mapped to arrays
* in the format described here
* - class (optional) the fully-qualified class name of the item, if
* it is represented by an object
*
* @param array $item The string type of input to parse.
*
* @return array
*/
public function parse(array $item);
}

View file

@ -0,0 +1,39 @@
<?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;
/**
* This is the interface parsers must implement in order to register a second parsing pass after the initial structure
* is populated..
*/
interface PostParserInterface
{
/**
* Reparses an object for additional documentation details after it has already been parsed once, to allow
* parsers to extend information initially documented by other parsers.
*
* Returns an array of class property metadata where each item is a key (the property name) and
* an array of data with the following keys:
* - dataType string
* - required boolean
* - description string
* - readonly boolean
* - 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.
*
* @return array
*/
public function postParse(array $item, array $parameters);
}

353
Parser/ValidationParser.php Normal file
View file

@ -0,0 +1,353 @@
<?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;
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.
*/
class ValidationParser implements ParserInterface, PostParserInterface
{
/**
* @var LegacyMetadataFactoryInterface
*/
protected $factory;
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|LegacyMetadataFactoryInterface $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;
}
public function supports(array $input)
{
$className = $input['class'];
return $this->factory->hasMetadataFor($className);
}
public function parse(array $input)
{
$className = $input['class'];
$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.
*
* @return array
*/
protected function doParse($className, array $visited, array $groups = [])
{
$params = [];
$classdata = $this->factory->getMetadataFor($className);
$properties = $classdata->getConstrainedProperties();
$refl = $classdata->getReflectionClass();
$defaults = $refl->getDefaultProperties();
foreach ($properties as $property) {
$vparams = [];
$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, $groups);
}
}
if (isset($vparams['format'])) {
$vparams['format'] = implode(', ', array_unique($vparams['format']));
}
foreach (['dataType', 'readonly', 'required', 'subType'] as $reqprop) {
if (!isset($vparams[$reqprop])) {
$vparams[$reqprop] = null;
}
}
// 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, $groups);
}
$vparams['actualType'] = $vparams['actualType'] ?? DataTypes::STRING;
$params[$property] = $vparams;
}
return $params;
}
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 = ['class' => $data['class'], 'groups' => $groups];
$parameters[$param]['children'] = array_merge(
$parameters[$param]['children'], $this->postParse($input, $parameters[$param]['children'])
);
$parameters[$param]['children'] = array_merge(
$parameters[$param]['children'], $this->parse($input, $parameters[$param]['children'])
);
}
}
return $parameters;
}
/**
* Create a valid documentation parameter based on an individual validation Constraint.
* Currently supports:
* - NotBlank/NotNull
* - Type
* - Email
* - Url
* - Ip
* - Length (min and max)
* - 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.
* @param array $groups Validation groups.
*
* @return mixed The parsed list of validation parameters.
*/
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;
case 'Type':
if (isset($this->typeMap[$constraint->type])) {
$vparams['actualType'] = $this->typeMap[$constraint->type];
}
$vparams['dataType'] = $constraint->type;
break;
case 'Email':
$vparams['format'][] = '{email address}';
break;
case 'Url':
$vparams['format'][] = '{url}';
break;
case 'Ip':
$vparams['format'][] = '{ip address}';
break;
case 'Date':
$vparams['format'][] = '{Date YYYY-MM-DD}';
$vparams['actualType'] = DataTypes::DATE;
break;
case 'DateTime':
$vparams['format'][] = '{DateTime YYYY-MM-DD HH:MM:SS}';
$vparams['actualType'] = DataTypes::DATETIME;
break;
case 'Time':
$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 = [];
if (isset($constraint->min)) {
$messages[] = "min: {$constraint->min}";
}
if (isset($constraint->max)) {
$messages[] = "max: {$constraint->max}";
}
$vparams['format'][] = '{length: {' . implode(', ', $messages) . '}}';
break;
case 'Choice':
$choices = $this->getChoices($constraint, $className);
sort($choices);
$format = '[' . implode('|', $choices) . ']';
if ($constraint->multiple) {
$vparams['actualType'] = DataTypes::COLLECTION;
$vparams['subType'] = DataTypes::ENUM;
$messages = [];
if (isset($constraint->min)) {
$messages[] = "min: {$constraint->min} ";
}
if (isset($constraint->max)) {
$messages[] = "max: {$constraint->max} ";
}
$vparams['format'][] = '{' . implode('', $messages) . 'choice of ' . $format . '}';
} else {
$vparams['actualType'] = DataTypes::ENUM;
$vparams['format'][] = $format;
}
break;
case 'Regex':
if ($constraint->match) {
$vparams['format'][] = '{match: ' . $constraint->pattern . '}';
} else {
$vparams['format'][] = '{not match: ' . $constraint->pattern . '}';
}
break;
case 'All':
foreach ($constraint->constraints as $childConstraint) {
if ($childConstraint instanceof Type) {
$nestedType = $childConstraint->type;
$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['actualType'] = DataTypes::COLLECTION;
$vparams['subType'] = $nestedType;
$vparams['class'] = $nestedType;
if (!in_array($nestedType, $visited)) {
$visited[] = $nestedType;
$vparams['children'] = $this->doParse($nestedType, $visited);
}
}
}
break;
}
return $vparams;
}
/**
* Return Choice constraint choices.
*
* @return array
*
* @throws ConstraintDefinitionException
*/
protected function getChoices(Constraint $constraint, $className)
{
if ($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 {
throw new ConstraintDefinitionException('The Choice constraint expects a valid callback');
}
} else {
$choices = $constraint->choices;
}
return $choices;
}
}

View file

@ -0,0 +1,85 @@
<?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 Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
/**
* Uses the Symfony Validation component to extract information about API objects. This is a backwards-compatible Validation component for Symfony2.1
*/
class ValidationParserLegacy extends ValidationParser
{
/**
* @var ClassMetadataFactoryInterface
*/
protected $factory;
/**
* Requires a validation MetadataFactory.
*
* @param MetadataFactoryInterface $factory
*/
public function __construct(ClassMetadataFactoryInterface $factory)
{
$this->factory = $factory;
}
public function supports(array $input)
{
$className = $input['class'];
return null !== $this->factory->getClassMetadata($className);
}
public function parse(array $input)
{
$params = [];
$className = $input['class'];
$classdata = $this->factory->getClassMetadata($className);
$properties = $classdata->getConstrainedProperties();
$refl = $classdata->getReflectionClass();
$defaults = $refl->getDefaultProperties();
foreach ($properties as $property) {
$vparams = [];
$vparams['default'] = $defaults[$property] ?? null;
$pds = $classdata->getMemberMetadatas($property);
foreach ($pds as $propdata) {
$constraints = $propdata->getConstraints();
foreach ($constraints as $constraint) {
$vparams = $this->parseConstraint($constraint, $vparams, $className);
}
}
if (isset($vparams['format'])) {
$vparams['format'] = implode(', ', $vparams['format']);
}
foreach (['dataType', 'readonly', 'required'] as $reqprop) {
if (!isset($vparams[$reqprop])) {
$vparams[$reqprop] = null;
}
}
$params[$property] = $vparams;
}
return $params;
}
}

175
README.md
View file

@ -1,166 +1,49 @@
NelmioApiDocBundle
==================
[![Build Status](https://secure.travis-ci.org/nelmio/NelmioApiDocBundle.png?branch=1.0.x)](http://travis-ci.org/nelmio/NelmioApiDocBundle)
The **NelmioApiDocBundle** bundle allows you to generate a decent documentation
for your APIs.
The **NelmioApiDocBundle** bundle allows you to generate a decent documentation for your APIs.
Documentation
-------------
[Read the documentation on symfony.com](https://symfony.com/doc/current/bundles/NelmioApiDocBundle/index.html)
## Installation ##
Contributing
------------
Register the namespace in `app/autoload.php`:
// app/autoload.php
$loader->registerNamespaces(array(
// ...
'Nelmio' => __DIR__.'/../vendor/bundles',
));
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: ~
See
[CONTRIBUTING](https://github.com/nelmio/NelmioApiDocBundle/blob/master/CONTRIBUTING.md)
file.
## Usage ##
Running the Tests
-----------------
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.
Install the [Composer](http://getcomposer.org/) `dev` dependencies:
### The ApiDoc() annotation ###
php composer.phar install --dev
The bundle provides an `ApiDoc()` annotation for your controllers:
Then, run the test suite using
[PHPUnit](https://github.com/sebastianbergmann/phpunit/):
``` php
<?php
namespace Your\Namespace;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
class YourController extends Controller
{
/**
* @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",
* formType="Your\Namespace\Form\Type\YourType"
* )
*/
public function postAction()
{
}
}
```
The following properties are available:
* `resource`: whether the method describes a main resource or not (default: `false`);
* `description`: a description of the API method;
* `filters`: an array of filters;
* `formType`: the Form Type associated to the method, useful for POST|PUT methods.
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 a `formType`, 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.
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.
phpunit
### Documentation on-the-fly ###
Credits
-------
By calling an URL with the parameter `?_doc=1`, you will get the corresponding documentation if available.
The design is heavily inspired by the
[swagger-ui](https://github.com/wordnik/swagger-ui) project.
Some icons from the [Glyphicons](http://glyphicons.com/) library are used to
render the documentation.
### Web Interface ###
License
-------
You can browse the whole documentation at: `http://example.org/api/doc`.
This bundle is released under the MIT license. See the complete license in the
bundle:
![](https://github.com/nelmio/NelmioApiDocBundle/raw/master/Resources/doc/webview.png)
![](https://github.com/nelmio/NelmioApiDocBundle/raw/master/Resources/doc/webview2.png)
### 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
## Configuration ##
You can specify your own API name:
# app/config/config.yml
nelmio_api_doc:
name: My API
## Credits ##
The design is heavily inspired by the [swagger-ui](https://github.com/wordnik/swagger-ui) project.
Resources/meta/LICENSE

View 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'

View file

@ -4,33 +4,64 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="nelmio_api_doc.parser.form_type_parser.class">Nelmio\ApiDocBundle\Parser\FormTypeParser</parameter>
<parameter key="nelmio_api_doc.formatter.abstract_formatter.class">Nelmio\ApiDocBundle\Formatter\AbstractFormatter</parameter>
<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.parser.form_type_parser" class="%nelmio_api_doc.parser.form_type_parser.class%">
<argument type="service" id="form.factory" />
</service>
<service id="nelmio_api_doc.formatter.abstract_formatter" class="%nelmio_api_doc.formatter.abstract_formatter.class%">
<argument type="service" id="nelmio_api_doc.parser.form_type_parser" />
</service>
<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>
</call>
<call method="setApiName">
<argument>%nelmio_api_doc.api_name%</argument>
</call>
<call method="setEnableSandbox">
<argument>%nelmio_api_doc.sandbox.enabled%</argument>
</call>
<call method="setEndpoint">
<argument>%nelmio_api_doc.sandbox.endpoint%</argument>
</call>
<call method="setRequestFormatMethod">
<argument>%nelmio_api_doc.sandbox.request_format.method%</argument>
</call>
<call method="setRequestFormats">
<argument>%nelmio_api_doc.sandbox.request_format.formats%</argument>
</call>
<call method="setDefaultRequestFormat">
<argument>%nelmio_api_doc.sandbox.request_format.default_format%</argument>
</call>
<call method="setAcceptType">
<argument>%nelmio_api_doc.sandbox.accept_type%</argument>
</call>
<call method="setBodyFormats">
<argument>%nelmio_api_doc.sandbox.body_format.formats%</argument>
</call>
<call method="setDefaultBodyFormat">
<argument>%nelmio_api_doc.sandbox.body_format.default_format%</argument>
</call>
<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>

View file

@ -11,6 +11,7 @@
<service id="nelmio_api_doc.event_listener.request" class="%nelmio_api_doc.event_listener.request.class%">
<argument type="service" id="nelmio_api_doc.extractor.api_doc_extractor" />
<argument type="service" id="nelmio_api_doc.formatter.html_formatter" />
<argument>%nelmio_api_doc.request_listener.parameter%</argument>
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
</service>
</services>

View file

@ -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]

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://nelmio.github.io/schema/dic/nelmio_api_doc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://nelmio.github.io/schema/dic/nelmio_api_doc"
elementFormDefault="qualified">
<xsd:element name="config">
<xsd:complexType>
<xsd:all>
<xsd:element name="motd" type="motd" minOccurs="0" maxOccurs="1"/>
<xsd:element name="request_listener" type="request_listener" minOccurs="0" maxOccurs="1"/>
<xsd:element name="sandbox" type="sandbox" minOccurs="0" maxOccurs="1"/>
</xsd:all>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="motd">
<xsd:attribute name="template" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="request_listener">
<xsd:attribute name="enabled" default="true" type="xsd:boolean"/>
<xsd:attribute name="parameter" default="_doc" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="sandbox">
<xsd:attribute name="enabled" default="true" type="xsd:boolean"/>
<xsd:attribute name="endpoint" type="xsd:string"/>
<xsd:attribute name="accept_type" type="xsd:string"/>
<xsd:attribute name="body_format" type="body_format_enum"/>
<xsd:attribute name="request_format" type="request_format"/>
<xsd:attribute name="authentication" type="authentication"/>
</xsd:complexType>
<xsd:simpleType name="body_format_enum">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="form"/>
<xsd:enumeration value="json"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="request_format">
<xsd:attribute name="method" type="request_format_method_enum"/>
<xsd:attribute name="default_format" type="request_format_default_format_enum"/>
</xsd:complexType>
<xsd:simpleType name="request_format_method_enum">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="format_param"/>
<xsd:enumeration value="accept_header"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="request_format_default_format_enum">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="json"/>
<xsd:enumeration value="xml"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="authentication">
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="delivery" type="authentication_delivery_enum"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="custom_endpoint" type="xsd:boolean" default="false"/>
</xsd:complexType>
<xsd:simpleType name="authentication_delivery_enum">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="query"/>
<xsd:enumeration value="http_basic"/>
<xsd:enumeration value="header"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="nelmio_api_doc.parser.form_type_parser.class">Nelmio\ApiDocBundle\Parser\FormTypeParser</parameter>
</parameters>
<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>
</container>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="nelmio_api_doc.parser.jms_metadata_parser.class">Nelmio\ApiDocBundle\Parser\JmsMetadataParser</parameter>
</parameters>
<services>
<service id="nelmio_api_doc.parser.jms_metadata_parser" class="%nelmio_api_doc.parser.jms_metadata_parser.class%">
<argument type="service" id="jms_serializer.metadata_factory" />
<argument type="service" id="jms_serializer.naming_strategy" />
<argument type="service" id="nelmio_api_doc.doc_comment_extractor" />
<tag name="nelmio_api_doc.extractor.parser" />
</service>
</services>
</container>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="nelmio_api_doc.parser.validation_parser.class">Nelmio\ApiDocBundle\Parser\ValidationParser</parameter>
</parameters>
<services>
<service id="nelmio_api_doc.parser.validation_parser" class="%nelmio_api_doc.parser.validation_parser.class%">
<argument type="service" id="validator.mapping.class_metadata_factory" />
<tag name="nelmio_api_doc.extractor.parser" />
</service>
</services>
</container>

View file

@ -5,17 +5,56 @@
<parameters>
<parameter key="nelmio_api_doc.extractor.api_doc_extractor.class">Nelmio\ApiDocBundle\Extractor\ApiDocExtractor</parameter>
<parameter key="nelmio_api_doc.form.extension.description_field_type_extension.class">Nelmio\ApiDocBundle\Form\Extension\DescriptionFieldTypeExtension</parameter>
<parameter key="nelmio_api_doc.form.extension.description_form_type_extension.class">Nelmio\ApiDocBundle\Form\Extension\DescriptionFormTypeExtension</parameter>
<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.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.extractor.api_doc_extractor" class="%nelmio_api_doc.extractor.api_doc_extractor.class%">
<argument type="service" id="service_container"/>
<argument type="service" id="router" />
<argument type="service" id="annotation_reader" />
<service id="nelmio_api_doc.doc_comment_extractor" class="%nelmio_api_doc.doc_comment_extractor.class%" />
<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.form.extension.description_field_type_extension" class="%nelmio_api_doc.form.extension.description_field_type_extension.class%">
<tag name="form.type_extension" alias="field" />
<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="nelmio_api_doc.doc_comment_extractor" />
<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" 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%">
<tag name="twig.extension" />
</service>
<!-- Extractor Annotation Handlers -->
<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>

View 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 }

View 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.

View 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"

View 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
View 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

143
Resources/doc/index.rst Normal file
View 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.

View 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

View 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
View 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

View 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

View 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.

View file

@ -1,4 +1,4 @@
Copyright (c) 2011 Nelmio
Copyright (c) 2012 Nelmio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
<div class="motd"></div>

View file

@ -0,0 +1,8 @@
{% if sinceVersion is empty and untilVersion is empty %}
*
{% else %}
{% if sinceVersion is not empty %}&gt;={{ sinceVersion }}{% endif %}
{% if untilVersion is not empty %}
{% if sinceVersion is not empty %},{% endif %}&lt;={{ untilVersion }}
{% endif %}
{% endif %}

View file

@ -5,16 +5,52 @@
<!-- Always force latest IE rendering engine (even in intranet) and Chrome Frame -->
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
<title>{{ apiName }}</title>
<link href="http://fonts.googleapis.com/css?family=Droid+Sans:400,700" rel="stylesheet" type="text/css" />
<style type="text/css">
{{ css|raw }}
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
{{ js|raw }}
</script>
</head>
<body>
<div id="header">
<a href=""><h1>{{ apiName }}</h1></a>
<a href="{{ path('nelmio_api_doc_index') }}"><h1>{{ apiName }}</h1></a>
{% if enableSandbox %}
<div id="sandbox_configuration">
{% if bodyFormats|length > 0 %}
body format:
<select id="body_format">
{% if 'form' in bodyFormats %}<option value="form"{{ defaultBodyFormat == 'form' ? ' selected' : '' }}>Form Data</option>{% endif %}
{% 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' %}
api login: <input type="text" id="api_login" value=""/>
api password: <input type="text" id="api_pass" value=""/>
{% elseif authentication.delivery in ['query', 'http', 'header'] %}
api key: <input type="text" id="api_key" value=""/>
{% endif %}
{% 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 %}
<br style="clear: both;" />
</div>
{% include motdTemplate %}
<div class="container" id="resources_container">
<ul id="resources">
{% block content %}{% endblock %}
@ -24,9 +60,760 @@
Documentation auto-generated on {{ date }}
</p>
<script type="text/javascript">
$('.toggler').click(function() {
$(this).next().slideToggle('slow');
var getHash = function() {
return window.location.hash || '';
};
var setHash = function(hash) {
window.location.hash = hash;
};
var clearHash = function() {
var scrollTop, scrollLeft;
if(typeof history === 'object' && typeof history.pushState === 'function') {
history.replaceState('', document.title, window.location.pathname + window.location.search);
} else {
scrollTop = document.body.scrollTop;
scrollLeft = document.body.scrollLeft;
setHash('');
document.body.scrollTop = scrollTop;
document.body.scrollLeft = scrollLeft;
}
};
$(window).load(function() {
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) {
var contentContainer = $(this).next();
if(contentContainer.is(':visible')) {
clearHash();
} else {
setHash($(this).data('href'));
}
contentContainer.slideToggle('fast');
return false;
});
$('.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 {
section.addClass('active');
section.find('.section-list').slideDown('fast');
}
});
$('.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') {
$btn.text('Raw');
} else {
$btn.text('Default');
}
};
var renderRawBody = function ($container) {
var rawData, $btn;
rawData = $container.data('raw-response');
$btn = $container.parents('.pane').find('.to-raw');
$container.addClass('prettyprinted');
$container.html($('<div/>').text(rawData).html());
$btn.removeClass('to-raw');
$btn.addClass('to-prettify');
toggleButtonText($btn);
};
var renderPrettifiedBody = function ($container) {
var rawData, $btn;
rawData = $container.data('raw-response');
$btn = $container.parents('.pane').find('.to-prettify');
$container.removeClass('prettyprinted');
$container.html(attachCollapseMarker(prettifyResponse(rawData)));
prettyPrint && prettyPrint();
$btn.removeClass('to-prettify');
$btn.addClass('to-raw');
toggleButtonText($btn);
};
var unflattenDict = function (body) {
var found = true;
while(found) {
found = false;
for (var key in body) {
var okey;
var value = body[key];
var dictMatch = key.match(/^(.+)\[([^\]]+)\]$/);
if(dictMatch) {
found = true;
okey = dictMatch[1];
var subkey = dictMatch[2];
body[okey] = body[okey] || {};
body[okey][subkey] = value;
delete body[key];
} else {
body[key] = value;
}
}
}
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');
$('.pane.selected', contentGroup).removeClass('selected');
$('.pane.' + $(this).data('pane'), contentGroup).addClass('selected');
$('li', $(this).parent()).removeClass('selected');
$(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('&#9663;')
).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;
text = JSON.stringify(data, undefined, ' ');
} catch (err) {
}
// HTML encode the result
return $('<div>').text(text).html();
};
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) {
var profilerUrl = xhr.getResponseHeader('X-Debug-Token-Link');
if (profilerUrl) {
link.attr('href', profilerUrl);
container.show();
} else {
link.attr('href', '');
container.hide();
}
};
var displayResponseData = function(xhr, container) {
var data = xhr.responseText;
container.data('raw-response', data);
renderPrettifiedBody(container);
container.parents('.pane').find('.to-prettify').text('Raw');
container.parents('.pane').find('.to-raw').text('Raw');
};
var displayResponseHeaders = function(xhr, container) {
var text = xhr.status + ' ' + xhr.statusText + "\n\n";
text += xhr.getAllResponseHeaders();
container.text(text);
};
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 = $('[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';
}
// set requestFormat
var requestFormatMethod = '{{ requestFormatMethod }}';
if (requestFormatMethod == 'format_param') {
params['_format'] = $('#request_format option:selected').text();
formData.append('_format',$('#request_format option:selected').text());
} else if (requestFormatMethod == 'accept_header') {
headers['Accept'] = $('#request_format').val();
}
// set default bodyFormat
var bodyFormat = $('#body_format').val() || '{{ defaultBodyFormat }}';
if(!('Content-type' in headers)) {
if (bodyFormat == 'form') {
headers['Content-type'] = 'application/x-www-form-urlencoded';
} else {
headers['Content-type'] = 'application/json';
}
}
var hasFileTypes = false;
$('.parameters .tuple_type', $(this)).each(function() {
if ($(this).val() == 'file') {
hasFileTypes = true;
}
});
if (hasFileTypes && method != 'POST') {
alert("Sorry, you can only submit files via POST.");
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() {
var key, value;
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();
}
if (value) {
formData.append(key,value);
}
});
}
// retrieve all the parameters to send
$('.parameters .tuple', $(this)).each(function() {
var key, value;
key = $('.key', $(this)).val();
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;
} else {
params[key] = value;
}
}
});
// 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() {
var key, value;
key = $('.key', $(this)).val();
value = $('.value', $(this)).val();
if (value) {
headers[key] = value;
}
});
// fix parameters in URL
for (var key in $.extend({}, params)) {
if (url.indexOf('{' + key + '}') !== -1) {
url = url.replace('{' + key + '}', params[key]);
delete params[key];
}
};
// merge additional params back to real params object
if (!$.isEmptyObject(doubledParams)) {
$.extend(params, doubledParams);
}
// disable all the fiels and buttons
$('input, button', $(this)).attr('disabled', 'disabled');
// append the query authentication
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;
}
// prepare the api enpoint
{% if endpoint == '' and app.request is not null and app.request.host -%}
var endpoint = '{{ app.request.getBaseUrl() }}';
{% else -%}
var endpoint = '{{ endpoint }}';
{% endif -%}
{% if authentication and authentication.custom_endpoint %}
if ($('#api_endpoint') && typeof($('#api_endpoint').val()) != 'undefined') {
endpoint = $('#api_endpoint').val();
}
{% endif %}
//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
var body = {};
if(bodyFormat == 'json' && method != 'GET') {
body = unflattenDict(params);
body = JSON.stringify(body);
} else {
body = params;
}
var data = content.length ? content : body;
var ajaxOptions = {
url: (url.indexOf('http')!=0?endpoint:'') + url,
xhrFields: { withCredentials: true },
type: method,
data: data,
headers: headers,
crossDomain: true,
beforeSend: function (xhr) {
if (authentication_delivery) {
var value;
if ('http' == authentication_delivery) {
if ('basic' == authentication_type) {
value = 'Basic ' + btoa($('#api_login').val() + ':' + $('#api_pass').val());
} else if ('bearer' == authentication_type) {
value = 'Bearer ' + $('#api_key').val();
}
} else if ('header' == authentication_delivery) {
value = $('#api_key').val();
}
xhr.setRequestHeader(api_key_parameter, value);
}
},
complete: function(xhr) {
displayResponse(xhr, method, url, headers, data, result_container);
// and enable them back
$('input:not(.content-type), button', $(self)).removeAttr('disabled');
}
};
// overrides body format to send data properly
if (hasFileTypes) {
ajaxOptions.data = formData;
ajaxOptions.processData = false;
ajaxOptions.contentType = false;
delete(headers['Content-type']);
}
// and trigger the API call
$.ajax(ajaxOptions);
return false;
});
$('.operations').on('click', '.operation > .heading', function(e) {
if (history.pushState) {
history.pushState(null, null, $(this).data('href'));
e.preventDefault();
}
});
$(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('&oplus;' + openChar + '...' + closingChar);
} else {
$(this).html('&#9663;' + $(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'));
e.preventDefault();
});
$('.pane.sandbox').on('click', '.to-prettify', function(e) {
renderPrettifiedBody($(this).parents('.pane').find('.response'));
e.preventDefault();
});
$('.pane.sandbox').on('click', '.to-expand, .to-shrink', function(e) {
var $headers = $(this).parents('.result').find('.headers');
var $label = $(this).parents('.result').find('a.to-expand');
if ($headers.hasClass('to-expand')) {
$headers.removeClass('to-expand');
$headers.addClass('to-shrink');
$label.text('Shrink');
} else {
$headers.removeClass('to-shrink');
$headers.addClass('to-expand');
$label.text('Expand');
}
e.preventDefault();
});
// sets the correct parameter type on load
$('.pane.sandbox .tuple_type').each(function() {
setParameterType($(this));
});
// handles parameter type change
$('.pane.sandbox').on('change', '.tuple_type', function() {
setParameterType($(this),$(this).val());
});
$('.pane.sandbox').on('click', '.add_parameter', function() {
var html = $(this).parents('.pane').find('.parameters_tuple_template').html();
$(this).before(html);
return false;
});
$('.pane.sandbox').on('click', '.add_header', function() {
var html = $(this).parents('.pane').find('.headers_tuple_template').html();
$(this).before(html);
return false;
});
$('.pane.sandbox').on('click', '.remove', function() {
$(this).parent().remove();
});
$('.pane.sandbox').on('click', '.set-content-type', function(e) {
var html;
var $element;
var $headers = $(this).parents('form').find('.headers');
var content_type = $(this).prev('input.value').val();
e.preventDefault();
if (content_type.length === 0) {
return;
}
$headers.find('input.key').each(function() {
if ($.trim($(this).val().toLowerCase()) === 'content-type') {
$element = $(this).parents('p');
return false;
}
});
if (typeof $element === 'undefined') {
html = $(this).parents('.pane').find('.tuple_template').html();
$element = $headers.find('legend').after(html).next('p');
}
$element.find('input.key').val('Content-Type');
$element.find('input.value').val(content_type);
});
{% if authentication and authentication.delivery == 'http' %}
var authentication_delivery = '{{ authentication.delivery }}';
var api_key_parameter = '{{ authentication.name }}';
var authentication_type = '{{ authentication.type }}';
{% elseif authentication and authentication.delivery == 'query' %}
var authentication_delivery = '{{ authentication.delivery }}';
var api_key_parameter = '{{ authentication.name }}';
var search = window.location.search;
var api_key_start = search.indexOf(api_key_parameter) + api_key_parameter.length + 1;
if (api_key_start > 0 ) {
var api_key_end = search.indexOf('&', api_key_start);
var api_key = -1 == api_key_end
? search.substr(api_key_start)
: search.substring(api_key_start, api_key_end);
$('#api_key').val(api_key);
}
{% elseif authentication and authentication.delivery == 'header' %}
var authentication_delivery = '{{ authentication.delivery }}';
var api_key_parameter = '{{ authentication.name }}';
{% else %}
var authentication_delivery = false;
{% endif %}
{% endif %}
</script>
</body>
</html>

View file

@ -1,92 +1,385 @@
<li class="{{ data.method|lower }} operation">
<div class="heading toggler">
<h3>
<span class="http_method">
<a>{{ data.method|upper }}</a>
</span>
<span class="path">
{{ data.uri }}
</span>
</h3>
<ul class="options">
{% if data.description is defined %}
<li>{{ data.description }}</li>
{% endif %}
</ul>
<li class="{{ data.method|lower }} operation" id="{{ data.id }}">
<div class="heading toggler{% if data.deprecated %} deprecated{% endif %}" data-href="#{{ data.id }}">
<h3>
<span class="http_method">
<i>{{ data.method|upper }}</i>
</span>
{% if data.deprecated %}
<span class="deprecated">
<i>DEPRECATED</i>
</span>
{% endif %}
<span class="path">
{% if data.host is defined -%}
{{ data.https ? 'https://' : 'http://' -}}
{{ data.host -}}
{% endif -%}
{{ data.uri }}
</span>
{% if data.tags is defined %}
{% 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>
<ul class="options">
{% if data.description is defined %}
<li>{{ data.description }}</li>
{% endif %}
</ul>
</div>
<div class="content" style="display: {% if displayContent is defined and displayContent == true %}display{% else %}none{% endif %};">
{% if data.requirements is defined and data.requirements is not empty %}
<h4>Requirements</h4>
<table class="fullwidth">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.requirements %}
<tr>
<td>{{ name }}</td>
<td>{{ infos.value }}</td>
<td>{{ infos.type }}</td>
<td>{{ infos.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if data.filters is defined and data.filters is not empty %}
<h4>Filters</h4>
<table class="fullwidth">
<thead>
<tr>
<th>Name</th>
<th>Information</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.filters %}
<tr>
<td>{{ name }}</td>
<td>
<ul>
{% for key, value in infos %}
<li><em>{{ key }}</em> : {{ value|json_encode|trim('"') }}</li>
<div class="content" style="display: {% if displayContent is defined and displayContent == true %}display{% else %}none{% endif %};">
<ul class="tabs">
{% if enableSandbox %}
<li class="selected" data-pane="content">Documentation</li>
<li data-pane="sandbox">Sandbox</li>
{% endif %}
</ul>
<div class="panes">
<div class="pane content selected">
{% if data.documentation is defined and data.documentation is not empty %}
<h4>Documentation</h4>
<div>{{ data.documentation|extra_markdown }}</div>
{% endif %}
{% if data.link is defined and data.link is not empty %}
<h4>Link</h4>
<div><a href="{{ data.link }}" target="_blank">{{ data.link }}</a></div>
{% endif %}
{% if data.requirements is defined and data.requirements is not empty %}
<h4>Requirements</h4>
<table class="fullwidth">
<thead>
<tr>
<th>Name</th>
<th>Requirement</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.requirements %}
<tr>
<td>{{ name }}</td>
<td>{{ infos.requirement is defined ? infos.requirement : ''}}</td>
<td>{{ infos.dataType is defined ? infos.dataType : ''}}</td>
<td>{{ infos.description is defined ? infos.description : ''}}</td>
</tr>
{% endfor %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</tbody>
</table>
{% endif %}
{% if data.parameters is defined and data.parameters is not empty %}
<h4>Parameters</h4>
<table class='fullwidth'>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Required?</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.parameters %}
{% if data.filters is defined and data.filters is not empty %}
<h4>Filters</h4>
<table class="fullwidth">
<thead>
<tr>
<th>Name</th>
<th>Information</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.filters %}
<tr>
<td>{{ name }}</td>
<td>
<table>
{% for key, value in infos %}
<tr>
<td>{{ key|title }}</td>
<td>{{ value|json_encode(constant('JSON_UNESCAPED_UNICODE'))|replace({'\\\\': '\\'})|trim('"') }}</td>
</tr>
{% endfor %}
</table>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if data.parameters is defined and data.parameters is not empty %}
<h4>Parameters</h4>
<table class='fullwidth'>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Required?</th>
<th>Format</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.parameters %}
{% if not infos.readonly %}
<tr>
<td>{{ name }}</td>
<td>{{ infos.dataType is defined ? infos.dataType : '' }}</td>
<td>{{ infos.required ? 'true' : 'false' }}</td>
<td class="format">{{ infos.format }}</td>
<td>{{ infos.description is defined ? infos.description|trans : '' }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% endif %}
{% if data.headers is defined and data.headers is not empty %}
<h4>Headers</h4>
<table class="fullwidth">
<thead>
<tr>
<td>{{ name }}</td>
<td>{{ infos.dataType }}</td>
<td>{{ infos.required ? 'true' : 'false' }}</td>
<td>{{ infos.description }}</td>
<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>
</thead>
{% for status_code, response in data.parsedResponseMap %}
<tbody>
<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 %}
</tbody>
</table>
{% endif %}
</table>
{% endif %}
{% if data.statusCodes is defined and data.statusCodes is not empty %}
<h4>Status Codes</h4>
<table class="fullwidth">
<thead>
<tr>
<th>Status Code</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for status_code, descriptions in data.statusCodes %}
<tr>
<td><a href="http://en.wikipedia.org/wiki/HTTP_{{ status_code }}" target="_blank">{{ status_code }}</a></td>
<td>
<ul>
{% for description in descriptions %}
<li>{{ description }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if data.cache is defined and data.cache is not empty %}
<h4>Cache</h4>
<div>{{ data.cache }}s</div>
{% endif %}
</div>
{% 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 HTTP if you want to use the sandbox.
{% else %}
<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 %}
<h4>Requirements</h4>
{% for name, infos in data.requirements %}
<p class="tuple">
<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>
</p>
{% endfor %}
{% endif %}
{% if data.filters is defined %}
<h4>Filters</h4>
{% for name, infos in data.filters %}
<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>
</p>
{% endfor %}
{% endif %}
{% if data.parameters is defined %}
<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|trans }}{% endif %}">
<input type="text" class="key" value="{{ name }}" placeholder="Key" />
<span>=</span>
<select class="tuple_type">
<option value="">Type</option>
<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|trans }}{% else %}Value{% endif %}" {% if infos.default is defined %} value="{{ infos.default }}" {% endif %}/> <span class="remove">-</span>
</p>
{% endif %}
{% endfor %}
<button type="button" class="add_parameter">New parameter</button>
{% endif %}
</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 %}
<p class="tuple">
<input type="text" class="key" value="Accept" />
<span>=</span>
<input type="text" class="value" value="{{ acceptType }}" /> <span class="remove">-</span>
</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>
<input type="text" class="value" placeholder="Value" /> <span class="remove">-</span>
</p>
<button type="button" class="add_header">New header</button>
</fieldset>
<fieldset class="request-content">
<legend>Content</legend>
<textarea class="content" placeholder="Content set here will override the parameters that do not match the url"></textarea>
<p class="tuple">
<input type="text" class="key content-type" value="Content-Type" disabled="disabled" />
<span>=</span>
<input type="text" class="value" placeholder="Value" />
<button type="button" class="set-content-type">Set header</button> <small>Replaces header if set</small>
</p>
</fieldset>
<div class="buttons">
<input type="submit" value="Try!" />
</div>
</form>
<script type="text/x-tmpl" class="parameters_tuple_template">
<p class="tuple">
<input type="text" class="key" placeholder="Key" />
<span>=</span>
<select class="tuple_type">
<option value="">Type</option>
<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>
</script>
<script type="text/x-tmpl" class="headers_tuple_template">
<p class="tuple">
<input type="text" class="key" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="Value" /> <span class="remove">-</span>
</p>
</script>
<div class="result">
<h4>Request URL</h4>
<pre class="url"></pre>
<h4 class="request-body-header">Request body</h4>
<pre class="request-body"></pre>
<h4>Response Headers&nbsp;<small>[<a href="" class="to-expand">Expand</a>]</small>&nbsp;<small class="profiler">[<a href="" class="profiler-link" target="_blank">Profiler</a>]</small></h4>
<pre class="headers to-expand"></pre>
<h4>Response Body&nbsp;<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>
{% endif %}
</div>
</div>
</li>

View file

@ -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>

View file

@ -1,20 +1,48 @@
{% extends "NelmioApiDocBundle::layout.html.twig" %}
{% extends "@NelmioApiDoc/layout.html.twig" %}
{% block content %}
{% for resource, methods in resources %}
<li class="resource">
<div class="heading">
<h2>{{ resource }}</h2>
</div>
<ul class="endpoints">
<li class="endpoint">
<ul class="operations">
{% for data in methods %}
{% include 'NelmioApiDocBundle::method.html.twig' %}
{% endfor %}
</ul>
</li>
</ul>
</li>
<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{{ 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 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' %}
<h2>{{ resource }}</h2>
{% elseif resource != 'others' %}
<h2>{{ resource }}</h2>
{% endif %}
</div>
<ul class="endpoints">
<li class="endpoint">
<ul class="operations">
{% for data in methods %}
{% include '@NelmioApiDoc/method.html.twig' %}
{% endfor %}
</ul>
</li>
</ul>
</li>
{% endfor %}
{% if section != '_others' %}
</ul>
</li>
{% endif %}
{% endfor %}
{% endblock content %}

236
Swagger/ModelRegistry.php Normal file
View 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 = [];
}
}

View file

@ -11,158 +11,400 @@
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();
$array = $annot->toArray();
$annot = new ApiDoc($data);
$this->assertTrue(is_array($annot->getFilters()));
$this->assertCount(0, $annot->getFilters());
$this->assertTrue(is_array($array));
$this->assertFalse(isset($array['filters']));
$this->assertFalse($annot->isResource());
$this->assertNull($annot->getDescription());
$this->assertNull($annot->getFormType());
$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(isset($array['headers']));
}
public function testConstructWithInvalidData()
public function testConstructWithInvalidData(): void
{
$data = array(
'unknown' => 'foo',
'array' => array('bar' => 'bar'),
);
$annot = new ApiDoc();
$array = $annot->toArray();
$annot = new ApiDoc($data);
$this->assertTrue(is_array($annot->getFilters()));
$this->assertCount(0, $annot->getFilters());
$this->assertTrue(is_array($array));
$this->assertFalse(isset($array['filters']));
$this->assertFalse($annot->isResource());
$this->assertNull($annot->getDescription());
$this->assertNull($annot->getFormType());
$this->assertFalse($annot->getDeprecated());
$this->assertFalse(isset($array['description']));
$this->assertFalse(isset($array['requirements']));
$this->assertFalse(isset($array['parameters']));
$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($annot->getFilters()));
$this->assertCount(0, $annot->getFilters());
$this->assertTrue(is_array($array));
$this->assertFalse(isset($array['filters']));
$this->assertFalse($annot->isResource());
$this->assertEquals($data['description'], $annot->getDescription());
$this->assertNull($annot->getFormType());
$this->assertFalse($annot->getDeprecated());
$this->assertEquals($data['description'], $array['description']);
$this->assertFalse(isset($array['requirements']));
$this->assertFalse(isset($array['parameters']));
$this->assertNull($annot->getInput());
}
public function testConstructDefinesAFormType()
public function testConstructDefinesAFormType(): void
{
$data = array(
'description' => 'Heya',
'formType' => 'My\Form\Type',
$data = [
'description' => 'Heya',
'input' => 'My\Form\Type',
];
$annot = new ApiDoc(
description: $data['description'],
input: $data['input']
);
$array = $annot->toArray();
$annot = new ApiDoc($data);
$this->assertTrue(is_array($annot->getFilters()));
$this->assertCount(0, $annot->getFilters());
$this->assertTrue(is_array($array));
$this->assertFalse(isset($array['filters']));
$this->assertFalse($annot->isResource());
$this->assertEquals($data['description'], $annot->getDescription());
$this->assertEquals($data['formType'], $annot->getFormType());
$this->assertFalse($annot->getDeprecated());
$this->assertEquals($data['description'], $array['description']);
$this->assertFalse(isset($array['requirements']));
$this->assertFalse(isset($array['parameters']));
$this->assertEquals($data['input'], $annot->getInput());
}
public function testConstructMethodIsResource()
public function testConstructMethodIsResource(): void
{
$data = array(
'resource' => true,
'description' => 'Heya',
'formType' => 'My\Form\Type',
$data = [
'resource' => true,
'description' => 'Heya',
'deprecated' => true,
'input' => 'My\Form\Type',
];
$annot = new ApiDoc(
resource: $data['resource'],
description: $data['description'],
deprecated: $data['deprecated'],
input: $data['input']
);
$array = $annot->toArray();
$annot = new ApiDoc($data);
$this->assertTrue(is_array($annot->getFilters()));
$this->assertCount(0, $annot->getFilters());
$this->assertTrue(is_array($array));
$this->assertFalse(isset($array['filters']));
$this->assertTrue($annot->isResource());
$this->assertEquals($data['description'], $annot->getDescription());
$this->assertEquals($data['formType'], $annot->getFormType());
$this->assertTrue($annot->getDeprecated());
$this->assertEquals($data['description'], $array['description']);
$this->assertFalse(isset($array['requirements']));
$this->assertFalse(isset($array['parameters']));
$this->assertEquals($data['input'], $annot->getInput());
}
public function testConstructMethodResourceIsFalse()
public function testConstructMethodResourceIsFalse(): void
{
$data = array(
'resource' => false,
'description' => 'Heya',
'formType' => 'My\Form\Type',
$data = [
'resource' => false,
'description' => 'Heya',
'deprecated' => false,
'input' => 'My\Form\Type',
];
$annot = new ApiDoc(
resource: $data['resource'],
description: $data['description'],
deprecated: $data['deprecated'],
input: $data['input']
);
$array = $annot->toArray();
$annot = new ApiDoc($data);
$this->assertTrue(is_array($annot->getFilters()));
$this->assertCount(0, $annot->getFilters());
$this->assertTrue(is_array($array));
$this->assertFalse(isset($array['filters']));
$this->assertFalse($annot->isResource());
$this->assertEquals($data['description'], $annot->getDescription());
$this->assertEquals($data['formType'], $annot->getFormType());
$this->assertEquals($data['description'], $array['description']);
$this->assertFalse(isset($array['requirements']));
$this->assertFalse(isset($array['parameters']));
$this->assertEquals($data['deprecated'], $array['deprecated']);
$this->assertEquals($data['input'], $annot->getInput());
}
public function testConstructMethodHasFilters()
public function testConstructMethodHasFilters(): void
{
$data = array(
'resource' => true,
'description' => 'Heya',
'filters' => array(
array('name' => 'a-filter'),
),
$data = [
'resource' => true,
'deprecated' => false,
'description' => 'Heya',
'filters' => [
['name' => 'a-filter'],
],
];
$annot = new ApiDoc(
resource: $data['resource'],
description: $data['description'],
deprecated: $data['deprecated'],
filters: $data['filters']
);
$array = $annot->toArray();
$annot = new ApiDoc($data);
$this->assertTrue(is_array($annot->getFilters()));
$this->assertCount(1, $annot->getFilters());
$this->assertEquals(array('a-filter' => array()), $annot->getFilters());
$this->assertTrue(is_array($array));
$this->assertTrue(is_array($array['filters']));
$this->assertCount(1, $array['filters']);
$this->assertEquals(['a-filter' => []], $array['filters']);
$this->assertTrue($annot->isResource());
$this->assertEquals($data['description'], $annot->getDescription());
$this->assertNull($annot->getFormType());
$this->assertEquals($data['description'], $array['description']);
$this->assertFalse(isset($array['requirements']));
$this->assertFalse(isset($array['parameters']));
$this->assertEquals($data['deprecated'], $array['deprecated']);
$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);
$data = [
'description' => 'Heya',
'filters' => [
['parameter' => 'foo'],
],
];
$annot = new ApiDoc(
description: $data['description'],
filters: $data['filters']
);
}
public function testConstructNoFiltersIfFormTypeDefined()
public function testConstructWithStatusCodes(): void
{
$data = array(
'resource' => true,
'description' => 'Heya',
'formType' => 'My\Form\Type',
'filters' => array(
array('name' => 'a-filter'),
),
$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) ? [$message] : $message);
}
}
public function testConstructWithRequirements(): void
{
$data = [
'requirements' => [
[
'name' => 'fooId',
'requirement' => '\d+',
'dataType' => 'integer',
'description' => 'This requirement might be used withing action method directly from Request object',
],
],
];
$annot = new ApiDoc(
requirements: $data['requirements']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
$this->assertTrue(isset($array['requirements']['fooId']));
$this->assertTrue(isset($array['requirements']['fooId']['dataType']));
}
public function testConstructWithParameters(): void
{
$data = [
'parameters' => [
[
'name' => 'fooId',
'dataType' => 'integer',
'description' => 'Some description',
],
],
];
$annot = new ApiDoc(
parameters: $data['parameters']
);
$array = $annot->toArray();
$this->assertTrue(is_array($array));
$this->assertTrue(isset($array['parameters']['fooId']));
$this->assertTrue(isset($array['parameters']['fooId']['dataType']));
}
public function testConstructWithHeaders(): void
{
$data = [
'headers' => [
[
'name' => 'headerName',
'description' => 'Some description',
],
],
];
$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(['beta'], $array['tags']);
}
public function testConstructWithOneTagAndColorCode(): void
{
$data = [
'tags' => [
'beta' => '#ff0000',
],
];
$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']
);
$annot = new ApiDoc($data);
$map = $apiDoc->getResponseMap();
$this->assertTrue(is_array($annot->getFilters()));
$this->assertCount(0, $annot->getFilters());
$this->assertEquals(array(), $annot->getFilters());
$this->assertTrue($annot->isResource());
$this->assertEquals($data['description'], $annot->getDescription());
$this->assertEquals($data['formType'], $annot->getFormType());
$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());
}
}

View 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}',
],
],
];
}
}

View 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());
}
}

View file

@ -15,12 +15,14 @@ use Nelmio\ApiDocBundle\Tests\WebTestCase;
class RequestListenerTest extends WebTestCase
{
public function testDocQueryArg()
public function testDocQueryArg(): void
{
$client = $this->createClient();
$crawler = $client->request('GET', '/tests?_doc=1');
$this->assertEquals('/tests', trim($crawler->filter(".operation .path:contains('/tests')")->text()), 'Event listener should capture ?_doc=1 requests');
$client->request('GET', '/tests?_doc=1');
$content = $client->getResponse()->getContent();
$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');

View file

@ -0,0 +1,405 @@
<?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\ApiDocExtractor;
use Nelmio\ApiDocBundle\Tests\WebTestCase;
class ApiDocExtractorTest extends WebTestCase
{
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(): void
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
set_error_handler([$this, 'handleDeprecation']);
$data = $extractor->all();
restore_error_handler();
$this->assertTrue(is_array($data));
$this->assertCount(self::$ROUTES_QUANTITY_DEFAULT, $data);
$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\Attribute\ApiDoc', $d['annotation']);
$this->assertInstanceOf('Symfony\Component\Routing\Route', $d['annotation']->getRoute());
$this->assertNotNull($d['resource']);
}
}
public function testRouteVersionChecking(): void
{
$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\Attribute\ApiDoc', $annotation);
$this->assertTrue($annotation->isResource());
$this->assertEquals('index action', $annotation->getDescription());
$array = $annotation->toArray();
$this->assertTrue(is_array($array['filters']));
$this->assertNull($annotation->getInput());
$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
;
$this->assertEquals($annotation, $annotation2);
}
public function testGetWithBadController(): void
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Undefined\Controller::indexAction', 'test_route_1');
$this->assertNull($data);
$data = $extractor->get('undefined_service:index', 'test_service_route_1');
$this->assertNull($data);
}
public function testGetWithBadRoute(): void
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::indexAction', 'invalid_route');
$this->assertNull($data);
$data = $extractor->get('nelmio.test.controller:indexAction', 'invalid_route');
$this->assertNull($data);
}
public function testGetWithInvalidPath(): void
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController', 'test_route_1');
$this->assertNull($data);
$data = $extractor->get('nelmio.test.controller', 'test_service_route_1');
$this->assertNull($data);
}
public function testGetWithMethodWithoutApiDocAnnotation(): void
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::anotherAction', 'test_route_3');
$this->assertNull($data);
$data = $extractor->get('nelmio.test.controller:anotherAction', 'test_service_route_1');
$this->assertNull($data);
}
public function testGetWithDocComment(): void
{
$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.',
$annotation->getDescription()
);
$data = $annotation->toArray();
$this->assertEquals(
4,
count($data['requirements'])
);
$this->assertEquals(
'The param type',
$data['requirements']['paramType']['description']
);
$this->assertEquals(
'The param id',
$data['requirements']['param']['description']
);
}
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::DeprecatedAction', 'test_route_14');
$this->assertNotNull($annotation);
$this->assertTrue(
$annotation->getDeprecated()
);
}
public function testOutputWithSelectedParsers(): void
{
$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',
$parsers[0]
);
$this->assertEquals(
'Nelmio\\ApiDocBundle\\Parser\\ValidationParser',
$parsers[1]
);
$this->assertCount(2, $parsers);
}
public function testInputWithSelectedParsers(): void
{
$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',
$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);
}
}

View file

@ -1,147 +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 ApiDocExtractorTest extends WebTestCase
{
public function testAll()
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->all();
$this->assertTrue(is_array($data));
$this->assertCount(8, $data);
foreach ($data as $d) {
$this->assertTrue(is_array($d));
$this->assertArrayHasKey('annotation', $d);
$this->assertArrayHasKey('route', $d);
$this->assertArrayHasKey('resource', $d);
$this->assertInstanceOf('Nelmio\ApiDocBundle\Annotation\ApiDoc', $d['annotation']);
$this->assertInstanceOf('Symfony\Component\Routing\Route', $d['route']);
$this->assertNotNull($d['resource']);
}
$a1 = $data[0]['annotation'];
$this->assertTrue($a1->isResource());
$this->assertEquals('index action', $a1->getDescription());
$this->assertTrue(is_array($a1->getFilters()));
$this->assertNull($a1->getFormType());
$a1 = $data[1]['annotation'];
$this->assertTrue($a1->isResource());
$this->assertEquals('index action', $a1->getDescription());
$this->assertTrue(is_array($a1->getFilters()));
$this->assertNull($a1->getFormType());
$a2 = $data[2]['annotation'];
$this->assertFalse($a2->isResource());
$this->assertEquals('create test', $a2->getDescription());
$this->assertTrue(is_array($a2->getFilters()));
$this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getFormType());
$a2 = $data[3]['annotation'];
$this->assertFalse($a2->isResource());
$this->assertEquals('create test', $a2->getDescription());
$this->assertTrue(is_array($a2->getFilters()));
$this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getFormType());
}
public function testGet()
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::indexAction', 'test_route_1');
$this->assertTrue(isset($data['route']));
$this->assertTrue(isset($data['annotation']));
$a = $data['annotation'];
$this->assertTrue($a->isResource());
$this->assertEquals('index action', $a->getDescription());
$this->assertTrue(is_array($a->getFilters()));
$this->assertNull($a->getFormType());
$data2 = $extractor->get('nemlio.test.controller:indexAction', 'test_service_route_1');
$data2['route']->setDefault('_controller', $data['route']->getDefault('_controller'));
$this->assertEquals($data, $data2);
}
public function testGetWithBadController()
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Undefined\Controller::indexAction', 'test_route_1');
$this->assertNull($data);
$data = $extractor->get('undefined_service:index', 'test_service_route_1');
$this->assertNull($data);
}
public function testGetWithBadRoute()
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::indexAction', 'invalid_route');
$this->assertNull($data);
$data = $extractor->get('nemlio.test.controller:indexAction', 'invalid_route');
$this->assertNull($data);
}
public function testGetWithInvalidPattern()
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController', 'test_route_1');
$this->assertNull($data);
$data = $extractor->get('nemlio.test.controller', 'test_service_route_1');
$this->assertNull($data);
}
public function testGetWithMethodWithoutApiDocAnnotation()
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::anotherAction', 'test_route_3');
$this->assertNull($data);
$data = $extractor->get('nemlio.test.controller:anotherAction', 'test_service_route_1');
$this->assertNull($data);
}
public function testGetWithDocComment()
{
$container = $this->getContainer();
$extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor');
$data = $extractor->get('Nelmio\ApiDocBundle\Tests\Fixtures\Controller\TestController::myCommentedAction', 'test_route_5');
$this->assertNotNull($data);
$this->assertEquals(
"This method is useful to test if the getDocComment works. And, it supports multilines until the first '@' char.",
$data['annotation']->getDescription()
);
}
}

View 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);
}
}

View 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 '],
];
}
}

View 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);
}
}

View 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
{
}
}

View file

@ -11,44 +11,72 @@
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Controller;
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;
class TestController
{
/**
* @ApiDoc(
* resource=true,
* description="index action",
* filters={
* {"name"="a", "dataType"="integer"},
* {"name"="b", "dataType"="string", "arbitrary"={"arg1", "arg2"}}
* }
* )
*/
#[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']],
],
)]
public function indexAction()
{
return new Response('tests');
}
/**
* @ApiDoc(
* description="create test",
* formType="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
{
}
public function anotherAction()
#[ApiDoc(
description: 'post test 2',
views: ['default', 'premium'],
resource: true
)]
public function postTest2Action(): void
{
}
/**
* @ApiDoc(description="Action without HTTP verb")
*/
public function anyAction()
#[ApiDoc(
description: 'Action with required parameters',
input: "Nelmio\ApiDocBundle\Tests\Fixtures\Form\RequiredType"
)]
public function requiredParametersAction(): void
{
}
public function anotherAction(): void
{
}
#[ApiDoc]
public function routeVersionAction(): void
{
}
#[ApiDoc(description: 'Action without HTTP verb')]
public function anyAction(): void
{
}
@ -56,19 +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 $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()
* @deprecated
*/
public function yetAnotherAction()
#[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
{
}
/**
* @see http://symfony.com
*/
#[ApiDoc]
public function withLinkAction(): void
{
}
#[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\JmsTest",
input: [
'class' => "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
],
)]
public function defaultJmsAnnotations(): void
{
}
#[ApiDoc(
description: 'Route with host placeholder',
views: ['default']
)]
public function routeWithHostAction(): void
{
}
}

View 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';
}
}

View file

@ -0,0 +1,46 @@
<?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 CollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$collectionType = 'Symfony\Component\Form\Extension\Core\Type\CollectionType';
$builder
->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';
}
}

View file

@ -0,0 +1,46 @@
<?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 CompoundType
*
* @author Bez Hermoso <bez@activelamp.com>
*/
class CompoundType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('sub_form', LegacyFormHelper::isLegacy() ? new SimpleType() : __NAMESPACE__ . '\SimpleType')
->add('a', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\NumberType'))
;
}
/**
* BC SF < 2.8
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
public function getBlockPrefix()
{
return '';
}
}

View file

@ -0,0 +1,62 @@
<?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 Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DependencyType extends AbstractType
{
public function __construct(array $mandatoryArgument)
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('a', null, ['description' => 'A nice description'])
;
}
/**
* @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\Test',
]);
return;
}
/**
* BC SF < 2.8
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
public function getBlockPrefix()
{
return 'dependency_type';
}
}

View 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';
}
}

View file

@ -0,0 +1,79 @@
<?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
{
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', $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']]]
)
;
}
/**
* @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\ImprovedTest',
]);
return;
}
/**
* BC SF < 2.8
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
public function getBlockPrefix()
{
return '';
}
}

View file

@ -0,0 +1,69 @@
<?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 Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class RequireConstructionType extends AbstractType
{
private $noThrow;
public function __construct($optionalArgs = null)
{
$this->noThrow = true;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
if (true !== $this->noThrow) {
throw new \RuntimeException(__CLASS__ . ' require contruction');
}
$builder
->add('a', null, ['description' => 'A nice description'])
;
}
/**
* @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\Test',
]);
return;
}
/**
* BC SF < 2.8
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
public function getBlockPrefix()
{
return 'require_construction_type';
}
}

View 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 '';
}
}

View file

@ -0,0 +1,53 @@
<?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 Bez Hermoso <bez@activelamp.com>
*/
class SimpleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('a', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'), [
'description' => 'Something that describes A.',
])
->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'))
;
}
/**
* BC SF < 2.8
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
public function getBlockPrefix()
{
return 'simple';
}
}

View file

@ -11,33 +11,52 @@
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Form;
use Nelmio\ApiDocBundle\Util\LegacyFormHelper;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TestType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilder $builder, array $options)
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('a', null, array('description' => 'A nice description'));
$builder->add('b');
$builder->add($builder->create('c', 'checkbox'));
$builder
->add('a', null, ['description' => 'A nice description'])
->add('b')
->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 getDefaultOptions(array $options)
public function setDefaultOptions(OptionsResolverInterface $resolver): void
{
return 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 'foobar';
return $this->getBlockPrefix();
}
public function getBlockPrefix()
{
return '';
}
}

View 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
{
}

View file

@ -0,0 +1,28 @@
<?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 ImprovedTest
{
public $dt1;
public $dt2;
public $dt3;
public $dt4;
public $dt5;
public $d1;
public $d2;
public $c1;
public $c2;
public $c3;
public $c4;
public $e1;
}

View file

@ -0,0 +1,22 @@
<?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 JmsChild extends JmsTest
{
/**
* @JMS\Type("string");
*/
public $child;
}

Some files were not shown because too many files have changed in this diff Show more