Skip to content

Commit 589014e

Browse files
committed
Added first tests
1 parent 0e25996 commit 589014e

File tree

5 files changed

+389
-273
lines changed

5 files changed

+389
-273
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"phpdocumentor/reflection-docblock": "~2.0"
1717
},
1818
"require-dev": {
19+
"phpunit/phpunit": "~5.0",
20+
"orchestra/testbench": "~3.0"
1921
},
2022
"autoload": {
2123
"psr-0": {

phpunit.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit backupGlobals="false"
3+
backupStaticAttributes="false"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
convertErrorsToExceptions="true"
7+
convertNoticesToExceptions="true"
8+
convertWarningsToExceptions="true"
9+
processIsolation="false"
10+
stopOnFailure="true"
11+
syntaxCheck="false">
12+
<testsuites>
13+
<testsuite name="Versionable Suite">
14+
<directory>tests/</directory>
15+
</testsuite>
16+
</testsuites>
17+
<filter>
18+
<whitelist>
19+
<directory suffix=".php">src/Mpociot/</directory>
20+
</whitelist>
21+
</filter>
22+
</phpunit>
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
<?php
2+
3+
namespace Mpociot\ApiDoc;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
use Illuminate\Routing\Route;
7+
use Illuminate\Support\Facades\App;
8+
use Illuminate\Support\Facades\Request;
9+
use Illuminate\Support\Facades\Validator;
10+
use Illuminate\Support\Str;
11+
use phpDocumentor\Reflection\DocBlock;
12+
use ReflectionClass;
13+
14+
class ApiDocGenerator
15+
{
16+
17+
/**
18+
* @param Route $route
19+
* @return array
20+
*/
21+
public function processRoute(Route $route)
22+
{
23+
$routeAction = $route->getAction();
24+
$response = $this->getRouteResponse($route);
25+
$routeDescription = $this->getRouteDescription($routeAction['uses']);
26+
$routeData = [
27+
'title' => $routeDescription['short'],
28+
'description' => $routeDescription['long'],
29+
'methods' => $route->getMethods(),
30+
'uri' => $route->getUri(),
31+
'parameters' => [],
32+
'response' => ($response->headers->get('Content-Type') === 'application/json') ? json_encode(json_decode($response->getContent()), JSON_PRETTY_PRINT) : $response->getContent()
33+
];
34+
35+
$validator = Validator::make([], $this->getRouteRules($routeAction['uses']));
36+
foreach ($validator->getRules() as $attribute => $rules) {
37+
$attributeData = [
38+
'required' => false,
39+
'type' => 'string',
40+
'default' => '',
41+
'description' => []
42+
];
43+
foreach ($rules as $rule) {
44+
$this->parseRule($rule, $attributeData);
45+
}
46+
$routeData['parameters'][$attribute] = $attributeData;
47+
}
48+
49+
return $routeData;
50+
}
51+
52+
/**
53+
* @param \Illuminate\Routing\Route $route
54+
* @return \Illuminate\Http\Response
55+
*/
56+
private function getRouteResponse(Route $route)
57+
{
58+
$methods = $route->getMethods();
59+
$response = $this->callRoute(array_shift($methods), $route->getUri());
60+
return $response;
61+
}
62+
63+
/**
64+
* @param $route
65+
* @return string
66+
*/
67+
private function getRouteDescription($route)
68+
{
69+
list($class, $method) = explode('@', $route);
70+
$reflection = new ReflectionClass($class);
71+
$reflectionMethod = $reflection->getMethod($method);
72+
73+
$comment = $reflectionMethod->getDocComment();
74+
$phpdoc = new DocBlock($comment);
75+
return [
76+
'short' => $phpdoc->getShortDescription(),
77+
'long' => $phpdoc->getLongDescription()->getContents()
78+
];
79+
}
80+
81+
82+
/**
83+
* @param $route
84+
* @return array
85+
*/
86+
private function getRouteRules($route)
87+
{
88+
list($class, $method) = explode('@', $route);
89+
$reflection = new ReflectionClass($class);
90+
$reflectionMethod = $reflection->getMethod($method);
91+
92+
foreach ($reflectionMethod->getParameters() as $parameter) {
93+
$parameterType = $parameter->getType();
94+
if (!is_null($parameterType) && class_exists($parameterType)) {
95+
$className = $parameterType->__toString();
96+
$parameterReflection = new $className;
97+
if ($parameterReflection instanceof FormRequest) {
98+
if (method_exists($parameterReflection, 'validator')) {
99+
return $parameterReflection->validator()->getRules();
100+
} else {
101+
return $parameterReflection->rules();
102+
}
103+
}
104+
}
105+
}
106+
107+
return [];
108+
}
109+
110+
/**
111+
* @param $rule
112+
* @param $attributeData
113+
*/
114+
protected function parseRule($rule, &$attributeData)
115+
{
116+
$parsedRule = $this->parseStringRule($rule);
117+
$parsedRule[0] = $this->normalizeRule($parsedRule[0]);
118+
list($rule, $parameters) = $parsedRule;
119+
120+
switch ($rule) {
121+
case 'required':
122+
$attributeData['required'] = true;
123+
break;
124+
case 'in':
125+
$attributeData['description'][] = implode(' or ', $parameters);
126+
break;
127+
case 'not_in':
128+
$attributeData['description'][] = 'Not in: ' . implode(' or ', $parameters);
129+
break;
130+
case 'min':
131+
$attributeData['description'][] = 'Minimum: `' . $parameters[0] . '`';
132+
break;
133+
case 'max':
134+
$attributeData['description'][] = 'Maximum: `' . $parameters[0] . '`';
135+
break;
136+
case 'between':
137+
$attributeData['description'][] = 'Between: `' . $parameters[0] . '` and ' . $parameters[1];
138+
break;
139+
case 'date_format':
140+
$attributeData['description'][] = 'Date format: ' . $parameters[0];
141+
break;
142+
case 'mimetypes':
143+
case 'mimes':
144+
$attributeData['description'][] = 'Allowed mime types: ' . implode(', ', $parameters);
145+
break;
146+
case 'required_if':
147+
$attributeData['description'][] = 'Required if `' . $parameters[0] . '` is `' . $parameters[1] . '`';
148+
break;
149+
case 'exists':
150+
$attributeData['description'][] = 'Valid ' . Str::singular($parameters[0]) . ' ' . $parameters[1];
151+
break;
152+
case 'active_url':
153+
$attributeData['type'] = 'url';
154+
break;
155+
case 'boolean':
156+
case 'email':
157+
case 'image':
158+
case 'string':
159+
case 'integer':
160+
case 'json':
161+
case 'numeric':
162+
case 'url':
163+
case 'ip':
164+
$attributeData['type'] = $rule;
165+
break;
166+
}
167+
}
168+
169+
/**
170+
* Call the given URI and return the Response.
171+
*
172+
* @param string $method
173+
* @param string $uri
174+
* @param array $parameters
175+
* @param array $cookies
176+
* @param array $files
177+
* @param array $server
178+
* @param string $content
179+
* @return \Illuminate\Http\Response
180+
*/
181+
public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
182+
{
183+
$kernel = App::make('Illuminate\Contracts\Http\Kernel');
184+
App::instance('middleware.disable', true);
185+
186+
$server = [
187+
'CONTENT_TYPE' => 'application/json',
188+
'Accept' => 'application/json',
189+
];
190+
191+
$request = Request::create(
192+
$uri, $method, $parameters,
193+
$cookies, $files, $this->transformHeadersToServerVars($server), $content
194+
);
195+
196+
$response = $kernel->handle($request);
197+
198+
$kernel->terminate($request, $response);
199+
200+
return $response;
201+
}
202+
203+
/**
204+
* Transform headers array to array of $_SERVER vars with HTTP_* format.
205+
*
206+
* @param array $headers
207+
* @return array
208+
*/
209+
protected function transformHeadersToServerVars(array $headers)
210+
{
211+
$server = [];
212+
$prefix = 'HTTP_';
213+
214+
foreach ($headers as $name => $value) {
215+
$name = strtr(strtoupper($name), '-', '_');
216+
217+
if (!starts_with($name, $prefix) && $name != 'CONTENT_TYPE') {
218+
$name = $prefix . $name;
219+
}
220+
221+
$server[$name] = $value;
222+
}
223+
224+
return $server;
225+
}
226+
227+
/**
228+
* Parse a string based rule.
229+
*
230+
* @param string $rules
231+
* @return array
232+
*/
233+
protected function parseStringRule($rules)
234+
{
235+
$parameters = [];
236+
237+
// The format for specifying validation rules and parameters follows an
238+
// easy {rule}:{parameters} formatting convention. For instance the
239+
// rule "Max:3" states that the value may only be three letters.
240+
if (strpos($rules, ':') !== false) {
241+
list($rules, $parameter) = explode(':', $rules, 2);
242+
243+
$parameters = $this->parseParameters($rules, $parameter);
244+
}
245+
246+
return [strtolower(trim($rules)), $parameters];
247+
}
248+
249+
/**
250+
* Parse a parameter list.
251+
*
252+
* @param string $rule
253+
* @param string $parameter
254+
* @return array
255+
*/
256+
protected function parseParameters($rule, $parameter)
257+
{
258+
if (strtolower($rule) == 'regex') {
259+
return [$parameter];
260+
}
261+
262+
return str_getcsv($parameter);
263+
}
264+
265+
/**
266+
* Normalizes a rule so that we can accept short types.
267+
*
268+
* @param string $rule
269+
* @return string
270+
*/
271+
protected function normalizeRule($rule)
272+
{
273+
switch ($rule) {
274+
case 'int':
275+
return 'integer';
276+
case 'bool':
277+
return 'boolean';
278+
default:
279+
return $rule;
280+
}
281+
}
282+
}

0 commit comments

Comments
 (0)