# 速查表 新版升級
## 歷史回顧
我感嘆于laravel的生態完好,tp5的類api的缺失(以前thinkphp3時代,幫助公司用apigen NetBeans里生成過一個[3.1版本的api](http://www.thinkphp.cn/api/))。

在我走之后,估計沒人用NetBeans了 所以就沒人做升級。
然后我覺得laravel的速查表不錯,見下圖:

于是就想到了把他移植到tp5上,作為自己回到thinkphp5開發上的第一個貢獻。
記得那個時候是清明前開始動手的。使用最土的,靜態頁面,一個個類手動去編輯。
后來發現類太多了,我每次在sublime 里 先打開一個類,復制出來臨時文件,處理好,粘貼到靜態index.html里,太麻煩了,要處理左側的導航,又要定位插入的位置。 于是乎在五一放假時 我換了個思路,我不搞靜態了,我搞動態的。
于是在海豚里建了一個插件,




以章節的形式添加。終于效率上去了,五一時候抽空把內容全補全了。
發布到了 掘金上,獲得了17個贊。

## 進擊的速查表
本來以為完成了。結果老大們太積極7月4日又發布了5.0.10,然后在開發5.1dev版。
既然老大把我的速查表都掛到了tp官網上。我也不能讓用的人等,看舊的5.0.7版、
可是我不想在人工去對比改了哪個類,增加還是刪除了哪些方法。20多章呢。
我想 “代碼的問題應該由代碼解決”。于是想到了php的注解、反射類。
正好項目用到了一個 **crada/php-apidoc** 的api文檔生成工具。
于是我就開始動手實現這通過類反射信息獲取方法的工程。
### 反射的資料
> PHP 5 具有完整的反射 API,添加了對類、接口、函數、方法和擴展進行反向工程的能力。 此外,反射 API 提供了方法來取出函數、類和方法中的文檔注釋。
官網手冊提到了反射有這多類:
Reflection
ReflectionClass
ReflectionZendExtension
ReflectionExtension
ReflectionFunction
ReflectionFunctionAbstract
ReflectionMethod
ReflectionObject
ReflectionParameter
ReflectionProperty
ReflectionType
ReflectionGenerator
Reflector
ReflectionException
### 實現思路
~~~
// TODO 獲取待處理的類命名空間數據
// TODO 遍歷類 反射
// TODO 獲取類的信息 (名稱、方法列表)
// TODO 遍歷方法列表, 獲取方法的類型 和注釋
~~~
我看了一遍反射是先反射類 再找到方法 再找到方法的參數
#### 類的獲取
tp5的核心類 都在一個目錄
于是我想到了用glob遍歷
~~~
public function get_core_class(){
$class_path = CORE_PATH;
$before_cwd = getcwd();
chdir($class_path);
$names = glob('*.php');
$ret = [];
foreach ($names as $key => $name) {
$ret[] = 'think\\'. str_ireplace('.php', '', $name);
}
chdir($before_cwd);
return $ret;
}
~~~
反射類的初始化要傳的是類的實際命名空間路徑。
`$class= new\ReflectionClass($className);` 這樣。然后有以下方法:
ReflectionClass::__construct — 初始化 ReflectionClass 類
ReflectionClass::export — 導出一個類
ReflectionClass::getConstant — 獲取定義過的一個常量
ReflectionClass::getConstants — 獲取一組常量
ReflectionClass::getConstructor — 獲取類的構造函數
ReflectionClass::getDefaultProperties — 獲取默認屬性
ReflectionClass::getDocComment — 獲取文檔注釋
ReflectionClass::getEndLine — 獲取最后一行的行數
ReflectionClass::getExtension — 根據已定義的類獲取所在擴展的 ReflectionExtension 對象
ReflectionClass::getExtensionName — 獲取定義的類所在的擴展的名稱
ReflectionClass::getFileName — 獲取定義類的文件名
ReflectionClass::getInterfaceNames — 獲取接口(interface)名稱
ReflectionClass::getInterfaces — 獲取接口
ReflectionClass::getMethod — 獲取一個類方法的 ReflectionMethod。
ReflectionClass::getMethods — 獲取方法的數組
ReflectionClass::getModifiers — 獲取類的修飾符
ReflectionClass::getName — 獲取類名
ReflectionClass::getNamespaceName — 獲取命名空間的名稱
ReflectionClass::getParentClass — 獲取父類
ReflectionClass::getProperties — 獲取一組屬性
ReflectionClass::getProperty — 獲取類的一個屬性的 ReflectionProperty
ReflectionClass::getShortName — 獲取短名
ReflectionClass::getStartLine — 獲取起始行號
ReflectionClass::getStaticProperties — 獲取靜態(static)屬性
ReflectionClass::getStaticPropertyValue — 獲取靜態(static)屬性的值
ReflectionClass::getTraitAliases — 返回 trait 別名的一個數組
ReflectionClass::getTraitNames — 返回這個類所使用 traits 的名稱的數組
ReflectionClass::getTraits — 返回這個類所使用的 traits 數組
ReflectionClass::hasConstant — 檢查常量是否已經定義
ReflectionClass::hasMethod — 檢查方法是否已定義
ReflectionClass::hasProperty — 檢查屬性是否已定義
ReflectionClass::implementsInterface — 接口的實現
ReflectionClass::inNamespace — 檢查是否位于命名空間中
ReflectionClass::isAbstract — 檢查類是否是抽象類(abstract)
ReflectionClass::isAnonymous — 檢查類是否是匿名類
ReflectionClass::isCloneable — 返回了一個類是否可復制
ReflectionClass::isFinal — 檢查類是否聲明為 final
ReflectionClass::isInstance — 檢查類的實例
ReflectionClass::isInstantiable — 檢查類是否可實例化
ReflectionClass::isInterface — 檢查類是否是一個接口(interface)
ReflectionClass::isInternal — 檢查類是否由擴展或核心在內部定義
ReflectionClass::isIterateable — 檢查是否可迭代(iterateable)
ReflectionClass::isSubclassOf — 檢查是否為一個子類
ReflectionClass::isTrait — 返回了是否為一個 trait
ReflectionClass::isUserDefined — 檢查是否由用戶定義的
ReflectionClass::newInstance — 從指定的參數創建一個新的類實例
ReflectionClass::newInstanceArgs — 從給出的參數創建一個新的類實例。
ReflectionClass::newInstanceWithoutConstructor — 創建一個新的類實例而不調用它的構造函數
ReflectionClass::setStaticPropertyValue — 設置靜態屬性的值
ReflectionClass::__toString — 返回 ReflectionClass 對象字符串的表示形式。
這些方法看著是靜態,其實可以 **$class->getShortName()** 直接使用。
我主要需要拿到類簡寫名和方法。
~~~
public function generate($classNames){
config('default_return_type', 'json');
// TODO 獲取待處理的類命名空間數據
// TODO 遍歷類 反射
// TODO 獲取類的信息 (名稱、方法列表)
// TODO 遍歷方法列表, 獲取方法的類型 和注釋
$outputs = [];
foreach ($classNames as $k => $className) {
$class= new\ReflectionClass($className);
$key = $class->getShortName();
// dump($key);
$outputs[$key] = $this->getClassAnnotation($class);
}
return $outputs;
}
~~~
getClassAnonation 方法就是我用來獲取類的全部方法的信息的方法。
#### 方法相關
拿到反射類之后 想獲取反射方法,得實例化 ReflectionMethod 類。
~~~
// 獲取類的注釋信息
public function getClassAnnotation($class){
$ret = [
'hasPublicMethods'=>0,
];
$ret['name'] = $class->getName();
$methods = $class->getMethods();
foreach ($methods as $key => $method) {
$class = $method->class;
$method_name = $method->name;
$rm = new \ReflectionMethod($class, $method_name);
// 忽略構造和析構
if($rm->isConstructor() || $rm->isDestructor()){
continue;
}
$foo = [];
$foo['docComment'] = $rm->getDocComment();
$foo['docComment_formated'] = $this->parseMethodDoc($foo['docComment']);
$foo['args'] = $rm->getParameters();
$foo['args_formated'] = $this->parseParameters($class, $method_name, $foo['args']);
if($rm->isPublic()){
$type = $rm->isStatic()? 'public_static' : 'public_public';
}else{
$type = $rm->isStatic()? 'private_static' : 'private_public';
}
// 只在hasPublicMethods 為0時更新值,保證設置1后不會被復寫為0
if(empty($ret['hasPublicMethods'])){
$ret['hasPublicMethods'] = stripos($type, '_public') !== false;
}
$foo['type'] = $type;
$ret['methods'][$method_name] = $foo;
}
return $ret;
// $className = 'think\\App';
// $class = new \ReflectionClass($className);
// config('default_return_type', 'json');
// 類名
// return $class->name;
// ReflectionClass 實例的一個字符串表示形式
// return $class->__toString();
// 同上
// return \ReflectionClass::export($className, 1);
// 獲取類常量
// return json_encode($class->getConstants());
// 獲取構造方法
// return $class->getConstructor();
// 類名相關
// var_dump($class->inNamespace());
// var_dump($class->getName());
// var_dump($class->getNamespaceName());
// var_dump($class->getShortName());
# 文件相關
// getFileName
// getExtensionName
// 屬性相關
// return $class->getDefaultProperties();
// return $class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED);
// const integer IS_STATIC = 1 ;
// const integer IS_PUBLIC = 256 ;
// const integer IS_PROTECTED = 512 ;
// const integer IS_PRIVATE = 1024 ;
// return $class->getStaticProperties();
// 類注釋
// return $class->getDocComment();
}
~~~
先獲取全部的類方法:
$class->getMethods();
官網的例子:
~~~
array(3) {
[0]=>
&object(ReflectionMethod)#2 (2) {
["name"]=>
string(11) "firstMethod"
["class"]=>
string(5) "Apple"
}
[1]=>
&object(ReflectionMethod)#3 (2) {
["name"]=>
string(12) "secondMethod"
["class"]=>
string(5) "Apple"
}
[2]=>
&object(ReflectionMethod)#4 (2) {
["name"]=>
string(11) "thirdMethod"
["class"]=>
string(5) "Apple"
}
}
~~~
在獲取時還可以傳屬性類型進行過濾:
~~~
<?php
class Apple {
public function firstMethod() { }
final protected function secondMethod() { }
private static function thirdMethod() { }
}
$class = new ReflectionClass('Apple');
$methods = $class->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_FINAL);
var_dump($methods);
?>
~~~
拿到方法后,我們需要獲得類的方法的公有私有、靜態等屬性。

