123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- <?php
- namespace Mpociot\ApiDoc;
- use Faker\Factory;
- use Illuminate\Foundation\Http\FormRequest;
- use Illuminate\Routing\Route;
- use Illuminate\Support\Facades\App;
- use Illuminate\Support\Facades\Request;
- use Illuminate\Support\Facades\Validator;
- use Illuminate\Support\Str;
- use phpDocumentor\Reflection\DocBlock;
- use ReflectionClass;
- class ApiDocGenerator
- {
- /**
- * @param Route $route
- *
- * @return array
- */
- public function processRoute(Route $route)
- {
- $routeAction = $route->getAction();
- $response = $this->getRouteResponse($route);
- $routeDescription = $this->getRouteDescription($routeAction['uses']);
- $routeData = [
- 'title' => $routeDescription['short'],
- 'description' => $routeDescription['long'],
- 'methods' => $route->getMethods(),
- 'uri' => $route->getUri(),
- 'parameters' => [],
- 'response' => ($response->headers->get('Content-Type') === 'application/json') ? json_encode(json_decode($response->getContent()), JSON_PRETTY_PRINT) : $response->getContent(),
- ];
- $validator = Validator::make([], $this->getRouteRules($routeAction['uses']));
- foreach ($validator->getRules() as $attribute => $rules) {
- $attributeData = [
- 'required' => false,
- 'type' => 'string',
- 'default' => '',
- 'value' => '',
- 'description' => [],
- ];
- foreach ($rules as $rule) {
- $this->parseRule($rule, $attributeData);
- }
- $routeData['parameters'][$attribute] = $attributeData;
- }
- return $routeData;
- }
- /**
- * @param \Illuminate\Routing\Route $route
- *
- * @return \Illuminate\Http\Response
- */
- private function getRouteResponse(Route $route)
- {
- $methods = $route->getMethods();
- $response = $this->callRoute(array_shift($methods), $route->getUri());
- return $response;
- }
- /**
- * @param $route
- *
- * @return string
- */
- private function getRouteDescription($route)
- {
- list($class, $method) = explode('@', $route);
- $reflection = new ReflectionClass($class);
- $reflectionMethod = $reflection->getMethod($method);
- $comment = $reflectionMethod->getDocComment();
- $phpdoc = new DocBlock($comment);
- return [
- 'short' => $phpdoc->getShortDescription(),
- 'long' => $phpdoc->getLongDescription()->getContents(),
- ];
- }
- /**
- * @param $route
- *
- * @return array
- */
- private function getRouteRules($route)
- {
- list($class, $method) = explode('@', $route);
- $reflection = new ReflectionClass($class);
- $reflectionMethod = $reflection->getMethod($method);
- foreach ($reflectionMethod->getParameters() as $parameter) {
- $parameterType = $parameter->getClass();
- if (! is_null($parameterType) && class_exists($parameterType->name)) {
- $className = $parameterType->name;
- $parameterReflection = new $className;
- if ($parameterReflection instanceof FormRequest) {
- if (method_exists($parameterReflection, 'validator')) {
- return $parameterReflection->validator()->getRules();
- } else {
- return $parameterReflection->rules();
- }
- }
- }
- }
- return [];
- }
- /**
- * @param $arr
- * @param $first
- * @param $last
- *
- * @return string
- */
- protected function fancyImplode($arr, $first, $last)
- {
- $arr = array_map(function ($value) {
- return '`'.$value.'`';
- }, $arr);
- array_push($arr, implode($last, array_splice($arr, -2)));
- return implode($first, $arr);
- }
- /**
- * @param $rule
- * @param $attributeData
- */
- protected function parseRule($rule, &$attributeData)
- {
- $faker = Factory::create();
- $parsedRule = $this->parseStringRule($rule);
- $parsedRule[0] = $this->normalizeRule($parsedRule[0]);
- list($rule, $parameters) = $parsedRule;
- switch ($rule) {
- case 'required':
- $attributeData['required'] = true;
- break;
- case 'accepted':
- $attributeData['required'] = true;
- $attributeData['type'] = 'boolean';
- $attributeData['value'] = true;
- break;
- case 'after':
- $attributeData['type'] = 'date';
- $attributeData['description'][] = 'Must be a date after: `'.date(DATE_RFC850, strtotime($parameters[0])).'`';
- $attributeData['value'] = date(DATE_RFC850, strtotime('+1 day', strtotime($parameters[0])));
- break;
- case 'alpha':
- $attributeData['description'][] = 'Only alphabetic characters allowed';
- $attributeData['value'] = $faker->word;
- break;
- case 'alpha_dash':
- $attributeData['description'][] = 'Allowed: alpha-numeric characters, as well as dashes and underscores.';
- break;
- case 'alpha_num':
- $attributeData['description'][] = 'Only alpha-numeric characters allowed';
- break;
- case 'in':
- $attributeData['description'][] = $this->fancyImplode($parameters, ', ', ' or ');
- $attributeData['value'] = $faker->randomElement($parameters);
- break;
- case 'not_in':
- $attributeData['description'][] = 'Not in: '.$this->fancyImplode($parameters, ', ', ' or ');
- $attributeData['value'] = $faker->word;
- break;
- case 'min':
- $attributeData['description'][] = 'Minimum: `'.$parameters[0].'`';
- break;
- case 'max':
- $attributeData['description'][] = 'Maximum: `'.$parameters[0].'`';
- break;
- case 'between':
- $attributeData['type'] = 'numeric';
- $attributeData['description'][] = 'Between: `'.$parameters[0].'` and `'.$parameters[1].'`';
- $attributeData['value'] = $faker->numberBetween($parameters[0], $parameters[1]);
- break;
- case 'before':
- $attributeData['type'] = 'date';
- $attributeData['description'][] = 'Must be a date preceding: `'.date(DATE_RFC850, strtotime($parameters[0])).'`';
- $attributeData['value'] = date(DATE_RFC850, strtotime('-1 day', strtotime($parameters[0])));
- break;
- case 'date_format':
- $attributeData['type'] = 'date';
- $attributeData['description'][] = 'Date format: `'.$parameters[0].'`';
- break;
- case 'different':
- $attributeData['description'][] = 'Must have a different value than parameter: `'.$parameters[0].'`';
- break;
- case 'digits':
- $attributeData['type'] = 'numeric';
- $attributeData['description'][] = 'Must have an exact length of `'.$parameters[0].'`';
- $attributeData['value'] = $faker->randomNumber($parameters[0], true);
- break;
- case 'digits_between':
- $attributeData['type'] = 'numeric';
- $attributeData['description'][] = 'Must have a length between `'.$parameters[0].'` and `'.$parameters[1].'`';
- break;
- case 'image':
- $attributeData['type'] = 'image';
- $attributeData['description'][] = 'Must be an image (jpeg, png, bmp, gif, or svg)';
- break;
- case 'json':
- $attributeData['type'] = 'string';
- $attributeData['description'][] = 'Must be a valid JSON string.';
- $attributeData['value'] = json_encode(['foo', 'bar', 'baz']);
- break;
- case 'mimetypes':
- case 'mimes':
- $attributeData['description'][] = 'Allowed mime types: '.$this->fancyImplode($parameters, ', ', ' or ');
- break;
- case 'required_if':
- $attributeData['description'][] = 'Required if `'.$parameters[0].'` is `'.$parameters[1].'`';
- break;
- case 'required_unless':
- $attributeData['description'][] = 'Required unless `'.$parameters[0].'` is `'.$parameters[1].'`';
- break;
- case 'required_with':
- $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' or ').' are present.';
- break;
- case 'required_with_all':
- $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' and ').' are present.';
- break;
- case 'required_without':
- $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' or ').' are not present.';
- break;
- case 'required_without_all':
- $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' and ').' are not present.';
- break;
- case 'same':
- $attributeData['description'][] = 'Must be the same as `'.$parameters[0].'`';
- break;
- case 'size':
- $attributeData['description'][] = 'Must have the size of `'.$parameters[0].'`';
- break;
- case 'timezone':
- $attributeData['description'][] = 'Must be a valid timezone identifier';
- $attributeData['value'] = $faker->timezone;
- break;
- case 'exists':
- $attributeData['description'][] = 'Valid '.Str::singular($parameters[0]).' '.$parameters[1];
- break;
- case 'active_url':
- $attributeData['type'] = 'url';
- $attributeData['value'] = $faker->url;
- break;
- case 'regex':
- $attributeData['type'] = 'string';
- $attributeData['description'][] = 'Must match this regular expression: `'.$parameters[0].'`';
- break;
- case 'boolean':
- $attributeData['value'] = true;
- $attributeData['type'] = $rule;
- break;
- case 'array':
- $attributeData['value'] = $faker->word;
- $attributeData['type'] = $rule;
- break;
- case 'date':
- $attributeData['value'] = $faker->date();
- $attributeData['type'] = $rule;
- break;
- case 'email':
- $attributeData['value'] = $faker->safeEmail;
- $attributeData['type'] = $rule;
- break;
- case 'string':
- $attributeData['value'] = $faker->word;
- $attributeData['type'] = $rule;
- break;
- case 'integer':
- $attributeData['value'] = $faker->randomNumber();
- $attributeData['type'] = $rule;
- break;
- case 'numeric':
- $attributeData['value'] = $faker->randomNumber();
- $attributeData['type'] = $rule;
- break;
- case 'url':
- $attributeData['value'] = $faker->url;
- $attributeData['type'] = $rule;
- break;
- case 'ip':
- $attributeData['value'] = $faker->ipv4;
- $attributeData['type'] = $rule;
- break;
- }
- if ($attributeData['value'] === '') {
- $attributeData['value'] = $faker->word;
- }
- }
- /**
- * Call the given URI and return the Response.
- *
- * @param string $method
- * @param string $uri
- * @param array $parameters
- * @param array $cookies
- * @param array $files
- * @param array $server
- * @param string $content
- *
- * @return \Illuminate\Http\Response
- */
- public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
- {
- $kernel = App::make('Illuminate\Contracts\Http\Kernel');
- App::instance('middleware.disable', true);
- $server = [
- 'CONTENT_TYPE' => 'application/json',
- 'Accept' => 'application/json',
- ];
- $request = Request::create(
- $uri, $method, $parameters,
- $cookies, $files, $this->transformHeadersToServerVars($server), $content
- );
- $response = $kernel->handle($request);
- $kernel->terminate($request, $response);
- return $response;
- }
- /**
- * Transform headers array to array of $_SERVER vars with HTTP_* format.
- *
- * @param array $headers
- *
- * @return array
- */
- protected function transformHeadersToServerVars(array $headers)
- {
- $server = [];
- $prefix = 'HTTP_';
- foreach ($headers as $name => $value) {
- $name = strtr(strtoupper($name), '-', '_');
- if (! starts_with($name, $prefix) && $name != 'CONTENT_TYPE') {
- $name = $prefix.$name;
- }
- $server[$name] = $value;
- }
- return $server;
- }
- /**
- * Parse a string based rule.
- *
- * @param string $rules
- *
- * @return array
- */
- protected function parseStringRule($rules)
- {
- $parameters = [];
- // The format for specifying validation rules and parameters follows an
- // easy {rule}:{parameters} formatting convention. For instance the
- // rule "Max:3" states that the value may only be three letters.
- if (strpos($rules, ':') !== false) {
- list($rules, $parameter) = explode(':', $rules, 2);
- $parameters = $this->parseParameters($rules, $parameter);
- }
- return [strtolower(trim($rules)), $parameters];
- }
- /**
- * Parse a parameter list.
- *
- * @param string $rule
- * @param string $parameter
- *
- * @return array
- */
- protected function parseParameters($rule, $parameter)
- {
- if (strtolower($rule) == 'regex') {
- return [$parameter];
- }
- return str_getcsv($parameter);
- }
- /**
- * Normalizes a rule so that we can accept short types.
- *
- * @param string $rule
- *
- * @return string
- */
- protected function normalizeRule($rule)
- {
- switch ($rule) {
- case 'int':
- return 'integer';
- case 'bool':
- return 'boolean';
- default:
- return $rule;
- }
- }
- }
|