diff --git a/extensions/yii/apidoc/.gitignore b/extensions/yii/apidoc/.gitignore new file mode 100644 index 0000000..8b7ef35 --- /dev/null +++ b/extensions/yii/apidoc/.gitignore @@ -0,0 +1,2 @@ +/vendor +composer.lock diff --git a/extensions/yii/apidoc/CHANGELOG.md b/extensions/yii/apidoc/CHANGELOG.md new file mode 100644 index 0000000..16875d2 --- /dev/null +++ b/extensions/yii/apidoc/CHANGELOG.md @@ -0,0 +1,7 @@ +Yii Framework 2 apidoc extension Change Log +=========================================== + +2.0.0 beta under development +---------------------------- + +- Initial release. diff --git a/extensions/yii/apidoc/LICENSE.md b/extensions/yii/apidoc/LICENSE.md new file mode 100644 index 0000000..e98f03d --- /dev/null +++ b/extensions/yii/apidoc/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/extensions/yii/apidoc/README.md b/extensions/yii/apidoc/README.md new file mode 100644 index 0000000..0ae1ec3 --- /dev/null +++ b/extensions/yii/apidoc/README.md @@ -0,0 +1,48 @@ +API documentation generator for Yii 2 +===================================== + +This extension provides an API documentation generator for the Yii framework 2.0. + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require yiisoft/yii2-apidoc "*" +``` + +or add + +```json +"yiisoft/yii2-apidoc": "*" +``` + +to the require section of your composer.json. + +Usage +----- + +To generate API documentation, run the `apidoc` command. + +``` +vendor/bin/apidoc source/directory ./output +``` + +By default the `offline` template will be used. You can choose a different templates with the `--template=name` parameter. +Currently there is only the `offline` template available. + +You may also add the `yii\apidoc\commands\RenderController` to your console application class map and +run it inside of your applications console app. + +Creating your own templates +--------------------------- + +TDB + +Using the model layer +--------------------- + +TDB \ No newline at end of file diff --git a/extensions/yii/apidoc/apidoc b/extensions/yii/apidoc/apidoc new file mode 100755 index 0000000..c19d642 --- /dev/null +++ b/extensions/yii/apidoc/apidoc @@ -0,0 +1,49 @@ +#!/usr/bin/env php + 'yii2-apidoc', + 'basePath' => __DIR__, + 'enableCoreCommands' => false, + 'controllerNamespace' => 'yii\\apidoc\\commands', + 'controllerPath' => '@yii/apidoc/commands', +]); +$exitCode = $application->run(); +exit($exitCode); diff --git a/extensions/yii/apidoc/apidoc.bat b/extensions/yii/apidoc/apidoc.bat new file mode 100644 index 0000000..ae00407 --- /dev/null +++ b/extensions/yii/apidoc/apidoc.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line bootstrap script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright © 2012 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%apidoc" %* + +@endlocal diff --git a/extensions/yii/apidoc/commands/RenderController.php b/extensions/yii/apidoc/commands/RenderController.php new file mode 100644 index 0000000..cbd97ae --- /dev/null +++ b/extensions/yii/apidoc/commands/RenderController.php @@ -0,0 +1,144 @@ + + * @since 2.0 + */ +class RenderController extends Controller +{ + public $template = 'offline'; + + /** + * Renders API documentation files + * @param array $sourceDirs + * @param string $targetDir + * @return int + */ + public function actionIndex(array $sourceDirs, $targetDir) + { + $targetDir = rtrim(Yii::getAlias($targetDir), '\\/'); + if (is_dir($targetDir) && !$this->confirm('TargetDirectory already exists. Overwrite?')) { + return 2; + } + if (!is_dir($targetDir)) { + mkdir($targetDir); + } + + $renderer = $this->findRenderer(); + $renderer->targetDir = $targetDir; + + $this->stdout('Searching files to process... '); + $files = []; + foreach($sourceDirs as $source) { + foreach($this->findFiles($source) as $fileName) { + $files[$fileName] = $fileName; + } + } + + $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); + + $context = new Context(); + + $cacheFile = $targetDir . '/cache/' . md5(serialize($files)) . '.tmp'; + if (file_exists($cacheFile)) { + $this->stdout('Loading processed data from cache... '); + $context = unserialize(file_get_contents($cacheFile)); + $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); + + $this->stdout('Checking for updated files... '); + foreach($context->files as $file => $sha) { + if (sha1_file($file) === $sha) { + unset($files[$file]); + } + } + $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); + } + + $fileCount = count($files); + $this->stdout($fileCount . ' file' . ($fileCount == 1 ? '' : 's') . ' to update.' . PHP_EOL); + Console::startProgress(0, $fileCount, 'Processing files... ', false); + $done = 0; + foreach($files as $file) { + $context->addFile($file); + Console::updateProgress(++$done, $fileCount); + } + Console::endProgress(true); + $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); + + // save processed data to cache + if (!is_dir(dirname($cacheFile))) { + mkdir(dirname($cacheFile)); + } + file_put_contents($cacheFile, serialize($context)); + + $this->stdout('Updating cross references and backlinks... '); + $context->updateReferences(); + $this->stdout('done.' . PHP_EOL, Console::FG_GREEN); + + // render models + $renderer->render($context, $this); + } + + /** + * @return BaseRenderer + */ + protected function findRenderer() + { + $file = Yii::getAlias('@yii/apidoc/templates/' . $this->template . '/Renderer.php'); + $reflection = new FileReflector($file, true); + $reflection->process(); + $classes = $reflection->getClasses(); + if (empty($classes)) { + $this->stderr('Renderer not found.' . PHP_EOL); + } + $rendererClass = reset($classes)->getName(); + require($file); + return new $rendererClass(); + } + + protected function findFiles($path, $except = []) + { + $path = FileHelper::normalizePath($path); + $options = [ + 'filter' => function ($path) { + if (is_file($path)) { + $file = basename($path); + if ($file[0] < 'A' || $file[0] > 'Z') { + return false; + } + } + return null; + }, + 'only' => ['.php'], + 'except' => $except, + ]; + return FileHelper::findFiles($path, $options); + } + + /** + * @inheritdoc + */ + public function globalOptions() + { + return array_merge(parent::globalOptions(), ['template']); + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/composer.json b/extensions/yii/apidoc/composer.json new file mode 100644 index 0000000..bf1c4b5 --- /dev/null +++ b/extensions/yii/apidoc/composer.json @@ -0,0 +1,30 @@ +{ + "name": "yiisoft/yii2-apidoc", + "description": "API Documentation generator for the Yii framework 2.0", + "keywords": ["yii", "phpdoc", "apidoc", "api", "documentation"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aapidoc", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" + } + ], + "minimum-stability": "dev", + "require": { + "yiisoft/yii2": "*", + "phpdocumentor/reflection": "1.0.2" + }, + "autoload": { + "psr-0": { "yii\\apidoc\\": "" } + }, + "target-dir": "yii/apidoc", + "bin": ["apidoc"] +} diff --git a/extensions/yii/apidoc/helpers/Markdown.php b/extensions/yii/apidoc/helpers/Markdown.php new file mode 100644 index 0000000..6139b15 --- /dev/null +++ b/extensions/yii/apidoc/helpers/Markdown.php @@ -0,0 +1,80 @@ + + * @since 2.0 + */ +class Markdown extends \yii\helpers\Markdown +{ + /** + * @var BaseRenderer + */ + public static $renderer; + + /** + * Converts markdown into HTML + * + * @param string $content + * @param TypeDoc $context + * @return string + */ + public static function process($content, $context) + { + $content = trim(parent::process($content, [])); + if (!strncmp($content, '

', 3) && substr($content, -4, 4) == '

') { + $content = substr($content, 3, -4); + } + + $content = preg_replace_callback('/\[\[([\w\d\\\\\(\):]+)(\|[\w\d ]*)?\]\]/xm', function($matches) use ($context) { + $object = $matches[1]; + $title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1); + + if (($pos = strpos($object, '::')) !== false) { + $typeName = substr($object, 0, $pos); + $subjectName = substr($object, $pos + 2); + // Collection resolves relative types + $typeName = (new Collection([$typeName], $context->phpDocContext))->__toString(); + $type = static::$renderer->context->getType($typeName); + if ($type === null) { + return '' . $typeName . '::' . $subjectName . ''; + } else { + if (($subject = $type->findSubject($subjectName)) !== null) { + if ($title === null) { + $title = $type->name . '::' . $subject->name; + if ($subject instanceof MethodDoc) { + $title .= '()'; + } + } + return static::$renderer->subjectLink($subject, $title); + } else { + return '' . $type->name . '::' . $subjectName . ''; + } + } + } elseif (($subject = $context->findSubject($object)) !== null) { + return static::$renderer->subjectLink($subject, $title); + } + // Collection resolves relative types + $object = (new Collection([$object], $context->phpDocContext))->__toString(); + if (($type = static::$renderer->context->getType($object)) !== null) { + return static::$renderer->typeLink($type, $title); + } + return '' . $object . ''; + }, $content); + + return $content; + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/helpers/PrettyPrinter.php b/extensions/yii/apidoc/helpers/PrettyPrinter.php new file mode 100644 index 0000000..288178d --- /dev/null +++ b/extensions/yii/apidoc/helpers/PrettyPrinter.php @@ -0,0 +1,41 @@ + + * @since 2.0 + */ +class PrettyPrinter extends \phpDocumentor\Reflection\PrettyPrinter +{ + public function pExpr_Array(PHPParser_Node_Expr_Array $node) + { + return '[' . $this->pCommaSeparated($node->items) . ']'; + } + + /** + * Returns a simple human readable output for a value. + * + * @param PHPParser_Node_Expr $value The value node as provided by PHP-Parser. + * @return string + */ + public static function getRepresentationOfValue(PHPParser_Node_Expr $value) + { + if ($value === null) { + return ''; + } + + $printer = new static(); + return $printer->prettyPrintExpr($value); + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/BaseDoc.php b/extensions/yii/apidoc/models/BaseDoc.php new file mode 100644 index 0000000..9c8cfb9 --- /dev/null +++ b/extensions/yii/apidoc/models/BaseDoc.php @@ -0,0 +1,105 @@ + + * @since 2.0 + */ +class BaseDoc extends Object +{ + /** + * @var \phpDocumentor\Reflection\DocBlock\Context + */ + public $phpDocContext; + + public $name; + + public $sourceFile; + public $startLine; + public $endLine; + + public $shortDescription; + public $description; + public $since; + public $deprecatedSince; + public $deprecatedReason; + + /** + * @var \phpDocumentor\Reflection\DocBlock\Tag[] + */ + public $tags = []; + + + /** + * @param \phpDocumentor\Reflection\BaseReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($config); + + if ($reflector === null) { + return; + } + + // base properties + $this->name = ltrim($reflector->getName(), '\\'); + $this->startLine = $reflector->getNode()->getAttribute('startLine'); + $this->endLine = $reflector->getNode()->getAttribute('endLine'); + + $docblock = $reflector->getDocBlock(); + if ($docblock !== null) { + $this->shortDescription = ucfirst($docblock->getShortDescription()); + $this->description = $docblock->getLongDescription(); + + $this->phpDocContext = $docblock->getContext(); + + $this->tags = $docblock->getTags(); + foreach($this->tags as $i => $tag) { + if ($tag instanceof SinceTag) { + $this->since = $tag->getVersion(); + unset($this->tags[$i]); + } elseif ($tag instanceof DeprecatedTag) { + $this->deprecatedSince = $tag->getVersion(); + $this->deprecatedReason = $tag->getDescription(); + unset($this->tags[$i]); + } + } + } + } + + + // TODO + 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)); + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/ClassDoc.php b/extensions/yii/apidoc/models/ClassDoc.php new file mode 100644 index 0000000..020105f --- /dev/null +++ b/extensions/yii/apidoc/models/ClassDoc.php @@ -0,0 +1,109 @@ + + * @since 2.0 + */ +class ClassDoc extends TypeDoc +{ + public $parentClass; + + public $isAbstract; + public $isFinal; + + public $interfaces = []; + public $traits = []; + // will be set by Context::updateReferences() + public $subclasses = []; + + /** + * @var EventDoc[] + */ + public $events = []; + /** + * @var ConstDoc[] + */ + public $constants = []; + + + public function findSubject($subjectName) + { + if (($subject = parent::findSubject($subjectName)) !== null) { + return $subject; + } + foreach($this->events as $name => $event) { + if ($subjectName == $name) { + return $event; + } + } + foreach($this->constants as $name => $constant) { + if ($subjectName == $name) { + return $constant; + } + } + return null; + } + + /** + * @return EventDoc[] + */ + public function getNativeEvents() + { + $events = []; + foreach($this->events as $name => $event) { + if ($event->definedBy != $this->name) { + continue; + } + $events[$name] = $event; + } + return $events; + } + + /** + * @param \phpDocumentor\Reflection\ClassReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + $this->parentClass = ltrim($reflector->getParentClass(), '\\'); + if (empty($this->parentClass)) { + $this->parentClass = null; + } + $this->isAbstract = $reflector->isAbstract(); + $this->isFinal = $reflector->isFinal(); + + foreach($reflector->getInterfaces() as $interface) { + $this->interfaces[] = ltrim($interface, '\\'); + } + foreach($reflector->getTraits() as $trait) { + $this->traits[] = ltrim($trait, '\\'); + } + foreach($reflector->getConstants() as $constantReflector) { + $docblock = $constantReflector->getDocBlock(); + if ($docblock !== null && count($docblock->getTagsByName('event')) > 0) { + $event = new EventDoc($constantReflector); + $event->definedBy = $this->name; + $this->events[$event->name] = $event; + } else { + $constant = new ConstDoc($constantReflector); + $constant->definedBy = $this->name; + $this->constants[$constant->name] = $constant; + } + } + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/ConstDoc.php b/extensions/yii/apidoc/models/ConstDoc.php new file mode 100644 index 0000000..1f185da --- /dev/null +++ b/extensions/yii/apidoc/models/ConstDoc.php @@ -0,0 +1,35 @@ + + * @since 2.0 + */ +class ConstDoc extends BaseDoc +{ + public $definedBy; + public $value; + + /** + * @param \phpDocumentor\Reflection\ClassReflector\ConstantReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + $this->value = $reflector->getValue(); + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/Context.php b/extensions/yii/apidoc/models/Context.php new file mode 100644 index 0000000..94fd1dc --- /dev/null +++ b/extensions/yii/apidoc/models/Context.php @@ -0,0 +1,258 @@ + + * @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; + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/EventDoc.php b/extensions/yii/apidoc/models/EventDoc.php new file mode 100644 index 0000000..2c15f3d --- /dev/null +++ b/extensions/yii/apidoc/models/EventDoc.php @@ -0,0 +1,51 @@ + + * @since 2.0 + */ +class EventDoc extends ConstDoc +{ + public $type; + public $types; + + /** + * @param \phpDocumentor\Reflection\ClassReflector\ConstantReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + foreach($this->tags as $i => $tag) { + if ($tag->getName() == 'event') { + $eventTag = new ReturnTag('event', $tag->getContent(), $tag->getDocBlock(), $tag->getLocation()); + $this->type = $eventTag->getType(); + $this->types = $eventTag->getTypes(); + $this->description = ucfirst($eventTag->getDescription()); + if (($pos = strpos($this->description, '.')) !== false) { + $this->shortDescription = substr($this->description, 0, $pos); + } else { + $this->shortDescription = $this->description; + } + unset($this->tags[$i]); + } + } + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/FunctionDoc.php b/extensions/yii/apidoc/models/FunctionDoc.php new file mode 100644 index 0000000..82c09ec --- /dev/null +++ b/extensions/yii/apidoc/models/FunctionDoc.php @@ -0,0 +1,77 @@ + + * @since 2.0 + */ +class FunctionDoc extends BaseDoc +{ + /** + * @var ParamDoc[] + */ + public $params = []; + public $exceptions = []; + public $return; + public $returnType; + public $returnTypes; + public $isReturnByReference; + + /** + * @param \phpDocumentor\Reflection\FunctionReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + $this->isReturnByReference = $reflector->isByRef(); + + foreach($reflector->getArguments() as $arg) { + $arg = new ParamDoc($arg); + $this->params[$arg->name] = $arg; + } + + foreach($this->tags as $i => $tag) { + if ($tag instanceof ThrowsTag) { + $this->exceptions[$tag->getType()] = $tag->getDescription(); + unset($this->tags[$i]); + } elseif ($tag instanceof PropertyTag) { + // ignore property tag + } elseif ($tag instanceof ParamTag) { + $paramName = $tag->getVariableName(); + if (!isset($this->params[$paramName])) { + echo 'undefined parameter documented: ' . $paramName . ' in ' . $this->name . "()\n"; // TODO log these messages somewhere + continue; + } + $this->params[$paramName]->description = ucfirst($tag->getDescription()); + $this->params[$paramName]->type = $tag->getType(); + $this->params[$paramName]->types = $tag->getTypes(); + unset($this->tags[$i]); + } elseif ($tag instanceof ReturnTag) { + $this->returnType = $tag->getType(); + $this->returnTypes = $tag->getTypes(); + $this->return = $tag->getDescription(); + unset($this->tags[$i]); + } + } + } +} diff --git a/extensions/yii/apidoc/models/InterfaceDoc.php b/extensions/yii/apidoc/models/InterfaceDoc.php new file mode 100644 index 0000000..10b6d65 --- /dev/null +++ b/extensions/yii/apidoc/models/InterfaceDoc.php @@ -0,0 +1,42 @@ + + * @since 2.0 + */ +class InterfaceDoc extends TypeDoc +{ + public $parentInterfaces = []; + + // will be set by Context::updateReferences() + public $implementedBy = []; + + /** + * @param \phpDocumentor\Reflection\InterfaceReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + foreach($reflector->getParentInterfaces() as $interface) { + $this->parentInterfaces[] = ltrim($interface, '\\'); + } + + // interface can not have properties + $this->properties = null; + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/MethodDoc.php b/extensions/yii/apidoc/models/MethodDoc.php new file mode 100644 index 0000000..e31d31c --- /dev/null +++ b/extensions/yii/apidoc/models/MethodDoc.php @@ -0,0 +1,46 @@ + + * @since 2.0 + */ +class MethodDoc extends FunctionDoc +{ + public $isAbstract; + public $isFinal; + + public $isStatic; + + public $visibility; + + // will be set by creating class + public $definedBy; + + /** + * @param \phpDocumentor\Reflection\ClassReflector\MethodReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + $this->isAbstract = $reflector->isAbstract(); + $this->isFinal = $reflector->isFinal(); + $this->isStatic = $reflector->isStatic(); + + $this->visibility = $reflector->getVisibility(); + } +} diff --git a/extensions/yii/apidoc/models/ParamDoc.php b/extensions/yii/apidoc/models/ParamDoc.php new file mode 100644 index 0000000..de6ef5d --- /dev/null +++ b/extensions/yii/apidoc/models/ParamDoc.php @@ -0,0 +1,54 @@ + + * @since 2.0 + */ +class ParamDoc extends Object +{ + public $name; + public $typeHint; + public $isOptional; + public $defaultValue; + public $isPassedByReference; + + // will be set by creating class + public $description; + public $type; + public $types; + + /** + * @param \phpDocumentor\Reflection\FunctionReflector\ArgumentReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($config); + + if ($reflector === null) { + return; + } + + $this->name = $reflector->getName(); + $this->typeHint = $reflector->getType(); + $this->isOptional = $reflector->getDefault() !== null; + + // bypass $reflector->getDefault() for short array syntax + if ($reflector->getNode()->default) { + $this->defaultValue = PrettyPrinter::getRepresentationOfValue($reflector->getNode()->default); + } + $this->isPassedByReference = $reflector->isByRef(); + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/PropertyDoc.php b/extensions/yii/apidoc/models/PropertyDoc.php new file mode 100644 index 0000000..b865fba --- /dev/null +++ b/extensions/yii/apidoc/models/PropertyDoc.php @@ -0,0 +1,78 @@ + + * @since 2.0 + */ +class PropertyDoc extends BaseDoc +{ + public $visibility; + public $isStatic; + + public $type; + public $types; + public $defaultValue; + + // will be set by creating class + public $getter; + public $setter; + + // will be set by creating class + public $definedBy; + + public function getIsReadOnly() + { + return $this->getter !== null && $this->setter === null; + } + + public function getIsWriteOnly() + { + return $this->getter === null && $this->setter !== null; + } + + /** + * @param \phpDocumentor\Reflection\ClassReflector\PropertyReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + $this->visibility = $reflector->getVisibility(); + $this->isStatic = $reflector->isStatic(); + + // bypass $reflector->getDefault() for short array syntax + if ($reflector->getNode()->default) { + $this->defaultValue = PrettyPrinter::getRepresentationOfValue($reflector->getNode()->default); + } + + foreach($this->tags as $i => $tag) { + if ($tag instanceof VarTag) { + $this->type = $tag->getType(); + $this->types = $tag->getTypes(); + $this->description = ucfirst($tag->getDescription()); + if (($pos = strpos($this->description, '.')) !== false) { + $this->shortDescription = substr($this->description, 0, $pos); + } else { + $this->shortDescription = $this->description; + } + } + } + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/TraitDoc.php b/extensions/yii/apidoc/models/TraitDoc.php new file mode 100644 index 0000000..1169f99 --- /dev/null +++ b/extensions/yii/apidoc/models/TraitDoc.php @@ -0,0 +1,40 @@ + + * @since 2.0 + */ +class TraitDoc extends TypeDoc +{ + // classes using the trait + // will be set by Context::updateReferences() + public $usedBy = []; + + public $traits = []; + + /** + * @param \phpDocumentor\Reflection\TraitReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + if ($reflector === null) { + return; + } + + foreach($reflector->getTraits() as $trait) { + $this->traits[] = ltrim($trait, '\\'); + } + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/models/TypeDoc.php b/extensions/yii/apidoc/models/TypeDoc.php new file mode 100644 index 0000000..6561fc7 --- /dev/null +++ b/extensions/yii/apidoc/models/TypeDoc.php @@ -0,0 +1,186 @@ + + * @since 2.0 + */ +class TypeDoc extends BaseDoc +{ + public $authors = []; + /** + * @var MethodDoc[] + */ + public $methods = []; + /** + * @var PropertyDoc[] + */ + public $properties = []; + + public $namespace; + + + public function findSubject($subjectName) + { + if ($subjectName[0] != '$') { + foreach($this->methods as $name => $method) { + if (rtrim($subjectName, '()') == $name) { + return $method; + } + } + } + if (substr($subjectName, -2, 2) == '()') { + return null; + } + if ($this->properties === null) { + return null; + } + foreach($this->properties as $name => $property) { + if (ltrim($subjectName, '$') == ltrim($name, '$')) { + return $property; + } + } + return null; + } + + /** + * @return MethodDoc[] + */ + public function getNativeMethods() + { + return $this->getFilteredMethods(null, $this->name); + } + + /** + * @return MethodDoc[] + */ + public function getPublicMethods() + { + return $this->getFilteredMethods('public'); + } + + /** + * @return MethodDoc[] + */ + public function getProtectedMethods() + { + return $this->getFilteredMethods('protected'); + } + + /** + * @param null $visibility + * @param null $definedBy + * @return MethodDoc[] + */ + private function getFilteredMethods($visibility = null, $definedBy = null) + { + $methods = []; + foreach($this->methods as $name => $method) { + if ($visibility !== null && $method->visibility != $visibility) { + continue; + } + if ($definedBy !== null && $method->definedBy != $definedBy) { + continue; + } + $methods[$name] = $method; + } + return $methods; + } + + /** + * @return PropertyDoc[] + */ + public function getNativeProperties() + { + return $this->getFilteredProperties(null, $this->name); + } + + /** + * @return PropertyDoc[] + */ + public function getPublicProperties() + { + return $this->getFilteredProperties('public'); + } + + /** + * @return PropertyDoc[] + */ + public function getProtectedProperties() + { + return $this->getFilteredProperties('protected'); + } + + /** + * @param null $visibility + * @param null $definedBy + * @return PropertyDoc[] + */ + private function getFilteredProperties($visibility = null, $definedBy = null) + { + if ($this->properties === null) { + return []; + } + $properties = []; + foreach($this->properties as $name => $property) { + if ($visibility !== null && $property->visibility != $visibility) { + continue; + } + if ($definedBy !== null && $property->definedBy != $definedBy) { + continue; + } + $properties[$name] = $property; + } + return $properties; + } + + /** + * @param \phpDocumentor\Reflection\InterfaceReflector $reflector + * @param array $config + */ + public function __construct($reflector = null, $config = []) + { + parent::__construct($reflector, $config); + + $this->namespace = StringHelper::basename($this->name); + + if ($reflector === null) { + return; + } + + foreach($this->tags as $i => $tag) { + if ($tag instanceof AuthorTag) { + $this->authors[$tag->getAuthorName()] = $tag->getAuthorEmail(); + unset($this->tags[$i]); + } + } + + foreach($reflector->getProperties() as $propertyReflector) { + if ($propertyReflector->getVisibility() != 'private') { + $property = new PropertyDoc($propertyReflector); + $property->definedBy = $this->name; + $this->properties[$property->name] = $property; + } + } + + foreach($reflector->getMethods() as $methodReflector) { + if ($methodReflector->getVisibility() != 'private') { + $method = new MethodDoc($methodReflector); + $method->definedBy = $this->name; + $this->methods[$method->name] = $method; + } + } + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/templates/BaseRenderer.php b/extensions/yii/apidoc/templates/BaseRenderer.php new file mode 100644 index 0000000..97d58a5 --- /dev/null +++ b/extensions/yii/apidoc/templates/BaseRenderer.php @@ -0,0 +1,59 @@ + + * @since 2.0 + */ +abstract class BaseRenderer extends Component +{ + /** + * @var Context the [[Context]] currently being rendered. + */ + public $context; + + + /** + * Renders a given [[Context]]. + * + * @param Context $context the api documentation context to render. + * @param Controller $controller the apidoc controller instance. Can be used to control output. + */ + public abstract function render($context, $controller); + + /** + * creates a link to a type (class, interface or trait) + * @param ClassDoc|InterfaceDoc|TraitDoc $types + * @param string $title + * @return string + */ + public abstract function typeLink($types, $title = null); + + /** + * creates a link to a subject + * @param PropertyDoc|MethodDoc|ConstDoc|EventDoc $subject + * @param string $title + * @return string + */ + public abstract function subjectLink($subject, $title = null); +} \ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/README.md b/extensions/yii/apidoc/templates/html/README.md new file mode 100644 index 0000000..90e3b35 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/README.md @@ -0,0 +1,4 @@ +The html API doc template +------------------------- + +This templates provides view files and a Renderer class that can be reused in other html templates. \ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/Renderer.php b/extensions/yii/apidoc/templates/html/Renderer.php new file mode 100644 index 0000000..bc53d3a --- /dev/null +++ b/extensions/yii/apidoc/templates/html/Renderer.php @@ -0,0 +1,353 @@ + + * @since 2.0 + */ +abstract class Renderer extends BaseRenderer implements ViewContextInterface +{ + /** + * @var string directory to use for output of html files. Can be a path alias. + */ + public $targetDir; + /** + * @var string string to use as the title of the generated page. + */ + public $pageTitle = 'Yii Framework 2.0 API Documentation'; + /** + * @var string path or alias of the layout file to use. + */ + public $layout; + /** + * @var string path or alias of the view file to use for rendering types (classes, interfaces, traits). + */ + public $typeView = '@yii/apidoc/templates/html/views/type.php'; + /** + * @var string path or alias of the view file to use for rendering the index page. + */ + public $indexView = '@yii/apidoc/templates/html/views/index.php'; + /** + * @var View + */ + private $_view; + + + public function init() + { + Markdown::$renderer = $this; + } + + /** + * @return View the view instance + */ + public function getView() + { + if ($this->_view === null) { + $this->_view = new View(); + $assetPath = Yii::getAlias($this->targetDir) . '/assets'; + if (!is_dir($assetPath)) { + mkdir($assetPath); + } + $this->_view->assetManager = new AssetManager([ + 'basePath' => $assetPath, + 'baseUrl' => './assets', + ]); + } + return $this->_view; + } + + /** + * Renders a given [[Context]]. + * + * @param Context $context the api documentation context to render. + * @param Controller $controller the apidoc controller instance. Can be used to control output. + */ + public function render($context, $controller) + { + $this->context = $context; + $dir = Yii::getAlias($this->targetDir); + if (!is_dir($dir)) { + mkdir($dir); + } + + $types = array_merge($context->classes, $context->interfaces, $context->traits); + $typeCount = count($types) + 1; + Console::startProgress(0, $typeCount, 'Rendering files: ', false); + $done = 0; + foreach($types as $type) { + $fileContent = $this->renderWithLayout($this->typeView, [ + 'type' => $type, + 'docContext' => $context, + ]); + file_put_contents($dir . '/' . $this->generateFileName($type->name), $fileContent); + Console::updateProgress(++$done, $typeCount); + } + $indexFileContent = $this->renderWithLayout($this->indexView, [ + 'docContext' => $context, + 'types' => $types, + ]); + file_put_contents($dir . '/index.html', $indexFileContent); + Console::updateProgress(++$done, $typeCount); + Console::endProgress(true); + $controller->stdout('done.' . PHP_EOL, Console::FG_GREEN); + } + + protected function renderWithLayout($viewFile, $params) + { + $output = $this->getView()->render($viewFile, $params, $this); + if ($this->layout !== false) { + $params['content'] = $output; + return $this->getView()->renderFile($this->layout, $params, $this); + } else { + return $output; + } + } + + /** + * creates a link to a type (class, interface or trait) + * @param ClassDoc|InterfaceDoc|TraitDoc $types + * @param BaseDoc $context + * @return string + */ + public function typeLink($types, $context = null) + { + if (!is_array($types)) { + $types = [$types]; + } + $links = []; + foreach($types as $type) { + $postfix = ''; + if (!is_object($type)) { + if (substr($type, -2, 2) == '[]') { + $postfix = '[]'; + $type = substr($type, 0, -2); + } + + if (($t = $this->context->getType(ltrim($type, '\\'))) !== null) { + $type = $t; + } elseif ($type[0] !== '\\' && ($t = $this->context->getType($this->resolveNamespace($context) . '\\' . ltrim($type, '\\'))) !== null) { + $type = $t; + } else { + ltrim($type, '\\'); + } + } + if (!is_object($type)) { + $links[] = $type; + } else { + $links[] = Html::a( + $type->name, + null, + ['href' => $this->generateFileName($type->name)] + ) . $postfix; + } + } + return implode('|', $links); + } + + /** + * creates a link to a subject + * @param PropertyDoc|MethodDoc|ConstDoc|EventDoc $subject + * @param string $title + * @return string + */ + public function subjectLink($subject, $title = null) + { + if ($title === null) { + if ($subject instanceof MethodDoc) { + $title = $subject->name . '()'; + } else { + $title = $subject->name; + } + } + if (($type = $this->context->getType($subject->definedBy)) === null) { + return $subject->name; + } else { + $link = $this->generateFileName($type->name); + if ($subject instanceof MethodDoc) { + $link .= '#' . $subject->name . '()'; + } else { + $link .= '#' . $subject->name; + } + $link .= '-detail'; + return Html::a($title, null, ['href' => $link]); + } + } + + /** + * @param BaseDoc $context + */ + private function resolveNamespace($context) + { + // TODO use phpdoc Context for this + if ($context === null) { + return ''; + } + if ($context instanceof TypeDoc) { + return $context->namespace; + } + if ($context->hasProperty('definedBy')) { + $type = $this->context->getType($context); + if ($type !== null) { + return $type->namespace; + } + } + return ''; + } + + /** + * @param ClassDoc $class + * @return string + */ + public function renderInheritance($class) + { + $parents[] = $this->typeLink($class); + while ($class->parentClass !== null) { + if(isset($this->context->classes[$class->parentClass])) { + $class = $this->context->classes[$class->parentClass]; + $parents[] = $this->typeLink($class); + } else { + $parents[] = $class->parentClass; // TODO link to php.net + break; + } + } + return implode(" »\n",$parents); + } + + /** + * @param array $names + * @return string + */ + public function renderInterfaces($names) + { + $interfaces = []; + sort($names, SORT_STRING); + foreach($names as $interface) { + if(isset($this->context->interfaces[$interface])) { + $interfaces[] = $this->typeLink($this->context->interfaces[$interface]); + } else { + $interfaces[] = $interface; // TODO link to php.net + } + } + return implode(', ',$interfaces); + } + + /** + * @param array $names + * @return string + */ + public function renderTraits($names) + { + $traits = []; + sort($names, SORT_STRING); + foreach($names as $trait) { + if(isset($this->context->traits[$trait])) { + $traits[] = $this->typeLink($this->context->traits[$trait]); + } else { + $traits[] = $trait; // TODO link to php.net + } + } + return implode(', ',$traits); + } + + /** + * @param array $names + * @return string + */ + public function renderClasses($names) + { + $classes = []; + sort($names, SORT_STRING); + foreach($names as $class) { + if(isset($this->context->classes[$class])) { + $classes[] = $this->typeLink($this->context->classes[$class]); + } else { + $classes[] = $class; // TODO link to php.net + } + } + return implode(', ',$classes); + } + + /** + * @param PropertyDoc $property + * @return string + */ + public function renderPropertySignature($property) + { + if ($property->getter !== null || $property->setter !== null) { + $sig = []; + if ($property->getter !== null) { + $sig[] = $this->renderMethodSignature($property->getter); + } + if ($property->setter !== null) { + $sig[] = $this->renderMethodSignature($property->setter); + } + return implode('
', $sig); + } + return $this->typeLink($property->types) . ' ' . $property->name . ' = ' . ($property->defaultValue === null ? 'null' : $property->defaultValue); + } + + /** + * @param MethodDoc $method + * @return string + */ + public function renderMethodSignature($method) + { + $params = []; + foreach($method->params as $param) { + $params[] = (empty($param->typeHint) ? '' : $param->typeHint . ' ') + . ($param->isPassedByReference ? '&' : '') + . $param->name + . ($param->isOptional ? ' = ' . $param->defaultValue : ''); + } + + return ($method->isReturnByReference ? '&' : '') + . ($method->returnType === null ? 'void' : $this->typeLink($method->returnTypes)) + . ' ' . $this->subjectLink($method, $method->name) . '( ' + . implode(', ', $params) + . ' )'; + } + + protected function generateFileName($typeName) + { + return strtolower(str_replace('\\', '_', $typeName)) . '.html'; + } + + /** + * Finds the view file corresponding to the specified relative view name. + * @param string $view a relative view name. The name does NOT start with a slash. + * @return string the view file path. Note that the file may not exist. + */ + public function findViewFile($view) + { + return Yii::getAlias('@yii/apidoc/templates/html/views/' . $view); + } +} \ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/views/constSummary.php b/extensions/yii/apidoc/templates/html/views/constSummary.php new file mode 100644 index 0000000..a872aa5 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/constSummary.php @@ -0,0 +1,36 @@ +constants)) { + return; +} ?> +
+

Constants

+ +

Hide inherited constants

+ + ++ + + + + + + +constants as $constant): ?> + definedBy != $type->name ? ' class="inherited"' : '' ?> id="name ?>"> + + + + + + +
ConstantValueDescriptionDefined By
name ?>value ?>shortDescription . "\n" . $constant->description, $type) ?>context->typeLink($constant->definedBy) ?>
+
\ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/views/eventDetails.php b/extensions/yii/apidoc/templates/html/views/eventDetails.php new file mode 100644 index 0000000..a5b3091 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/eventDetails.php @@ -0,0 +1,35 @@ +getNativeEvents(); +if (empty($events)) { + return; +} ?> +

Event Details

+ +
+ name; ?> + + event + since)): ?> + (available since version since ?>) + + +
+ + + trigger->signature; ?> + */ ?> + +

description, $type); ?>

+ + render('seeAlso', ['object' => $event]); ?> + + diff --git a/extensions/yii/apidoc/templates/html/views/eventSummary.php b/extensions/yii/apidoc/templates/html/views/eventSummary.php new file mode 100644 index 0000000..0c179b6 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/eventSummary.php @@ -0,0 +1,41 @@ +events)) { + return; +} ?> +
+

Events

+ +

Hide inherited events

+ + ++ + + + + + + +events as $event): ?> +definedBy != $type->name ? ' class="inherited"' : '' ?> id="name ?>"> + + + + + + +
EventTypeDescriptionDefined By
context->subjectLink($event) ?>context->typeLink($event->types) ?> + shortDescription, $type) ?> + since)): ?> + (available since version since; ?>) + + context->typeLink($event->definedBy) ?>
+
\ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/views/methodDetails.php b/extensions/yii/apidoc/templates/html/views/methodDetails.php new file mode 100644 index 0000000..5501133 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/methodDetails.php @@ -0,0 +1,67 @@ +getNativeMethods(); +if (empty($methods)) { + return; +} ?> +

Method Details

+ + + +
+ name ?>() + + method + since)): ?> + (available since version since; ?>) + + +
+ + + + params) || !empty($method->return) || !empty($method->exceptions)): ?> + params as $param): ?> + + + + + + + return)): ?> + + + + + + + exceptions as $exception => $description): ?> + + + + + + + +
+
+ context->renderMethodSignature($method) ?> +
+
name ?>context->typeLink($param->types) ?>description, $type) ?>
context->typeLink($method->returnTypes); ?>return, $type); ?>
context->typeLink($exception) ?>
+ +renderPartial('sourceCode',array('object'=>$method)); ?> + +

shortDescription, $type) ?>

+

description, $type) ?>

+ + render('seeAlso', ['object' => $method]); ?> + + \ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/views/methodSummary.php b/extensions/yii/apidoc/templates/html/views/methodSummary.php new file mode 100644 index 0000000..c3ca6d5 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/methodSummary.php @@ -0,0 +1,41 @@ +getProtectedMethods()) == 0 || !$protected && count($type->getPublicMethods()) == 0) { + return; +} ?> + +
+

+ +

Hide inherited methods

+ + ++ + + + + + + +methods as $method): ?> + visibility == 'protected' || !$protected && $method->visibility != 'protected'): ?> + definedBy != $type->name ? ' class="inherited"' : '' ?> id="name ?>()"> + + + + + + +
MethodDescriptionDefined By
context->subjectLink($method, $method->name.'()') ?>shortDescription, $type) ?>context->typeLink($method->definedBy, $type) ?>
+
\ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/views/propertyDetails.php b/extensions/yii/apidoc/templates/html/views/propertyDetails.php new file mode 100644 index 0000000..628f9bd --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/propertyDetails.php @@ -0,0 +1,39 @@ +getNativeProperties(); +if (empty($properties)) { + return; +} ?> +

Property Details

+ + + +
+ name; ?> + + property + getIsReadOnly()) echo ' read-only '; ?> + getIsWriteOnly()) echo ' write-only '; ?> + since)): ?> + (available since version since; ?>) + + +
+ +
+ context->renderPropertySignature($property); ?> +
+ +

description, $type) ?>

+ + render('seeAlso', ['object' => $property]); ?> + + diff --git a/extensions/yii/apidoc/templates/html/views/propertySummary.php b/extensions/yii/apidoc/templates/html/views/propertySummary.php new file mode 100644 index 0000000..3730431 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/propertySummary.php @@ -0,0 +1,42 @@ +getProtectedProperties()) == 0 || !$protected && count($type->getPublicProperties()) == 0) { + return; +} ?> + +
+

+ +

Hide inherited properties

+ + ++ + + + + + + + +properties as $property): ?> + visibility == 'protected' || !$protected && $property->visibility != 'protected'): ?> + definedBy != $type->name ? ' class="inherited"' : '' ?> id="name ?>"> + + + + + + + +
PropertyTypeDescriptionDefined By
context->subjectLink($property) ?>context->typeLink($property->types) ?>shortDescription, $type) ?>context->typeLink($property->definedBy) ?>
+
\ No newline at end of file diff --git a/extensions/yii/apidoc/templates/html/views/seeAlso.php b/extensions/yii/apidoc/templates/html/views/seeAlso.php new file mode 100644 index 0000000..9990714 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/seeAlso.php @@ -0,0 +1,31 @@ +tags as $tag) { + /** @var $tag phpDocumentor\Reflection\DocBlock\Tag\SeeTag */ + if (get_class($tag) == 'phpDocumentor\Reflection\DocBlock\Tag\SeeTag') { + $ref = $tag->getReference(); + if (strpos($ref, '://') === false) { + $see[] = '[[' . $ref . ']]'; + } else { + $see[] = $ref; + } + } +} +if (empty($see)) { + return; +} +?> +
+

See Also

+ +
diff --git a/extensions/yii/apidoc/templates/html/views/type.php b/extensions/yii/apidoc/templates/html/views/type.php new file mode 100644 index 0000000..64de2d1 --- /dev/null +++ b/extensions/yii/apidoc/templates/html/views/type.php @@ -0,0 +1,102 @@ +context; +?> +

isFinal) { + echo 'Final '; + } + if ($type->isAbstract) { + echo 'Abstract '; + } + echo 'Class '; + } + echo $type->name; +?>

+ + + + + + + + + + + interfaces)): ?> + + + traits)): ?> + + + subclasses)): ?> + + + implementedBy)): ?> + + + usedBy)): ?> + + + since)): ?> + + + + + + +
InheritancerenderInheritance($type) ?>
ImplementsrenderInterfaces($type->interfaces) ?>
Uses TraitsrenderTraits($type->traits) ?>
SubclassesrenderClasses($type->subclasses) ?>
Implemented byrenderClasses($type->implementedBy) ?>
Implemented byrenderClasses($type->usedBy) ?>
Available since versionsince ?>
Source CoderenderSourceLink($type->sourcePath) ?>
+ +
+ shortDescription, $type) ?> +

description, $type) ?>

+
+ + +render('@yii/apidoc/templates/html/views/propertySummary', ['type' => $type,'protected' => false]) ?> +render('@yii/apidoc/templates/html/views/propertySummary', ['type' => $type,'protected' => true]) ?> + + +render('@yii/apidoc/templates/html/views/methodSummary', ['type' => $type, 'protected' => false]) ?> +render('@yii/apidoc/templates/html/views/methodSummary', ['type' => $type, 'protected' => true]) ?> + + +render('@yii/apidoc/templates/html/views/eventSummary', ['type' => $type]) ?> + + +render('@yii/apidoc/templates/html/views/constSummary', ['type' => $type]) ?> + +render('@yii/apidoc/templates/html/views/propertyDetails', ['type' => $type]) ?> +render('@yii/apidoc/templates/html/views/methodDetails', ['type' => $type]) ?> + + render('@yii/apidoc/templates/html/views/eventDetails', ['type' => $type]) ?> + diff --git a/extensions/yii/apidoc/templates/offline/Renderer.php b/extensions/yii/apidoc/templates/offline/Renderer.php new file mode 100644 index 0000000..a618fc8 --- /dev/null +++ b/extensions/yii/apidoc/templates/offline/Renderer.php @@ -0,0 +1,26 @@ + + * @since 2.0 + */ +class Renderer extends \yii\apidoc\templates\html\Renderer +{ + public $layout = '@yii/apidoc/templates/offline/views/offline.php'; + public $indexView = '@yii/apidoc/templates/offline/views/index.php'; + + public $pageTitle = 'Yii Framework 2.0 API Documentation'; +} \ No newline at end of file diff --git a/extensions/yii/apidoc/templates/offline/assets/AssetBundle.php b/extensions/yii/apidoc/templates/offline/assets/AssetBundle.php new file mode 100644 index 0000000..e8c7024 --- /dev/null +++ b/extensions/yii/apidoc/templates/offline/assets/AssetBundle.php @@ -0,0 +1,31 @@ + + * @since 2.0 + */ +class AssetBundle extends \yii\web\AssetBundle +{ + public $sourcePath = '@yii/apidoc/templates/offline/assets/css'; + public $css = [ + 'api.css', + 'style.css', + ]; + public $depends = [ + 'yii\web\JqueryAsset', + ]; + public $jsOptions = [ + 'position' => View::POS_HEAD, + ]; +} diff --git a/extensions/yii/apidoc/templates/offline/assets/css/api.css b/extensions/yii/apidoc/templates/offline/assets/css/api.css new file mode 100644 index 0000000..8ad7e07 --- /dev/null +++ b/extensions/yii/apidoc/templates/offline/assets/css/api.css @@ -0,0 +1,111 @@ +pre { + color: #000000; + background-color: #FFF5E6; + font-family: "courier new", "times new roman", monospace; + line-height: 1.3em; + /* Put a nice border around it. */ + padding: 1px; + width: 90%; + /* Don't wrap its contents, and show scrollbars. */ + /* white-space: nowrap;*/ + overflow: auto; + /* Stop after about 24 lines, and just show a scrollbar. */ + /* max-height: 24em; */ + margin: 5px; + padding-left: 20px; + border: 1px solid #FFE6BF; + border-left: 6px solid #FFE6BF; +} + +code { + color: #000000; + background-color: #FFF5E6; + padding: 1px; +} + +div.code { + display: none; + color: #000000; + background-color: #FFF5E6; + font-family: "courier new", "times new roman", monospace; + line-height: 1.3em; + /* Put a nice border around it. */ + padding: 1px; + width: 90%; + /* Don't wrap its contents, and show scrollbars. */ + /* white-space: nowrap;*/ + overflow: auto; + /* Stop after about 24 lines, and just show a scrollbar. */ + /* max-height: 24em; */ + margin: 5px; + padding-left: 20px; + border-left: 6px solid #FFE6BF; +} + +table.summaryTable { + background: #E6ECFF; + border-collapse: collapse; + width: 100%; +} + +table.summaryTable th, table.summaryTable td { + border: 1px #BFCFFF solid; + padding: 0.2em; +} + +table.summaryTable th { + background: #CCD9FF; + text-align: left; +} + +#nav { + padding: 3px; + margin: 0 0 10px 0; + border-top: 1px #BFCFFF solid; +} + +#classDescription { + padding: 5px; + margin: 10px 0 20px 0; + border-bottom: 1px solid #BFCFFF; +} + +.detailHeader { + font-weight: bold; + font-size: 12pt; + margin: 30px 0 5px 0; + border-bottom: 1px solid #BFCFFF; +} + +.detailHeaderTag { + font-weight: normal; + font-size: 10pt; +} + +.signature, .signature2 { + padding: 3px; + color: #000000; + font-family: "courier new", "times new roman", monospace; + line-height: 1.3em; +} + +.signature { + margin: 10px 0 10px 0; + background: #E6ECFF; + border: 1px #BFCFFF solid; +} + +.paramNameCol { + width: 12%; + font-weight: bold; +} + +.paramTypeCol { + width: 12%; +} + +.sourceCode { + margin: 5px 0; + padding:5px; + background:#FFF5E6; +} \ No newline at end of file diff --git a/extensions/yii/apidoc/templates/offline/assets/css/style.css b/extensions/yii/apidoc/templates/offline/assets/css/style.css new file mode 100644 index 0000000..009c218 --- /dev/null +++ b/extensions/yii/apidoc/templates/offline/assets/css/style.css @@ -0,0 +1,32 @@ +body +{ +} + +body, div, span, p, input +{ + font-family: Verdana, Arial, sans-serif; + font-size: 10pt; + color: #333333; +} + +#apiPage { +} + +#apiHeader { + padding: 3px; + color: white; + background: #6078BF; + margin-bottom: 5px; + font-weight: bold; +} + +#apiHeader a { + color: white; +} + +#apiFooter { + margin-top: 5px; + padding: 3px; + border-top: 1px solid #BFCFFF; + text-align: center; +} diff --git a/extensions/yii/apidoc/templates/offline/views/index.php b/extensions/yii/apidoc/templates/offline/views/index.php new file mode 100644 index 0000000..310e9af --- /dev/null +++ b/extensions/yii/apidoc/templates/offline/views/index.php @@ -0,0 +1,30 @@ +

Class Reference

+ + ++ + + + + + + +$class): ?> + + + + + +
ClassDescription
context->typeLink($class, $class->name); ?>shortDescription; ?>
diff --git a/extensions/yii/apidoc/templates/offline/views/offline.php b/extensions/yii/apidoc/templates/offline/views/offline.php new file mode 100644 index 0000000..b5565c5 --- /dev/null +++ b/extensions/yii/apidoc/templates/offline/views/offline.php @@ -0,0 +1,71 @@ +beginPage(); +?> + + + + + +head(); ?> +<?php echo $this->context->pageTitle; ?> + + + +beginBody(); ?> +
+ +
+Yii Framework v Class Reference +
+ +
+ +
+ +
+© 2008-2013 by Yii Software LLC
+All Rights Reserved.
+
+ + + +
+endBody(); ?> + + +endPage(); ?> \ No newline at end of file diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index 7e01bd4..8f1af5e 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -34,7 +34,7 @@ class Exception extends \Exception implements Arrayable /** * Returns the array representation of the exception and all previous exceptions recursively. - * @param \Exception exception object + * @param \Exception $exception object * @return array the array representation of the exception. */ protected function toArrayRecursive($exception) diff --git a/framework/yii/db/BaseActiveRecord.php b/framework/yii/db/BaseActiveRecord.php index cefcfe6..dcd613e 100644 --- a/framework/yii/db/BaseActiveRecord.php +++ b/framework/yii/db/BaseActiveRecord.php @@ -138,7 +138,6 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * @param array $attributes attribute values (name-value pairs) to be saved into the table * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name => value) to be bound to the query. * @return integer the number of rows updated */ public static function updateAll($attributes, $condition = '') @@ -379,7 +378,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * Populates the named relation with the related records. * Note that this method does not check if the relation exists or not. * @param string $name the relation name (case-sensitive) - * @param ActiveRecord|array|null the related records to be populated into the relation. + * @param ActiveRecord|array|null $records the related records to be populated into the relation. */ public function populateRelation($name, $records) { diff --git a/framework/yii/helpers/BaseHtml.php b/framework/yii/helpers/BaseHtml.php index b3a88c1..b25ffa4 100644 --- a/framework/yii/helpers/BaseHtml.php +++ b/framework/yii/helpers/BaseHtml.php @@ -1471,7 +1471,7 @@ class BaseHtml return Yii::$app->getRequest()->getUrl(); } else { $url = Yii::getAlias($url); - if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { + if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://') || !strncmp($url, './', 2))) { return $url; } else { return Yii::$app->getRequest()->getBaseUrl() . '/' . $url;