You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							258 lines
						
					
					
						
							7.3 KiB
						
					
					
				
			
		
		
	
	
							258 lines
						
					
					
						
							7.3 KiB
						
					
					
				| <?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; | |
| use yii\base\Exception; | |
|  | |
| /** | |
|  * | |
|  * @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 = []; | |
|  | |
|  | |
| 	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; | |
| 	} | |
|  | |
| 	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); | |
| 			$class->sourceFile = $fileName; | |
| 			$this->classes[$class->name] = $class; | |
| 		} | |
| 		foreach($reflection->getInterfaces() as $interface) { | |
| 			$interface = new InterfaceDoc($interface); | |
| 			$interface->sourceFile = $fileName; | |
| 			$this->interfaces[$interface->name] = $interface; | |
| 		} | |
| 		foreach($reflection->getTraits() as $trait) { | |
| 			$trait = new TraitDoc($trait); | |
| 			$trait->sourceFile = $fileName; | |
| 			$this->traits[$trait->name] = $trait; | |
| 		} | |
| 	} | |
|  | |
| 	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->updateSubclassInferfacesTraits($class); | |
| 		} | |
| 		// update implementedBy and usedBy for interfaces and traits | |
| 		foreach($this->classes as $class) { | |
| 			foreach($class->interfaces as $interface) { | |
| 				if (isset($this->interfaces[$interface])) { | |
| 					$this->interfaces[$interface]->implementedBy[] = $class->name; | |
| 				} | |
| 			} | |
| 			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); | |
| 				} | |
| 			} | |
| 		} | |
| 		// inherit properties, methods, contants and events to subclasses | |
| 		foreach($this->classes as $class) { | |
| 			$this->updateSubclassInheritance($class); | |
| 		} | |
| 		// 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 updateSubclassInferfacesTraits($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->updateSubclassInferfacesTraits($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 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 (!strncmp($name, 'get', 3) && $this->paramsOptional($method)) { | |
| 				$propertyName = '$' . lcfirst(substr($method->name, 3)); | |
| 				if (isset($class->properties[$propertyName])) { | |
| 					$property = $class->properties[$propertyName]; | |
| 					if ($property->getter === null && $property->setter === null) { | |
| 						echo "Property $propertyName conflicts with a defined getter {$method->name} in {$class->name}.\n"; // TODO log these messages somewhere | |
| 					} | |
| 					$property->getter = $method; | |
| 				} else { | |
| 					$class->properties[$propertyName] = new PropertyDoc(null, [ | |
| 						'name' => $propertyName, | |
| 						'definedBy' => $class->name, | |
| 						'visibility' => 'public', | |
| 						'isStatic' => false, | |
| 						'type' => $method->returnType, | |
| 						'types' => $method->returnTypes, | |
| 						'shortDescription' => (($pos = strpos($method->return, '.')) !== false) ? | |
| 								substr($method->return, 0, $pos) : $method->return, | |
| 						'description' => $method->return, | |
| 						'getter' => $method | |
| 						// TODO set default value | |
| 					]); | |
| 				} | |
| 			} | |
| 			if (!strncmp($name, 'set', 3) && $this->paramsOptional($method, 1)) { | |
| 				$propertyName = '$' . lcfirst(substr($method->name, 3)); | |
| 				if (isset($class->properties[$propertyName])) { | |
| 					$property = $class->properties[$propertyName]; | |
| 					if ($property->getter === null && $property->setter === null) { | |
| 						echo "Property $propertyName conflicts with a defined setter {$method->name} in {$class->name}.\n"; // TODO log these messages somewhere | |
| 					} | |
| 					$property->setter = $method; | |
| 				} else { | |
| 					$param = $this->getFirstNotOptionalParameter($method); | |
| 					$class->properties[$propertyName] = new PropertyDoc(null, [ | |
| 						'name' => $propertyName, | |
| 						'definedBy' => $class->name, | |
| 						'visibility' => 'public', | |
| 						'isStatic' => false, | |
| 						'type' => $param->type, | |
| 						'types' => $param->types, | |
| 						'shortDescription' => (($pos = strpos($param->description, '.')) !== false) ? | |
| 								substr($param->description, 0, $pos) : $param->description, | |
| 						'description' => $param->description, | |
| 						'setter' => $method | |
| 					]); | |
| 				} | |
| 			} | |
| 		} | |
| 	} | |
|  | |
| 	/** | |
| 	 * @param MethodDoc $method | |
| 	 * @param integer $number number of not optional parameters | |
| 	 * @return bool | |
| 	 */ | |
| 	private function paramsOptional($method, $number = 0) | |
| 	{ | |
| 		foreach($method->params as $param) { | |
| 			if (!$param->isOptional && $number-- <= 0) { | |
| 				return false; | |
| 			} | |
| 		} | |
| 		return true; | |
| 	} | |
|  | |
| 	/** | |
| 	 * @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 $classB | |
| 	 */ | |
| 	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; | |
| 	} | |
| } |