ThinkPHP

ThinkApiAdmin 回调机制实现

ThinkApiAdmin 是糅合了thinkadmin和apiadmin的一个合体版本。均采用上述两种框架的v2版本来进行整合的。其中有一个比较重要的父类:BasicAdmin

此时仅来说一下它其中的一个父类方法:_list

/**
 * 列表集成处理方法
 * @param Query $dbQuery 数据库查询对象
 * @param bool $isPage 是启用分页
 * @param bool $isDisplay 是否直接输出显示
 * @param bool $total 总记录数
 * @param array $result
 * @return array|mixed
 * @throws \think\db\exception\DataNotFoundException
 * @throws \think\db\exception\ModelNotFoundException
 * @throws \think\exception\DbException
 */
protected function _list($dbQuery = null, $isPage = true, $isDisplay = true, $total = false, $result = [])
{
    $db = is_null($dbQuery) ? Db::name($this->table) : (is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery);
    // 列表排序默认处理
    if ($this->request->isPost() && $this->request->post('action') === 'resort') {
        $data = $this->request->post();
        unset($data['action']);
        foreach ($data as $key => &$value) {
            if (false === $db->where('id', intval(ltrim($key, '_')))->setField('sort', $value)) {
                $this->error('列表排序失败, 请稍候再试');
            }
        }
        $this->success('列表排序成功, 正在刷新列表', '');
    }
    // 列表数据查询与显示
    if (null === $db->getOptions('order')) {
        $fields = $db->getTableFields($db->getTable());
        in_array('sort', $fields) && $db->order('sort asc');
    }
    if ($isPage) {
        $rows = intval($this->request->get('rows', cookie('rows')));
        cookie('rows', $rows >= 10 ? $rows : 20);
        $page = $db->paginate($rows, $total, ['query' => $this->request->get('', '', 'urlencode')]);
        list($pattern, $replacement) = [['|href="(.*?)"|', '|pagination|'], ['data-open="$1"', 'pagination pull-right']];
        list($result['list'], $result['page']) = [$page->all(), preg_replace($pattern, $replacement, $page->render())];
    } else {
        $result['list'] = $db->select();
    }
    if (false !== $this->_callback('_data_filter', $result['list']) && $isDisplay) {
        !empty($this->title) && $this->assign('title', $this->title);
        return $this->fetch('', $result);
    }
    return $result;
}

前面基本是都是数据的刷新,排序等一些方法。注意最后面几行

if (false !== $this->_callback('_data_filter', $result['list']) && $isDisplay) {
    !empty($this->title) && $this->assign('title', $this->title);
    return $this->fetch('', $result);
}

这几行代码就是实现回调机制的,回调如果用C/C++来理解的话应该是指针传值,通过指针访问函数。例如下面的代码:

void callBack(l callback)
{
    /** NULL */
}
void subCallback(char *s) 
{
    /** NULL */
} 
callBack(&subCallback);

上述代码是一段回调,如果可以理解,那么就可以继续下去。如果没有学过指针,那就不要在意了。只要知道回调函数是指的是将函数已参数的形式传递并调用就好了。

可以看到它是首先调用了callback这个函数传递了一个字符串和一个关联数组的内容,掉用完后判断一下是否等于true 并且 判断是否可以显示。那么关键的就是看看这个callback函数了。

    /**
     * 当前对象回调成员方法
     * @param string $method
     * @param array|bool $data
     * @return bool
     */
    protected function _callback($method, &$data)
    {
        foreach ([$method, "_" . $this->request->action() . "{$method}"] as $_method) {
            if (method_exists($this, $_method) && false === $this->$_method($data)) {
                return false;
            }
        }
        return true;
    }

这一句话:[$method, "_" . $this->request->action() . "{$method}"]  实际是一个索引数组。

array(2) {
  [0] => string(12) "_data_filter"
  [1] => string(18) "_index_data_filter"
}

当第二次循环到_index_data_filter的时候就可以进行调用了

 /**
     * 列表数据处理
     * @param $list
     */
    protected function _index_data_filter(&$list)
    {
        dump("www");die();
        $alert = [
            'type' => 'danger',
            'title' => '操作安全警告(谨慎刷新接口路由)',
            'content' => '新增或编辑接口后必须刷新接口路由才能正常访问!请根据实际情况刷新路由!'
        ];
        $tags = Db::name($this->table_api_group)->column('id,name');
        $handlers = Db::name($this->table_admin)->column('id,username');
        foreach ($list as &$vo) {
            $vo['tags_list'] = [];
            $vo['handler_name'] = Db::name($this->table_admin)->where('id', $vo['handler'])->value('username');
            foreach (explode(',', $vo['gid']) as $tag) {
                if ($tag !== '' && isset($tags[$tag])) {
                    $vo['tags_list'][$tag] = $tags[$tag];
                } elseif ($tag !== '') {
                    $vo['tags_list'][$tag] = $tag;
                }
            }
        }
        $this->assign(['handlers' => $handlers, 'alert' => $alert, 'tags' => $tags]);
    }

