跳至主要内容

Yaf 框架基础指南

博主有话说:本篇博客本来主题是《Yaf 框架实战》,准备在其他电子书平台发布。由于,平台觉得过多涉及基础而放弃了合作。于是,我现在把该电子发布出来。如果,你在学习中遇到了什么问题,可以留言告诉我。
众所周知,Yaf 框架的作者是惠新宸。英文名: Laruence。在大部分 PHPer 或其他 IT 领域的开发者心里,都亲切地喊他“鸟哥”。如果你不了解 Yaf 框架的作者,仅凭上面的介绍,你一定觉得他并没有多少出奇的地方。那么,我接下来要讲鸟哥的其他名头:
PHP 开发组核心成员,Zend 顾问,PHP 7 主要开发者,Yaf、Yar、Yac 等开源项目作者。曾任新浪微博架构师,现任链家技术副总裁。中国加入 PHP 开发组第一人。
本书讲的就是他的开源代表作:Yaf。
Yaf 目前在国内大小公司都有应用。比如大家熟知的百度、微博、腾讯。
本书你能学到什么?
1)Yaf 框架在实际开发中的熟练使用。
2)PHP Cli 命令行模式的一些编程心得。该知识不局限于 Yaf 框架。在其他框架依然非常有用。
3)Redis 相关操作。如:锁、队列案例。该知识也不局限于 Yaf 框架。在其他框架或语言也能有指导作用。
4)API 设计。本书的 API 设计理念并不局限于 Yaf 框架。在其他框架与语言里面,依然具有实际开发指导作用。
5)Session 分布式存储。在当今互联网应用当中。Session 分布式存储可谓是 PHPer 必学技巧了。
本书适合阅读对象:
1)对 Yaf 框架感兴趣的 PHPer
2)对 Yaf 框架掌握不够牢的 PHPer
3)对 Yaf 框架在实际开发中缺乏最佳实践的 PHPer

第一章 Yaf 框架基础

1.1 Yaf 特点

Yaf 框架的核心特点就一个:用 C 语言编写的 PHP 框架。
说到这个特点,很多人就会问:有那么多用 PHP 编写的框架,诸如 ZendFramework、Laravel、Yii,为什么要用 C 来开发一款 PHP 框架呢?
这个问题也是本书作者初识 Yaf 框架时在大脑中出现的第一个问题。关于这个问题,我们的 Yaf 框架作者这样解释的:
随着 PHP 的发展, PHP 框架层出不穷, 但到底用不用 PHP 框架, 还存在很大的争论, 反对者认为使用框架会降低性能, 
经常举例的就是Zend Framework. 而支持者则认为,采用框架能提高开发效率, 损失点性能也是值得的.

而这些也正是公司内框架种类繁多的一个原因, 有的项目组为了性能而选择某些框架, 而另外一些项目组, 
则为了更好的封装选择了另外的框架。

那, 有没有俩全的办法呢? 也就是说, 有没有那么一个框架, 既不会有损性能, 又能提高开发效率呢?

Yaf, 就是为了这个目标而生的.

Yaf 有着和 Zend Framework 相似的 API,相似的理念, 而同时又保持着对 Bingo 的兼容,以此来提高开发效率, 规范开发习惯。
本着对性能的追求,Yaf 把框架中不易变的部分抽象出来,采用 PHP 扩展实现(C 语言),以此来保证性能.在作者自己做的简单测试中,
Yaf 和原生的 PHP 在同样功能下,性能损失小于 10%,而和 Zend Framework 的对比中,Yaf 的性能是 Zend Framework 的 50-60 倍。
作者解释的引用地址:http://www.laruence.com/manual/preface.html
根据我这十年在多家公司的开发经历,也印证了 Yaf 框架作者的这些说法。在 PHP 开发群体内,对框架有着不同的看法与声音。有的人看重简洁与性能,有的人看重开发效率而愿意牺牲一点性能。我们不能说谁对谁错,关键得看这个框架是否能完成开发任务,自己是否喜欢这种理念和这个框架。
Oh!忘记说了。Yaf 框架应该也算是 PHP 领域第一款用 C 语言开发的 PHP 框架。

1.2 Yaf 框架的优点

我们要使用一款框架,肯定是看中了框架的优点才会用它。那么,我们就来了解一下 Yaf 框架到底有哪些优点:
  1. 用 C 语言开发的 PHP 框架,相比原生的 PHP,几乎不会带来额外的性能开销。
  2. 因为 Yaf 是以 PHP 扩展形式加载,所以,所有框架类在 PHP 启动的时候就已经加载,并常驻内存。
  3. 内建多种路由,可以兼容目前常见的各种路由协议。同时也支持自定义路由。
  4. 高性能的视图引擎。同时也支持自定义视图引擎。比如,Smarty,Twig。
  5. 在框架本身,对危险的操作习惯做了禁止。
  6. 强大而又高度灵活的配置文件支持。并支持缓存配置文件,避免复杂的配置结构带来的性能损失。
  7. 更快的执行速度,更少的内存占用。这个特性是 C 编写的 PHP 框架所获得的优势。

1.3 Yaf 框架的缺点

一款框架有优点就有缺点,有些缺点是客观的,有些优点是主观的。通过各种渠道我收集了如下:
  1. Yaf 框架未实现 ORM。
  2. Yaf 框架采用 C 语言编写。遇到 BUG 自己无法修复。毕竟,会写 PHP 的不一定会 C。
  3. 没有成熟的视图引擎。因为 Yaf 框架采用原生 PHP 来做视图解析引擎。
  4. 不支持 RESTFUl。PHP 社区认为这是一款成熟框架必备的东西。
  5. 运维不好部署。毕竟,要多安装一个 Yaf 框架。
