/build/commands/api/ApiModel.php
PHP | 819 lines | 683 code | 74 blank | 62 comment | 96 complexity | 0751144b95b6f8571e8f3e3d28af464f MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, BSD-2-Clause
- <?php
- /**
- * ApiModel class file.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @link http://www.yiiframework.com/
- * @copyright Copyright © 2008-2011 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- * @version $Id$
- */
- /**
- * ApiModel represents the documentation for the Yii framework.
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @version $Id$
- * @package system.build
- * @since 1.0
- */
- class ApiModel
- {
- public $classes=array();
- public $packages;
- private $_currentClass;
- public function build($sourceFiles)
- {
- $this->findClasses($sourceFiles);
- $this->processClasses();
- }
- protected function findClasses($sourceFiles)
- {
- $this->classes=array();
- foreach($sourceFiles as $file)
- require_once($file);
- $classes=array_merge(get_declared_classes(),get_declared_interfaces());
- foreach($classes as $class)
- {
- $r=new ReflectionClass($class);
- if(in_array($r->getFileName(),$sourceFiles))
- $this->classes[$class]=true;
- }
- ksort($this->classes);
- }
- protected function processClasses()
- {
- $this->packages=array();
- foreach($this->classes as $class=>$value)
- {
- $doc=$this->processClass(new ReflectionClass($class));
- $this->classes[$class]=$doc;
- $this->packages[$doc->package][]=$class;
- }
- ksort($this->packages);
- // find out child classes for each class or interface
- foreach($this->classes as $class)
- {
- if(isset($class->parentClasses[0]))
- {
- $parent=$class->parentClasses[0];
- if(isset($this->classes[$parent]))
- $this->classes[$parent]->subclasses[]=$class->name;
- }
- foreach($class->interfaces as $interface)
- {
- if(isset($this->classes[$interface]))
- $this->classes[$interface]->subclasses[]=$class->name;
- }
- }
- }
- protected function processClass($class)
- {
- $doc=new ClassDoc;
- $doc->name=$class->getName();
- $doc->loadSource($class);
- $this->_currentClass=$doc->name;
- for($parent=$class;$parent=$parent->getParentClass();)
- $doc->parentClasses[]=$parent->getName();
- foreach($class->getInterfaces() as $interface)
- $doc->interfaces[]=$interface->getName();
- $doc->isInterface=$class->isInterface();
- $doc->isAbstract=$class->isAbstract();
- $doc->isFinal=$class->isFinal();
- $doc->methods=$this->processMethods($class);
- $doc->properties=$this->processProperties($class);
- $doc->signature=($doc->isInterface?'interface ':'class ').$doc->name;
- if($doc->isFinal)
- $doc->signature='final '.$doc->signature;
- if($doc->isAbstract && !$doc->isInterface)
- $doc->signature='abstract '.$doc->signature;
- if(in_array('CComponent',$doc->parentClasses))
- {
- $doc->properties=array_merge($doc->properties,$this->processComponentProperties($class));
- $doc->events=$this->processComponentEvents($class);
- }
- ksort($doc->properties);
- foreach($doc->properties as $property)
- {
- if($property->isProtected)
- $doc->protectedPropertyCount++;
- else
- $doc->publicPropertyCount++;
- if(!$property->isInherited)
- $doc->nativePropertyCount++;
- }
- foreach($doc->methods as $method)
- {
- if($method->isProtected)
- $doc->protectedMethodCount++;
- else
- $doc->publicMethodCount++;
- if(!$method->isInherited)
- $doc->nativeMethodCount++;
- }
- foreach($doc->events as $event)
- {
- if(!$event->isInherited)
- $doc->nativeEventCount++;
- }
- $this->processComment($doc,$class->getDocComment());
- return $doc;
- }
- protected function processComment($doc,$comment)
- {
- $comment=strtr(trim(preg_replace('/^\s*\**( |\t)?/m','',trim($comment,'/'))),"\r",'');
- if(preg_match('/^\s*@\w+/m',$comment,$matches,PREG_OFFSET_CAPTURE))
- {
- $meta=substr($comment,$matches[0][1]);
- $comment=trim(substr($comment,0,$matches[0][1]));
- }
- else
- $meta='';
- if(($pos=strpos($comment,"\n"))!==false)
- $doc->introduction=$this->processDescription(substr($comment,0,$pos));
- else
- $doc->introduction=$this->processDescription($comment);
- $doc->description=$this->processDescription($comment);
- $this->processTags($doc,$meta);
- }
- protected function processDescription($text)
- {
- if(($text=trim($text))==='')
- return '';
- $text=preg_replace_callback('/\{@include\s+([^\s\}]+)\s*\}/s',array($this,'processInclude'),$text);
- $text=preg_replace('/^(\r| |\t)*$/m',"<br/><br/>",$text);
- $text=preg_replace_callback('/<pre>(.*?)<\/pre>/is',array($this,'processCode'),$text);
- $text=preg_replace_callback('/\{@link\s+([^\s\}]+)(.*?)\}/s',array($this,'processLink'),$text);
- return $text;
- }
- protected function processCode($matches)
- {
- $match=preg_replace('/<br\/><br\/>/','',$matches[1]);
- return "<pre>".htmlspecialchars($match)."</pre>";
- }
- protected function resolveInternalUrl($url)
- {
- $url=rtrim($url,'()');
- if(($pos=strpos($url,'::'))!==false)
- {
- $class=substr($url,0,$pos);
- $method=substr($url,$pos+2);
- }
- else if(isset($this->classes[$url]))
- return $url;
- else
- {
- $class=$this->_currentClass;
- $method=$url;
- }
- return $this->getMethodUrl($class,$method);
- }
- protected function getMethodUrl($class,$method)
- {
- if(!isset($this->classes[$class]))
- return '';
- if(method_exists($class,$method) || property_exists($class,$method))
- return $class.'::'.$method;
- if(method_exists($class,'get'.$method) || method_exists($class,'set'.$method))
- return $class.'::'.$method;
- if(($parent=get_parent_class($class))!==false)
- return $this->getMethodUrl($parent,$method);
- else
- return '';
- }
- protected function processLink($matches)
- {
- $url=$matches[1];
- if(($text=trim($matches[2]))==='')
- $text=$url;
- if(preg_match('/^(http|ftp):\/\//i',$url)) // an external URL
- return "<a href=\"$url\">$text</a>";
- $url=$this->resolveInternalUrl($url);
- return $url===''?$text:'{{'.$url.'|'.$text.'}}';
- }
- protected function processInclude($matches)
- {
- $class=new ReflectionClass($this->_currentClass);
- $fileName=dirname($class->getFileName()).DIRECTORY_SEPARATOR.$matches[1];
- if(is_file($fileName))
- return file_get_contents($fileName);
- else
- return $matches[0];
- }
- protected function processTags($object,$comment)
- {
- $tags=preg_split('/^\s*@/m',$comment,-1,PREG_SPLIT_NO_EMPTY);
- foreach($tags as $tag)
- {
- $segs=preg_split('/\s+/',trim($tag),2);
- $tagName=$segs[0];
- $param=isset($segs[1])?trim($segs[1]):'';
- $tagMethod='tag'.ucfirst($tagName);
- if(method_exists($this,$tagMethod))
- $this->$tagMethod($object,$param);
- else if(property_exists($object,$tagName))
- $object->$tagName=$param;
- }
- }
- protected function processMethods($class)
- {
- $methods=array();
- foreach($class->getMethods() as $method)
- {
- if($method->isPublic() || $method->isProtected())
- {
- $doc=$this->processMethod($class,$method);
- $methods[$doc->name]=$doc;
- }
- }
- ksort($methods);
- return $methods;
- }
- protected function processMethod($class,$method)
- {
- $doc=new MethodDoc;
- $doc->name=$method->getName();
- $doc->loadSource($method);
- $doc->definedBy=$method->getDeclaringClass()->getName();
- $doc->isAbstract=$method->isAbstract();
- $doc->isFinal=$method->isFinal();
- $doc->isProtected=$method->isProtected();
- $doc->isStatic=$method->isStatic();
- $doc->isInherited=$doc->definedBy!==$class->getName();
- $doc->input=array();
- foreach($method->getParameters() as $param)
- {
- $p=new ParamDoc;
- $p->name=$param->getName();
- $p->isOptional=$param->isOptional();
- if($param->isDefaultValueAvailable())
- $p->defaultValue=$param->getDefaultValue();
- $p->isPassedByReference=$param->isPassedByReference();
- $doc->input[]=$p;
- }
- reset($doc->input);
- $this->processComment($doc,$method->getDocComment());
- $params=array();
- foreach($doc->input as $param)
- {
- $type=empty($param->type)?'':$this->getTypeUrl($param->type).' ';
- if($param->isOptional)
- $params[]=$type.($param->isPassedByReference?'&':'').'$'.$param->name.'='.str_replace("\r",'',var_export($param->defaultValue,true));
- else
- $params[]=$type.($param->isPassedByReference?'&':'').'$'.$param->name;
- }
- $doc->signature='{{'.$class->name.'::'.$doc->name.'|<b>'.$doc->name.'</b>}}('.implode(', ',$params).')';
- if($doc->output!==null)
- $doc->signature=$this->getTypeUrl($doc->output->type).' '.$doc->signature;
- else
- $doc->signature='void '.$doc->signature;
- if(($modifier=implode(' ',Reflection::getModifierNames($method->getModifiers())))!=='')
- $doc->signature=$modifier.' '.$doc->signature;
- return $doc;
- }
- protected function getTypeUrl($type)
- {
- if(isset($this->classes[$type]) && $type!==$this->_currentClass)
- return '{{'.$type.'|'.$type.'}}';
- else
- return $type;
- }
- protected function processProperties($class)
- {
- $properties=array();
- foreach($class->getProperties() as $property)
- {
- if($property->isPublic() || $property->isProtected())
- {
- $p=$this->processProperty($class,$property);
- $properties[$p->name]=$p;
- }
- }
- return $properties;
- }
- protected function processProperty($class,$property)
- {
- $doc=new PropertyDoc;
- $doc->name=$property->getName();
- $doc->definedBy=$property->getDeclaringClass()->getName();
- $doc->readOnly=false;
- $doc->isStatic=$property->isStatic();
- $doc->isProtected=$property->isProtected();
- $doc->isInherited=$doc->definedBy!==$class->getName();
- $this->processComment($doc,$property->getDocComment());
- $doc->signature='<b>$'.$doc->name.'</b>;';
- if($doc->type!==null)
- $doc->signature=$this->getTypeUrl($doc->type) . ' ' . $doc->signature;
- if(($modifier=implode(' ',Reflection::getModifierNames($property->getModifiers())))!=='')
- $doc->signature=$modifier.' '.$doc->signature;
- return $doc;
- }
- protected function processComponentProperties($class)
- {
- $properties=array();
- foreach($class->getMethods() as $method)
- {
- if($this->isPropertyMethod($method) && ($method->isPublic() || $method->isProtected()))
- {
- $p=$this->processComponentProperty($class,$method);
- $properties[$p->name]=$p;
- }
- }
- return $properties;
- }
- protected function processComponentProperty($class,$method)
- {
- $doc=new PropertyDoc;
- $name=$method->getName();
- $doc->name=strtolower($name[3]).substr($name,4);
- $doc->isProtected=$method->isProtected();
- $doc->isStatic=false;
- $doc->readOnly=!$class->hasMethod('set'.substr($name,3));
- $doc->definedBy=$method->getDeclaringClass()->getName();
- $doc->isInherited=$doc->definedBy!==$class->getName();
- $doc->getter=$this->processMethod($class,$method);
- if(!$doc->readOnly)
- $doc->setter=$this->processMethod($class,$class->getMethod('set'.substr($name,3)));
- $this->processComment($doc,$method->getDocComment());
- return $doc;
- }
- protected function processComponentEvents($class)
- {
- $events=array();
- foreach($class->getMethods() as $method)
- {
- if($this->isEventMethod($method) && ($method->isPublic() || $method->isProtected()))
- {
- $e=$this->processComponentEvent($class,$method);
- $events[$e->name]=$e;
- }
- }
- return $events;
- }
- protected function processComponentEvent($class,$method)
- {
- $doc=new EventDoc;
- $doc->name=$method->getName();
- $doc->definedBy=$method->getDeclaringClass()->getName();
- $doc->isInherited=$doc->definedBy!==$class->getName();
- $doc->trigger=$this->processMethod($class,$method);
- $this->processComment($doc,$method->getDocComment());
- return $doc;
- }
- protected function tagParam($object,$comment)
- {
- if($object instanceof FunctionDoc)
- {
- $param=current($object->input);
- if($param!==false)
- {
- $segs=preg_split('/\s+/',$comment,2);
- $param->type=$segs[0];
- if(preg_match('/\[\s*\]/',$param->type))
- $param->type='array';
- if(isset($segs[1]))
- {
- /*
- * remove $variablename from description
- */
- $segs[1]=trim(preg_replace('/^\$\w+/','',$segs[1]));
- $param->description=$this->processDescription($segs[1]);
- if(empty($object->introduction))
- {
- if(substr($object->name,0,3)=='set')
- $object->introduction='Sets '.$param->description;
- }
- }
- next($object->input);
- }
- }
- }
- protected function tagReturn($object,$comment)
- {
- $segs=preg_split('/\s+/',$comment,2);
- if($object instanceof FunctionDoc)
- {
- $object->output=new ParamDoc;
- $object->output->type=$segs[0];
- if(isset($segs[1]))
- {
- $object->output->description=$this->processDescription($segs[1]);
- if(empty($object->introduction))
- {
- /*
- * If no custom introduction, add automatically
- * with this getters introduction displayed in public methods table is resolved
- */
- if(substr($object->name,0,5)=='getIs')
- $object->introduction='Checks '.$object->output->description;
- elseif(substr($object->name,0,3)=='get')
- $object->introduction='Returns '.$object->output->description;
- elseif(substr($object->name,0,3)=='has')
- $object->introduction='Determines '.$object->output->description;
- }
- }
- }
- else if($object instanceof PropertyDoc)
- {
- $object->type=$segs[0];
- if(isset($segs[1]) && empty($object->description))
- {
- if(($pos=strpos($segs[1],'.'))!==false)
- $object->introduction=$this->processDescription(substr($segs[1],0,$pos+1));
- else
- $object->introduction=$this->processDescription($segs[1]);
- $object->description=$this->processDescription($segs[1]);
- }
- }
- }
- protected function tagVar($object,$comment)
- {
- if($object instanceof PropertyDoc)
- {
- $segs=preg_split('/\s+/',$comment,2);
- $object->type=$segs[0];
- if(isset($segs[1]) && empty($object->description))
- {
- if(($pos=strpos($segs[1],'.'))!==false)
- $object->introduction=$this->processDescription(substr($segs[1],0,$pos+1));
- else
- $object->introduction=$this->processDescription($segs[1]);
- $object->description=$this->processDescription($segs[1]);
- }
- }
- }
- protected function tagSee($object,$comment)
- {
- $segs=preg_split('/\s+/',trim($comment),2);
- $matches[1]=$segs[0];
- $matches[2]=isset($segs[1])?$segs[1]:'';
- $object->see[]=$this->processLink($matches);
- }
- protected function isPropertyMethod($method)
- {
- $methodName=$method->getName();
- return $method->getNumberOfRequiredParameters()===0
- && !$method->isStatic()
- && strncasecmp($methodName,'get',3)===0
- && isset($methodName[3]);
- }
- protected function isEventMethod($method)
- {
- $methodName=$method->getName();
- return strncasecmp($methodName,'on',2)===0
- && !$method->isStatic()
- && isset($methodName[2]);
- }
- protected function getClassFiles($basePath)
- {
- $files=array();
- $folder=opendir($basePath);
- while($file=readdir($folder))
- {
- if($file==='.' || $file==='..')
- continue;
- $fullPath=realpath($basePath.DIRECTORY_SEPARATOR.$file);
- if($this->isValidPath($fullPath))
- {
- if(is_file($fullPath))
- $files[]=$fullPath;
- else
- $files=array_merge($files,$this->getClassFiles($fullPath));
- }
- }
- closedir($folder);
- return $files;
- }
- protected function isValidPath($path)
- {
- if(is_file($path) && substr($path,-4)!=='.php')
- return false;
- $path=strtr($path,'\\','/');
- foreach($this->_excludes as $exclude)
- {
- if(($exclude[0]==='/' && $this->_sourcePath.$exclude===$path) || ($exclude[0]!=='/' && basename($path)===$exclude))
- return false;
- }
- return true;
- }
- protected function findTargets()
- {
- $oldClasses=get_declared_classes();
- $oldInterfaces=get_declared_interfaces();
- $oldFunctions=get_defined_functions();
- $oldConstants=get_defined_constants(true);
- $classFiles=$this->getClassFiles($this->_sourcePath);
- require_once($this->_sourcePath.'/yii.php');
- foreach($classFiles as $classFile)
- require_once($classFile);
- $classes=array_values(array_diff(get_declared_classes(),$oldClasses));
- $interfaces=array_values(array_diff(get_declared_interfaces(),$oldInterfaces));
- $classes=array_merge($classes,$interfaces);
- $n=count($classes);
- for($i=0;$i<$n;++$i)
- {
- $class=new ReflectionClass($classes[$i]);
- $fileName=strtr($class->getFileName(),'\\','/');
- foreach($this->_excludes as $exclude)
- {
- if(($exclude[0]==='/' && strpos($fileName,$this->_sourcePath.$exclude)===0))
- {
- unset($classes[$i]);
- break;
- }
- }
- }
- sort($classes);
- $newFunctions=get_defined_functions();
- $newConstants=get_defined_constants(true);
- $functions=array_values(array_diff($newFunctions['user'],$oldFunctions['user']));
- $constants=$newConstants['user'];
- return array($classes,$functions,$constants);
- }
- /*
- * Calls checkSource for every file in $sourceFiles
- * @param array $sourceFiles array of source file path that we need to check
- */
- public function check($sourceFiles)
- {
- echo "Checking PHPDoc @param in source files ...\n";
- foreach($sourceFiles as $no=>$sourceFile)
- {
- $this->checkSource($sourceFile);
- }
- echo "Done.\n\n";
- }
- /*
- * Checks @param directives in a source file
- * Detects:
- * missing @param directive (there is no @param directive for a function parameter)
- * missing function parameter (@param directive exists but that parameter is not in a function declaration)
- * missmatch parameters (if @param directive has different parameter name than a function - possible spelling error or wrong order of @param directives)
- */
- protected function checkSource($sourceFile)
- {
- $fileContent=file($sourceFile);
- $docParam=array();
- foreach($fileContent as $no=>$line)
- {
- /*
- * Get lines with @param, and parameter name
- */
- if(preg_match('/^\s*\*\s*@param\s[A-Za-z0-9_\|]+\s(\$\w+)\s./',$line,$matches,PREG_OFFSET_CAPTURE))
- {
- $docParam[]=array(
- 'docLine'=>$no+1,
- 'docName'=>$matches[1][0],
- );
- continue;
- }
- /*
- * If function without parameters, there should be no parameters in $docParam
- */
- if(preg_match('/^\s*\w+[\s\w]*\sfunction\s\w+\(\s*\)/',$line,$matches,PREG_OFFSET_CAPTURE))
- {
- if(isset($docParam[0])) {
- $value=$docParam[0];
- echo "ERROR.............: Parameter name not found!\n";
- echo "Source file.......: ".$sourceFile."\n";
- echo "PHPDoc line.......: ".$value['docLine']."\n";
- echo "PHPDoc parameter..: ".$value['docName']."\n\n";
- $docParam=array();
- }
- continue;
- }
- /*
- * Get function variables in $matches[1][0]
- */
- if(preg_match('/^\s*\w+[\s\w]*\sfunction\s\w+\((.+)\)/',$line,$matches,PREG_OFFSET_CAPTURE))
- {
- $params=explode(",",$matches[1][0]);
- foreach($params as $br=>$param)
- {
- /*
- * Strip anything that does not begin with $ (class types) eg. CHttpRequest $request
- */
- $param=preg_replace('/^\w+/','',trim($param));
- /*
- * Strip default value if exists ex. data=array() (with spaces)
- */
- $param=preg_replace('/\s*=.+/','',trim($param));
- /*
- * Strip & if pass by reference
- */
- if($param[0]=='&')
- $param=substr($param,1);
- /*
- * add parameter info to the docParam array
- */
- $docParam[$br]['parameterName']=$param;
- $docParam[$br]['parameterLine']=$no+1;
- }
- /*
- * All info gathered, let's make some checking
- */
- foreach($docParam as $value)
- {
- if(!isset($value['docLine']) || !isset($value['docName']) && isset($value['parameterName']))
- {
- echo "ERROR.............: Documentation not found!\n";
- echo "Source file.......: ".$sourceFile."\n";
- echo "Parameter line....: ".$value['parameterLine']."\n";
- echo "Parameter name....: ".$value['parameterName']."\n\n";
- }
- if(!isset($value['parameterName']) || !isset($value['parameterLine']))
- {
- echo "ERROR.............: Parameter name not found!\n";
- echo "Source file.......: ".$sourceFile."\n";
- echo "PHPDoc line.......: ".$value['docLine']."\n";
- echo "PHPDoc parameter..: ".$value['docName']."\n\n";
- }
- if( isset($value['docName']) && isset($value['parameterName']) && $value['docName']!==$value['parameterName'])
- {
- echo "ERROR.............: Wrong parameter order!\n";
- echo "Source file.......: ".$sourceFile."\n";
- echo "PHPDoc line.......: ".$value['docLine']."\n";
- echo "PHPDoc parameter..: ".$value['docName']."\n";
- echo "Parameter line....: ".$value['parameterLine']."\n";
- echo "Parameter name....: ".$value['parameterName']."\n\n";
- }
- }
- /*
- * reset $docParam
- */
- $docParam=array();
- }
- }
- }
- }
- class BaseDoc
- {
- public $name;
- public $since;
- public $see;
- public $introduction;
- public $description;
- public $sourcePath;
- public $startLine;
- public $endLine;
- public function loadSource($reflection)
- {
- $this->sourcePath=str_replace('\\','/',str_replace(YII_PATH,'',$reflection->getFileName()));
- $this->startLine=$reflection->getStartLine();
- $this->endLine=$reflection->getEndLine();
- }
- public function getSourceUrl($baseUrl,$line=null)
- {
- if($line===null)
- return $baseUrl.$this->sourcePath;
- else
- return $baseUrl.$this->sourcePath.'#'.$line;
- }
- public function getSourceCode()
- {
- $lines=file(YII_PATH.$this->sourcePath);
- return implode("",array_slice($lines,$this->startLine-1,$this->endLine-$this->startLine+1));
- }
- }
- class ClassDoc extends BaseDoc
- {
- public $parentClasses=array();
- public $subclasses=array();
- public $interfaces=array();
- public $isInterface;
- public $isAbstract;
- public $isFinal;
- public $signature;
- public $properties=array();
- public $methods=array();
- public $events=array();
- public $constants=array();
- public $protectedPropertyCount=0;
- public $publicPropertyCount=0;
- public $protectedMethodCount=0;
- public $publicMethodCount=0;
- public $nativePropertyCount=0;
- public $nativeMethodCount=0;
- public $nativeEventCount=0;
- public $package;
- public $version;
- }
- class PropertyDoc extends BaseDoc
- {
- public $isProtected;
- public $isStatic;
- public $readOnly;
- public $isInherited;
- public $definedBy;
- public $type;
- public $signature;
- public $getter;
- public $setter;
- }
- class FunctionDoc extends BaseDoc
- {
- public $signature;
- public $input=array();
- public $output;
- }
- class MethodDoc extends FunctionDoc
- {
- public $isAbstract;
- public $isFinal;
- public $isProtected;
- public $isStatic;
- public $isInherited;
- public $definedBy;
- }
- class EventDoc extends BaseDoc
- {
- public $isInherited;
- public $definedBy;
- public $trigger;
- }
- class ParamDoc
- {
- public $name;
- public $description;
- public $type;
- public $isOptional;
- public $defaultValue;
- public $isPassedByReference;
- }