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}."; // 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}."; // 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; | 
						|
	} | 
						|
} |