针对上述几点,我觉得有必要来说明一下为什么坚持学习并使用 Yaf 框架:
1) Yaf 框架为何实现 ORM?
Yaf 框架作者曾经说过:“Yaf 的理念是简洁与性能。简洁是因为 Yaf 框架只实现了核心的 MVC。性能是因为采用 C 语言编写。Yaf 要实现一个 ORM 其实很简单。开源的 ORM 包也有很多。觉得暂时没有必要实现。如果后续确实有需要会实现。其次,PDO 已经是很优秀的数据库封装了。”
我比较认同 Yaf 框架作者的说法的另一个原因是:ORM 在编写简单的增删改查的时候,确实可以帮我们省掉很多代码,提高开发效率。但是,我们实际开发的时候,业务往往是简单与复杂地交织一起的。如果用 ORM 来编写就会导致代码量上并不比写纯 SQL 来得少。有时候 ORM 会帮我们优化一些 SQL,导致优化顺序不合理导致索引使用失效。这个是通过我们在实际开发中 DBA 反馈得到的教训。
2) Yaf 框架采用 C 语言编写,有 BUG 难修复?
我曾经也有这种想法。后来我也释然了。因为,Yaf 仅实现了核心的 MVC,并且在新浪微博、百度得到了充分 的压测。Yaf 框架功能小且经过大厂检验过,自然出现 BUG 的概率是非常低的。即使出现,也如我们的 Yaf 框架作者所说:他能保证最短时间内修复。
毕竟,Yaf 框架这几年并未增加新功能。自然也不会引入其他的功能 BUG 需要修复。所以,大家放心使用。
3) 没有成熟的视图引擎?
有很多人使用惯了诸如 Laravel、ThinkPHP 等框架。使用 Yaf 的时候就会有这样一种疑问。其实,Yaf 是一个非常注意性能的框架。任何框架的视图引擎最终都会翻译成 PHP 原生代码来执行。而我们的 Yaf 框架直接采用原生 PHP 来写视图。就省掉了中间从模板翻译为 PHP 原生代码这一步。自然性能优异。
其次,Yaf 框架提供了一个视图的接口。通过该接口你可以使用其他知名的开源模板引擎:Smarty、Twig。
4) 不支持 RESTFul?
这个本书作者的判断比较主观。因为,我在实际开发项目中不会采用 RESTFul 风格来编写 API 接口。而是采用阿里风格。这个我们后面的章节会详细介绍。
其次,RESTFul 这种风格在国内被很多大牛所诟病。并且,很多公司在自己的 API 开发中,采用 RESTFul 时,并没有安全按照 RESTFul 来设计。只学其形,未得其神。这也是被大多数开发者所诟病的原因。当然,并不是说 RESTFul 不好,而是各有各好,最重要是喜欢。不能因为喜欢 RESTFul 而贬低其他设计风格。
5) 运维不好部署?
我觉得把这个问题当作问题就有点太牵强了。根据我的了解 Laravel、ThinkPHP 等框架在安装的时候,都会要求安装一堆必须的扩展。不安装的话,框架内部分功能会使用不了。
其次,现如今运维在安装 PHP 环境的时候,都是编写一套自动化脚本来安装。然后,用镜像批量部署到其他机器。所以,这个问题根本不是问题。

1.4 Yaf 框架安装

目前很多的 PHP 扩展,都会针对 PHP 5 系列版本与 PHP 7 系列版本做单独的优化设计。而我们的 Yaf 框架扩展也是。
(1) PHP 5.x 版本安装
Yaf 2 系列版本是针对 PHP 5 系列版本的。如果你的 PHP 版本是 PHP 5 系列的,那么请安装当前 Yaf 2 版本系列最新版本即可。
当前 Yaf 2 系列最新版本是:2.3.5。
$ wget https://pecl.php.net/get/yaf-2.3.5.tgz
$ phpize
$ ./configure
$ make && make install
(2) PHP 7.x 版本安装
Yaf 3 系列版本是针对 PHP 7 系列版本的。如果你的 PHP 版本是 PHP 7 系列的,那么请安装当前 Yaf 3 版本系列最新版本即可。
当前 Yaf 3 系列最新版本是:3.0.7。
$ wget https://pecl.php.net/get/yaf-3.0.7.tgz
$ phpize
$ ./configure
$ make && make install
注意:本小节假定你的系统是 Linux,并且已经安装了 PHP,同时 PHP 的 bin 目录已经加入系统 PATH 变量。不然, phpize 命令就会提示不存在。
(3) php.ini 加入 yaf.so 扩展
如果你忘记 php.ini 文件的位置,可以通过如下命令快速找到:
[root@localhost vhost]# php --ini
Configuration File (php.ini) Path: /usr/local/php72/etc
Loaded Configuration File:         /usr/local/php72/etc/php.ini
Scan for additional .ini files in: /usr/local/php72/etc/php.d
Additional .ini files parsed:      (none)
[root@localhost vhost]# 
这时候我们在 php.ini 末尾增加如下代码:
[yaf]
extension = yaf.so
yaf.use_namespace = 0
yaf.use_spl_autoload = 1
yaf.use_namespace = 0 是不开启 Yaf 框架类的命名空间模式。
yaf.use_spl_autoload = 1 是允许开启第三方类的加载。如我们可以通过 composer 加载第三方 PHP 包。
更多的 php.ini 级别的配置,我们后面会详细介绍。
(4) 验证扩展是否安装成功
有两种方式验证:
方式一:
[root@localhost vhost]# php -m|grep yaf
yaf
如下成功安装,执行上述命令会输出 yaf 。否则,那就是安装失败了。
方式二:
[root@localhost vhost]# php --ri yaf

yaf

yaf support => enabled
Version => 3.0.7
Supports => http://pecl.php.net/package/yaf

Directive => Local Value => Master Value
yaf.library => no value => no value
yaf.action_prefer => Off => Off
yaf.lowcase_path => Off => Off
yaf.use_spl_autoload => On => On
yaf.forward_limit => 5 => 5
yaf.name_suffix => On => On
yaf.name_separator => no value => no value
yaf.st_compatible => Off => Off
yaf.environ => product => product
yaf.use_namespace => Off => Off
个人推荐第二种方式,能清晰知道我们的扩展的配置信息。

第二章 第一个 Yaf 项目

Yaf 框架扩展我们已经成功安装。作为学习 Yaf 框架的同学来说,快速 hellod world 一把是最好不过的了。

2.1 项目目录结构

对于 Yaf 的应用,都应该遵循类似下面的目录结构。(本书作者注:这是 Yaf 框架作者对 Yaf 使用者的建议)
+ public
  |- index.php // 入口文件
  |- .htaccess // 重写规则    
  |+ css
  |+ img
  |+ js
+ conf
  |- application.ini // 配置文件   
+ application
  |+ controllers
     |- Index.php // 默认控制器
  |+ views    
     |+ index   // 控制器
        |- index.phtml // 默认视图
  |+ modules // 其他模块
  |+ library // 本地类库
  |+ models  // model目录
  |+ plugins // 插件目录
所以,我们的第一个 hello world 项目也要严格按照 Yaf 框架作者的建议在我们的电脑上创建一个这样的目录。
注意:目录与文件大小写的问题。

2.2 入口文件

入口文件是所有请求的入口。通常我们会通过 rewrite 进行重写,把所有请求都重定向到这个入口文件。
编辑 public/index.php 文件为如下代码:
<?php
define('APP_PATH', dirname(dirname(__FILE__)));
$app = new \Yaf_Application(APP_PATH . '/conf/application.ini');
$app->run();

2.3 控制器

编辑 application/controllers/Index.php 控制器文件为如下代码:
<?php
class IndexController extends Yaf_Controller_Abstract
{
    /**
     * 默认 Action
     */
    public function indexAction()
    {
        $this->getView()->assign("content", "Hello World");
    }
 }
有几点需要记住:
  1. Yaf 框架控制器的文件名没有 controller 字样。但是,在类名上必须有。且按照大驼峰命名。
  2. 每个操作必须有 Action。否则,无法通过浏览器访问该
  3. $this->getView() 是获取当前视图引擎的方法。这样我们就可以向模板当中传递数据。
  4. 控制器的文件名首字母必须大写。切记!

2.4 视图文件

视图文件也被称作为模板文件。只叫法迥异,都是指同一个东西。因为我们有一个默认的控制器里面的默认 Action 会调用模板。所以,我们需要在模板文件夹中创建。
application/views 目录下创建一个 index 目录,在 index 目录下创建一个 index.phtml 文件夹。
index.phtml 当中写入如下代码:
<html>
 <head>
   <title>Hello World</title>
 </head>
 <body>
  <?php echo $content;?>
 </body>
