Skip to content

Commit 3e21c5f

Browse files
authored
Merge pull request #1 from mpociot/master
Update from base
2 parents d3c565c + 91d1191 commit 3e21c5f

File tree

12 files changed

+163
-62
lines changed

12 files changed

+163
-62
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ To generate your API documentation, use the `api:generate` artisan command.
3434

3535
```sh
3636
$ php artisan api:generate --routePrefix="api/v1/*"
37+
3738
```
39+
You can pass in multiple prefixes by spearating each prefix with comma.
40+
41+
```sh
42+
$ php artisan api:generate --routePrefix="api/v1/*,api/public/*"
43+
```
44+
It will generate documentation for all of the routes whose prefixes are `api/v1/` and `api/public/`
45+
3846

3947
This command will scan your applications routes for the URIs matching `api/v1/*` and will parse these controller methods and form requests. For example:
4048

@@ -53,7 +61,7 @@ Route::group(array('prefix' => 'api/v1', 'middleware' => []), function () {
5361
Option | Description
5462
--------- | -------
5563
`output` | The output path used for the generated documentation. Default: `public/docs`
56-
`routePrefix` | The route prefix to use for generation - `*` can be used as a wildcard
64+
`routePrefix` | The route prefix to use for generation - `*` can be used as a wildcard
5765
`routes` | The route names to use for generation - Required if no routePrefix is provided
5866
`middleware` | The middlewares to use for generation
5967
`noResponseCalls` | Disable API response calls

src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class GenerateDocumentation extends Command
2222
*/
2323
protected $signature = 'api:generate
2424
{--output=public/docs : The output path for the generated documentation}
25-
{--routePrefix= : The route prefix to use for generation}
25+
{--routeDomain= : The route domain (or domains) to use for generation}
26+
{--routePrefix= : The route prefix (or prefixes) to use for generation}
2627
{--routes=* : The route names to use for generation}
2728
{--middleware= : The middleware to use for generation}
2829
{--noResponseCalls : Disable API response calls}
@@ -68,23 +69,37 @@ public function handle()
6869
}
6970

7071
$allowedRoutes = $this->option('routes');
72+
$routeDomain = $this->option('routeDomain');
7173
$routePrefix = $this->option('routePrefix');
7274
$middleware = $this->option('middleware');
7375

7476
$this->setUserToBeImpersonated($this->option('actAsUserId'));
7577

76-
if ($routePrefix === null && ! count($allowedRoutes) && $middleware === null) {
77-
$this->error('You must provide either a route prefix or a route or a middleware to generate the documentation.');
78+
if ($routePrefix === null && $routeDomain === null && ! count($allowedRoutes) && $middleware === null) {
79+
$this->error('You must provide either a route prefix, a route domain, a route or a middleware to generate the documentation.');
7880

7981
return false;
8082
}
8183

8284
$generator->prepareMiddleware($this->option('useMiddlewares'));
8385

86+
$routePrefixes = explode(',', $routePrefix ?: '*');
87+
$routeDomains = explode(',', $routeDomain ?: '*');
88+
89+
$parsedRoutes = [];
90+
8491
if ($this->option('router') === 'laravel') {
85-
$parsedRoutes = $this->processLaravelRoutes($generator, $allowedRoutes, $routePrefix, $middleware);
92+
foreach ($routeDomains as $routeDomain) {
93+
foreach ($routePrefixes as $routePrefix) {
94+
$parsedRoutes += $this->processLaravelRoutes($generator, $allowedRoutes, $routeDomain, $routePrefix, $middleware);
95+
}
96+
}
8697
} else {
87-
$parsedRoutes = $this->processDingoRoutes($generator, $allowedRoutes, $routePrefix, $middleware);
98+
foreach ($routeDomains as $routeDomain) {
99+
foreach ($routePrefixes as $routePrefix) {
100+
$parsedRoutes += $this->processDingoRoutes($generator, $allowedRoutes, $routeDomain, $routePrefix, $middleware);
101+
}
102+
}
88103
}
89104
$parsedRoutes = collect($parsedRoutes)->groupBy('resource')->sort(function ($a, $b) {
90105
return strcmp($a->first()['resource'], $b->first()['resource']);
@@ -125,10 +140,6 @@ private function writeMarkdown($parsedRoutes)
125140
$generatedDocumentation = file_get_contents($targetFile);
126141
$compareDocumentation = file_get_contents($compareFile);
127142

128-
if (preg_match('/<!-- START_INFO -->(.*)<!-- END_INFO -->/is', $generatedDocumentation, $generatedInfoText)) {
129-
$infoText = trim($generatedInfoText[1], "\n");
130-
}
131-
132143
if (preg_match('/---(.*)---\\s<!-- START_INFO -->/is', $generatedDocumentation, $generatedFrontmatter)) {
133144
$frontmatter = trim($generatedFrontmatter[1], "\n");
134145
}
@@ -222,12 +233,12 @@ private function setUserToBeImpersonated($actAs)
222233
if (! empty($actAs)) {
223234
if (version_compare($this->laravel->version(), '5.2.0', '<')) {
224235
$userModel = config('auth.model');
225-
$user = $userModel::find((int) $actAs);
236+
$user = $userModel::find($actAs);
226237
$this->laravel['auth']->setUser($user);
227238
} else {
228239
$provider = $this->option('authProvider');
229240
$userModel = config("auth.providers.$provider.model");
230-
$user = $userModel::find((int) $actAs);
241+
$user = $userModel::find($actAs);
231242
$this->laravel['auth']->guard($this->option('authGuard'))->setUser($user);
232243
}
233244
}
@@ -248,20 +259,25 @@ private function getRoutes()
248259
/**
249260
* @param AbstractGenerator $generator
250261
* @param $allowedRoutes
262+
* @param $routeDomain
251263
* @param $routePrefix
252264
*
253265
* @return array
254266
*/
255-
private function processLaravelRoutes(AbstractGenerator $generator, $allowedRoutes, $routePrefix, $middleware)
267+
private function processLaravelRoutes(AbstractGenerator $generator, $allowedRoutes, $routeDomain, $routePrefix, $middleware)
256268
{
257269
$withResponse = $this->option('noResponseCalls') === false;
258270
$routes = $this->getRoutes();
259271
$bindings = $this->getBindings();
260272
$parsedRoutes = [];
261273
foreach ($routes as $route) {
262-
if (in_array($route->getName(), $allowedRoutes) || str_is($routePrefix, $generator->getUri($route)) || in_array($middleware, $route->middleware())) {
274+
if (in_array($route->getName(), $allowedRoutes)
275+
|| (str_is($routeDomain, $generator->getDomain($route))
276+
&& str_is($routePrefix, $generator->getUri($route)))
277+
|| in_array($middleware, $route->middleware())
278+
) {
263279
if ($this->isValidRoute($route) && $this->isRouteVisibleForDocumentation($route->getAction()['uses'])) {
264-
$parsedRoutes[] = $generator->processRoute($route, $bindings, $this->option('header'), $withResponse);
280+
$parsedRoutes[] = $generator->processRoute($route, $bindings, $this->option('header'), $withResponse && in_array('GET', $generator->getMethods($route)));
265281
$this->info('Processed route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route));
266282
} else {
267283
$this->warn('Skipping route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route));
@@ -275,18 +291,25 @@ private function processLaravelRoutes(AbstractGenerator $generator, $allowedRout
275291
/**
276292
* @param AbstractGenerator $generator
277293
* @param $allowedRoutes
294+
* @param $routeDomain
278295
* @param $routePrefix
279296
*
280297
* @return array
281298
*/
282-
private function processDingoRoutes(AbstractGenerator $generator, $allowedRoutes, $routePrefix, $middleware)
299+
private function processDingoRoutes(AbstractGenerator $generator, $allowedRoutes, $routeDomain, $routePrefix, $middleware)
283300
{
284301
$withResponse = $this->option('noResponseCalls') === false;
285302
$routes = $this->getRoutes();
286303
$bindings = $this->getBindings();
287304
$parsedRoutes = [];
288305
foreach ($routes as $route) {
289-
if (empty($allowedRoutes) || in_array($route->getName(), $allowedRoutes) || str_is($routePrefix, $route->uri()) || in_array($middleware, $route->middleware())) {
306+
if (empty($allowedRoutes)
307+
// TODO extract this into a method
308+
|| in_array($route->getName(), $allowedRoutes)
309+
|| (str_is($routeDomain, $generator->getDomain($route))
310+
&& str_is($routePrefix, $generator->getUri($route)))
311+
|| in_array($middleware, $route->middleware())
312+
) {
290313
if ($this->isValidRoute($route) && $this->isRouteVisibleForDocumentation($route->getAction()['uses'])) {
291314
$parsedRoutes[] = $generator->processRoute($route, $bindings, $this->option('header'), $withResponse);
292315
$this->info('Processed route: ['.implode(',', $route->getMethods()).'] '.$route->uri());

src/Mpociot/ApiDoc/Generators/AbstractGenerator.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
abstract class AbstractGenerator
1616
{
17+
/**
18+
* @param $route
19+
*
20+
* @return mixed
21+
*/
22+
abstract public function getDomain($route);
23+
1724
/**
1825
* @param $route
1926
*
@@ -44,7 +51,7 @@ abstract public function processRoute($route, $bindings = [], $withResponse = tr
4451
*
4552
* @return void
4653
*/
47-
abstract public function prepareMiddleware($disable = false);
54+
abstract public function prepareMiddleware($enable = false);
4855

4956
/**
5057
* Get the response from the docblock if available.
@@ -147,7 +154,7 @@ protected function getRouteResponse($route, $bindings, $headers = [])
147154
})->collapse()->toArray();
148155

149156
//Changes url with parameters like /users/{user} to /users/1
150-
$uri = preg_replace('/{(.*?)}/', 1, $uri);
157+
$uri = preg_replace('/{(.*?)}/', 1, $uri); // 1 is the default value for route parameters
151158

152159
return $this->callRoute(array_shift($methods), $uri, [], [], [], $headers);
153160
}
@@ -163,6 +170,7 @@ protected function addRouteModelBindings($route, $bindings)
163170
$uri = $this->getUri($route);
164171
foreach ($bindings as $model => $id) {
165172
$uri = str_replace('{'.$model.'}', $id, $uri);
173+
$uri = str_replace('{'.$model.'?}', $id, $uri);
166174
}
167175

168176
return $uri;
@@ -462,6 +470,12 @@ protected function parseRule($rule, $ruleName, &$attributeData, $seed)
462470
$attributeData['value'] = $faker->ipv4;
463471
$attributeData['type'] = $rule;
464472
break;
473+
default:
474+
$unknownRuleDescription = Description::parse($rule)->getDescription();
475+
if ($unknownRuleDescription) {
476+
$attributeData['description'][] = $unknownRuleDescription;
477+
}
478+
break;
465479
}
466480

467481
if ($attributeData['value'] === '') {

src/Mpociot/ApiDoc/Generators/DingoGenerator.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ public function callRoute($method, $uri, $parameters = [], $cookies = [], $files
6868
return call_user_func_array([$dispatcher, strtolower($method)], [$uri]);
6969
}
7070

71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function getDomain($route)
75+
{
76+
return $route->domain();
77+
}
78+
7179
/**
7280
* {@inheritdoc}
7381
*/
@@ -81,6 +89,6 @@ public function getUri($route)
8189
*/
8290
public function getMethods($route)
8391
{
84-
return $route->getMethods();
92+
return array_diff($route->getMethods(), ['HEAD']);
8593
}
8694
}

src/Mpociot/ApiDoc/Generators/LaravelGenerator.php

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,20 @@
1111
use Illuminate\Support\Facades\Request;
1212
use League\Fractal\Resource\Collection;
1313
use Illuminate\Foundation\Http\FormRequest;
14+
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
1415

1516
class LaravelGenerator extends AbstractGenerator
1617
{
18+
/**
19+
* @param Route $route
20+
*
21+
* @return mixed
22+
*/
23+
public function getDomain($route)
24+
{
25+
return $route->domain();
26+
}
27+
1728
/**
1829
* @param Route $route
1930
*
@@ -36,10 +47,12 @@ public function getUri($route)
3647
public function getMethods($route)
3748
{
3849
if (version_compare(app()->version(), '5.4', '<')) {
39-
return $route->getMethods();
50+
$methods = $route->getMethods();
51+
} else {
52+
$methods = $route->methods();
4053
}
4154

42-
return $route->methods();
55+
return array_diff($methods, ['HEAD']);
4356
}
4457

4558
/**
@@ -54,11 +67,16 @@ public function processRoute($route, $bindings = [], $headers = [], $withRespons
5467
{
5568
$content = '';
5669

70+
$routeDomain = $route->domain();
5771
$routeAction = $route->getAction();
5872
$routeGroup = $this->getRouteGroup($routeAction['uses']);
5973
$routeDescription = $this->getRouteDescription($routeAction['uses']);
6074
$showresponse = null;
6175

76+
// set correct route domain
77+
$headers[] = "HTTP_HOST: {$routeDomain}";
78+
$headers[] = "SERVER_NAME: {$routeDomain}";
79+
6280
if ($withResponse) {
6381
$response = null;
6482
$docblockResponse = $this->getDocblockResponse($routeDescription['tags']);
@@ -105,9 +123,9 @@ public function processRoute($route, $bindings = [], $headers = [], $withRespons
105123
*
106124
* @return void
107125
*/
108-
public function prepareMiddleware($disable = true)
126+
public function prepareMiddleware($enable = true)
109127
{
110-
App::instance('middleware.disable', true);
128+
App::instance('middleware.disable', ! $enable);
111129
}
112130

113131
/**
@@ -140,11 +158,6 @@ public function callRoute($method, $uri, $parameters = [], $cookies = [], $files
140158

141159
$kernel->terminate($request, $response);
142160

143-
if (file_exists($file = App::bootstrapPath().'/app.php')) {
144-
$app = require $file;
145-
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
146-
}
147-
148161
return $response;
149162
}
150163

@@ -266,7 +279,9 @@ protected function getRouteRules($route, $bindings)
266279
$parameterReflection->request->add($bindings);
267280

268281
if (method_exists($parameterReflection, 'validator')) {
269-
return app()->call([$parameterReflection, 'validator'])
282+
$factory = app()->make(ValidationFactory::class);
283+
284+
return app()->call([$parameterReflection, 'validator'], [$factory])
270285
->getRules();
271286
} else {
272287
return app()->call([$parameterReflection, 'rules']);

tests/ApiDocGeneratorTest.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function testCanParseRouteMethods()
5353

5454
$route = new Route(['GET'], '/get', ['uses' => TestController::class.'@parseMethodDescription']);
5555
$parsed = $this->generator->processRoute($route);
56-
$this->assertSame(['GET', 'HEAD'], $parsed['methods']);
56+
$this->assertSame(['GET'], $parsed['methods']);
5757

5858
$route = new Route(['POST'], '/post', ['uses' => TestController::class.'@parseMethodDescription']);
5959
$parsed = $this->generator->processRoute($route);
@@ -337,6 +337,16 @@ public function testCanParseFormRequestRules()
337337
}
338338
}
339339

340+
public function testCustomFormRequestValidatorIsSupported()
341+
{
342+
RouteFacade::post('/post', TestController::class.'@customFormRequestValidator');
343+
$route = new Route(['POST'], '/post', ['uses' => TestController::class.'@customFormRequestValidator']);
344+
$parsed = $this->generator->processRoute($route);
345+
$parameters = $parsed['parameters'];
346+
347+
$this->assertNotEmpty($parameters);
348+
}
349+
340350
public function testCanParseResponseTag()
341351
{
342352
RouteFacade::post('/responseTag', TestController::class.'@responseTag');

tests/DingoGeneratorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function testCanParseRouteMethods()
6868
});
6969
$route = app('Dingo\Api\Routing\Router')->getRoutes()['v1']->getRoutes()[0];
7070
$parsed = $this->generator->processRoute($route);
71-
$this->assertSame(['GET', 'HEAD'], $parsed['methods']);
71+
$this->assertSame(['GET'], $parsed['methods']);
7272

7373
$route = app('Dingo\Api\Routing\Router')->getRoutes()['v1']->getRoutes()[1];
7474
$parsed = $this->generator->processRoute($route);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Mpociot\ApiDoc\Tests\Fixtures;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class CustomValidatorRequest extends FormRequest
8+
{
9+
/**
10+
* Validate the input.
11+
*
12+
* @param \Illuminate\Validation\Factory $factory
13+
*
14+
* @return \Illuminate\Validation\Validator
15+
*/
16+
public function validator($factory)
17+
{
18+
return $factory->make(
19+
$this->validationData(), $this->container->call([$this, 'foo']),
20+
$this->messages(), $this->attributes()
21+
);
22+
}
23+
24+
public function foo()
25+
{
26+
return [
27+
'required' => 'required',
28+
];
29+
}
30+
}

0 commit comments

Comments
 (0)