Refactoring

This commit is contained in:
gamonoid
2017-09-03 20:39:22 +02:00
parent af40881847
commit a7274d3cfd
5075 changed files with 238202 additions and 16291 deletions

View File

@@ -0,0 +1,9 @@
language: php
php:
- 5.5
script:
- composer self-update
- composer install --no-interaction --prefer-source
- phpunit

View File

@@ -0,0 +1,96 @@
# PHP-Markdown-Documentation-Generator
Documentation is just as important as the code it's refering to. With this command line tool
you will be able to write your documentation once, and only once!
This project will write a single-page markdown-formatted API document based on the DocBlock comments in your source code. The [phpdoc](http://www.phpdoc.org/) standard is used.
![Travis](https://travis-ci.org/victorjonsson/PHP-Markdown-Documentation-Generator.svg)
### Example
Let's say you have your PHP classes in a directory named "src". Each class has its own file that is named after the class.
```
- src/
- MyObject.php
- OtherObject.php
```
Write your code documentation following the standard set by [phpdoc](http://www.phpdoc.org/).
```php
namespace Acme;
/**
* This is a description of this class
*/
class MyObject {
/**
* This is a function description
* @param string $str
* @param array $arr
* @return Acme\OtherObject
*/
function someFunc($str, $arr=array()) {
}
}
```
Then, running `$ phpdocs-md generate src > api.md` will write your API documentation to the file api.md.
[Here you can see a rendered example](https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator/blob/master/api.md)
Only public and protected functions will be a part of the documentation, but you can also add `@ignore` to any function or class to exclude it from the docs. Phpdocs-md will try to guess the return type of functions that don't explicitly declare one. The program uses reflection to get as much information as possible out of the code so that functions that are missing DocBlock comments will still be included in the generated documentation.
### Requirements
- PHP version >= 5.3.2
- Reflection must be enabled in php.ini
- Each class must be defined in its own file with the file name being the same as the class name
- The project should use [Composer](https://getcomposer.org/)
### Installation / Usage
This command line tool can be installed using [composer](https://getcomposer.org/).
From the local working directory of the project that you would like to document, run:
```
$ composer require --dev victorjonsson/markdowndocs
```
This will add victorjonsson/markdowndocs to the `require-dev` section of your project's composer.json file. The phpdocs-md executable will automatically be copied to your project's `vendor/bin` directory.
##### Generating docs
The `generate` command generates your project's API documentation file. The command line tool needs to know whether you want to generate docs for a certain class, or if it should process every class in a specified directory search path.
```
# Generate docs for a certain class
$ ./vendor/bin/phpdocs-md generate Acme\\NS\\MyClass
# Generate docs for several classes (comma separated)
$ ./vendor/bin/phpdocs-md generate Acme\\NS\\MyClass,Acme\\OtherNS\\OtherClass
# Generate docs for all classes in a source directory
$ ./vendor/bin/phpdocs-md generate includes/src
# Generate docs for all classes in a source directory and send output to the file api.md
$ ./vendor/bin/phpdocs-md generate includes/src > api.md
```
* Note that any class to be documented must be loadable using the autoloader provided by composer. *
##### Bootstrapping
If you are not using the composer autoloader, or if there is something else that needs to be done before your classes can be instantiated, then you may request phpdocs-md to load a php bootstrap file prior to generating the docs
`$ ./vendor/bin/phpdocs-md generate --bootstrap=includes/init.php includes/src > api.md`
##### Excluding directories
You can tell the command line tool to ignore certain directories in your class path by using the `--ignore` option.
`$ ./phpdocs-md generate --ignore=test,examples includes/src > api.md`

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env php
<?php
$autoLoadPaths = array(__DIR__.'/vendor/autoload.php', __DIR__.'/../vendor/autoload.php', __DIR__.'/../../../autoload.php');
foreach($autoLoadPaths as $autoloader) {
if( file_exists($autoloader) ) {
require $autoloader;
break;
}
}
use PHPDocsMD\Console\CLI;
$cli = new CLI();
$code = $cli->run();
exit($code);

View File

@@ -0,0 +1,23 @@
{
"name": "victorjonsson/markdowndocs",
"description": "Command line tool for generating markdown-formatted class documentation",
"minimum-stability": "dev",
"homepage" : "https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator",
"license" : "MIT",
"version" : "1.3.7",
"authors": [{
"name": "Victor Jonsson",
"email": "kontakt@victorjonsson.se"
}],
"require" : {
"php" : ">=5.5.0",
"symfony/console": ">=2.6"
},
"require-dev" : {
"phpunit/phpunit": "3.7.23"
},
"bin":["bin/phpdoc-md"],
"autoload": {
"psr-0": {"PHPDocsMD": "src/"}
}
}

View File

@@ -0,0 +1,251 @@
## Table of contents
- [\PHPDocsMD\ClassEntity](#class-phpdocsmdclassentity)
- [\PHPDocsMD\ClassEntityFactory](#class-phpdocsmdclassentityfactory)
- [\PHPDocsMD\CodeEntity](#class-phpdocsmdcodeentity)
- [\PHPDocsMD\DocInfo](#class-phpdocsmddocinfo)
- [\PHPDocsMD\DocInfoExtractor](#class-phpdocsmddocinfoextractor)
- [\PHPDocsMD\FunctionEntity](#class-phpdocsmdfunctionentity)
- [\PHPDocsMD\FunctionFinder](#class-phpdocsmdfunctionfinder)
- [\PHPDocsMD\MDTableGenerator](#class-phpdocsmdmdtablegenerator)
- [\PHPDocsMD\ParamEntity](#class-phpdocsmdparamentity)
- [\PHPDocsMD\Reflector](#class-phpdocsmdreflector)
- [\PHPDocsMD\ReflectorInterface (interface)](#interface-phpdocsmdreflectorinterface)
- [\PHPDocsMD\UseInspector](#class-phpdocsmduseinspector)
- [\PHPDocsMD\Utils](#class-phpdocsmdutils)
- [\PHPDocsMD\Console\CLI](#class-phpdocsmdconsolecli)
- [\PHPDocsMD\Console\PHPDocsMDCommand](#class-phpdocsmdconsolephpdocsmdcommand)
<hr />
### Class: \PHPDocsMD\ClassEntity
> Object describing a class or an interface
| Visibility | Function |
|:-----------|:---------|
| public | <strong>generateAnchor()</strong> : <em>string</em><br /><em>Generates an anchor link out of the generated title (see generateTitle)</em> |
| public | <strong>generateTitle(</strong><em>string</em> <strong>$format=`'%label%: %name% %extra%'`</strong>)</strong> : <em>string</em><br /><em>Generate a title describing the class this object is referring to</em> |
| public | <strong>getExtends()</strong> : <em>string</em> |
| public | <strong>getFunctions()</strong> : <em>[\PHPDocsMD\FunctionEntity](#class-phpdocsmdfunctionentity)[]</em> |
| public | <strong>getInterfaces()</strong> : <em>array</em> |
| public | <strong>hasIgnoreTag(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>bool</em> |
| public | <strong>isAbstract(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>bool</em> |
| public | <strong>isInterface(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>bool</em> |
| public | <strong>isNative(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>bool</em> |
| public | <strong>isSame(</strong><em>string/object</em> <strong>$class</strong>)</strong> : <em>bool</em><br /><em>Check whether this object is referring to given class name or object instance</em> |
| public | <strong>setExtends(</strong><em>string</em> <strong>$extends</strong>)</strong> : <em>void</em> |
| public | <strong>setFunctions(</strong><em>[\PHPDocsMD\FunctionEntity](#class-phpdocsmdfunctionentity)[]</em> <strong>$functions</strong>)</strong> : <em>void</em> |
| public | <strong>setInterfaces(</strong><em>array</em> <strong>$implements</strong>)</strong> : <em>void</em> |
| public | <strong>setName(</strong><em>string</em> <strong>$name</strong>)</strong> : <em>void</em> |
*This class extends [\PHPDocsMD\CodeEntity](#class-phpdocsmdcodeentity)*
<hr />
### Class: \PHPDocsMD\ClassEntityFactory
> Class capable of creating ClassEntity objects
| Visibility | Function |
|:-----------|:---------|
| public | <strong>__construct(</strong><em>[\PHPDocsMD\DocInfoExtractor](#class-phpdocsmddocinfoextractor)</em> <strong>$docInfoExtractor</strong>)</strong> : <em>void</em> |
| public | <strong>create(</strong><em>[\ReflectionClass](http://php.net/manual/en/class.reflectionclass.php)</em> <strong>$reflection</strong>)</strong> : <em>mixed</em> |
<hr />
### Class: \PHPDocsMD\CodeEntity
> Object describing a piece of code
| Visibility | Function |
|:-----------|:---------|
| public | <strong>getDeprecationMessage()</strong> : <em>string</em> |
| public | <strong>getDescription()</strong> : <em>string</em> |
| public | <strong>getExample()</strong> : <em>string</em> |
| public | <strong>getName()</strong> : <em>string</em> |
| public | <strong>isDeprecated(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>void/bool</em> |
| public | <strong>setDeprecationMessage(</strong><em>string</em> <strong>$deprecationMessage</strong>)</strong> : <em>void</em> |
| public | <strong>setDescription(</strong><em>string</em> <strong>$description</strong>)</strong> : <em>void</em> |
| public | <strong>setExample(</strong><em>string</em> <strong>$example</strong>)</strong> : <em>void</em> |
| public | <strong>setName(</strong><em>string</em> <strong>$name</strong>)</strong> : <em>void</em> |
<hr />
### Class: \PHPDocsMD\DocInfo
> Class containing information about a function/class that's being made available via a comment block
| Visibility | Function |
|:-----------|:---------|
| public | <strong>__construct(</strong><em>array</em> <strong>$data</strong>)</strong> : <em>void</em> |
| public | <strong>getDeprecationMessage()</strong> : <em>string</em> |
| public | <strong>getDescription()</strong> : <em>string</em> |
| public | <strong>getExample()</strong> : <em>string</em> |
| public | <strong>getParameterInfo(</strong><em>string</em> <strong>$name</strong>)</strong> : <em>array</em> |
| public | <strong>getParameters()</strong> : <em>array</em> |
| public | <strong>getReturnType()</strong> : <em>string</em> |
| public | <strong>shouldBeIgnored()</strong> : <em>bool</em> |
| public | <strong>shouldInheritDoc()</strong> : <em>bool</em> |
<hr />
### Class: \PHPDocsMD\DocInfoExtractor
> Class that can extract information from a function/class comment
| Visibility | Function |
|:-----------|:---------|
| public | <strong>applyInfoToEntity(</strong><em>[\ReflectionClass](http://php.net/manual/en/class.reflectionclass.php)/[\ReflectionMethod](http://php.net/manual/en/class.reflectionmethod.php)</em> <strong>$reflection</strong>, <em>[\PHPDocsMD\DocInfo](#class-phpdocsmddocinfo)</em> <strong>$docInfo</strong>, <em>[\PHPDocsMD\CodeEntity](#class-phpdocsmdcodeentity)</em> <strong>$code</strong>)</strong> : <em>void</em> |
| public | <strong>extractInfo(</strong><em>[\ReflectionClass](http://php.net/manual/en/class.reflectionclass.php)/[\ReflectionMethod](http://php.net/manual/en/class.reflectionmethod.php)</em> <strong>$reflection</strong>)</strong> : <em>[\PHPDocsMD\DocInfo](#class-phpdocsmddocinfo)</em> |
<hr />
### Class: \PHPDocsMD\FunctionEntity
> Object describing a function
| Visibility | Function |
|:-----------|:---------|
| public | <strong>getClass()</strong> : <em>string</em> |
| public | <strong>getParams()</strong> : <em>[\PHPDocsMD\ParamEntity](#class-phpdocsmdparamentity)[]</em> |
| public | <strong>getReturnType()</strong> : <em>string</em> |
| public | <strong>getVisibility()</strong> : <em>string</em> |
| public | <strong>hasParams()</strong> : <em>bool</em> |
| public | <strong>isAbstract(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>bool</em> |
| public | <strong>isReturningNativeClass(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>bool</em> |
| public | <strong>isStatic(</strong><em>bool</em> <strong>$toggle=null</strong>)</strong> : <em>bool</em> |
| public | <strong>setClass(</strong><em>string</em> <strong>$class</strong>)</strong> : <em>void</em> |
| public | <strong>setParams(</strong><em>[\PHPDocsMD\ParamEntity](#class-phpdocsmdparamentity)[]</em> <strong>$params</strong>)</strong> : <em>void</em> |
| public | <strong>setReturnType(</strong><em>string</em> <strong>$returnType</strong>)</strong> : <em>void</em> |
| public | <strong>setVisibility(</strong><em>string</em> <strong>$visibility</strong>)</strong> : <em>void</em> |
*This class extends [\PHPDocsMD\CodeEntity](#class-phpdocsmdcodeentity)*
<hr />
### Class: \PHPDocsMD\FunctionFinder
> Find a specific function in a class or an array of classes
| Visibility | Function |
|:-----------|:---------|
| public | <strong>find(</strong><em>string</em> <strong>$methodName</strong>, <em>string</em> <strong>$className</strong>)</strong> : <em>bool/[\PHPDocsMD\FunctionEntity](#class-phpdocsmdfunctionentity)</em> |
| public | <strong>findInClasses(</strong><em>string</em> <strong>$methodName</strong>, <em>array</em> <strong>$classes</strong>)</strong> : <em>bool/[\PHPDocsMD\FunctionEntity](#class-phpdocsmdfunctionentity)</em> |
<hr />
### Class: \PHPDocsMD\MDTableGenerator
> Class that can create a markdown-formatted table describing class functions referred to via FunctionEntity objects
###### Example
```php
<?php
$generator = new PHPDocsMD\MDTableGenerator();
$generator->openTable();
foreach($classEntity->getFunctions() as $func) {
$generator->addFunc( $func );
}
echo $generator->getTable();
```
| Visibility | Function |
|:-----------|:---------|
| public | <strong>addFunc(</strong><em>[\PHPDocsMD\FunctionEntity](#class-phpdocsmdfunctionentity)</em> <strong>$func</strong>)</strong> : <em>string</em><br /><em>Generates a markdown formatted table row with information about given function. Then adds the row to the table and returns the markdown formatted string.</em> |
| public | <strong>appendExamplesToEndOfTable(</strong><em>bool</em> <strong>$toggle</strong>)</strong> : <em>void</em><br /><em>All example comments found while generating the table will be appended to the end of the table. Set $toggle to false to prevent this behaviour</em> |
| public | <strong>doDeclareAbstraction(</strong><em>bool</em> <strong>$toggle</strong>)</strong> : <em>void</em><br /><em>Toggle whether or not methods being abstract (or part of an interface) should be declared as abstract in the table</em> |
| public static | <strong>formatExampleComment(</strong><em>string</em> <strong>$example</strong>)</strong> : <em>string</em><br /><em>Create a markdown-formatted code view out of an example comment</em> |
| public | <strong>getTable()</strong> : <em>string</em> |
| public | <strong>openTable()</strong> : <em>void</em><br /><em>Begin generating a new markdown-formatted table</em> |
<hr />
### Class: \PHPDocsMD\ParamEntity
> Object describing a function parameter
| Visibility | Function |
|:-----------|:---------|
| public | <strong>getDefault()</strong> : <em>boolean</em> |
| public | <strong>getNativeClassType()</strong> : <em>string/null</em> |
| public | <strong>getType()</strong> : <em>string</em> |
| public | <strong>setDefault(</strong><em>boolean</em> <strong>$default</strong>)</strong> : <em>void</em> |
| public | <strong>setType(</strong><em>string</em> <strong>$type</strong>)</strong> : <em>void</em> |
*This class extends [\PHPDocsMD\CodeEntity](#class-phpdocsmdcodeentity)*
<hr />
### Class: \PHPDocsMD\Reflector
> Class that can compute ClassEntity objects out of real classes
| Visibility | Function |
|:-----------|:---------|
| public | <strong>__construct(</strong><em>string</em> <strong>$className</strong>, <em>[\PHPDocsMD\FunctionFinder](#class-phpdocsmdfunctionfinder)</em> <strong>$functionFinder=null</strong>, <em>[\PHPDocsMD\DocInfoExtractor](#class-phpdocsmddocinfoextractor)</em> <strong>$docInfoExtractor=null</strong>, <em>[\PHPDocsMD\UseInspector](#class-phpdocsmduseinspector)</em> <strong>$useInspector=null</strong>, <em>[\PHPDocsMD\ClassEntityFactory](#class-phpdocsmdclassentityfactory)</em> <strong>$classEntityFactory=null</strong>)</strong> : <em>void</em> |
| public | <strong>getClassEntity()</strong> : <em>[\PHPDocsMD\ClassEntity](#class-phpdocsmdclassentity)</em> |
| public static | <strong>getParamType(</strong><em>[\ReflectionParameter](http://php.net/manual/en/class.reflectionparameter.php)</em> <strong>$refParam</strong>)</strong> : <em>string</em><br /><em>Tries to find out if the type of the given parameter. Will return empty string if not possible.</em> |
| protected | <strong>createFunctionEntity(</strong><em>[\ReflectionMethod](http://php.net/manual/en/class.reflectionmethod.php)</em> <strong>$method</strong>, <em>[\PHPDocsMD\ClassEntity](#class-phpdocsmdclassentity)</em> <strong>$class</strong>, <em>array</em> <strong>$useStatements</strong>)</strong> : <em>bool/[\PHPDocsMD\FunctionEntity](#class-phpdocsmdfunctionentity)</em> |
| protected | <strong>shouldIgnoreFunction(</strong><em>[\PHPDocsMD\DocInfo](#class-phpdocsmddocinfo)</em> <strong>$info</strong>, <em>[\ReflectionMethod](http://php.net/manual/en/class.reflectionmethod.php)</em> <strong>$method</strong>, <em>[\PHPDocsMD\ClassEntity](#class-phpdocsmdclassentity)</em> <strong>$class</strong>)</strong> : <em>bool</em> |
###### Examples of Reflector::getParamType()
```php
<?php
$reflector = new \\ReflectionClass('MyClass');
foreach($reflector->getMethods() as $method ) {
foreach($method->getParameters() as $param) {
$name = $param->getName();
$type = Reflector::getParamType($param);
printf("%s = %s\n", $name, $type);
}
}
```
*This class implements [\PHPDocsMD\ReflectorInterface](#interface-phpdocsmdreflectorinterface)*
<hr />
### Interface: \PHPDocsMD\ReflectorInterface
> Interface for classes that can compute ClassEntity objects
| Visibility | Function |
|:-----------|:---------|
| public | <strong>getClassEntity()</strong> : <em>[\PHPDocsMD\ClassEntity](#class-phpdocsmdclassentity)</em> |
<hr />
### Class: \PHPDocsMD\UseInspector
> Class that can extract all use statements in a file
| Visibility | Function |
|:-----------|:---------|
| public | <strong>getUseStatements(</strong><em>[\ReflectionClass](http://php.net/manual/en/class.reflectionclass.php)</em> <strong>$reflectionClass</strong>)</strong> : <em>array</em> |
| public | <strong>getUseStatementsInFile(</strong><em>string</em> <strong>$filePath</strong>)</strong> : <em>array</em> |
| public | <strong>getUseStatementsInString(</strong><em>string</em> <strong>$content</strong>)</strong> : <em>string[]</em> |
<hr />
### Class: \PHPDocsMD\Utils
| Visibility | Function |
|:-----------|:---------|
| public static | <strong>getClassBaseName(</strong><em>string</em> <strong>$fullClassName</strong>)</strong> : <em>string</em> |
| public static | <strong>isClassReference(</strong><em>string</em> <strong>$typeDeclaration</strong>)</strong> : <em>bool</em> |
| public static | <strong>isNativeClassReference(</strong><em>mixed</em> <strong>$typeDeclaration</strong>)</strong> : <em>bool</em> |
| public static | <strong>sanitizeClassName(</strong><em>string</em> <strong>$name</strong>)</strong> : <em>string</em> |
| public static | <strong>sanitizeDeclaration(</strong><em>string</em> <strong>$typeDeclaration</strong>, <em>string</em> <strong>$currentNameSpace</strong>, <em>string</em> <strong>$delimiter=`'|'`</strong>)</strong> : <em>string</em> |
<hr />
### Class: \PHPDocsMD\Console\CLI
> Command line interface used to extract markdown-formatted documentation from classes
| Visibility | Function |
|:-----------|:---------|
| public | <strong>__construct()</strong> : <em>void</em> |
| public | <strong>run(</strong><em>\Symfony\Component\Console\Input\InputInterface</em> <strong>$input=null</strong>, <em>\Symfony\Component\Console\Output\OutputInterface</em> <strong>$output=null</strong>)</strong> : <em>int</em> |
*This class extends \Symfony\Component\Console\Application*
<hr />
### Class: \PHPDocsMD\Console\PHPDocsMDCommand
> Console command used to extract markdown-formatted documentation from classes
| Visibility | Function |
|:-----------|:---------|
| public | <strong>extractClassNameFromLine(</strong><em>string</em> <strong>$type</strong>, <em>string</em> <strong>$line</strong>)</strong> : <em>string</em> |
| protected | <strong>configure()</strong> : <em>void</em> |
| protected | <strong>execute(</strong><em>\Symfony\Component\Console\Input\InputInterface</em> <strong>$input</strong>, <em>\Symfony\Component\Console\Output\OutputInterface</em> <strong>$output</strong>)</strong> : <em>int/null</em> |
*This class extends \Symfony\Component\Console\Command\Command*

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env php
<?php
$autoLoadPaths = array(__DIR__.'/vendor/autoload.php', __DIR__.'/../vendor/autoload.php', getcwd().'/vendor/autoload.php');
foreach($autoLoadPaths as $autoloader) {
if( file_exists($autoloader) ) {
require $autoloader;
break;
}
}
use PHPDocsMD\Console\CLI;
$cli = new CLI();
$code = $cli->run();
exit($code);

View File

@@ -0,0 +1,7 @@
<phpunit bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Source">
<directory>test</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,202 @@
<?php
namespace PHPDocsMD;
/**
* Object describing a class or an interface
* @package PHPDocsMD
*/
class ClassEntity extends CodeEntity {
/**
* @var \PHPDocsMD\FunctionEntity[]
*/
private $functions = [];
/**
* @var bool
*/
private $isInterface = false;
/**
* @var bool
*/
private $abstract = false;
/**
* @var bool
*/
private $hasIgnoreTag = false;
/**
* @var string
*/
private $extends = '';
/**
* @var array
*/
private $interfaces = [];
/**
* @var bool
*/
private $isNative;
/**
* @param bool $toggle
* @return bool
*/
public function isAbstract($toggle=null)
{
if ( $toggle === null ) {
return $this->abstract;
} else {
return $this->abstract = (bool)$toggle;
}
}
/**
* @param bool $toggle
* @return bool
*/
public function hasIgnoreTag($toggle=null)
{
if( $toggle === null ) {
return $this->hasIgnoreTag;
} else {
return $this->hasIgnoreTag = (bool)$toggle;
}
}
/**
* @param bool $toggle
* @return bool
*/
public function isInterface($toggle=null)
{
if( $toggle === null ) {
return $this->isInterface;
} else {
return $this->isInterface = (bool)$toggle;
}
}
/**
* @param bool $toggle
* @return bool
*/
public function isNative($toggle=null)
{
if( $toggle === null ) {
return $this->isNative;
} else {
return $this->isNative = (bool)$toggle;
}
}
/**
* @param string $extends
*/
public function setExtends($extends)
{
$this->extends = Utils::sanitizeClassName($extends);
}
/**
* @return string
*/
public function getExtends()
{
return $this->extends;
}
/**
* @param \PHPDocsMD\FunctionEntity[] $functions
*/
public function setFunctions(array $functions)
{
$this->functions = $functions;
}
/**
* @param array $implements
*/
public function setInterfaces(array $implements)
{
$this->interfaces = [];
foreach($implements as $interface) {
$this->interfaces[] = Utils::sanitizeClassName($interface);
}
}
/**
* @return array
*/
public function getInterfaces()
{
return $this->interfaces;
}
/**
* @return \PHPDocsMD\FunctionEntity[]
*/
public function getFunctions()
{
return $this->functions;
}
/**
* @param string $name
*/
function setName($name)
{
parent::setName(Utils::sanitizeClassName($name));
}
/**
* Check whether this object is referring to given class name or object instance
* @param string|object $class
* @return bool
*/
function isSame($class)
{
$className = is_object($class) ? get_class($class) : $class;
return Utils::sanitizeClassName($className) == $this->getName();
}
/**
* Generate a title describing the class this object is referring to
* @param string $format
* @return string
*/
function generateTitle($format='%label%: %name% %extra%')
{
$translate = [
'%label%' => $this->isInterface() ? 'Interface' : 'Class',
'%name%' => substr_count($this->getName(), '\\') == 1 ? substr($this->getName(), 1) : $this->getName(),
'%extra%' => ''
];
if( strpos($format, '%label%') === false ) {
if( $this->isInterface() )
$translate['%extra%'] = '(interface)';
elseif( $this->isAbstract() )
$translate['%extra%'] = '(abstract)';
} else {
$translate['%extra%'] = $this->isAbstract() && !$this->isInterface() ? '(abstract)' : '';
}
return trim(strtr($format, $translate));
}
/**
* Generates an anchor link out of the generated title (see generateTitle)
* @return string
*/
function generateAnchor()
{
$title = $this->generateTitle();
return strtolower(str_replace([':', ' ', '\\', '(', ')'], ['', '-', '', '', ''], $title));
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace PHPDocsMD;
/**
* Class capable of creating ClassEntity objects
* @package PHPDocsMD
*/
class ClassEntityFactory
{
/**
* @var DocInfoExtractor
*/
private $docInfoExtractor;
/**
* @param DocInfoExtractor $docInfoExtractor
*/
public function __construct(DocInfoExtractor $docInfoExtractor)
{
$this->docInfoExtractor = $docInfoExtractor;
}
public function create(\ReflectionClass $reflection)
{
$class = new ClassEntity();
$docInfo = $this->docInfoExtractor->extractInfo($reflection);
$this->docInfoExtractor->applyInfoToEntity($reflection, $docInfo, $class);
$class->isInterface($reflection->isInterface());
$class->isAbstract($reflection->isAbstract());
$class->setInterfaces(array_keys($reflection->getInterfaces()));
$class->hasIgnoreTag($docInfo->shouldBeIgnored());
if ($reflection->getParentClass()) {
$class->setExtends($reflection->getParentClass()->getName());
}
return $class;
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace PHPDocsMD;
/**
* Object describing a piece of code
* @package PHPDocsMD
*/
class CodeEntity {
/**
* @var string
*/
private $name='';
/**
* @var string
*/
private $description = '';
/**
* @var bool
*/
private $isDeprecated = false;
/**
* @var string
*/
private $deprecationMessage = '';
/**
* @var string
*/
private $example = '';
/**
* @param bool $toggle
* @return void|bool
*/
public function isDeprecated($toggle=null)
{
if( $toggle === null ) {
return $this->isDeprecated;
} else {
return $this->isDeprecated = (bool)$toggle;
}
}
/**
* @param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $deprecationMessage
*/
public function setDeprecationMessage($deprecationMessage)
{
$this->deprecationMessage = $deprecationMessage;
}
/**
* @return string
*/
public function getDeprecationMessage()
{
return $this->deprecationMessage;
}
/**
* @param string $example
*/
public function setExample($example)
{
$this->example = $example;
}
/**
* @return string
*/
public function getExample()
{
return $this->example;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace PHPDocsMD\Console;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command line interface used to extract markdown-formatted documentation from classes
* @package PHPDocsMD\Console
*/
class CLI extends Application {
public function __construct()
{
$json = json_decode(file_get_contents(__DIR__.'/../../../composer.json'));
parent::__construct('PHP Markdown Documentation Generator', $json->version);
}
/**
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Input\OutputInterface $output
* @return int
*/
public function run(InputInterface $input=null, OutputInterface $output=null)
{
$this->add(new PHPDocsMDCommand());
return parent::run($input, $output);
}
}

View File

@@ -0,0 +1,288 @@
<?php
namespace PHPDocsMD\Console;
use PHPDocsMD\MDTableGenerator;
use PHPDocsMD\Reflector;
use PHPDocsMD\Utils;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Console command used to extract markdown-formatted documentation from classes
* @package PHPDocsMD\Console
*/
class PHPDocsMDCommand extends \Symfony\Component\Console\Command\Command {
const ARG_CLASS = 'class';
const OPT_BOOTSTRAP = 'bootstrap';
const OPT_IGNORE = 'ignore';
/**
* @var array
*/
private $memory = [];
/**
* @param $name
* @return \PHPDocsMD\ClassEntity
*/
private function getClassEntity($name) {
if( !isset($this->memory[$name]) ) {
$reflector = new Reflector($name);
$this->memory[$name] = $reflector->getClassEntity();
}
return $this->memory[$name];
}
protected function configure()
{
$this
->setName('generate')
->setDescription('Get docs for given class/source directory)')
->addArgument(
self::ARG_CLASS,
InputArgument::REQUIRED,
'Class or source directory'
)
->addOption(
self::OPT_BOOTSTRAP,
'b',
InputOption::VALUE_REQUIRED,
'File to be included before generating documentation'
)
->addOption(
self::OPT_IGNORE,
'i',
InputOption::VALUE_REQUIRED,
'Directories to ignore',
''
);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int|null
* @throws \InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$classes = $input->getArgument(self::ARG_CLASS);
$bootstrap = $input->getOption(self::OPT_BOOTSTRAP);
$ignore = explode(',', $input->getOption(self::OPT_IGNORE));
$requestingOneClass = false;
if( $bootstrap ) {
require_once strpos($bootstrap,'/') === 0 ? $bootstrap : getcwd().'/'.$bootstrap;
}
$classCollection = [];
if( strpos($classes, ',') !== false ) {
foreach(explode(',', $classes) as $class) {
if( class_exists($class) || interface_exists($class) )
$classCollection[0][] = $class;
}
}
elseif( class_exists($classes) || interface_exists($classes) ) {
$classCollection[] = array($classes);
$requestingOneClass = true;
} elseif( is_dir($classes) ) {
$classCollection = $this->findClassesInDir($classes, [], $ignore);
} else {
throw new \InvalidArgumentException('Given input is neither a class nor a source directory');
}
$tableGenerator = new MDTableGenerator();
$tableOfContent = [];
$body = [];
$classLinks = [];
foreach($classCollection as $ns => $classes) {
foreach($classes as $className) {
$class = $this->getClassEntity($className);
if( $class->hasIgnoreTag() )
continue;
// Add to tbl of contents
$tableOfContent[] = sprintf('- [%s](#%s)', $class->generateTitle('%name% %extra%'), $class->generateAnchor());
$classLinks[$class->getName()] = '#'.$class->generateAnchor();
// generate function table
$tableGenerator->openTable();
$tableGenerator->doDeclareAbstraction(!$class->isInterface());
foreach($class->getFunctions() as $func) {
if ($func->isReturningNativeClass()) {
$classLinks[$func->getReturnType()] = 'http://php.net/manual/en/class.'.
strtolower(str_replace(array('[]', '\\'), '', $func->getReturnType())).
'.php';
}
foreach($func->getParams() as $param) {
if ($param->getNativeClassType()) {
$classLinks[$param->getNativeClassType()] = 'http://php.net/manual/en/class.'.
strtolower(str_replace(array('[]', '\\'), '', $param->getNativeClassType())).
'.php';
}
}
$tableGenerator->addFunc($func);
}
$docs = ($requestingOneClass ? '':'<hr /> ').PHP_EOL;
if( $class->isDeprecated() ) {
$docs .= '### <strike>'.$class->generateTitle().'</strike>'.PHP_EOL.PHP_EOL.
'> **DEPRECATED** '.$class->getDeprecationMessage().PHP_EOL.PHP_EOL;
}
else {
$docs .= '### '.$class->generateTitle().PHP_EOL.PHP_EOL;
if( $class->getDescription() )
$docs .= '> '.$class->getDescription().PHP_EOL.PHP_EOL;
}
if( $example = $class->getExample() ) {
$docs .= '###### Example' . PHP_EOL . MDTableGenerator::formatExampleComment($example) .PHP_EOL.PHP_EOL;
}
$docs .= $tableGenerator->getTable().PHP_EOL;
if( $class->getExtends() ) {
$link = $class->getExtends();
if( $anchor = $this->getAnchorFromClassCollection($classCollection, $class->getExtends()) ) {
$link = sprintf('[%s](#%s)', $link, $anchor);
}
$docs .= PHP_EOL.'*This class extends '.$link.'*'.PHP_EOL;
}
if( $interfaces = $class->getInterfaces() ) {
$interfaceNames = [];
foreach($interfaces as $interface) {
$anchor = $this->getAnchorFromClassCollection($classCollection, $interface);
$interfaceNames[] = $anchor ? sprintf('[%s](#%s)', $interface, $anchor) : $interface;
}
$docs .= PHP_EOL.'*This class implements '.implode(', ', $interfaceNames).'*'.PHP_EOL;
}
$body[] = $docs;
}
}
if( empty($tableOfContent) ) {
throw new \InvalidArgumentException('No classes found');
} elseif( !$requestingOneClass ) {
$output->writeln('## Table of contents'.PHP_EOL);
$output->writeln(implode(PHP_EOL, $tableOfContent));
}
// Convert references to classes into links
asort($classLinks);
$classLinks = array_reverse($classLinks, true);
$docString = implode(PHP_EOL, $body);
foreach($classLinks as $className => $url) {
$link = sprintf('[%s](%s)', $className, $url);
$find = array('<em>'.$className, '/'.$className);
$replace = array('<em>'.$link, '/'.$link);
$docString = str_replace($find, $replace, $docString);
}
$output->writeln(PHP_EOL.$docString);
}
/**
* @param $coll
* @param $find
* @return bool|string
*/
private function getAnchorFromClassCollection($coll, $find)
{
foreach($coll as $ns => $classes) {
foreach($classes as $className) {
if( $className == $find ) {
return $this->getClassEntity($className)->generateAnchor();
}
}
}
return false;
}
/**
* @param $file
* @return array
*/
private function findClassInFile($file)
{
$ns = '';
$class = false;
foreach(explode(PHP_EOL, file_get_contents($file)) as $line) {
if ( strpos($line, '*') === false ) {
if( strpos($line, 'namespace') !== false ) {
$ns = trim(current(array_slice(explode('namespace', $line), 1)), '; ');
$ns = Utils::sanitizeClassName($ns);
} elseif( strpos($line, 'class') !== false ) {
$class = $this->extractClassNameFromLine('class', $line);
break;
} elseif( strpos($line, 'interface') !== false ) {
$class = $this->extractClassNameFromLine('interface', $line);
break;
}
}
}
return $class ? array($ns, $ns .'\\'. $class) : array(false, false);
}
/**
* @param string $type
* @param string $line
* @return string
*/
function extractClassNameFromLine($type, $line)
{
$class = trim(current(array_slice(explode($type, $line), 1)), '; ');
return trim(current(explode(' ', $class)));
}
/**
* @param $dir
* @param array $collection
* @param array $ignores
* @return array
*/
private function findClassesInDir($dir, $collection=[], $ignores=[])
{
foreach(new \FilesystemIterator($dir) as $f) {
/** @var \SplFileInfo $f */
if( $f->isFile() && !$f->isLink() ) {
list($ns, $className) = $this->findClassInFile($f->getRealPath());
if( $className && (class_exists($className, true) || interface_exists($className)) ) {
$collection[$ns][] = $className;
}
} elseif( $f->isDir() && !$f->isLink() && !$this->shouldIgnoreDirectory($f->getFilename(), $ignores) ) {
$collection = $this->findClassesInDir($f->getRealPath(), $collection);
}
}
ksort($collection);
return $collection;
}
/**
* @param $dirName
* @param $ignores
* @return bool
*/
private function shouldIgnoreDirectory($dirName, $ignores) {
foreach($ignores as $dir) {
$dir = trim($dir);
if( !empty($dir) && substr($dirName, -1 * strlen($dir)) == $dir ) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace PHPDocsMD;
/**
* Class containing information about a function/class that's being made
* available via a comment block
*
* @package PHPDocsMD
*/
class DocInfo
{
/**
* @var array
*/
private $data = [];
/**
* @param array $data
*/
public function __construct(array $data)
{
$this->data = array_merge([
'return' => '',
'params' => [],
'description' => '',
'example' => false,
'deprecated' => false
], $data);
}
/**
* @return string
*/
public function getReturnType()
{
return $this->data['return'];
}
/**
* @return array
*/
public function getParameters()
{
return $this->data['params'];
}
/**
* @param string $name
* @return array
*/
public function getParameterInfo($name)
{
if (isset($this->data['params'][$name])) {
return $this->data['params'][$name];
}
return [];
}
/**
* @return string
*/
public function getExample()
{
return $this->data['example'];
}
/**
* @return string
*/
public function getDescription()
{
return $this->data['description'];
}
/**
* @return string
*/
public function getDeprecationMessage()
{
return $this->data['deprecated'];
}
/**
* @return bool
*/
public function shouldInheritDoc()
{
return isset($this->data['inheritDoc']) || isset($this->data['inheritdoc']);
}
/**
* @return bool
*/
public function shouldBeIgnored()
{
return isset($this->data['ignore']);
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace PHPDocsMD;
/**
* Class that can extract information from a function/class comment
* @package PHPDocsMD
*/
class DocInfoExtractor
{
/**
* @param \ReflectionClass|\ReflectionMethod $reflection
* @return DocInfo
*/
public function extractInfo($reflection)
{
$comment = $this->getCleanDocComment($reflection);
$data = $this->extractInfoFromComment($comment, $reflection);
return new DocInfo($data);
}
/**
* @param \ReflectionClass|\ReflectionMethod $reflection
* @param DocInfo $docInfo
* @param CodeEntity $code
*/
public function applyInfoToEntity($reflection, DocInfo $docInfo, CodeEntity $code)
{
$code->setName($reflection->getName());
$code->setDescription($docInfo->getDescription());
$code->setExample($docInfo->getExample());
if ($docInfo->getDeprecationMessage()) {
$code->isDeprecated(true);
$code->setDeprecationMessage($docInfo->getDeprecationMessage());
}
}
/**
* @param \ReflectionClass $reflection
* @return string
*/
private function getCleanDocComment($reflection)
{
$comment = str_replace(['/*', '*/'], '', $reflection->getDocComment());
return trim(trim(preg_replace('/([\s|^]\*\s)/', '', $comment)), '*');
}
/**
* @param string $comment
* @param string $current_tag
* @param \ReflectionMethod|\ReflectionClass $reflection
* @return array
*/
private function extractInfoFromComment($comment, $reflection, $current_tag='description')
{
$currentNamespace = $this->getNameSpace($reflection);
$tags = [$current_tag=>''];
foreach(explode(PHP_EOL, $comment) as $line) {
if( $current_tag != 'example' )
$line = trim($line);
$words = $this->getWordsFromLine($line);
if( empty($words) )
continue;
if( strpos($words[0], '@') === false ) {
// Append to tag
$joinWith = $current_tag == 'example' ? PHP_EOL : ' ';
$tags[$current_tag] .= $joinWith . $line;
}
elseif( $words[0] == '@param' ) {
// Get parameter declaration
if( $paramData = $this->figureOutParamDeclaration($words, $currentNamespace) ) {
list($name, $data) = $paramData;
$tags['params'][$name] = $data;
}
}
else {
// Start new tag
$current_tag = substr($words[0], 1);
array_splice($words, 0 ,1);
if( empty($tags[$current_tag]) ) {
$tags[$current_tag] = '';
}
$tags[$current_tag] .= trim(join(' ', $words));
}
}
foreach($tags as $name => $val) {
if( is_array($val) ) {
foreach($val as $subName=>$subVal) {
if( is_string($subVal) )
$tags[$name][$subName] = trim($subVal);
}
} else {
$tags[$name] = trim($val);
}
}
return $tags;
}
/**
* @param \ReflectionClass|\ReflectionMethod $reflection
* @return string
*/
private function getNameSpace($reflection)
{
if ($reflection instanceof \ReflectionClass) {
return $reflection->getNamespaceName();
} else {
return $reflection->getDeclaringClass()->getNamespaceName();
}
}
/**
* @param $line
* @return array
*/
private function getWordsFromLine($line)
{
$words = [];
foreach(explode(' ', trim($line)) as $w) {
if( !empty($w) ) {
$words[] = $w;
}
}
return $words;
}
/**
* @param $words
* @param $currentNameSpace
* @return array|bool
*/
private function figureOutParamDeclaration($words, $currentNameSpace)
{
$description = '';
$type = '';
$name = '';
if (isset($words[1]) && strpos($words[1], '$') === 0) {
$name = $words[1];
$type = 'mixed';
array_splice($words, 0, 2);
} elseif (isset($words[2])) {
$name = $words[2];
$type = $words[1];
array_splice($words, 0, 3);
}
if (!empty($name)) {
$name = current(explode('=', $name));
if( count($words) > 1 ) {
$description = join(' ', $words);
}
$type = Utils::sanitizeDeclaration($type, $currentNameSpace);
$data = [
'description' => $description,
'name' => $name,
'type' => $type,
'default' => false
];
return [$name, $data];
}
return false;
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace PHPDocsMD;
/**
* Object describing a function
* @package PHPDocsMD
*/
class FunctionEntity extends CodeEntity {
/**
* @var \PHPDocsMD\ParamEntity[]
*/
private $params = [];
/**
* @var string
*/
private $returnType = 'void';
/**
* @var string
*/
private $visibility = 'public';
/**
* @var bool
*/
private $abstract = false;
/**
* @var bool
*/
private $isStatic = false;
/**
* @var string
*/
private $class = '';
/**
* @var bool
*/
private $isReturningNativeClass = false;
/**
* @param bool $toggle
*/
public function isStatic($toggle=null)
{
if ( $toggle === null ) {
return $this->isStatic;
} else {
return $this->isStatic = (bool)$toggle;
}
}
/**
* @param bool $toggle
*/
public function isAbstract($toggle=null)
{
if ( $toggle === null ) {
return $this->abstract;
} else {
return $this->abstract = (bool)$toggle;
}
}
/**
* @param bool $toggle
*/
public function isReturningNativeClass($toggle=null)
{
if ( $toggle === null ) {
return $this->isReturningNativeClass;
} else {
return $this->isReturningNativeClass = (bool)$toggle;
}
}
/**
* @return bool
*/
public function hasParams()
{
return !empty($this->params);
}
/**
* @param \PHPDocsMD\ParamEntity[] $params
*/
public function setParams(array $params)
{
$this->params = $params;
}
/**
* @return \PHPDocsMD\ParamEntity[]
*/
public function getParams()
{
return $this->params;
}
/**
* @param string $returnType
*/
public function setReturnType($returnType)
{
$this->returnType = $returnType;
}
/**
* @return string
*/
public function getReturnType()
{
return $this->returnType;
}
/**
* @param string $visibility
*/
public function setVisibility($visibility)
{
$this->visibility = $visibility;
}
/**
* @return string
*/
public function getVisibility()
{
return $this->visibility;
}
/**
* @param string $class
*/
public function setClass($class)
{
$this->class = $class;
}
/**
* @return string
*/
public function getClass()
{
return $this->class;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace PHPDocsMD;
/**
* Find a specific function in a class or an array of classes
* @package PHPDocsMD
*/
class FunctionFinder
{
/**
* @var array
*/
private $cache = [];
/**
* @param string $methodName
* @param array $classes
* @return bool|FunctionEntity
*/
public function findInClasses($methodName, $classes)
{
foreach ($classes as $className) {
$function = $this->find($methodName, $className);
if (false !== $function) {
return $function;
}
}
return false;
}
/**
* @param string $methodName
* @param string $className
* @return bool|FunctionEntity
*/
public function find($methodName, $className)
{
if ($className) {
$classEntity = $this->loadClassEntity($className);
$functions = $classEntity->getFunctions();
foreach($functions as $function) {
if($function->getName() == $methodName) {
return $function;
}
}
if($classEntity->getExtends()) {
return $this->find($methodName, $classEntity->getExtends());
}
}
return false;
}
/**
* @param $className
* @return ClassEntity
*/
private function loadClassEntity($className)
{
if (empty($this->cache[$className])) {
$reflector = new Reflector($className, $this);
$this->cache[$className] = $reflector->getClassEntity();
}
return $this->cache[$className];
}
}

View File

@@ -0,0 +1,204 @@
<?php
namespace PHPDocsMD;
/**
* Class that can create a markdown-formatted table describing class functions
* referred to via FunctionEntity objects
*
* @example
* <code>
* <?php
* $generator = new PHPDocsMD\MDTableGenerator();
* $generator->openTable();
* foreach($classEntity->getFunctions() as $func) {
* $generator->addFunc( $func );
* }
* echo $generator->getTable();
* </code>
*
* @package PHPDocsMD
*/
class MDTableGenerator {
/**
* @var string
*/
private $fullClassName = '';
/**
* @var string
*/
private $markdown = '';
/**
* @var array
*/
private $examples = [];
/**
* @var bool
*/
private $appendExamples = true;
/**
* @var bool
*/
private $declareAbstraction = true;
/**
* @param $example
* @return mixed
*/
private static function stripCodeTags($example)
{
if (strpos($example, '<code') !== false) {
$parts = array_slice(explode('</code>', $example), -2);
$example = current($parts);
$parts = array_slice(explode('<code>', $example), 1);
$example = current($parts);
}
return $example;
}
/**
* All example comments found while generating the table will be
* appended to the end of the table. Set $toggle to false to
* prevent this behaviour
*
* @param bool $toggle
*/
function appendExamplesToEndOfTable($toggle)
{
$this->appendExamples = (bool)$toggle;
}
/**
* Begin generating a new markdown-formatted table
*/
function openTable()
{
$this->examples = [];
$this->markdown = ''; // Clear table
$this->declareAbstraction = true;
$this->add('| Visibility | Function |');
$this->add('|:-----------|:---------|');
}
/**
* Toggle whether or not methods being abstract (or part of an interface)
* should be declared as abstract in the table
* @param bool $toggle
*/
function doDeclareAbstraction($toggle) {
$this->declareAbstraction = (bool)$toggle;
}
/**
* Generates a markdown formatted table row with information about given function. Then adds the
* row to the table and returns the markdown formatted string.
*
* @param FunctionEntity $func
* @return string
*/
function addFunc(FunctionEntity $func)
{
$this->fullClassName = $func->getClass();
$str = '<strong>';
if( $this->declareAbstraction && $func->isAbstract() )
$str .= 'abstract ';
$str .= $func->getName().'(';
if( $func->hasParams() ) {
$params = [];
foreach($func->getParams() as $param) {
$paramStr = '<em>'.$param->getType().'</em> <strong>'.$param->getName();
if( $param->getDefault() ) {
$paramStr .= '='.$param->getDefault();
}
$paramStr .= '</strong>';
$params[] = $paramStr;
}
$str .= '</strong>'.implode(', ', $params) .')';
} else {
$str .= ')';
}
$str .= '</strong> : <em>'.$func->getReturnType().'</em>';
if( $func->isDeprecated() ) {
$str = '<strike>'.$str.'</strike>';
$str .= '<br /><em>DEPRECATED - '.$func->getDeprecationMessage().'</em>';
} elseif( $func->getDescription() ) {
$str .= '<br /><em>'.$func->getDescription().'</em>';
}
$str = str_replace(['</strong><strong>', '</strong></strong> '], ['','</strong>'], trim($str));
if( $func->getExample() )
$this->examples[$func->getName()] = $func->getExample();
$firstCol = $func->getVisibility() . ($func->isStatic() ? ' static':'');
$markDown = '| '.$firstCol.' | '.$str.' |';
$this->add($markDown);
return $markDown;
}
/**
* @return string
*/
function getTable()
{
$tbl = trim($this->markdown);
if( $this->appendExamples && !empty($this->examples) ) {
$className = Utils::getClassBaseName($this->fullClassName);
foreach ($this->examples as $funcName => $example) {
$tbl .= sprintf("\n###### Examples of %s::%s()\n%s", $className, $funcName, self::formatExampleComment($example));
}
}
return $tbl;
}
/**
* Create a markdown-formatted code view out of an example comment
* @param string $example
* @return string
*/
public static function formatExampleComment($example)
{
// Remove possible code tag
$example = self::stripCodeTags($example);
if( preg_match('/(\n )/', $example) ) {
$example = preg_replace('/(\n )/', "\n", $example);
}
elseif( preg_match('/(\n )/', $example) ) {
$example = preg_replace('/(\n )/', "\n", $example);
} else {
$example = preg_replace('/(\n )/', "\n", $example);
}
$type = '';
// A very naive analysis of the programming language used in the comment
if( strpos($example, '<?php') !== false ) {
$type = 'php';
}
elseif( strpos($example, 'var ') !== false && strpos($example, '</') === false ) {
$type = 'js';
}
return sprintf("```%s\n%s\n```", $type, trim($example));
}
/**
* @param $str
*/
private function add($str)
{
$this->markdown .= $str .PHP_EOL;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace PHPDocsMD;
/**
* Object describing a function parameter
* @package PHPDocsMD
*/
class ParamEntity extends CodeEntity {
/**
* @var bool
*/
private $default=false;
/**
* @var string
*/
private $type='mixed';
/**
* @param boolean $default
*/
public function setDefault($default)
{
$this->default = $default;
}
/**
* @return boolean
*/
public function getDefault()
{
return $this->default;
}
/**
* @param string $type
*/
public function setType($type)
{
$this->type = $type;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return string|null
*/
public function getNativeClassType()
{
foreach(explode('/', $this->type) as $typeDeclaration) {
if (Utils::isNativeClassReference($typeDeclaration)) {
return $typeDeclaration;
}
}
return null;
}
}

View File

@@ -0,0 +1,417 @@
<?php
namespace PHPDocsMD;
use ReflectionMethod;
/**
* Class that can compute ClassEntity objects out of real classes
* @package PHPDocsMD
*/
class Reflector implements ReflectorInterface
{
/**
* @var string
*/
private $className;
/**
* @var FunctionFinder
*/
private $functionFinder;
/**
* @var DocInfoExtractor
*/
private $docInfoExtractor;
/**
* @var ClassEntityFactory
*/
private $classEntityFactory;
/**
* @var UseInspector
*/
private $useInspector;
/**
* @param string $className
* @param FunctionFinder $functionFinder
* @param DocInfoExtractor $docInfoExtractor
* @param UseInspector $useInspector
* @param ClassEntityFactory $classEntityFactory
*/
function __construct(
$className,
FunctionFinder $functionFinder = null,
DocInfoExtractor $docInfoExtractor = null,
UseInspector $useInspector = null,
ClassEntityFactory $classEntityFactory = null
) {
$this->className = $className;
$this->functionFinder = $this->loadIfNull($functionFinder, FunctionFinder::class);
$this->docInfoExtractor = $this->loadIfNull($docInfoExtractor, DocInfoExtractor::class);
$this->useInspector = $this->loadIfNull($useInspector, UseInspector::class);
$this->classEntityFactory = $this->loadIfNull(
$classEntityFactory,
ClassEntityFactory::class,
$this->docInfoExtractor
);
}
private function loadIfNull($obj, $className, $in=null)
{
return is_object($obj) ? $obj : new $className($in);
}
/**
* @return \PHPDocsMD\ClassEntity
*/
function getClassEntity() {
$classReflection = new \ReflectionClass($this->className);
$classEntity = $this->classEntityFactory->create($classReflection);
$classEntity->setFunctions($this->getClassFunctions($classEntity, $classReflection));
return $classEntity;
}
/**
* @param ClassEntity $classEntity
* @param \ReflectionClass $reflectionClass
* @return FunctionEntity[]
*/
private function getClassFunctions(ClassEntity $classEntity, \ReflectionClass $reflectionClass)
{
$classUseStatements = $this->useInspector->getUseStatements($reflectionClass);
$publicFunctions = [];
$protectedFunctions = [];
foreach($reflectionClass->getMethods() as $methodReflection) {
$func = $this->createFunctionEntity(
$methodReflection,
$classEntity,
$classUseStatements
);
if( $func ) {
if( $func->getVisibility() == 'public' ) {
$publicFunctions[$func->getName()] = $func;
} else {
$protectedFunctions[$func->getName()] = $func;
}
}
}
ksort($publicFunctions);
ksort($protectedFunctions);
return array_values(array_merge($publicFunctions, $protectedFunctions));
}
/**
* @param ReflectionMethod $method
* @param ClassEntity $class
* @param array $useStatements
* @return bool|FunctionEntity
*/
protected function createFunctionEntity(ReflectionMethod $method, ClassEntity $class, $useStatements)
{
$func = new FunctionEntity();
$docInfo = $this->docInfoExtractor->extractInfo($method);
$this->docInfoExtractor->applyInfoToEntity($method, $docInfo, $func);
if ($docInfo->shouldInheritDoc()) {
return $this->findInheritedFunctionDeclaration($func, $class);
}
if ($this->shouldIgnoreFunction($docInfo, $method, $class)) {
return false;
}
$returnType = $this->getReturnType($docInfo, $method, $func, $useStatements);
$func->setReturnType($returnType);
$func->setParams($this->getParams($method, $docInfo));
$func->isStatic($method->isStatic());
$func->setVisibility($method->isPublic() ? 'public' : 'protected');
$func->isAbstract($method->isAbstract());
$func->setClass($class->getName());
$func->isReturningNativeClass(Utils::isNativeClassReference($returnType));
return $func;
}
/**
* @param DocInfo $docInfo
* @param ReflectionMethod $method
* @param FunctionEntity $func
* @param array $useStatements
* @return string
*/
private function getReturnType(
DocInfo $docInfo,
ReflectionMethod $method,
FunctionEntity $func,
array $useStatements
) {
$returnType = $docInfo->getReturnType();
if (empty($returnType)) {
$returnType = $this->guessReturnTypeFromFuncName($func->getName());
} elseif(Utils::isClassReference($returnType) && !self::classExists($returnType)) {
$isReferenceToArrayOfObjects = substr($returnType, -2) == '[]' ? '[]':'';
if ($isReferenceToArrayOfObjects) {
$returnType = substr($returnType, 0, strlen($returnType)-2);
}
$className = $this->stripAwayNamespace($returnType);
foreach ($useStatements as $usedClass) {
if ($this->stripAwayNamespace($usedClass) == $className) {
$returnType = $usedClass;
break;
}
}
if ($isReferenceToArrayOfObjects) {
$returnType .= '[]';
}
}
return Utils::sanitizeDeclaration(
$returnType,
$method->getDeclaringClass()->getNamespaceName()
);
}
/**
* @param string $classRef
* @return bool
*/
private function classExists($classRef)
{
return class_exists(trim($classRef, '[]'));
}
/**
* @param string $className
* @return string
*/
private function stripAwayNamespace($className)
{
return trim(substr($className, strrpos($className, '\\')), '\\');
}
/**
* @param DocInfo $info
* @param ReflectionMethod $method
* @param ClassEntity $class
* @return bool
*/
protected function shouldIgnoreFunction($info, ReflectionMethod $method, $class)
{
return $info->shouldBeIgnored() ||
$method->isPrivate() ||
!$class->isSame($method->getDeclaringClass()->getName());
}
/**
* @todo Turn this into a class "FunctionEntityFactory"
* @param \ReflectionParameter $reflection
* @param array $docs
* @return FunctionEntity
*/
private function createParameterEntity(\ReflectionParameter $reflection, $docs)
{
// need to use slash instead of pipe or md-generation will get it wrong
$def = false;
$type = 'mixed';
$declaredType = self::getParamType($reflection);
if( !isset($docs['type']) )
$docs['type'] = '';
if( $declaredType && !($declaredType=='array' && substr($docs['type'], -2) == '[]') && $declaredType != $docs['type']) {
if( $declaredType && $docs['type'] ) {
$posClassA = Utils::getClassBaseName($docs['type']);
$posClassB = Utils::getClassBaseName($declaredType);
if( $posClassA == $posClassB ) {
$docs['type'] = $declaredType;
} else {
$docs['type'] = empty($docs['type']) ? $declaredType : $docs['type'].'/'.$declaredType;
}
} else {
$docs['type'] = empty($docs['type']) ? $declaredType : $docs['type'].'/'.$declaredType;
}
}
try {
$def = $reflection->getDefaultValue();
$type = $this->getTypeFromVal($def);
if( is_string($def) ) {
$def = "`'$def'`";
} elseif( is_bool($def) ) {
$def = $def ? 'true':'false';
} elseif( is_null($def) ) {
$def = 'null';
} elseif( is_array($def) ) {
$def = 'array()';
}
} catch(\Exception $e) {}
$varName = '$'.$reflection->getName();
if( !empty($docs) ) {
$docs['default'] = $def;
if( $type == 'mixed' && $def == 'null' && strpos($docs['type'], '\\') === 0 ) {
$type = false;
}
if( $type && $def && !empty($docs['type']) && $docs['type'] != $type && strpos($docs['type'], '|') === false) {
if( substr($docs['type'], strpos($docs['type'], '\\')) == substr($declaredType, strpos($declaredType, '\\')) ) {
$docs['type'] = $declaredType;
} else {
$docs['type'] = ($type == 'mixed' ? '':$type.'/').$docs['type'];
}
} elseif( $type && empty($docs['type']) ) {
$docs['type'] = $type;
}
} else {
$docs = [
'descriptions'=>'',
'name' => $varName,
'default' => $def,
'type' => $type
];
}
$param = new ParamEntity();
$param->setDescription(isset($docs['description']) ? $docs['description']:'');
$param->setName($varName);
$param->setDefault($docs['default']);
$param->setType(empty($docs['type']) ? 'mixed':str_replace(['|', '\\\\'], ['/', '\\'], $docs['type']));
return $param;
}
/**
* Tries to find out if the type of the given parameter. Will
* return empty string if not possible.
*
* @example
* <code>
* <?php
* $reflector = new \\ReflectionClass('MyClass');
* foreach($reflector->getMethods() as $method ) {
* foreach($method->getParameters() as $param) {
* $name = $param->getName();
* $type = Reflector::getParamType($param);
* printf("%s = %s\n", $name, $type);
* }
* }
* </code>
*
* @param \ReflectionParameter $refParam
* @return string
*/
static function getParamType(\ReflectionParameter $refParam)
{
$export = \ReflectionParameter::export([
$refParam->getDeclaringClass()->name,
$refParam->getDeclaringFunction()->name
],
$refParam->name,
true
);
$export = str_replace(' or NULL', '', $export);
$type = preg_replace('/.*?([\w\\\]+)\s+\$'.current(explode('=', $refParam->name)).'.*/', '\\1', $export);
if( strpos($type, 'Parameter ') !== false ) {
return '';
}
if( $type != 'array' && strpos($type, '\\') !== 0 ) {
$type = '\\'.$type;
}
return $type;
}
/**
* @param string $name
* @return string
*/
private function guessReturnTypeFromFuncName($name)
{
$mixed = ['get', 'load', 'fetch', 'find', 'create'];
$bool = ['is', 'can', 'has', 'have', 'should'];
foreach($mixed as $prefix) {
if( strpos($name, $prefix) === 0 )
return 'mixed';
}
foreach($bool as $prefix) {
if( strpos($name, $prefix) === 0 )
return 'bool';
}
return 'void';
}
/**
* @param string $def
* @return string
*/
private function getTypeFromVal($def)
{
if( is_string($def) ) {
return 'string';
} elseif( is_bool($def) ) {
return 'bool';
} elseif( is_array($def) ) {
return 'array';
} else {
return 'mixed';
}
}
/**
* @param FunctionEntity $func
* @param ClassEntity $class
* @return FunctionEntity
*/
private function findInheritedFunctionDeclaration(FunctionEntity $func, ClassEntity $class)
{
$funcName = $func->getName();
$inheritedFuncDeclaration = $this->functionFinder->find(
$funcName,
$class->getExtends()
);
if (!$inheritedFuncDeclaration) {
$inheritedFuncDeclaration = $this->functionFinder->findInClasses(
$funcName,
$class->getInterfaces()
);
if (!$inheritedFuncDeclaration) {
throw new \RuntimeException(
'Function '.$funcName.' tries to inherit docs but no parent method is found'
);
}
}
if (!$func->isAbstract() && !$class->isAbstract() && $inheritedFuncDeclaration->isAbstract()) {
$inheritedFuncDeclaration->isAbstract(false);
}
return $inheritedFuncDeclaration;
}
/**
* @param ReflectionMethod $method
* @param DocInfo $docInfo
* @return array
*/
private function getParams(ReflectionMethod $method, $docInfo)
{
$params = [];
foreach ($method->getParameters() as $param) {
$paramName = '$' . $param->getName();
$params[$param->getName()] = $this->createParameterEntity(
$param,
$docInfo->getParameterInfo($paramName)
);
}
return array_values($params);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace PHPDocsMD;
/**
* Interface for classes that can compute ClassEntity objects
* @package PHPDocsMD
*/
interface ReflectorInterface
{
/**
* @return \PHPDocsMD\ClassEntity
*/
function getClassEntity();
}

View File

@@ -0,0 +1,50 @@
<?php
namespace PHPDocsMD;
/**
* Class that can extract all use statements in a file
* @package PHPDocsMD
*/
class UseInspector
{
/**
* @param string $content
* @return string[]
*/
public function getUseStatementsInString($content)
{
$usages = [];
$chunks = array_slice(preg_split('/use[\s+]/', $content), 1);
foreach ($chunks as $chunk) {
$usage = trim(current(explode(';', $chunk)));
$usages[] = Utils::sanitizeClassName($usage);
}
return $usages;
}
/**
* @param string $filePath
* @return array
*/
public function getUseStatementsInFile($filePath)
{
return $this->getUseStatementsInString(file_get_contents($filePath));
}
/**
* @param \ReflectionClass $reflectionClass
* @return array
*/
public function getUseStatements(\ReflectionClass $reflectionClass)
{
$classUseStatements = [];
$classFile = $reflectionClass->getFileName();
if ($classFile) {
$classUseStatements = $this->getUseStatementsInFile($classFile);
}
return $classUseStatements;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace PHPDocsMD;
/**
* @package PHPDocsMD
*/
class Utils
{
/**
* @param string $name
* @return string
*/
public static function sanitizeClassName($name)
{
return '\\'.trim($name, ' \\');
}
/**
* @param string $fullClassName
* @return string
*/
public static function getClassBaseName($fullClassName)
{
$parts = explode('\\', trim($fullClassName));
return end($parts);
}
/**
* @param string $typeDeclaration
* @param string $currentNameSpace
* @param string $delimiter
* @return string
*/
public static function sanitizeDeclaration($typeDeclaration, $currentNameSpace, $delimiter='|')
{
$parts = explode($delimiter, $typeDeclaration);
foreach($parts as $i=>$p) {
if (self::shouldPrefixWithNamespace($p)) {
$p = self::sanitizeClassName('\\' . trim($currentNameSpace, '\\') . '\\' . $p);
} elseif (self::isClassReference($p)) {
$p = self::sanitizeClassName($p);
}
$parts[$i] = $p;
}
return implode('/', $parts);
}
/**
* @param string $typeDeclaration
* @return bool
*/
private static function shouldPrefixWithNameSpace($typeDeclaration)
{
return strpos($typeDeclaration, '\\') !== 0 && self::isClassReference($typeDeclaration);
}
/**
* @param string $typeDeclaration
* @return bool
*/
public static function isClassReference($typeDeclaration)
{
$natives = [
'mixed',
'string',
'int',
'float',
'integer',
'number',
'bool',
'boolean',
'object',
'false',
'true',
'null',
'array',
'void',
'callable'
];
$sanitizedTypeDeclaration = rtrim(trim(strtolower($typeDeclaration)), '[]');
return !in_array($sanitizedTypeDeclaration, $natives) &&
strpos($typeDeclaration, ' ') === false;
}
public static function isNativeClassReference($typeDeclaration)
{
$sanitizedType = str_replace('[]', '', $typeDeclaration);
if (Utils::isClassReference($typeDeclaration) && class_exists($sanitizedType, false)) {
$reflectionClass = new \ReflectionClass($sanitizedType);
return !$reflectionClass->getFileName();
}
return false;
}
}

View File

@@ -0,0 +1,157 @@
<?php
namespace Acme;
/**
* This is a description
* of this class
*
* @package Acme
*/
abstract class ExampleClass implements \Reflector {
/**
* Description of a*a
* @param $arg
* @param array $arr
* @param int $bool
*/
public function funcA($arg, array $arr, $bool=10) {
}
/**
* Description of c
* @deprecated This one is deprecated
* @param $arg
* @param array $arr
* @param int $bool
* @return \Acme\ExampleClass
*/
protected function funcC($arg, array $arr, $bool=10) {
}
/**
* Description of b
*
* @example
* <code>
* <?php
* $lorem = 'te';
* $ipsum = 'dolor';
* </code>
*
* @param int $arg
* @param array $arr
* @param int $bool
*/
function funcB($arg, array $arr, $bool=10) {
}
function funcD($arg, $arr=array(), ExampleInterface $depr=null, \stdClass $class) {
}
function getFunc() {}
function hasFunc() {}
abstract function isFunc();
/**
* @ignore
*/
function someFunc() {
}
private function privFunc() {
}
}
/**
* @deprecated This one is deprecated
*
* Lorem te ipsum
*
* @package Acme
*/
class ExampleClassDepr {
}
/**
* Interface ExampleInterface
* @package Acme
* @ignore
*/
interface ExampleInterface {
/**
* @param string $arg
* @return \stdClass
*/
public function func($arg='a');
}
class SomeClass {
/**
* @return int
*/
public function aMethod() {}
}
class ClassImplementingInterface extends SomeClass implements ExampleInterface {
/**
* @inheritdoc
*/
public function func($arg='a') {}
/**
* @inheritDoc
*/
public function aMethod() {}
/**
* @return \FilesystemIterator
*/
public function methodReturnNativeClass() {}
/**
* @return \FilesystemIterator[]
*/
public function methodReturningArrayNativeClass() {}
}
class ClassWithStaticFunc {
/**
* @return float
*/
static function somStaticFunc() {
}
}
use PHPDocsMD\Console\CLI;
interface InterfaceReferringToImportedClass {
/**
* @return CLI
*/
function theFunc();
/**
* @return CLI[]
*/
function funcReturningArr();
}

View File

@@ -0,0 +1,123 @@
<?php
class MDTableGeneratorTest extends PHPUnit_Framework_TestCase {
function testDeprecatedFunc()
{
$tbl = new \PHPDocsMD\MDTableGenerator();
$tbl->openTable();
$deprecated = new \PHPDocsMD\FunctionEntity();
$deprecated->isDeprecated(true);
$deprecated->setDeprecationMessage('Is deprecated');
$deprecated->setName('myFunc');
$deprecated->setReturnType('mixed');
$this->assertTrue($deprecated->isDeprecated());
$tbl->addFunc($deprecated);
$tblMarkdown = $tbl->getTable();
$expect = '| Visibility | Function |'.PHP_EOL.
'|:-----------|:---------|'.PHP_EOL.
'| public | <strike><strong>myFunc()</strong> : <em>mixed</em></strike><br /><em>DEPRECATED - Is deprecated</em> |';
$this->assertEquals($expect, $tblMarkdown);
}
function testFunc()
{
$tbl = new \PHPDocsMD\MDTableGenerator();
$tbl->openTable();
$func = new \PHPDocsMD\FunctionEntity();
$func->setName('myFunc');
$tbl->addFunc($func);
$tblMarkdown = $tbl->getTable();
$expect = '| Visibility | Function |'.PHP_EOL.
'|:-----------|:---------|'.PHP_EOL.
'| public | <strong>myFunc()</strong> : <em>void</em> |';
$this->assertEquals($expect, $tblMarkdown);
}
function testFuncWithAllFeatures()
{
$tbl = new \PHPDocsMD\MDTableGenerator();
$tbl->openTable();
$func = new \PHPDocsMD\FunctionEntity();
$this->assertFalse($func->isStatic());
$this->assertFalse($func->hasParams());
$this->assertFalse($func->isDeprecated());
$this->assertFalse($func->isAbstract());
$this->assertEquals('public', $func->getVisibility());
$func->isStatic(true);
$func->setVisibility('protected');
$func->setName('someFunc');
$func->setDescription('desc...');
$func->setReturnType('\\stdClass');
$params = array();
$paramA = new \PHPDocsMD\ParamEntity();
$paramA->setName('$var');
$paramA->setType('mixed');
$paramA->setDefault('null');
$params[] = $paramA;
$paramB = new \PHPDocsMD\ParamEntity();
$paramB->setName('$other');
$paramB->setType('string');
$paramB->setDefault("'test'");
$params[] = $paramB;
$func->setParams($params);
$tbl->addFunc($func);
$this->assertTrue($func->isStatic());
$this->assertTrue($func->hasParams());
$this->assertEquals('protected', $func->getVisibility());
$tblMarkdown = $tbl->getTable();
$expect = '| Visibility | Function |'.PHP_EOL.
'|:-----------|:---------|'.PHP_EOL.
'| protected static | <strong>someFunc(</strong><em>mixed</em> <strong>$var=null</strong>, <em>string</em> <strong>$other=\'test\'</strong>)</strong> : <em>\\stdClass</em><br /><em>desc...</em> |';
$this->assertEquals($expect, $tblMarkdown);
}
function testToggleDeclaringAbstraction()
{
$tbl = new \PHPDocsMD\MDTableGenerator();
$tbl->openTable();
$func = new \PHPDocsMD\FunctionEntity();
$func->isAbstract(true);
$func->setName('someFunc');
$tbl->addFunc($func);
$tblMarkdown = $tbl->getTable();
$expect = '| Visibility | Function |'.PHP_EOL.
'|:-----------|:---------|'.PHP_EOL.
'| public | <strong>abstract someFunc()</strong> : <em>void</em> |';
$this->assertEquals($expect, $tblMarkdown);
$tbl->openTable();
$tbl->doDeclareAbstraction(false);
$tbl->addFunc($func);
$tblMarkdown = $tbl->getTable();
$expect = '| Visibility | Function |'.PHP_EOL.
'|:-----------|:---------|'.PHP_EOL.
'| public | <strong>someFunc()</strong> : <em>void</em> |';
$this->assertEquals($expect, $tblMarkdown);
}
}

View File

@@ -0,0 +1,160 @@
<?php
class ReflectorTest extends PHPUnit_Framework_TestCase {
/**
* @var \PHPDocsMD\Reflector
*/
private $reflector;
/**
* @var \PHPDocsMD\ClassEntity
*/
private $class;
protected function setUp()
{
require_once __DIR__ . '/ExampleClass.php';
$this->reflector = new \PHPDocsMD\Reflector('Acme\\ExampleClass');
$this->class = $this->reflector->getClassEntity();
}
function testClass()
{
$this->assertEquals('\\Acme\\ExampleClass', $this->class->getName());
$this->assertEquals('This is a description of this class', $this->class->getDescription());
$this->assertEquals('Class: \\Acme\\ExampleClass (abstract)', $this->class->generateTitle());
$this->assertEquals('class-acmeexampleclass-abstract', $this->class->generateAnchor());
$this->assertFalse($this->class->isDeprecated());
$this->assertFalse($this->class->hasIgnoreTag());
$refl = new \PHPDocsMD\Reflector('Acme\\ExampleClassDepr');
$class = $refl->getClassEntity();
$this->assertTrue($class->isDeprecated());
$this->assertEquals('This one is deprecated Lorem te ipsum', $class->getDeprecationMessage());
$this->assertFalse($class->hasIgnoreTag());
$refl = new \PHPDocsMD\Reflector('Acme\\ExampleInterface');
$class = $refl->getClassEntity();
$this->assertTrue($class->isInterface());
$this->assertTrue($class->hasIgnoreTag());
}
function testFunctions()
{
$functions = $this->class->getFunctions();
$this->assertNotEmpty($functions);
$this->assertEquals('Description of a*a', $functions[0]->getDescription());
$this->assertEquals(false, $functions[0]->isDeprecated());
$this->assertEquals('funcA', $functions[0]->getName());
$this->assertEquals('void', $functions[0]->getReturnType());
$this->assertEquals('public', $functions[0]->getVisibility());
$this->assertEquals('Description of b', $functions[1]->getDescription());
$this->assertEquals(false, $functions[1]->isDeprecated());
$this->assertEquals('funcB', $functions[1]->getName());
$this->assertEquals('void', $functions[1]->getReturnType());
$this->assertEquals('public', $functions[1]->getVisibility());
$this->assertEquals('', $functions[2]->getDescription());
$this->assertEquals('funcD', $functions[2]->getName());
$this->assertEquals('void', $functions[2]->getReturnType());
$this->assertEquals('public', $functions[2]->getVisibility());
$this->assertEquals(false, $functions[2]->isDeprecated());
// These function does not declare return type but the return
// type should be guessable
$this->assertEquals('mixed', $functions[3]->getReturnType());
$this->assertEquals('bool', $functions[4]->getReturnType());
$this->assertEquals('bool', $functions[5]->getReturnType());
$this->assertTrue($functions[5]->isAbstract());
$this->assertTrue($this->class->isAbstract());
// Protected function have been put last
$this->assertEquals('Description of c', $functions[6]->getDescription());
$this->assertEquals(true, $functions[6]->isDeprecated());
$this->assertEquals('This one is deprecated', $functions[6]->getDeprecationMessage());
$this->assertEquals('funcC', $functions[6]->getName());
$this->assertEquals('\\Acme\\ExampleClass', $functions[6]->getReturnType());
$this->assertEquals('protected', $functions[6]->getVisibility());
$this->assertTrue( empty($functions[7]) ); // Should be skipped since tagged with @ignore */
}
function testStaticFunc() {
$reflector = new \PHPDocsMD\Reflector('Acme\\ClassWithStaticFunc');
$functions = $reflector->getClassEntity()->getFunctions();
$this->assertNotEmpty($functions);
$this->assertEquals('', $functions[0]->getDescription());
$this->assertEquals(false, $functions[0]->isDeprecated());
$this->assertEquals(true, $functions[0]->isStatic());
$this->assertEquals('', $functions[0]->getDeprecationMessage());
$this->assertEquals('somStaticFunc', $functions[0]->getName());
$this->assertEquals('public', $functions[0]->getVisibility());
$this->assertEquals('float', $functions[0]->getReturnType());
}
function testParams()
{
$paramA = new ReflectionParameter(array('Acme\\ExampleClass', 'funcD'), 2);
$paramB = new ReflectionParameter(array('Acme\\ExampleClass', 'funcD'), 3);
$paramC = new ReflectionParameter(array('Acme\\ExampleClass', 'funcD'), 0);
$typeA = \PHPDocsMD\Reflector::getParamType($paramA);
$typeB = \PHPDocsMD\Reflector::getParamType($paramB);
$typeC = \PHPDocsMD\Reflector::getParamType($paramC);
$this->assertEmpty($typeC);
$this->assertEquals('\\stdClass', $typeB);
$this->assertEquals('\\Acme\\ExampleInterface', $typeA);
$functions = $this->class->getFunctions();
$this->assertTrue($functions[2]->hasParams());
$this->assertFalse($functions[5]->hasParams());
$params = $functions[1]->getParams();
$this->assertEquals('int', $params[0]->getType());
$params = $functions[2]->getParams();
$this->assertEquals(4, count($params));
$this->assertEquals(false, $params[0]->getDefault());
$this->assertEquals('$arg', $params[0]->getName());
$this->assertEquals('mixed', $params[0]->getType());
$this->assertEquals('array()', $params[1]->getDefault());
$this->assertEquals('$arr', $params[1]->getName());
$this->assertEquals('array', $params[1]->getType());
$this->assertEquals('null', $params[2]->getDefault());
$this->assertEquals('$depr', $params[2]->getName());
$this->assertEquals('\\Acme\\ExampleInterface', $params[2]->getType());
}
function testInheritedDocs()
{
$reflector = new \PHPDocsMD\Reflector('Acme\\ClassImplementingInterface');
$functions = $reflector->getClassEntity()->getFunctions();
$this->assertEquals(4, count($functions));
$this->assertEquals('aMethod', $functions[0]->getName());
$this->assertEquals('int', $functions[0]->getReturnType());
$this->assertFalse($functions[0]->isReturningNativeClass());
$this->assertEquals('func', $functions[1]->getName());
$this->assertEquals('\\stdClass', $functions[1]->getReturnType());
$this->assertFalse($functions[1]->isAbstract());
$this->assertTrue($functions[2]->isReturningNativeClass());
$this->assertTrue($functions[3]->isReturningNativeClass());
}
function testReferenceToImportedClass()
{
$reflector = new \PHPDocsMD\Reflector('Acme\\InterfaceReferringToImportedClass');
$functions = $reflector->getClassEntity()->getFunctions();
$this->assertEquals('\\PHPDocsMD\\Console\\CLI', $functions[1]->getReturnType());
$this->assertEquals('\\PHPDocsMD\\Console\\CLI[]', $functions[0]->getReturnType());
}
}

View File

@@ -0,0 +1,42 @@
<?php
class UseInspectorTest extends PHPUnit_Framework_TestCase {
function testInspection()
{
$code = '
Abra
use apa\\sten\\groda;
use apa\\sten\\BjornGroda;
use apa\\sten\\groda;
use \\apa\\sten\\groda;
use apa
use \\apa ;
use \\apa\\Sten
;
Kadabra
use \apa ;
useBala;
';
$expected = array(
'\\apa\\sten\\groda',
'\\apa\\sten\\BjornGroda',
'\\apa\\sten\\groda',
'\\apa\\sten\\groda',
'\\apa',
'\\apa',
'\\apa\\Sten',
'\\apa'
);
$inspector = new \PHPDocsMD\UseInspector();
$this->assertEquals($expected, $inspector->getUseStatementsInString($code));
}
}