</html>
注意:我们的不管 Controller 和 Action 的名称是否是大写,对应的模板目录与模板文件名称必须是全部小写。记住了。这点非常重要!这点非常重要!这点非常重要!重要的事情说三遍!!!

2.5 项目配置文件

因为我们在入口文件当中加载了 conf/application.ini 配置文件。所以,我们要把配置文件写入该文件:
[product]
;支持直接写PHP中的已定义常量
application.directory = APP_PATH "/application/"
该配置的意思是指定 Yaf 框架去哪里加载 modules、controllers、views 等东西。
因为,我们在加载这个配置文件的时候没有指定配置文件 [section] 名称。所以,默认是 [product] 。也就是说在我们的 application.ini 文件当中必须有一个 [product] 节。

2.6 nginx/apache 重写配置

因为,我们需要将所有的请求全部重定向到入口文件当中。所以,我们需要重写配置。
(1) Nginx 的重写规则
server {
  listen 80;
  server_name  local.study.com;
  root   /data/web/study/public;
  index  index.php index.html index.htm;

  if (!-e $request_filename) {
    rewrite ^/(.*)  /index.php/$1 last;
  }
}
其中以下代码就是重写规则:
if (!-e $request_filename) {
    rewrite ^/(.*)  /index.php/$1 last;
}
记得要重启 Nginx 服务器噢。
$ nginx -s reload
(2) Apache 的重写规则
#.htaccess, 当然也可以写在httpd.conf
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php
如果是针对 httpd.conf 修改的,要记得重启 Apache 服务器噢。

2.7 访问

当我们做好所有一切之后,现在就可以在浏览器输入:
http://www.yourhostname.com
上述的域名指的是你在本地为学习 Yaf 框架所搭建的环境域名。
有的开发者在 Windows 开发的时候喜欢用一些集成环境。会把 Yaf 项目代码直接放到了 localhost 能访问到的目录下。假如,你给项目的名字为 study , 那么此时的访问地址为:
http://localhost/study
如果你像我一样是通过配置一个虚拟机的形式用一个域名指向了这个项目。假如,这个域名是 http://local.study.com 。那么直接在浏览器输入这个域名即可。
如果成功,将会输出“Hello World ”。
恭喜您!成功了!

第三章 Bootstrap

3.1 简介

Bootstrap,也叫做引导程序。它是 Yaf 提供的一个全局配置的入口,在 Bootstrap 中,你可以做很多全局自定义/初始化的工作。
如:记录访问日志,初始化数据库连接,初始化缓存等一切您想在程序启动但还未执行实际业务代码时想做的事情。

3.2 使用 Bootstrap

Bootstrap 的调用主要在入口文件当中。如下:
<?php
define('APP_PATH', dirname(dirname(__FILE__)));
$app = new \Yaf_Application(APP_PATH . '/conf/application.ini');
$app
    ->bootstrap()
    ->run();
大家会发现,在 2.2 小节的入口文件中,我们并未显示调用 bootstrap() 这个方法。是因为,如果要显示调用这个方法,就必须在 application 目录下创建一个 Bootstrap.php 脚本。并且该脚本当中必须包含一个继承了 Yaf_Bootstrap_Abstract 类的 Bootstrap 类。
这也是很多人学习 Yaf 框架时,调用了 bootstrap() 方法报错的原因所在。
所以,我们现在可以将 2.2 小节的入口文件更改为上述代码。然后创建 Bootstrap.php 脚本。

3.3 创建 Bootstrap 脚本

单独把创建 Bootstrap 脚本拎出来讲。是因为它真的太重要了。因为很多业务场景中,它可以解决我们很多的问题。
(1) 在 application 目录创建名为 Bootstrap.php 的脚本文件。
(2) 脚本文件必须包含一个继承 Yaf_Bootstrap_Abstract 类的 Bootstrap 类。
(3) Bootstrap 类中所有的方法必须以 _init 开头才会被调用。调用顺序为方法定义的顺序。切记!
(4) 每个方法都可以定义一个 Yaf_Dispatcher 类型的参数。该参数与路由分发/请求相关。非常有用!!!
一个 Bootstrap 例子:
<?php
/**
 * 所有在 Bootstrap 类中, 以 _init 开头的方法, 都会被 Yaf 调用
 * 这些方法, 都接受一个参数:Yaf_Dispatcher $dispatcher
 * 调用的次序, 和申明的次序相同
 */

class Bootstrap extends Yaf_Bootstrap_Abstract
{
    public function _initConfig()
    {
        $config = Yaf_Application::app()->getConfig();
        Yaf_Registry::set("config", $config);
    }

    public function _initDefaultName(Yaf_Dispatcher $dispatcher)
    {
        $dispatcher
            ->setDefaultModule("Index")
            ->setDefaultController("Index")
            ->setDefaultAction("index");
    }
}
我们在 _initConfig() 方法当中获取了当前应用的配置,并且通过 Yaf_Registry::set 注册到了全局环境。这样就可以在任何位置通过 Yaf_Registry::get 调用了。可以说是非常经典的应用案例了。
_initDefaultName() 方法设置了当前请求的默认模块、控制器、操作。除了可以在这里设置,也可以在 application.ini 配置文件当中指定。非常地灵活。

第四章 配置文件

Yaf 配置分三种:常量、php.ini 级别配置、项目应用级配置。常量属于不可更改型配置,其他两种属于可更改型配置。

4.1 Yaf 定义的常量

Yaf 常量有如下:
常量(启用命名空间后的常量名)说明
YAF_VERSION(YafVERSION)Yaf框架的三位版本信息
YAF_ENVIRON(YafENVIRON)Yaf的环境常量, 指明了要读取的配置的节, 默认的是product
YAF_ERR_STARTUP_FAILED(YafERRSTARTUP_FAILED)Yaf的错误代码常量, 表示启动失败, 值为512
YAF_ERR_ROUTE_FAILED(YafERRROUTE_FAILED)Yaf的错误代码常量, 表示路由失败, 值为513
YAF_ERR_DISPATCH_FAILED(YafERRDISPATCH_FAILED)Yaf的错误代码常量, 表示分发失败, 值为514
YAF_ERR_NOTFOUND_MODULE(YafERRNOTFOUDMODULE)Yaf的错误代码常量, 表示找不到指定的模块, 值为515
YAF_ERR_NOTFOUND_CONTROLLER(YafERRNOTFOUDCONTROLLER)Yaf的错误代码常量, 表示找不到指定的Controller, 值为516
YAF_ERR_NOTFOUND_ACTION(YafERRNOTFOUDACTION)Yaf的错误代码常量, 表示找不到指定的Action, 值为517
YAF_ERR_NOTFOUND_VIEW(YafERRNOTFOUDVIEW)Yaf的错误代码常量, 表示找不到指定的视图文件, 值为518
YAF_ERR_CALL_FAILED(YafERRCALL_FAILED)Yaf的错误代码常量, 表示调用失败, 值为519
YAF_ERR_AUTOLOAD_FAILED(YafERRAUTOLOAD_FAILED)Yaf的错误代码常量, 表示自动加载类失败, 值为520
YAF_ERR_TYPE_ERROR(YafERRTYPE_ERROR)Yaf的错误代码常量, 表示关键逻辑的参数错误, 值为521
通过对这个表格中的常量观察,我们发现除了 YAF_VERSIONYAF_ENVIRON,其他都是与错误相关的常量。这些与错误相关的常量到底有什么用呢?
在实际开发中,我们可以在框架发生异常的时候判断当前错误码是否与为错误常量的值。相同的话,我们可以根据严重级别写入不同的错误日志文件并根据级别进行报警。
通常框架级别的错误不会发生。一旦发生就属于严重级别。一定要引起注意。

