title: 聊一聊 php 代码提示

date: 2017-8-25 15:05:49

这次我们来聊一聊 php 的代码提示, 不使用 IDE 的同学也可以瞧瞧看, PHP IDE 推荐 phpstorm.

phpstorm 使用代码提示非常简单, 只需要将代码提示文件放到项目中就好, 我目前放到 vendor/ 目录下

起源

最近开发的项目中, 有使用到 PHP 魔术方法 和 单例模式, 导致了需要代码提示的问题

最近在尝试用 swoole 写 tcp server, 有需要用到 swoole IDE helper, swoole wiki首页就有推荐

数据库模型

在 laravel 中, 如果有一张数据表 lessons 如下:

CREATE TABLE `lessons` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,

`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,

`intro` text COLLATE utf8mb4_unicode_ci NOT NULL,

`image` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,

`published_at` timestamp NOT NULL,

`created_at` timestamp NULL DEFAULT NULL,

`updated_at` timestamp NULL DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

那么可以建立一个 Lesson 模型和他对应:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Lesson extends Model

{

//

}

之后, 我们就可以直接使用下面的方法了:

$lesson = new Lesson();

$lesson->title = 'far'; // set

$lesson = Lession::find(1);

echo $lesson->title; // get

这样写是不是很舒服, 或者说很「优雅」?

而实现起来, 非常简单, __get() / __set() 就可以了:

// laravel 文件: Illuminate\Database\Eloquent\Model

/**

* Dynamically retrieve attributes on the model.

*

* @param string $key

* @return mixed

*/

public function __get($key)

{

return $this->getAttribute($key);

}

/**

* Dynamically set attributes on the model.

*

* @param string $key

* @param mixed $value

* @return void

*/

public function __set($key, $value)

{

$this->setAttribute($key, $value);

}

在 laravel 中, 这样的实现方式随处可见, 比如:

// Illuminate\Http\Request $request

$request->abc; // 获取 http 请求中的 abc 参数, 无论是 get 请求还是 post 请求

$request->get('abc'); // 和上面等效

好了, 原理清楚了. 写起来确实「舒服」了, 但是, 代码提示呢? 难道要自己去数据库查看字段么?

在我们的另一个使用 hyperframework框架的项目中, 我们使用了 代码自动生成的方法:

// lib/Table/Trade.php 文件

namespace App\Table;

class Trade

{

public function getId() {

return $this->getColumn('id');

}

public function setId($id) {

$this->setColumn('id', $id);

}

...

}

// lib/Model/Trade.php 文件

namespace App\Model;

use App\Table\Trade as Base

class Trade extends BaseTable

{

...

}

// 这样我们就可以愉快的使用下面代码了

$trade = new Trade();

$trade->setId(1); // set

$trade = Trade::find(1);

$trade->getId(); // get

上面的 lib/Table/Trade.php 文件使用一个 php 脚本, 读取 mysql 中 information_schema.COLUMNS 的记录, 然后处理字符串生成的. 但是, 缺点也非常明显:

多了一个脚本需要维护

字段修改了, 需要重新生成

代码结构中, 多了一层 Table 层, 而这层其实就只干了 get / set

虽然有了代码提示了, 这样做真的好么? 那好, 我们来按照上面的套路改造一下:

// lib/Models/BaseModel.php

namespace App\Models;

use Hyperframework\Db\DbActiveRecord;

class BaseModel extends DbActiveRecord

{

// 获取 model 对应的数据库 table 名

public static function getTableName()

{

// 反射, 这个后面会讲到

$class = new \ReflectionClass(static::class);

return strtolower(preg_replace('/((?<=[a-z])(?=[A-Z]))/', '_', $class->getShortName()));

}

public function __get($key) {

return $this->getColumn($key);

}

public function __set($key, $value) {

$this->setColumn($key, $value);

}

}

// lib/Models/User.php

namespace App\Models;

class User extends BaseModel

{

...

}

好了, 问题又来了, 代码提示怎么办? 这样常见的问题, 当然有成熟的解决方案:

laravel-ide-helper: laravel package, 用来生成 ide helper

上面 Lesson model 的问题, 就可以这样解决了, 只要执行 php artisan ide-helper:models, 就会帮我们生成这样的文件:

namespace App{

/**

* App\Lesson

*

* @property int $id

* @property string $title

* @property string $intro

* @property string $image

* @property string $published_at

* @property \Carbon\Carbon|null $created_at

* @property \Carbon\Carbon|null $updated_at

* @method static \Illuminate\Database\Eloquent\Builder|\App\Lesson whereCreatedAt($value)

* @method static \Illuminate\Database\Eloquent\Builder|\App\Lesson whereId($value)

* @method static \Illuminate\Database\Eloquent\Builder|\App\Lesson whereImage($value)

* @method static \Illuminate\Database\Eloquent\Builder|\App\Lesson whereIntro($value)

* @method static \Illuminate\Database\Eloquent\Builder|\App\Lesson wherePublishedAt($value)

* @method static \Illuminate\Database\Eloquent\Builder|\App\Lesson whereTitle($value)

* @method static \Illuminate\Database\Eloquent\Builder|\App\Lesson whereUpdatedAt($value)

* @mixin \Eloquent

*/

class Lesson extends \Eloquent {}

}

通过注释, 我们的代码提示, 又回来了!

Facade 设计模式 / 单例设计模式

了解 laravel 的话, 对 Facede 一定不陌生, 不熟悉的同学, 可以通过这篇博客 设计模式(九)外观模式Facade(结构型) 了解一下.

现在来看看, 如果我们需要使用 redis, 在 laravel 中, 我们可以这样写:

Redis::get('foo');

Redis::set('foo', 'bar');

底层依旧是通过 ext-redis 扩展来实现, 而实际上, 我们使用 ext-redis, 需要这样写:

$cache = new \Redis();

$cache->connect('127.0.0.1', '6379');

$cache->auth('woshimima');

$redis->get('foo');

$redis->set('foo', 'bar');

2 个明显的区别: 1. new 不见了(有时候会不会感觉 new 很烦人); 2. 一个是静态方法, 一个是普通方法

如果稍微了解一点设计模式, 单例模式 肯定听过了, 因为使用场景实在是太普遍了, 比如 db 连接, 而且实现也非常简单:

// 简单实现

class User {

private static $_instance = null; // 静态变量保存全局实例

// 私有构造函数,防止外界实例化对象

private function __construct() {}

// 私有克隆函数,防止外办克隆对象

private function __clone() {}

//静态方法,单例统一访问入口

public static function getInstance() {

if (is_null ( self::$_instance ) || isset ( self::$_instance )) {

self::$_instance = new self ();

}

return self::$_instance;

}

}

// 使用

$user = User::getInstance();

好了, 关于 new 的问题解决了. 接下来再看看静态方法. 在我们的另一个使用 hyperframework框架的项目中, 我们也实现了自己的 Redis service 类:

// lib/Services/Redis.php 文件

namespace App\Services;

use Hyperframework\Common\Config;

use Hyperframework\Common\Registry;

class Redis

{

/**

* 将 redis 注册到 Hyperframework 的容器中

* 容器这个概念先留个坑, 下次讲 laravel 核心的时候, 再一起好好讲讲

* 这里只要简单理解我们已经实现了 redis 的单例模式就好了

*/

public static function getEngine()

{

return Registry::get('services.redis', function () {

$redis = new \Redis();

$redis->connect(

Config::getString('redis.host'),

Config::getString('redis.port'),

Config::getString('redis.expire')

);

$redisPwd = Config::getString('redis.pwd');

if ($redisPwd !== null) {

$redis->auth($redisPwd);

}

return $redis;

});

}

// 重点来了

public static function __callStatic($name, $arguments)

{

return static::getEngine()->$name(...$arguments);

}

// k-v

public static function get($key)

{

return static::getEngine()->get($key);

}

}

拍黑板划重点: __callStatic(), 就是这个魔术方法了. 另外再看看 ...$arguments, 知识点!

仔细看的话, 我们下面按照 ext-redis 中的方法, 再次实现了一次 $redis->get() 方法, 有 2 点理由:

魔术方法会有一定性能损失

我们又有代码提示可以用了, 只是要用啥, 就要自己把 ext-redis 里的方法封装一次

好了, 来看看我们的老朋友, laravel 是怎么实现的吧:

laravel: Illuminate\Support\Facades\Facade

// 获取 service 的单例

protected static function resolveFacadeInstance($name)

{

if (is_object($name)) {

return $name;

}

if (isset(static::$resolvedInstance[$name])) {

return static::$resolvedInstance[$name];

}

return static::$resolvedInstance[$name] = static::$app[$name];

}

// 魔术方法实现静态函数调用

public static function __callStatic($method, $args)

{

$instance = static::getFacadeRoot();

if (! $instance) {

throw new RuntimeException('A facade root has not been set.');

}

return $instance->$method(...$args);

}

然后, 使用上面的 package, 执行 php artisan ide-helper:generate, 就可以得到代码提示了:

namespace Illuminate\Support\Facades {

...

class Redirect {

/**

* Create a new redirect response to the "home" route.

*

* @param int $status

* @return \Illuminate\Http\RedirectResponse

* @static

*/

public static function home($status = 302)

{

return \Illuminate\Routing\Redirector::home($status);

}

/**

* Create a new redirect response to the previous location.

*

* @param int $status

* @param array $headers

* @param mixed $fallback

* @return \Illuminate\Http\RedirectResponse

* @static

*/

public static function back($status = 302, $headers = array(), $fallback = false)

{

return \Illuminate\Routing\Redirector::back($status, $headers, $fallback);

}

...

}

...

}

通过反射实现 swoole 代码提示

通过反射实现 swoole 代码提示来自此项目 flyhope/php-reflection-code, 核心代码其实很简单, 如下

static public function showDoc($class_name) {

try {

// 初始化反射实例

$reflection = new ReflectionClass($class_name);

} catch(ReflectionException $e) {

return false;

}

// 之后都是字符串处理之类的工作了

// Class 定义

$doc_title = ucfirst($class_name) . " Document";

$result = self::showTitle($doc_title);

$result .= self::showClass($class_name, $reflection) . " {\n\n";

// 输出常量

foreach ($reflection->getConstants() as $key => $value) {

$result .= "const {$key} = " . var_export($value, true) . ";\n";

}

// 输出属性

foreach ($reflection->getProperties() as $propertie) {

$result .= self::showPropertie($propertie) . "\n";

}

//输出方法

$result .= "\n";

foreach($reflection->getmethods() as $value) {

$result .= self::showMethod($value) . "\n";

}

// 文件结尾

$result .= "}\n";

return $result;

}

再回到上面我们使用反射的例子:

// 获取 model 对应的数据库 table 名

public static function getTableName()

{

// 反射, 这个后面会讲到

$class = new \ReflectionClass(static::class);

return strtolower(preg_replace('/((?<=[a-z])(?=[A-Z]))/', '_', $class->getShortName()));

}

注意, 这里要使用 static, 如果你使用 self 得到的就是 BaseModel 了. 至于一个简单的理解 static & self 的方式: static 是指当前内存中运行的实例, 所以永远都是 所见即所得.

魔术方法的性能损失

本来我也想做一下 profile 的, 还折腾起了 xhprof 和 xdebug, 但是其实可以简单的测试:

$start = microtime();

dosomething();

echo microtime() - $start; // 单位: 微秒

感谢这位仁兄做的测试 PHP 魔术方法性能测试, 实测结果下来性能损失在 10us 内, 这个数量级, 我个人认为除非少数极端要求性能的场景, 完全是可以接受的.

最后, 补充一下 单例模式 的优缺点:

优点:

改进系统的设计

是对全局变量的一种改进

缺点:

难于调试

隐藏的依赖关系

无法用错误类型的数据覆写一个单例

mysql代码提示_聊一聊 php 代码提示相关推荐

  1. 使用python代码表白_用Python代码花式表白小姐姐,她会不会生气啊

    对于程序员来说,情人节表白当然少不了代码打辅助.小笨聪这次给大家带来两份不同的表白代码(Python版),原理都很基础,第一份主要用到pygame库,第二份主要用到turtle库[附:小笨聪的运行环境 ...

  2. python函数增强代码可读性_如何提高代码的可读性 学习笔记

    本文整理自 taowen 师傅在滴滴内部的分享. 1.Why 对一线开发人员来说,每天工作内容大多是在已有项目的基础上继续堆代码.当项目实在堆不动时就需要寻找收益来重构代码.既然我们的大多数时间都花在 ...

  3. java重复代码重构_重构重复代码

    java重复代码重构 As a software engineer working on a large project, you'll likely be asked to do cleanup w ...

  4. Python_代码风格_合理分解代码,提高代码可读性

    一.什么是PEP8 PEP 是 Python Enhancement Proposal 的缩写,翻译过来叫"Python 增强规范".正如我们写文章,会有句式.标点.段落格式.开头 ...

  5. 代码命名_肮脏的代码问题:通过良好的命名习惯来改善您的游戏

    代码命名 规则:每当您命名变量,函数或类时,都要询问它是否简洁 , 诚实 , 富有表现力和完整 . 说明 一贯地写好名字很难,但是阅读和理解坏名字却很难. 使用好名字意味着我们允许读者停止阅读更高级别 ...

  6. ssms2008 代码自动提示_使用 SSMS 的提示和技巧 - SQL Server Management Studio (SSMS) | Microsoft Docs...

    使用 SQL Server Management Studio (SSMS) 的提示和技巧Tips and tricks for using SQL Server Management Studio ...

  7. mysql小鸭子_可读代码编写炸鸡十一 - 小黄鸭从你的心里游到脑子里

    可读代码编写炸鸡十一 - 小黄鸭从你的心里游到脑子里 多选参数推荐搜索 数据结构与算法 可读代码编写 Java Redis MySQL 大家好,我是多选参数的大炮. 可读代码编写的炸鸡很快要写到头了, ...

  8. html简单网页代码表白_表白网页代码,不会代码也可以操作,告别单身

    表白,或称告白意为向他人表示自己的想法或心意,特指表达爱意,又称示爱,在这种情况下通常被认为是建立恋爱关系的方式. 表白可以通过各种方式,如当面表达,写情书,打电话,网络即时通讯,送礼物如送花等.这些 ...

  9. 简单编程代码表白_用简单代码实现抖音表白神器

    话不多说,直接进入教学 代码部分: from tkinter import * from tkinter import messagebox import randomdef no_close():r ...

最新文章

  1. java 搜索机制_Java爬虫搜索原理实现
  2. 【Spring框架家族】Spring--Security权限控制密码加密
  3. (2014年2月7日升级)Ubuntu-14.04-Alpha2-32位简体中文优化封装版
  4. python 遍历_Python手撕广度优先遍历
  5. 一个免费的网站长链接转短链接的工具
  6. 前端学习(540):node.js简介
  7. 哈哈哈,我来啦~~(纯灌水帖)
  8. php中下载csv文件怎么打开,php – 下载csv文件
  9. hosts—20111010
  10. 主题颜色提取 ——— Palette
  11. 在微信小程序开发中遇见的bug及解决方案
  12. 测试用例入门(一)-如何编写用例标题、前置条件、测试步骤和预期结果?
  13. 快速写出高质量IEEE论文的经验总结
  14. 【VPP】 VPP CLI 命令
  15. 写给程序猿的把妹指南:概述篇
  16. 内置CRC于文本文件中的方法
  17. 北斗定位模块成就智慧物流
  18. 都在讲降本增效,优化师如何借力“卷起来”?
  19. CleanMyMac X软件不为人知的实用功能技巧
  20. c语输入单引号_c语言单引号的用法指导

热门文章

  1. linux命令dh f,linux 下 find 命令的高级用法
  2. sprintf函数_C语言源代码展示:常用转换函数实现原理
  3. hj212协议如何和php通讯,5G/4G边缘计算网关如何实现HJ212协议上报
  4. 计算机音乐夜里,电脑自动播放音乐提醒你起床,晚上自动关机!
  5. onu光功率多少是正常_熔融拉锥型(FBT) VS平面波导型(PLC)光分路器,如何选择?...
  6. Cron 触发器及相关内容 (第一部分)
  7. 基于JAVA+SpringMVC+Mybatis+MYSQL的医院在线预约挂号系统
  8. 基于JAVA+SpringMVC+Mybatis+MYSQL的家乡美管理系统
  9. c# xls 复制一行_c# – 将excel工作簿中的第一行复制到新的Excel工作簿
  10. linux mysql 内存监控_MySQL监控性能的一些方法总结