123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- <?php
- /**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
- namespace yii\apidoc\models;
- use phpDocumentor\Reflection\FileReflector;
- use yii\base\Component;
- /**
- *
- * @author Carsten Brandt <mail@cebe.cc>
- * @since 2.0
- */
- class Context extends Component
- {
- /**
- * @var array list of php files that have been added to this context.
- */
- public $files = [];
- /**
- * @var ClassDoc[]
- */
- public $classes = [];
- /**
- * @var InterfaceDoc[]
- */
- public $interfaces = [];
- /**
- * @var TraitDoc[]
- */
- public $traits = [];
- /**
- * @var array
- */
- public $errors = [];
- /**
- * @var array
- * @since 2.0.6
- */
- public $warnings = [];
- /**
- * Returning TypeDoc for a type given
- * @param string $type
- * @return null|ClassDoc|InterfaceDoc|TraitDoc
- */
- public function getType($type)
- {
- $type = ltrim($type, '\\');
- if (isset($this->classes[$type])) {
- return $this->classes[$type];
- } elseif (isset($this->interfaces[$type])) {
- return $this->interfaces[$type];
- } elseif (isset($this->traits[$type])) {
- return $this->traits[$type];
- }
- return null;
- }
- /**
- * Adds file to context
- * @param string $fileName
- */
- public function addFile($fileName)
- {
- $this->files[$fileName] = sha1_file($fileName);
- $reflection = new FileReflector($fileName, true);
- $reflection->process();
- foreach ($reflection->getClasses() as $class) {
- $class = new ClassDoc($class, $this, ['sourceFile' => $fileName]);
- $this->classes[$class->name] = $class;
- }
- foreach ($reflection->getInterfaces() as $interface) {
- $interface = new InterfaceDoc($interface, $this, ['sourceFile' => $fileName]);
- $this->interfaces[$interface->name] = $interface;
- }
- foreach ($reflection->getTraits() as $trait) {
- $trait = new TraitDoc($trait, $this, ['sourceFile' => $fileName]);
- $this->traits[$trait->name] = $trait;
- }
- }
- /**
- * Updates references
- */
- public function updateReferences()
- {
- // update all subclass references
- foreach ($this->classes as $class) {
- $className = $class->name;
- while (isset($this->classes[$class->parentClass])) {
- $class = $this->classes[$class->parentClass];
- $class->subclasses[] = $className;
- }
- }
- // update interfaces of subclasses
- foreach ($this->classes as $class) {
- $this->updateSubclassInterfacesTraits($class);
- }
- // update implementedBy and usedBy for interfaces and traits
- foreach ($this->classes as $class) {
- foreach ($class->traits as $trait) {
- if (isset($this->traits[$trait])) {
- $trait = $this->traits[$trait];
- $trait->usedBy[] = $class->name;
- $class->properties = array_merge($trait->properties, $class->properties);
- $class->methods = array_merge($trait->methods, $class->methods);
- }
- }
- foreach ($class->interfaces as $interface) {
- if (isset($this->interfaces[$interface])) {
- $this->interfaces[$interface]->implementedBy[] = $class->name;
- if ($class->isAbstract) {
- // add not implemented interface methods
- foreach ($this->interfaces[$interface]->methods as $method) {
- if (!isset($class->methods[$method->name])) {
- $class->methods[$method->name] = $method;
- }
- }
- }
- }
- }
- }
- foreach ($this->interfaces as $interface) {
- foreach ($interface->parentInterfaces as $pInterface) {
- if (isset($this->interfaces[$pInterface])) {
- $this->interfaces[$pInterface]->implementedBy[] = $interface->name;
- }
- }
- }
- // inherit docs
- foreach ($this->classes as $class) {
- $this->inheritDocs($class);
- }
- // inherit properties, methods, contants and events to subclasses
- foreach ($this->classes as $class) {
- $this->updateSubclassInheritance($class);
- }
- foreach ($this->interfaces as $interface) {
- $this->updateSubInterfaceInheritance($interface);
- }
- // add properties from getters and setters
- foreach ($this->classes as $class) {
- $this->handlePropertyFeature($class);
- }
- // TODO reference exceptions to methods where they are thrown
- }
- /**
- * Add implemented interfaces and used traits to subclasses
- * @param ClassDoc $class
- */
- protected function updateSubclassInterfacesTraits($class)
- {
- foreach ($class->subclasses as $subclass) {
- $subclass = $this->classes[$subclass];
- $subclass->interfaces = array_unique(array_merge($subclass->interfaces, $class->interfaces));
- $subclass->traits = array_unique(array_merge($subclass->traits, $class->traits));
- $this->updateSubclassInterfacesTraits($subclass);
- }
- }
- /**
- * Add implemented interfaces and used traits to subclasses
- * @param ClassDoc $class
- */
- protected function updateSubclassInheritance($class)
- {
- foreach ($class->subclasses as $subclass) {
- $subclass = $this->classes[$subclass];
- $subclass->events = array_merge($class->events, $subclass->events);
- $subclass->constants = array_merge($class->constants, $subclass->constants);
- $subclass->properties = array_merge($class->properties, $subclass->properties);
- $subclass->methods = array_merge($class->methods, $subclass->methods);
- $this->updateSubclassInheritance($subclass);
- }
- }
- /**
- * Add methods to subinterfaces
- * @param InterfaceDoc $class
- */
- protected function updateSubInterfaceInheritance($interface)
- {
- foreach ($interface->implementedBy as $subInterface) {
- if (isset($this->interfaces[$subInterface])) {
- $subInterface = $this->interfaces[$subInterface];
- $subInterface->methods = array_merge($interface->methods, $subInterface->methods);
- $this->updateSubInterfaceInheritance($subInterface);
- }
- }
- }
- /**
- * Inhertit docsblocks using `@inheritDoc` tag.
- * @param ClassDoc $class
- * @see http://phpdoc.org/docs/latest/guides/inheritance.html
- */
- protected function inheritDocs($class)
- {
- // inherit for properties
- foreach ($class->properties as $p) {
- if ($p->hasTag('inheritdoc') && ($inheritTag = $p->getFirstTag('inheritdoc')) !== null) {
- $inheritedProperty = $this->inheritPropertyRecursive($p, $class);
- if (!$inheritedProperty) {
- $this->errors[] = [
- 'line' => $p->startLine,
- 'file' => $class->sourceFile,
- 'message' => "Method {$p->name} has no parent to inherit from in {$class->name}.",
- ];
- continue;
- }
- // set all properties that are empty.
- foreach (['shortDescription', 'type', 'types'] as $property) {
- if (empty($p->$property) || is_string($p->$property) && trim($p->$property) === '') {
- $p->$property = $inheritedProperty->$property;
- }
- }
- // descriptions will be concatenated.
- $p->description = trim($p->description) . "\n\n"
- . trim($inheritedProperty->description) . "\n\n"
- . $inheritTag->getContent();
- $p->removeTag('inheritdoc');
- }
- }
- // inherit for methods
- foreach ($class->methods as $m) {
- if ($m->hasTag('inheritdoc') && ($inheritTag = $m->getFirstTag('inheritdoc')) !== null) {
- $inheritedMethod = $this->inheritMethodRecursive($m, $class);
- if (!$inheritedMethod) {
- $this->errors[] = [
- 'line' => $m->startLine,
- 'file' => $class->sourceFile,
- 'message' => "Method {$m->name} has no parent to inherit from in {$class->name}.",
- ];
- continue;
- }
- // set all properties that are empty.
- foreach (['shortDescription', 'return', 'returnType', 'returnTypes', 'exceptions'] as $property) {
- if (empty($m->$property) || is_string($m->$property) && trim($m->$property) === '') {
- $m->$property = $inheritedMethod->$property;
- }
- }
- // descriptions will be concatenated.
- $m->description = trim($m->description) . "\n\n"
- . trim($inheritedMethod->description) . "\n\n"
- . $inheritTag->getContent();
- foreach ($m->params as $i => $param) {
- if (!isset($inheritedMethod->params[$i])) {
- $this->errors[] = [
- 'line' => $m->startLine,
- 'file' => $class->sourceFile,
- 'message' => "Method param $i does not exist in parent method, @inheritdoc not possible in {$m->name} in {$class->name}.",
- ];
- continue;
- }
- if (empty($param->description) || trim($param->description) === '') {
- $param->description = $inheritedMethod->params[$i]->description;
- }
- if (empty($param->type) || trim($param->type) === '') {
- $param->type = $inheritedMethod->params[$i]->type;
- }
- if (empty($param->types)) {
- $param->types = $inheritedMethod->params[$i]->types;
- }
- }
- $m->removeTag('inheritdoc');
- }
- }
- }
- /**
- * @param MethodDoc $method
- * @param ClassDoc $class
- * @return mixed
- */
- private function inheritMethodRecursive($method, $class)
- {
- $inheritanceCandidates = array_merge(
- $this->getParents($class),
- $this->getInterfaces($class)
- );
- $methods = [];
- foreach($inheritanceCandidates as $candidate) {
- if (isset($candidate->methods[$method->name])) {
- $cmethod = $candidate->methods[$method->name];
- if ($cmethod->hasTag('inheritdoc')) {
- $this->inheritDocs($candidate);
- }
- $methods[] = $cmethod;
- }
- }
- return reset($methods);
- }
- /**
- * @param PropertyDoc $method
- * @param ClassDoc $class
- * @return mixed
- */
- private function inheritPropertyRecursive($method, $class)
- {
- $inheritanceCandidates = array_merge(
- $this->getParents($class),
- $this->getInterfaces($class)
- );
- $properties = [];
- foreach($inheritanceCandidates as $candidate) {
- if (isset($candidate->properties[$method->name])) {
- $cproperty = $candidate->properties[$method->name];
- if ($cproperty->hasTag('inheritdoc')) {
- $this->inheritDocs($candidate);
- }
- $properties[] = $cproperty;
- }
- }
- return reset($properties);
- }
- /**
- * @param ClassDoc $class
- * @return array
- */
- private function getParents($class)
- {
- if ($class->parentClass === null || !isset($this->classes[$class->parentClass])) {
- return [];
- }
- return array_merge([$this->classes[$class->parentClass]], $this->getParents($this->classes[$class->parentClass]));
- }
- /**
- * @param ClassDoc $class
- * @return array
- */
- private function getInterfaces($class)
- {
- $interfaces = [];
- foreach($class->interfaces as $interface) {
- if (isset($this->interfaces[$interface])) {
- $interfaces[] = $this->interfaces[$interface];
- }
- }
- return $interfaces;
- }
- /**
- * Add properties for getters and setters if class is subclass of [[\yii\base\Object]].
- * @param ClassDoc $class
- */
- protected function handlePropertyFeature($class)
- {
- if (!$this->isSubclassOf($class, 'yii\base\Object')) {
- return;
- }
- foreach ($class->getPublicMethods() as $name => $method) {
- if ($method->isStatic) {
- continue;
- }
- if (!strncmp($name, 'get', 3) && strlen($name) > 3 && $this->hasNonOptionalParams($method)) {
- $propertyName = '$' . lcfirst(substr($method->name, 3));
- if (isset($class->properties[$propertyName])) {
- $property = $class->properties[$propertyName];
- if ($property->getter === null && $property->setter === null) {
- $this->errors[] = [
- 'line' => $property->startLine,
- 'file' => $class->sourceFile,
- 'message' => "Property $propertyName conflicts with a defined getter {$method->name} in {$class->name}.",
- ];
- }
- $property->getter = $method;
- } else {
- $class->properties[$propertyName] = new PropertyDoc(null, $this, [
- 'name' => $propertyName,
- 'definedBy' => $method->definedBy,
- 'sourceFile' => $class->sourceFile,
- 'visibility' => 'public',
- 'isStatic' => false,
- 'type' => $method->returnType,
- 'types' => $method->returnTypes,
- 'shortDescription' => BaseDoc::extractFirstSentence($method->return),
- 'description' => $method->return,
- 'getter' => $method
- // TODO set default value
- ]);
- }
- }
- if (!strncmp($name, 'set', 3) && strlen($name) > 3 && $this->hasNonOptionalParams($method, 1)) {
- $propertyName = '$' . lcfirst(substr($method->name, 3));
- if (isset($class->properties[$propertyName])) {
- $property = $class->properties[$propertyName];
- if ($property->getter === null && $property->setter === null) {
- $this->errors[] = [
- 'line' => $property->startLine,
- 'file' => $class->sourceFile,
- 'message' => "Property $propertyName conflicts with a defined setter {$method->name} in {$class->name}.",
- ];
- }
- $property->setter = $method;
- } else {
- $param = $this->getFirstNotOptionalParameter($method);
- $class->properties[$propertyName] = new PropertyDoc(null, $this, [
- 'name' => $propertyName,
- 'definedBy' => $method->definedBy,
- 'sourceFile' => $class->sourceFile,
- 'visibility' => 'public',
- 'isStatic' => false,
- 'type' => $param->type,
- 'types' => $param->types,
- 'shortDescription' => BaseDoc::extractFirstSentence($param->description),
- 'description' => $param->description,
- 'setter' => $method
- ]);
- }
- }
- }
- }
- /**
- * Check whether a method has `$number` non-optional parameters.
- * @param MethodDoc $method
- * @param int $number number of not optional parameters
- * @return bool
- */
- private function hasNonOptionalParams($method, $number = 0)
- {
- $count = 0;
- foreach ($method->params as $param) {
- if (!$param->isOptional) {
- $count++;
- }
- }
- return $count == $number;
- }
- /**
- * @param MethodDoc $method
- * @return ParamDoc
- */
- private function getFirstNotOptionalParameter($method)
- {
- foreach ($method->params as $param) {
- if (!$param->isOptional) {
- return $param;
- }
- }
- return null;
- }
- /**
- * @param ClassDoc $classA
- * @param ClassDoc|string $classB
- * @return bool
- */
- protected function isSubclassOf($classA, $classB)
- {
- if (is_object($classB)) {
- $classB = $classB->name;
- }
- if ($classA->name == $classB) {
- return true;
- }
- while ($classA->parentClass !== null && isset($this->classes[$classA->parentClass])) {
- $classA = $this->classes[$classA->parentClass];
- if ($classA->name == $classB) {
- return true;
- }
- }
- return false;
- }
- }
|