4.2 Yaf 的 php.ini 配置项

我们在安装 Yaf 框架扩展的时候,已经使用过两个 php.ini 配置项。现在我们来看看 Yaf 全部的 php.ini 配置项。
选项名称默认值可修改范围更新记录
yaf.environproductPHP_INI_ALL环境名称,当用 INI 作为 Yaf 的配置文件时, 这个指明了Yaf将要在 INI 配置中读取的节的名字
yaf.libraryNULLPHP_INI_ALL全局类库的目录路径
yaf.cache_config0PHP_INI_SYSTEM是否缓存配置文件(只针对 INI 配置文件生效), 打开此选项可在复杂配置的情况下提高性能
yaf.name_suffix1PHP_INI_ALL在处理 Controller, Action, Plugin, Model 的时候, 类名中关键信息是否是后缀式, 比如 UserModel, 而在前缀模式下则是 ModelUser
yaf.name_separator""PHP_INI_ALL在处理 Controller, Action, Plugin, Model 的时候, 前缀和名字之间的分隔符, 默认为空, 也就是UserPlugin, 加入设置为"_", 则判断的依据就会变成:"User_Plugin", 这个主要是为了兼容ST已有的命名规范
yaf.forward_limit5PHP_INI_ALLforward 最大嵌套深度
yaf.use_namespace0PHP_INI_SYSTEM开启的情况下, Yaf 将会使用命名空间方式注册自己的类, 比如 Yaf_Application 将会变成YafApplication
yaf.use_spl_autoload0PHP_INI_ALL开启的情况下, Yaf 在加载不成功的情况下, 会继续让 PHP 的自动加载函数加载, 从性能考虑, 除非特殊情况, 否则保持这个选项关闭
注:PHP_INI_SYSTEM 表示只能在 php.ini 配置当中修改。PHP_INI_ALL 表示除了在 php.ini 配置当中修改外,还可以通过在项目当中使用 ini_set() 方法修改。
虽然,我们的 Yaf 框架提供了 8 种 php.ini 配置项。但是,真正常用的就以下 3 个:
(1) yaf.cache_config
正如表格中所说,当我们的项目应用配置非常复杂。为了提高配置文件的解析速度,我们会将这个配置设置为 1。但是,通常的项目配置不是很复杂,所以这个配置设置为 0 与 1 区别不大。我在实际开发中,一般保持为默认值 0 即可。
(2) yaf.use_namespace
正如表格中所说,一旦设置为 1,那么所有的 Yaf 框架类都必须写成命名空间方式。否则,Yaf 框架将加载不到这个类。
如:Yaf_Application 必须写成 \Yaf\ApplicationYaf_Registry 写成 \Yaf\Registry
之所以说这个配置常用。是因为很多开发者喜欢这种命名空间方式。觉得用下划线形式太丑陋。对于我来讲,哪种方式都行。我通常用默认的下划线模式。你可以根据自己的喜好或团队的风格进行选择。
(3) yaf.use_spl_autoload
这个配置就相当有用了。可以说有用到了极致。虽然,表格当中说了,开启会影响性能。但是,我们有不得不开启的理由:通过 Composer 工具加载第三方 PHP 包。
本身 Yaf 框架是以简洁与性能著称。这就导致了整个框架当中并未实现太多的功能。这时我们就必须借助 Composer 加载第三方 PHP 包到项目完成相应的功能。比如,模板引擎 Smarty/Twig、数据库 ORM、其他知名厂商提供的 SDK 包。
注:除非你明确表示自己项目当中不会加载第三方 PHP 包。否则请保持这个配置项是开启状态。

4.3 项目配置的必要配置项

所谓项目配置项,指的是只能在项目当中配置(conf/application.ini)。是属于项目级别的。
名称值类型说明
application.directoryString应用的绝对目录路径
是的。我们的必要配置项,只有这么一个。它必须在项目 config/application.ini 配置。
在 2.5 小节当中,我们已经使用过这个配置。它的作用就是告诉 Yaf 框架我们的项目与 MVC 相关的类全部放在这儿噢。你要到这儿来加载这些类哟。
那么哪些类会在这里加载呢?
通过对项目目录结构发现,有以下类会在这里加载:
  • controllers(控制器)
  • views(模板)
  • models(模型)
  • plugins(插件)
  • modules(模块)
  • library(本地类)
关于这些文件的加载规则,我们放到下一章节讲解。

4.4 项目配置的可选配置项