实际就是执行了上述代码。具体的执行指令就是: $this->$_method($data)php5+这种写法是允许的即直接指向一个变量,这个变量如果是函数名的话,则会直接运行这个函数。虽然运行这个函数的上下文句柄是Row.php文件,但是其实你是基于BasicAdmin这个类的。而这个函数定义在BasicAdmin类中,所以你可以进行引用。


流程:parent::_list(ROW模块) -> _callback(BasicAdmin模块 -> _index_data_filter (ROW模块)


php5+的版本中你还可以直接 new $var 。这样来初始化类,即使这个变量只存了一个类名,但是你依然可以引用。

ThinkPHP5 Mac 安装 mkdir 错误

安装的时候提示:mkdir(): Permission denied 。大致看了一下说是没有权限进行文件读写,如果知道ThinkPHP5的目录结构的话,就会了解有一个runtime的文件夹,这个文件夹主要是运行时产生的一些临时文件的存放位置。于是给这个文件配置777的权限。

进入:cd /Applications/XAMPP/xamppfiles/htdocs/thinkphp5

输入:chmod -R 777 runtime




ThinkPHP5 Extend 扩展写法

首先ThinkPHP是基于PHP来编写的一套框架,所以它的扩展自动加载的方法也必须依托于PHP,而PHP对自动加载进行了PSR-4规则限定。下面两个是中英文说明。

英文:https://www.php-fig.org/psr/psr-4/

中文:https://www.jianshu.com/p/e0a33214688b


了解了规则后编写就相对简单,ThinkPHP扩展的编写过程,实际就是一个类文件的编写过程。只不过不能避免的要加上命名空间等等内容,这些内容上述说明也有写。这里主要说一下父类或者超类的问题。


父类是可以继承的类,在其他语言中可以存在多继承,而PHP中只能单继承,即只继承一个父类。


类的构造方法:__construct 这个方法是你在New一个类的时候,由解释器进行类初始化的时候第一个执行的页面函数。这里可以进行一些初始化的行为。


注意的是:如果在子类中定义与父类中相同的函数或者方法。那么父类中相同的函数或方法会被覆盖,即不会执行。哪怕他是构造函数等一些魔术方法。所以如果你在子类中定义了__construct那么父类不会被执行。


例子:

/** 父类 */
class TopParent
{
    public function __construct()
    {
        echo "TopParent";
    }
}
/** 子类 */
class SubClass extends TopParent
{
     public function __construct(Request $request = null)
     {
         echo "SubClass";
     }
}
/** Tp引用 */
class Index extends SubClass
{
    public function index()
    {
        echo "";
    }
}

结果输出:

*~:SubClass


这里定义了一个SubClass类,并且是基于Controller的子类。基于ThinkPHP5的运行机制,会将要给Controller的初始化参数,即__construct参数所需要的值传递给SubClass子类。在你新建这个类的时候IDE会自动帮你加上一句话:parent::__construct($request);这句话就是手动运行父类的构造方法。因为父类的构造方法不会再自己执行。当然你也可以不执行父类的构造方法。但是如果不执行的话,如果你的派生类用到了父类的方法。而这个方法恰恰需要父类自己进行初始化。那么很有可能会报错。所以在执行一遍父类的构造方法很有必要。


对于为什么控制器可以有一个默认的$request参数。这里可以参考如下代码:

文件路径:thinkphp\library\think\Loader.php

/**
     * 实例化(分层)控制器 格式:[模块名/]控制器名
     * @access public
     * @param  string $name         资源地址
     * @param  string $layer        控制层名称
     * @param  bool   $appendSuffix 是否添加类名后缀
     * @param  string $empty        空控制器名称
     * @return object
     * @throws ClassNotFoundException
     */
    public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
    {
        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
        if (class_exists($class)) {
            return App::invokeClass($class);
        }
        if ($empty) {
            $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
            if (class_exists($emptyClass)) {
                return new $emptyClass(Request::instance());
            }
        }
        throw new ClassNotFoundException('class not exists:' . $class, $class);
    }