因為我在顯示時做了方法不同類型的區分演示。
~~~
$rm = new \ReflectionMethod($class, $method_name);
// 忽略構造和析構
if($rm->isConstructor() || $rm->isDestructor()){
continue;
}
~~~
先過濾掉構造和析構方法。
~~~
$foo = [];
$foo['docComment'] = $rm->getDocComment();
$foo['docComment_formated'] = $this->parseMethodDoc($foo['docComment']);
$foo['args'] = $rm->getParameters();
$foo['args_formated'] = $this->parseParameters($class, $method_name, $foo['args']);
~~~
我先獲取了原有方法的文檔信息和參數信息,并且按照我需要的進行格式化。
獲取參數的要注意,返回的是參數數組
官方示例:
~~~
<?php
public static function fire_theme_method($class, $method)
{
$fire_args=array();
$reflection = new ReflectionMethod($class, $method);
foreach($reflection->getParameters() AS $arg)
{
if($_REQUEST[$arg->name])
$fire_args[$arg->name]=$_REQUEST[$arg->name];
else
$fire_args[$arg->name]=null;
}
return call_user_func_array(array($class, $method), $fire_args);
}
?>
~~~
主要咱獲取到參數名稱 然后結合methodName 去實例化 ReflectionParameter 來獲取參數信息
#### 類的參數信息
~~~
public function parseParameters($class, $method, $args){
if($args){
$args_str = [];
foreach ($args as $key => $arg) {
$p = new \ReflectionParameter(array($class, $method), $key);
// 判斷是否引用參數
if($p->isPassedByReference()){
$arg_str_new = "&\$".$p->getName();
}else{
$arg_str_new = "\$".$p->getName();
}
if ($p->isOptional() && $p->isDefaultValueAvailable()) {
$a_clsss = $class;
// 獲取某些內部類的參數會拋異常,且異常時$class會變化不是我們想知道的哪個類方法一場了
try{
$defaul = $p->getDefaultValue();
$arg_str_new .= is_array($defaul) ? ' = '. '[]': ' = '. var_export($defaul, 1);
}catch(\Exception $e){
trace($p->isVariadic());
trace($a_clsss.'/'.$method.'_'.$key);
}
}
$args_str[] = $arg_str_new;
}
return implode(', ', $args_str);
}
return '';
}
~~~
有的方法沒參數直接返回空,有的有參數,咱們想拼接處 參數字符串 比如
~~~
<?php
class A{
function foo($a){
}
}
~~~
php不會返回` foo($a)`這串的。得自己處理
主要留意參數的 是否有默認值、是否引用
* getName
* isOptional
* isDefaultValueAvailable
* getDefaultValue
在獲取默認值參數的時候 我發現有的時候會拋異常,而且都報的是內部類的。
所以一定要try catch 去獲取默認值。
至此 整個獲取某些類的 方法及方法注釋和參數信息 的功能全部實現。
大家如果想獲取某一組class的 速查表 只需 修改get_core_class返回值,只要是tp5里自動加載的類。都可以解析。
整體思路,根據類數組獲取方法->方法文檔、獲取參數->獲取參數信息
這樣,我就可以安心的偷懶了。下次tp升級,我直接composer update一下,刷新一下首頁,拿到靜態html 更新到我的 gh-pages 分支。就完成了新框架速寫表的更新。
一勞永逸啊。
## 尾聲
具體源碼參考 :https://github.com/yangweijie/thinkphp-lts
查看速查表 直接:https://yangweijie.github.io/thinkphp-lts/

歡迎給我提供建議。