可选的项目配置项如下:
application.extStringphpPHP脚本的扩展名
application.bootstrapStringBootstrapplication.phpBootstrap 路径(绝对路径)
application.libraryStringapplication.directory + "/library"本地(自身)类库的绝对目录地址
application.baseUriStringNULL在路由中, 需要忽略的路径前缀, 一般不需要设置, Yaf 会自动判断.
application.dispatcher.defaultModuleStringindex默认的模块
application.dispatcher.throwExceptionBoolTrue在出错的时候, 是否抛出异常
application.dispatcher.catchExceptionBoolFalse是否使用默认的异常捕获Controller, 如果开启, 在有未捕获的异常的时候, 控制权会交给ErrorController的errorAction方法, 可以通过$request->getException()获得此异常对象
application.dispatcher.defaultControllerStringindex默认的控制器
application.dispatcher.defaultActionStringindex默认的动作
application.view.extStringphtml视图模板扩展名
application.modulesStringIndex声明存在的模块名, 请注意, 如果你要定义这个值, 一定要定义Index Module
application.system.*String*通过这个属性, 可以修改yaf的runtime configure, 比如application.system.lowcase_path, 但是请注意只有PHP_INI_ALL的配置项才可以在这里被修改, 此选项从2.2.0开始引入
可选的配置项有点多。但是,在实际使用中,也只会使用那么几个。
(1) application.view.ext
在 2.4 小节当中,我们创建的视图模板文件的扩展名是 .phtml 。那是因为这个扩展名取决于 application.view.ext 配置。默认值为 phtml 。有些开发者喜欢用 html,有些喜欢用 php 。而我喜欢 php。所以,我会在项目应用配置文件当中如下配置:
[product]
;支持直接写PHP中的已定义常量
application.directory = APP_PATH "/application/" 
application.view.ext  = php
这样,我就可以在创建模板的时候,以 .php 为扩展名了。
(2) application.library
正如上述表格中所说,该配置是决定我们的本地类存放位置。这个配置直接影响了我们后续本地类加载的功能。非常重要。
在实际开发中,我们的很多工作都会在这个配置指定的目录下干活。比如:业务类、SDK、公共类、工具类等全部会放到这个目录下。
(3) application.modules
这个配置可以说是非常有用且一定会用到。我们一个项目当中难免会有多个模块。比如,我们一个项目分前台后台。此时,我们通常的做法是前台一个模块,后台一个模块。前台通常就是默认的模块Index,后面就是 Admin
那么,此时我们的配置应该如下:
[product]
;支持直接写PHP中的已定义常量
application.directory = APP_PATH "/application/" 
application.view.ext  = php
application.modules   = "Index,Admin"
注意:因为,我们所有的模块不在这里显示配置指定。将不能通过任何方式请求到这个模块。很多同学在学习 Yaf 框架时,经常会犯这个错误。而且,报了错还不知道是这个问题所致。就会非常气馁。
(4) application.dispatcher.defaultModule
通过名字我们就知道它是要指定默认的模块名。默认值为 Indexapplication 目录下的 controllersviews 都属于默认模块的目录。在 modules 模块里面才能定义自己的模块。后续我们会讲自定义模块。
(5) application.dispatcher.defaultController
指定默认的控制器。这里的控制器名称必须是 application.dispatcher.defaultModule 指定的模块下的有效控制器名称。不能指定不存在的控制器名称。
(6) application.dispatcher.defaultAction
指定默认的操作。这个与指定默认的控制器一样。必须有效。
(7) application.dispatcher.throwException
这个功能可以说也是相当有用了。就是告诉 Yaf 框架,当代码发生错误时,是否以异常形式抛出来。因为,主流的开发者都喜欢这种异常模式。可以利用异常的特性,在任何位置进行处理及记录日志等。请把它设置为 1。
(8) application.dispatcher.catchException
这个功能可以说是巨好用了。我在实际项目当中,用这个特性来集中处理业务中抛出的异常。记录错误日志,根据异常类别提示相应的信息。请把它设置为 1。我们会在第八章《异常和错误》章节详细讲解。
注:虽然这些都属于可选配置。并不代表它们作用不大。利用好这些配置,可以为我们写出高质量项目提供助力。

4.5 解析配置文件

我们在实际开发当中,在配置文件 conf/application.ini 当中会定义一些全局的配置。比如:数据库、Redis、时区等全局性配置。该配置文件我们要尽量保证少修改它。因为,修改这个配置一旦出错就会影响整个系统。并且,这个配置文件无限制往里面增加配置,就会导致配置文件尺寸越来越大,解析配置文件就会影响性能。
此时,我们可以再创建其他配置文件。比如,我们在做多语言项目时,会根据不同语言显示不同的错误信息。这里我们只需要在 conf 目录下创建对应的语言配置文件即可。
这时,我们就会遇到一个问题。主配置文件已经在 Yaf 框架启动的时候加载并解析了。那么我们这个新创建的配置如何解析呢?这时候我们就会用到 Yaf_Config_Ini 类了。
<?php
class Xxx
{
    public function parseLangIni()
    {
        $zhLangPath = APP_PATH . '/conf/zh.ini'; // 假如有这样一个文件。
        $config     = new \Yaf_Config_Ini($zhLangPath);
        return $config->toArray();
    }
}
假如 conf/zh.ini 配置内容如下:
error        = 服务器异常,请稍候重试
not_login    = 您当前未登录
not_register = 您的账号还未注册
大家可以实际运行看看效果。

第四章 自动加载器

Yaf 在自启动的时候,会通过 SPL 注册一个自己的 Autoloader,出于性能的考虑,对于框架相关的 MVC 类,Yaf Autoloader 只以目录映射的方式尝试一次。
以上这段话是官方文档说的。翻译过来就是任何关于 MVC 的类,Yaf 框架只会加载一次。不管你有何种办法,它也不会再加载第二次了。说白了,你只能按照 Yaf 框架定义的规则在指定的地方定义这些类。
Yaf 框架关于 MVC 映射的规则:
类型后缀(或者前缀, 可以通过php.ini中ap.name_suffix来切换)映射路径
控制器Controller默认模块下为{项目路径}/controllers/, 否则为{项目路径}/modules/{模块名}/controllers/
数据模型Model{项目路径}/models/
插件Plugin{项目路径}/plugins/
通过这个表格我们应该知道在什么地方定义 Controller、Model、Plugin 了。
注意!!!注意!!!注意!!!重要事情说三遍?
不管是全局类还是本地类请不要出现 Controller、Model、Plugin 打头或结尾命令。当 yaf.name_suffix 在 php.ini 当中的值为 1 的时候,结尾不可出现。为 0 的时候前缀不可出现。为了代码的通用性,干脆不用这些关键词就好。
如果是通过 Composer 包加载第三方 PHP 扩展包的类。则不受这个影响。
曾经,我就踩过这个坑。希望你也不要再踩这个坑了。切记!!!

4.1 全局类和本地类

Yaf 为了方便在一台服务器上部署的不同产品之间共享公司级别的共享库,支持全局类和本地类两种加载方式。
全局类是指,所有产品之间共享的类,这些类库的路径是通过 yaf.library 在 php.ini (当然,如果 PHP 在编译的时候,支持了 with-config-file-scan-dir,那么也可以写在单独的 ap.ini 中)。
而本地类是指,产品自身的类库,这些类库的路径是通过在产品的配置文件中,通过ap.library配置的。
在 Yaf 中,通过调用 Yaf_Loader 的 registerLocalNamespace 方法,来申明那些类前缀是本地类,即可。
举个例子:
我有一个朋友曾经在新浪微博设计微博客户端的 API。在这套 API 当中,大量调用了其他语言(Java)封装的服务。这些服务又在客户端的多个模块中调用。不同模块又由不同的项目组负责。这样就会有一个问题:不同的项目用到了相同的服务。不可能在每个项目当中都封装一套。如果这样的话,工作量自然会巨大的。因为要承担开发与测试的双倍工作量。于是,他们就把这些服务封装成了一个包。通过在 yaf.library 中注册成为全局类。
除了通过 yaf.library 这种方式。还可以使用封装成一个 Composer 包,然后在项目当中加载。这块涉及到 Composer 的知识。本书不作讲解。

4.2 类的加载规则

