diff --git a/.travis.yml b/.travis.yml index 35bd6f4..60a2f59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: php +dist: trusty + php: - 5.5.9 - 5.5 diff --git a/src/Annotation/Attribute.php b/src/Annotation/Attribute.php index 1317c21..e3e5f3e 100644 --- a/src/Annotation/Attribute.php +++ b/src/Annotation/Attribute.php @@ -27,8 +27,18 @@ class Attribute */ public $description; + /** + * @var string + */ + public $default; + /** * @var mixed */ public $sample; + + /** + * @array + */ + public $members; } diff --git a/src/Blueprint.php b/src/Blueprint.php index b95a66e..c8e47b7 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -9,6 +9,11 @@ use Illuminate\Filesystem\Filesystem; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\SimpleAnnotationReader; +use Dingo\Blueprint\Annotation\Attributes; +use Dingo\Blueprint\Annotation\Attribute; +use Dingo\Blueprint\Annotation\Member; +use Dingo\Blueprint\Annotation\Parameters; +use Dingo\Blueprint\Annotation\Parameter; class Blueprint { @@ -100,12 +105,12 @@ public function generate(Collection $controllers, $name, $version, $includePath } if ($annotations = $this->reader->getMethodAnnotations($method)) { - if (! $actions->contains($method)) { + if (!$actions->contains($method)) { + $annotations = $this->fillAnnotations($annotations, $method); $actions->push(new Action($method, new Collection($annotations))); } } } - $annotations = new Collection($this->reader->getClassAnnotations($controller)); return new Resource($controller->getName(), $controller, $annotations, $actions); @@ -118,6 +123,217 @@ public function generate(Collection $controllers, $name, $version, $includePath return $contents; } + /** + * Add the parameters and attributes objects to annotations if needed + * + * @param array $annotations the annotations to fill + * @param object $method the method we are working on + * + * @return array + */ + private function fillAnnotations(array $annotations, $method) + { + $controllerName = substr($method->class, strrpos($method->class, '\\')); + if ($controllerName != '\\OAuthController') { + $rulesClass = 'App\\Validators\\Rules' . $controllerName; + $parameters = $this->extractParams($this->getMethodUri($annotations)); + + $rules = $rulesClass::$rules[$method->name]; + if (count($rules)) { + $annotations[] = $this->getAttributes($rules, $parameters); + $annotations[] = $this->getParameters($rules, $parameters); + } + } + return $annotations; + } + + /** + * Get the URI of a method from it's annotations + * + * @param array $annotations + * + * @return string + */ + private function getMethodUri(array $annotations) + { + return array_first($annotations, function ($key, $annotation) { + $type = 'Dingo\\Blueprint\\Annotation\\Method\\Method'; + return is_object($annotation) ? $annotation instanceof $type : $key instanceof $type; + })->uri; + } + + /** + * Gets a list of all parameters name from mutiple arrays extracted from the uri + * + * @param array $uriParams + * + * @return array + */ + private function extractParams($uri) { + preg_match_all("/{(.*?)}/", $uri, $matches); + return $this->parseParams($matches[1]); + } + + /** + * Parse the params as they are in the uri into correct array + * + * @param array $uriParams + * + * @return array + */ + private function parseParams($uriParams) + { + $params = []; + + foreach ($uriParams as $uriParam) { + $uriParam = ltrim($uriParam, '?'); + $uriParamExploded = explode(',', $uriParam); + foreach ($uriParamExploded as $param) { + $params[] = $param; + } + } + return $params; + } + + /** + * Transforms a laravel validation array in Attribute for dingo blueprint + * + * @param array $rules + * + * @return array + */ + private function getAttributes($rules, $parameters) + { + $attributes = new Attributes(); + + reset($rules); + do { + $identifier = key($rules); + if (in_array($identifier, $parameters)) { + continue; + } + $attribute = $this->parseInfos(new Attribute(), $rules); + $attribute->identifier = $identifier; + + $attributes->value[] = $attribute; + } while (next($rules)); + return $attributes->value ? $attributes : null; + } + + /** + * Transforms a laravel validation array in Parameter for dingo blueprint + * + * @param array $rules + * + * @return array + */ + private function getParameters($rules, $params) + { + $parameters = new Parameters(); + + reset($rules); + do { + $identifier = key($rules); + if (!in_array($identifier, $params)) { + continue; + } + $parameter = $this->parseInfos(new Parameter(), $rules); + $parameter->identifier = $identifier; + + $parameters->value[] = $parameter; + } while (next($rules)); + return $parameters->value ? $parameters : null; + } + + /** + * Determine if an array in a laravel validator is an array or an object in apiary + * + * @param array $rules + * + * @return string object or array + */ + private function findArrayType(array $rules) + { + $key = key($rules); + $key2 = false; + if (next($rules)) { + $key2 = key($rules); + } + $key3 = false; + if (next($rules)) { + $key3 = key($rules); + } + $toMatch = '/^' . preg_quote($key) . '\.[a-zA-Z0-9]*$/'; + if ($key2 && preg_match($toMatch, $key2) === 1 && $key3 && preg_match($toMatch, $key2)) { + $type = 'object'; + } else { + $type = 'array'; + } + return $type; + } + + /** + * Parse the validation array to fill a parameter or an attribute + * + * @param Parameter|Attribute $toFill + * @param array $rules + * + * @return Parameter|Attribute + */ + private function parseInfos($toFill, array $rules) + { + $infos = current($rules); + $toFill->description = $infos['description']; + + $propertiesExploded = explode('|', $infos['rules']); + foreach ($propertiesExploded as $property) { + switch ($property) { + case 'numeric': + case 'integer': + $toFill->type = 'number'; + continue 2; + case 'array': + $toFill->type = $this->findArrayType($rules); + continue 2; + case 'present': + case 'required': + $toFill->required = true; + continue 2; + } + $tofill = $this->parseComplexProperties($toFill, $property); + } + return $toFill; + } + + /** + * Parse the property to find enum or defaults + * + * @param Parameter|Attribute $toFill + * @param string $property + * + * @return Parameter|Attribute + */ + private function parseComplexProperties($toFill, $property) + { + $propertyExploded = explode(':', $property); + if (count($propertyExploded) > 1) { + switch ($propertyExploded[0]) { + case 'default': + $toFill->default = $propertyExploded[1]; + break; + case 'in': + $members = explode(',', $propertyExploded[1]); + foreach ($members as $identifier) { + $member = new Member(); + $member->identifier = $identifier; + $toFill->members[] = $member; + } + break; + } + } + return $toFill; + } + /** * Generate the documentation contents from the resources collection. * @@ -131,6 +347,7 @@ protected function generateContentsFromResources(Collection $resources, $name) $contents = ''; $contents .= $this->getFormat(); + $contents .= $this->getHost(); $contents .= $this->line(2); $contents .= sprintf('# %s', $name); $contents .= $this->line(2); @@ -209,9 +426,12 @@ protected function appendAttributes(&$contents, Collection $attributes, $indent $this->appendSection($contents, 'Attributes', $indent); $attributes->each(function ($attribute) use (&$contents, $indent) { + $explodedIdentifier = explode('.', $attribute->identifier); + $indent += count($explodedIdentifier); $contents .= $this->line(); - $contents .= $this->tab(1 + $indent); - $contents .= sprintf('+ %s', $attribute->identifier); + $contents .= $this->tab($indent); + $identifier = $explodedIdentifier[count($explodedIdentifier) - 1]; + $contents .= sprintf('+ %s', $identifier == '*' ? '' : $identifier); if ($attribute->sample) { $contents .= sprintf(': %s', $attribute->sample); @@ -219,11 +439,23 @@ protected function appendAttributes(&$contents, Collection $attributes, $indent $contents .= sprintf( ' (%s, %s) - %s', - $attribute->type, + $attribute->members ? sprintf('enum[%s]', $attribute->type) : $attribute->type, $attribute->required ? 'required' : 'optional', $attribute->description ); + + if (isset($attribute->default)) { + $this->appendSection($contents, sprintf('Default: %s', $attribute->default), $indent + 1, 1); + } + + if (isset($attribute->members)) { + $this->appendSection($contents, 'Members', $indent + 1, 1); + foreach ($attribute->members as $member) { + $this->appendSection($contents, sprintf('`%s` - %s', $member->identifier, $member->description), $indent + 2, 1); + } + } }); + } /** @@ -241,10 +473,14 @@ protected function appendParameters(&$contents, Collection $parameters) $parameters->each(function ($parameter) use (&$contents) { $contents .= $this->line(); $contents .= $this->tab(); + $contents .= sprintf('+ %s', $parameter->identifier); + + if ($parameter->example) { + $contents .= sprintf(': %s', $parameter->example); + } + $contents .= sprintf( - '+ %s:%s (%s, %s) - %s', - $parameter->identifier, - $parameter->example ? " `{$parameter->example}`" : '', + ' (%s, %s) - %s', $parameter->members ? sprintf('enum[%s]', $parameter->type) : $parameter->type, $parameter->required ? 'required' : 'optional', $parameter->description @@ -312,7 +548,10 @@ protected function appendRequest(&$contents, $request, Resource $resource) $contents .= ' ('.$request->contentType.')'; - if (! empty($request->headers) || $resource->hasRequestHeaders()) { + if (!isset($request->headers['Authorization'])) { + $request->headers['Authorization'] = 'OAuth: oauth_consumer_key={consumer_key},oauth_signature={consumer_secret}&{user_secret},oauth_signature_method=PLAINTEXT,oauth_nonce={random_string},oauth_timestamp={current_timestamp},oauth_token={user_token}'; + } + if (!empty($request->headers) || $resource->hasRequestHeaders()) { $this->appendHeaders($contents, array_merge($resource->getRequestHeaders(), $request->headers)); } @@ -454,4 +693,11 @@ protected function getFormat() { return 'FORMAT: 1A'; } + + private function getHost() + { + if (class_exists('\Illuminate\Config\Repository')) { + return $this->line(1) . 'HOST: https://' . app('config')->get('api.domain') . '/' . app('config')->get('api.prefix'); + } + } }