全局类与本地类的加载规则,都是一样的:Yaf 规定类名中必须包含路径信息,也就是以下划线"_"分割的目录信息。Yaf 将依照类名中的目录信息,完成自动加载。
如下的例子, 在没有申明本地类的情况下:
一个映射的例子 Zend_Dummy_Foo
// Yaf将在如下路径寻找类 Foo_Dummy_Bar
{类库路径(php.ini 中指定的 yaf.library)}/Foo/Dummy/Bar.php
并且在 Bar.php 这个类文件当中的类名必须是:Zend_Dummy_Foo 。很显然这种类命名是非常丑陋不符合主流开发者胃口的。
如果通过如下方式调用了registerLocalNamespace:
// 申明, 凡是以Foo和Local开头的类, 都是本地类
$loader = Yaf_Loader::getIgnstance();
$loader->registerLocalNamespace(array("Foo", "Local"));
那么对于刚才的例子,将会在如下路径寻找 Foo_Dummy_Bar
// Yaf将在如下路径寻找类 Foo_Dummy_Bar
{类库路径(conf/application.ini 中指定的 application.library)}/Foo/Dummy/Bar.php
如果全局类与本地类都有这个类加载顺序是怎样的呢?
因为在实际项目当中,主流的开发者都并不会以下划分命名一个类。包括我本人,也不喜欢这种下划线命名类名的方式。所以,我本人并未会验证它的加载优先级。
所以,这个小节知道 Yaf 有这个功能即可。如果你确定需要这种形式的加载。可以使用这个特性。否则,请采用封装为 Composer 来加载你的全局类吧。

第五章 插件

Yaf 支持用户定义插件来扩展 Yaf 的功能,这些插件都是一些类。它们都必须继承自 Yaf_Plugin_Abstract。插件要发挥功效,也必须现实的在 Yaf 中进行注册,然后在适当的时机 Yaf 就会调用它。

5.1 Yaf 支持的 Hook

所谓 Hook,中文译为“钩子”。即,当指定的事件或动作产生之后,会自动被调用。
Yaf 定义了 6 个 Hook,它们分别是:
触发顺序名称触发时机说明
1routerStartup在路由之前触发这个是 6 个事件中, 最早的一个. 但是一些全局自定的工作, 还是应该放在 Bootstrap 中去完成
2routerShutdown路由结束之后触发此时路由一定正确完成, 否则这个事件不会触发
3dispatchLoopStartup分发循环开始之前被触发
4preDispatch分发之前触发如果在一个请求处理过程中, 发生了 forward, 则这个事件会被触发多次
5postDispatch分发结束之后触发此时动作已经执行结束, 视图也已经渲染完成. 和 preDispatch 类似, 此事件也可能触发多次
6dispatchLoopShutdown分发循环结束之后触发此时表示所有的业务逻辑都已经运行完成, 但是响应还没有发送

5.2 定义插件

插件类是用户编写的。但是它需要继承自 Yaf_Plugin_Abstract。对于插件来说,上一节提到的 6 个 Hook,它不需要全部关心。它只需要在插件类中定义和上面事件同名的方法,那么这个方法就会在该事件触发的时候被调用。
而插件方法,可以接受俩个参数 Yaf_Request_Abstract 实例和 Yaf_Response_Abstract 实例。
一个插件类例子如下:
<?php
class UserPlugin extends Yaf_Plugin_Abstract
{
    public function routerStartup(Yaf_Request_Abstract $request, Yaf_Response_Abstract $response) 
    {
    }

    public function routerShutdown(Yaf_Request_Abstract $request, Yaf_Response_Abstract $response) 
    {
    }
}
插件有 6 个事件,但是,我们的 UserPlugin 插件只关于两个事件,所以,就定义了两个方法。这个时候我们可以拿到 Request 请求的数据操作 Response 响应数据了。
一个实际的场景可能这样的:
我们想对某个页面进行缓存 30 分钟。这个时候,我们可以通过 Yaf_Request_Abstract 对象拿到模块名、控制器名、操作名进行判断是否为想缓存的页面,然后再进行缓存。下次请求过来的时候,缓存数据存在就直接响应给用户端了。从而加速页面的响应速度。
当然,插件可以实现的功能有很多。各位可以根本实际的场景进行操作。

5.3 注册插件

插件要生效,还需要向 Yaf_Dispatcher 注册。那么一般的插件的注册都会放在 Bootstrap 中进行。
一个注册插件的例子如下:
<?php
class Bootstrap extends Yaf_Bootstrap_Abstract
{
    public function _initPlugin(Yaf_Dispatcher $dispatcher)
    {
        $user = new UserPlugin();
        $dispatcher->registerPlugin($user);
    }
}

5.4 插件目录

一般地,插件应该放置在 application.directory 指定的目录下的 plugins 目录。这样在自动加载的时候,加载器通过类名,发现这是个插件类,就会在这个目录下查找。
当然,插件也可以放在任何你想防止的地方,只要你能把这个类加载进来就可以。
在实际开发中,最佳的做法不会放到默认的目录当中。我们会在 application.library 指定的目录下创建一个 plugins 目录。但是,此时在这个目录下的创建的自定义插件就不能以 Plugin 打头或结尾命令了。参见 4.2 小节加载规则说明。
这个目录放置在哪个位置更合适。每个开发者都有自己独特的理解。所以,正如 Yaf 框架作者所说:只要能把这个类加载进来就好,放哪都可以。

第六章 路由和路由协议

6.1 概述

6.2 设计

6.3 默认情况

6.4 使用路由

6.5 路由协议详解

6.6 自定义路由协议

第七章 在命令行使用 Yaf

Yaf 支持在命令行下运行。它有两个重要的用途:
1)方便开发人员调试。
这个很好理解。开发人员通过命令行就可以对编写的业务代码进行调试。无须搭建一个 Web 环境就可以运行业务代码。那是相当的好用。对不对?
2)编写定时任务/常驻进程。
一些定时任务需要通过编写命令行运行的脚本来运行。比如,每天的生日提醒。
常驻进程常常用来解决短信异步发送、APP 消息推送。
所以, 编写命令行脚本的重要性并不局限于 Yaf 框架。在 Laravel、ThinkPHP、YII2 框架中也显示同等重要。大家一定要掌握好这个技术点。

7.1 命令行入口文件

Yaf 框架提供了两种模式在命令行模式运行。
第一种方式
专门为用 Yaf 开发 Crontab 等任务脚本设计的方式。这种方式下,对 Yaf 的唯一要求就是能自动加载所需要的 Model 或者类库, 所以可以简单的通过 Yaf_Application::execute 来实现。但是,这种方式实际开发中几乎不用。因为没有第二种灵活方便。所以,本书并不会讲解这种执行方式。
第二种方式
在命令行下模拟请求,运行和 Web 请求一样的流程。从而可以用来在命令行下测试我们的 Yaf 应用。对于这种方式,唯一的关键点就是请求体。默认的请求是由 Yaf_application 实例化,并且交给 Yaf_dispatcher 的。而在命令行模式下, Yaf_Application 并不能正确的实例化一个命令行请求。所以需要变更一下, 请求需要手动实例化。
所以,我们本小节只讲第二种方式。
我们依然在 public 目录创建入口文件:cli.php。文件名并没有特殊限制。为了便于理解我命名为 cli.php
入口文件代码如下:
<?php
define('APP_PATH', dirname(dirname(__FILE__)));

(new \Yaf_Application(APP_PATH . "/conf/application.ini", 'product'))->bootstrap();

if (!isset($argv[1])) {
    exit("Please enter the route to execute. Example: the php cli.php Index/Index!\n");
}

$routeArr = explode('/', $argv[1]);
if (count($routeArr) != 2) {
    exit("Please enter the route to execute. Example: the php cli.php Index/Index!\n");
}

$controllerName = $routeArr[0];
$actionName     = $routeArr[1];

$request = new \Yaf_Request_Simple('CLI', 'Cli', $controllerName, $actionName);
\Yaf_Application::app()->getDispatcher()->returnResponse(true)->dispatch($request);
大家可以发现这入口文件与 index.php 入口文件的差别在于以下两点:
  1. 自己实例化一个请求体。通过 Yaf_Request_Simple 类。
  2. 接收命令行传递的参数交给实例化的请求体。
Yaf_Request_Simple 类构造方法有 5 个参数。
  • 第一个参数:固定值 CLI。创建一个基于 CLI 模式的请求。
  • 第二个参数:模块名称。因为本例中我们的代码在新创建的 Cli 模块中。所以,设置为 Cli。
  • 第三个参数:控制器名称。这个值由命令行传递进来。
  • 第四个参数:操作名称。这个值由命令行传递进来。
  • 第五个参数:请求参数。这个可以用 POST/GET 提交参数来理解。
因为我们创建了新的模块 Cli。所以,我们要在 application/modules 目录下创建一个 Cli 的目录。由于是 Cli 目录下的所有 Controller 仅在命令行运行。所以,不需要渲染模板。所以,我们不需要创建 views 模板目录。
最终在 modules 目录下创建的目录结构为如下:
└── Cli
    └── controllers
是的,我们在 modules 模块目录下创建了一个 Cli 目录,这个目录就代表 Cli 模块。Cli 目录下创建了一个 controllers 来存放 Controller 文件。
我们在 controllers 目录中创建一个默认的控制器:Index.php。代码如下:
<?php
/**
 * 命令行控制器。
 */

class IndexController extends \controllers\Cli
{
    /**
     * 默认 Action
     */
    public function indexAction()
    {
        echo "Hello world!\n";
        exit(0);
    }
}
我们定义了一个 IndexController 并继承了 \controllers\Cli ,关于 Cli 类,我们在 7.3 小节会讲到。
大家会发现,我们的这个将在命令行运行的 Controller 及 Action 与普通的 Web 访问的并无不同。是的,它们是完全没有任何区别。

7.2 配置文件

因为,采用第二种命令行运行方式。那么,此时就是共用了 Web 模式的配置文件 conf/application.ini 了。除了入口文件稍有差别。其他没有任何差别。
但是,我们因为创建了新的 Cli 模块。为了让 Cli 模块能被访问。所以,我们要在 Cli 模块注册。
application.modules   = "Index,Cli"

7.3 禁止 Web 访问

为什么要禁止 Web 访问呢?试想一下,我们通常会在命令行做什么操作呢?
通常都会做一些诸如短信发送、定时推送等系统级别的操作。而默认情况下,所有的模块都能通过 URL 地址被请求。
要是被用户猜解或者离职员工恶意攻击呢?这个风险有多大,相信各位都有自己的判断。
那么要怎样禁止 Web 访问呢?
我们在实际开发过程中,通常会定义几个公共基类 Controller。
Web 模式又分前台与后台。所以就会有两个基类:
  • Frontend.php
  • Backend.php
Cli 模式的基类:
  • Cli.php
这三个基类,我们通过都会让它们继承自同一个 Controller。毕竟,Cli 模式与 Web 模式很大一部分是相同的。仅仅在细微功能不一样。
我们将该公共基类命名为:
  • Base.php
为什么不用 Controller 为后缀呢?因为,这是自定义的本地类,本地类是存放在 application.library 配置指定的目录中,不是通过 Composer 加载的第三方类。所以,不能以 Controller、Model、Plugin 打头或结尾命令类名。类名不允许,那自然文件名也不被允许了。
现在我们分别来看这几个公共基类的代码:
(1) {根目录}/application/library/controllers/Base.php
<?php
namespace controllers;

class Base extends \Yaf_Controller_Abstract
{
    /**
     * 关闭模板渲染。
     */
    protected function end()
    {
        \Yaf_Dispatcher::getInstance()->autoRender(false);
    }
}
因为,所有的 Controller 都必须继承 Yaf_Controller_Abstract 抽象类。所以,当我们定义 Base 这个顶级基类的时候,也必须继承这个类。
我们在 Base 基类里面定义了一个 end() 方法。这个方法就是关闭 Action 自动渲染模板。当我们有的功能仅仅只想输出 JSON,不想渲染模板文件的时候。我们就会在 Action 方法最后一行调用该方法来禁止渲染模板。这个方法非常有用。学习 Yaf 的很多 PHPer,在实际开发中都会遇到这个问题。
(2) {根目录}/application/library/controllers/Frontend.php
<?php
namespace controllers;

class Frontend extends Base
{
    
}
是的。咱们仅仅继承了 Base 类。其他啥也没干。主要是现在我们还没有真正想干的事情。
(3) {根目录}/application/library/controllers/Backend.php
<?php
namespace controllers;

class Backend extends Base
{
    
}
与 Frontend 类一样。
(4) {根目录}/application/library/controllers/Cli.php
<?php
namespace controllers;

class Cli extends Base
{
    /**
     * 重写父方法, Cli 模式关闭模板渲染。
     * @return void
     */
    public function init()
    {
        $this->end();
        if (PHP_SAPI != 'cli') { // 非 CLI 模式运行则报错。
            exit("access forbidden!");
        }
    }
}
我们在 Cli 类中重写了父类的 init() 方法。因为,父类 Base 继承自 Yaf_Controller_Abstract 类。所以,凡是继承了 Cli 这个基类的 Controller 被访问时都会自动调用 init() 这个方法。
这就实现了我们禁止访问 Web 访问 Cli 模式脚本的目的。
记住!一定要禁用 Web 访问!否则,后果非常严重!!!在其他成熟的框架应该都禁止了 Web 访问,如果没有限制,请一定要限制它!

7.4 执行

当一切准备就绪之后,剩下的就是执行它。
打开命令行,进入项目 public 所在目录,然后执行如下命令:
$ php cli.php Index/Index
此时就会输出:“Hello world!”。
说明我们的 Cli 模式就成功执行了。
现在我们来解释一下它是怎样运行的。
  • php 是调用系统 PATH 变量指定的 php 命令来执行的。
  • cli.php 是入口文件。所以,可以理解为我们向 cli.php 入口文件发送一个请求。
  • Index/Index 向 cli.php 传递数据。传递的数据会以 / 拆成两个值。前面的值为 Controller 的名称,后面的值为 Action 的名称。因为,在 cli.php 入口文件当中,我们限制所有的 Cli 执行只能映射 Cli 模块当中。所以,我们这里不需要指定模块的名称。
说白了,我们花了这么篇幅就是在命令行模拟一个类似于 Web 模式的请求到 Controller/Action 而已。

7.5 命令行执行知识扩展

由于很多 PHP 初学者,可能对 PHP 命令行执行并未接触过或很少接触。对其中的一些命令或特性还不是特别明白。
所以,本小节算是一个 PHP 命令行执行的知识普及。
命令行模式与 Web 运行模式最大的区别在于: 命令行模式不能直接接收 Web 的请求。其余的运行机制一模一样。
所以,不要把命令行模式执行想象得太难掌握。
(1) php 命令加入系统 PATH
不管是在 Windows 或 Linux/Unix 系统。要想在命令行调用 php 命令,就必须把 php 命令加入到系统 PATH 变量中。
windows 系统只需要将 PHP 安装目录路径直接加入到系统 PATH 变量即可。具体操作可以 Google 搜索获知。
Linux/Unix 系统需要将 PHP 安装目录下的 bin目录路径加入到系统 PATH 变量。通常是修改 /etc/profile 文件来加入。具体操作也通过 Google 搜索获知。
(2) PHP 命令行常用命令
2.1 执行一个 PHP 文件
假如,我们有这样一个脚本 test.php 的代码如下:
<?php
echo md5('test');
那么,我们要在命令行如何执行这个脚本呢?
$ php test.php
此时会输入如下内容:
098f6bcd4621d373cade4e832627b4f6
通过这样的运行原理。我们可以在 test.php 脚本当中做更多操作。如:数据库、Redis、文件操作。比如,我们实际开发中会在脚本中写生日祝福提醒的业务。然后,通过 Linux Crontab 每天定时执行。
说到这里,我不得不说一个我曾经在接手其他团队项目的时候遇到的一个奇葩定时任务。
这个项目中有一个开发,他需要每天定时清理一些数据。具体清理什么数据我这里就不细说了。然后,他把所有业务都写好了。然后,通过 Linux Crontab 定时请求这个程序对的 URL。仅管他很聪明地在 URL 当中携带了一个密码串,避免被外面误请求而导致数据被恶意清除。
但是,这依然有以下几个风险:
  • 离职人员可以轻松获知这个 URL 及对应的密码。造成不必要的潜在风险。
  • 通过 URL 访问都会通过 DNS 解析,DNS 解析是有概率超时或其他失败的。
  • 通过 URL 访问,会占用 Web 服务器的进程资源。会持续占用直到定时任务结束。这对服务器资源是一种浪费。
2.2 快速获知 PHP 配置信息
有时候,我们想快速知道当前环境的 PHP 的配置信息。可以执行如下命令:
[root@localhost public]# php --ini
Configuration File (php.ini) Path: /usr/local/php72/etc
Loaded Configuration File:         /usr/local/php72/etc/php.ini
Scan for additional .ini files in: /usr/local/php72/etc/php.d
Additional .ini files parsed:      (none)
这样我们就可以快速知道配置文件位置了。
2.3 快速获知 PHP 安装的扩展
每个 PHP 都会安装很多内置的或第三方的扩展。有时候我们开发一些应用,需要 PHP 开启某某扩展。这时候,我们就可以通过以下命令查阅扩展是否安装。
[root@localhost public]# php -m
[PHP Modules]
bcmath
Core
ctype
curl
......
2.4 快速获知扩展的配置信息
有时候,我们可能不仅仅想知道是否安装某个扩展,并且还想知道扩展的版本,以及扩展对应的 php.ini 配置。这时候如下命令就非常有用了。
我们以 Yaf 为例:
[root@localhost public]# php --ri yaf

yaf

yaf support => enabled
Version => 3.0.7
Supports => http://pecl.php.net/package/yaf

Directive => Local Value => Master Value
yaf.library => no value => no value
yaf.action_prefer => Off => Off
yaf.lowcase_path => Off => Off
yaf.use_spl_autoload => On => On
yaf.forward_limit => 5 => 5
yaf.name_suffix => On => On
yaf.name_separator => no value => no value
yaf.st_compatible => Off => Off
yaf.environ => product => product
yaf.use_namespace => Off => Off
我们清晰地知道了 Yaf 版本是 3.0.7,并且扩展处于开启状态。还知道了 yaf 相关 php.ini 的值。
2.5 更多 PHP 命令行参数
常用的操作就以上 4 个。PHP 命令支持很多的参数。大家可以通过执行 php --help 命令查看。还有很多命令是比较有用的。大家可以自行 Google 深入了解学习。这里仅作抛砖引玉。
2.6 命令行脚本接收参数
假如有脚本 test.php 的代码如下:
<?php
var_dump($argv, $argc);
现在我们在命令行如下执行此脚本:
$ php test.php first second
此时会输出如下内容:
array(3) {
  [0] =>
  string(9) "index.php"
  [1] =>
  string(5) "first"
  [2] =>
  string(6) "second"
}
int(3)
PHP 在命令行模式下会自动初始化两个变量:
  • $argv - 命令行所有的参数都将保存到此变量中。
  • $argc - 命令行所有的参数个数保存到此变量中。
$argv 是一个数组,第一个元素是被执行脚本的脚本名称。如果执行的时候给的路径是绝对的就是绝对路径,是相对的就是相对路径。
在 $argc 命令中仅仅保存的是参数个数。作用有限。可以用来做一些辅助的判断。比如,参数未传指定个数。
重点来了。重点来了。重点来了。重要的事情真的要说三遍才行噢!
CLI 模式与 Web 模式的区别:
  1. CLI 其输出没有任何头(Header)信息。
  2. 出错时输出纯文本的错误信息(非 HTML 格式)。
  3. 所有来自 print 和 echo 的输出将被立即写到输出端。而不作任何缓冲操作。
  4. 最大运行时间(max_execution_time)被设置为无限值。
  5. $argc 与 $argv 两个变量总是存在。并且携带了参数个数与实际的参数数组值。
  6. CLI SAPI 不会将当前目录改为已运行的脚本所在的目录。
  7. CLI 模式提供了几个专用常量:STDIN、STDOUT、STDERR。
这 7 点当中,最需要关注的是第 4 和第 5 点即可。
相信大家都知道,有时候我们的一些 PHP 业务执行时间很长。而 PHP 默认的脚本执行超时时间是 60s。我们通常会采用如下方式来控制脚本执行的最大时间:
set_time_limit(3600); // 设置脚本最大执行时间为 1 个小时。
但是,以上这种做法仅仅是在 Web 模式有效。在 CLI 模式下,这个值始终为无限值。即永远不会超时。为何会这样呢?
那是因为,我们通常在 CLI 模式执行的程序都属于一些运行时间比较长的业务,有些业务甚至需要永远不超时。
举两个例子:
(1)每天生日祝福。一般的系统应用,可能几分钟到几个小时不等。
(2)短信发送。一旦有短信的发送触发,那么 PHP 守护进程就会立即处理。此时就需要在系统后台长期运行一个短信发送的守护进程。

来源:https://www.phpjieshuo.com/archives/18/

评论

此博客中的热门博文

Tailscale 开源版中文部署指南(支持无限设备数、自定义多网段 、自建中继等高级特性)

iOS任意版本号APP下载(含itunes 12.6.5.3 最后带AppStore版本)

关于 N1 旁路由的设置

Mifare Classic card(M1卡)破解过程记录(准备+理论+获取扇区密钥+数据分析)

Blogger搭建国内可正常访问博客(超详细教程)

一些免费的云资源

打造一个可国内访问的Blogger(Blogspot)方法

百度站长平台中接入Blogger博客

Mifare Classic card(M1)卡破解过程

重新学习并解锁emby4.6.7,4.7.2版本