Yaf框架学习笔记-安装和使用

Yaf,全称 Yet Another Framework,是一个C语言编写的、基于PHP扩展开发的PHP框架。

Yaf是一款以鸟哥以C语言写的PHP框架,以PHP扩展的方式运行框架,它提供了Bootstrap、路由、分发、视图、插件功能。

Yaf内核够精简稳定,所以,几乎不会遇到运行上的问题,风险可控,性能优异。

注:百度的odp框架基于yaf开发的。

Yaf的优点:

  • C语言开发的PHP框架, 相比原生的PHP, 几乎不会带来额外的性能开销.
  • 所有的框架类, 不需要编译, 在PHP启动的时候加载, 并常驻内存.
  • 更短的内存周转周期, 提高内存利用率, 降低内存占用率.
  • 灵巧的自动加载. 支持全局和局部两种加载规则, 方便类库共享.
  • 高性能的视图引擎.
  • 高度灵活可扩展的框架, 支持自定义视图引擎, 支持插件, 支持自定义路由等等.
  • 内建多种路由, 可以兼容目前常见的各种路由协议.
  • 强大而又高度灵活的配置文件支持. 并支持缓存配置文件, 避免复杂的配置结构带来的性能损失.
  • 在框架本身,对危险的操作习惯做了禁止.
  • 更快的执行速度, 更少的内存占用.

以上内容引用鸟哥的官方介绍,当然,Yaf不是一个Full-Stack的web框架,它没有对数据库操作的封装,更不用说ORM;很多人认为这是Yaf的不足,但这又是Yaf的优点,这代表着一种精神,就是追求简单,追求高效,追求”简单可依赖“, 所以Yaf专注于实现最核心的功能,提供最稳定的实现。

说完它的优点,我们来谈谈不足。

Yaf的不足:

  • 维护成本高,要维护PHP扩展,需要熟练C开发和Zend Api。
  • 目标用户群小,现在国内很多中小型站都是使用虚拟主机,并不能随意的给PHP添加扩展。
  • 不像其他框架一样提供各种丰富功能的类库和各种优雅的写法,它只提供一个MVC的基本骨架。

尽管Yaf的功能有限,但Yaf是可扩展的!

它提供的插件机制,可以和其它类库整合在一起。

Yaf非常适合基于Yaf再扩展一套适合自己的业务层框架。

总之Yaf非常适合互联网产品的开发。

框架安装

Yaf扩展主页:http://pecl.php.net/package/yaf

$ wget http://pecl.php.net/get/yaf-3.0.7.tgz
$ tar -zxvf yaf-3.0.7.tgz
$ cd yaf-3.0.7
$ /path/to/phpize
$./configure --with-php-config=/path/to/php-config
$ make && make test && make install

编译完成生成扩展之后,修改php.ini,在php.ini文件末尾加如下配置:

[yaf]
yaf.use_namespace = 0
yaf.environ = 'product'
yaf.cache_config = 0
yaf.name_suffix = 1
yaf.lowcase_path = 1
extension = yaf.so

添加完之后,可查看phpinfo是否存在yaf扩展。


配置说明:

yaf.user_namespace 为1是开启命名空间模式,0关闭
yaf.environ 是默认情况下Yaf读取的环境配置是什么
yaf.cache_config 是否缓存项目配置
yaf.name_suffix 开启后缀,即为1之后,类名将以XxxModel.php、XxxController.php模式加载
yaf.lowcase_path 路径信息中的目录部分都会被转换成小写

基本程序结构

基本程序目录结构如下,yaf框架是精简的,本身不含数据访问模块,只能自行编写数据库访问模块或使用现成的如轻量级PHP数据库框架Medoo,WHERE 语法非常精练,轻量级的yaf搭轻量级的Medoo是绝配,如果还需要视图模板可以考虑使用 Smarty,当然我更喜欢直接使用vue前端框架来实现视图。

+ public
  |- index.php //入口文件
  |- .htaccess //重写规则    
  |+ css
  |+ img
  |+ js
+ conf
  |- application.ini //配置文件   
+ application
  |+ controllers
     |- Index.php //默认控制器
  |+ views    
     |+ index    //控制器
        |- index.phtml //默认视图
  |+ modules //其他模块
  |+ library //本地类库
  |+ models  //model目录
  |+ plugins //插件目录

入口文件:

入口文件是所有请求的入口, 一般都借助于rewrite规则, 把所有的请求都重定向到这个入口文件。

一个经典的入口文件piublic/index.php:

<?php

define("APP_PATH", realpath(dirname(__FILE__) . '/../')); /* 指向public的上一级 */

$app  = new Yaf_Application(APP_PATH . "/conf/application.ini");

$app->run();

重写规则:

#Apache的Rewrite (httpd.conf):
#.htaccess, 当然也可以写在httpd.conf
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php

#Nginx的Rewrite (nginx.conf):
server {
listen 8080;
server_name  domain.com;
root   document_root;
index  index.php index.html index.htm;
      if (!-e $request_filename) {
        rewrite ^/(.*)  /index.php/$1 last;
    }
}

配置文件:

在Yaf中, 配置文件支持继承, 支持分节. 并对PHP的常量进行支持. 你不用担心配置文件太大造成解析性能问题, 因为Yaf会在第一个运行的时候载入配置文件, 把格式化后的内容保持在内存中. 直到配置文件有了修改, 才会再次载入。

一个简单的配置文件application/application.ini:

[product]
;支持直接写PHP中的已定义常量
application.directory=APP_PATH "/application/"

控制器:

在Yaf中, 默认的模块/控制器/动作, 都是以Index命名的, 当然,这是可通过配置文件修改的.
对于默认模块, 控制器的目录是在application目录下的controllers目录下, Action的命名规则是"名字+Action"
默认控制器application/controllers/Index.php

<?php
class IndexController extends Yaf_Controller_Abstract {
   public function indexAction() {//默认Action
       $this->getView()->assign("content", "Hello World");
   }
}
?>

视图文件:

Yaf支持简单的视图引擎, 并且支持用户自定义自己的视图引擎, 比如Smarty。

对于默认模块, 视图文件的路径是在application目录下的views目录中以小写的action名的目录中。

一个默认Action的视图application/views/index/index.phtml

<html>
 <head>
   <title>Hello World</title>
 </head>
 <body>
  <?php echo $content;?>
 </body>
</html>

运行:

在浏览器输入你服务器配置的域名即可:

http://www.yourhostname.com/application/index.php

如果在application.ini中定义了模块功能,则需要在程序目录下modules目录下建立模块目录,再建controllers目录,测试中没有发现有大小写要求,但尽量保持目录字母大写一到致。

一个简单的 index.php 入口程序可以实例化一个Yaf程序类,执行run()方法,bootstrap() 是可选的,它指示Yaf_Application去寻找 Bootstrap.php, 而这个文件中, 必须定义一个Bootstrap类, 而这个类也必须继承自Yaf_Bootstrap_Abstract。

Bootstrap

Bootstrap, 也叫做引导程序。

它是Yaf提供的一个全局配置的入口, 在Bootstrap中, 你可以做很多全局自定义的工作。

所有在Bootstrap类中定义的, 以_init开头的方法, 都会被依次调用, 而这些方法都可以接受一个Yaf_Dispatcher实例作为参数。方法在Bootstrap类中的定义出现顺序, 决定了它们的被调用顺序。

define("APP_PATH",  dirname(__FILE__));
$app  = new Yaf_Application(APP_PATH . "/yaf.ini");
$app->bootstrap()->run();

Bootstrap 类样例:

class Bootstrap extends Yaf_Bootstrap_Abstract 
{ 

    public function _initRoute(Yaf_Dispatcher $dispatcher) {
        $router = Yaf_Dispatcher::getInstance()->getRouter();
        $router->addConfig(Yaf_Registry::get("config")->routes);
    }

    public function _initSession(Yaf_Dispatcher $dispatcher) 
    { 
        $session = new Vendor\Session(); 
        $session->start(); 
    } 

    public function _initDatabase(Yaf_Dispatcher $dispatcher) 
    { 
        $config = Yaf_Application::app()->getConfig()->application->database; 
        Yaf_Registry::set('db', Vendor\Database($config)); 
    } 
}

配置文件可以这样写

[common]
application.directory=APP_PATH "/application/"
[product : common]

可以添加其它配置组内容,通过 application.modules 添加了两个模块,多模块配置用逗号分隔,使用模块后目录结,如app这个模块的控制器及视图就要存放到 application/modules/controllers 目录下:

[modules]
application.ext=php
application.modules="app,index"

[redis]
;用作缓存的redis服务器
redis.cache.host = 192.168.254.128
redis.cache.port = 6379
redis.cache.dbIndex = 1

;用作存储用户信息的redis服务器
redis.user.host = 192.168.254.128
redis.user.port = 6379
redis.user.dbIndex = 12

;别忘了在这里加上你要读取的配置组名
[product : common : redis : modules]

配置好后需要一个控制器,保存到程序目录的 controllers 子目录下,控制器的默认动作方法是 indexAction。

class IndexController extends Yaf_Controller_Abstract {
    public function indexAction() {
        $this->getView()->assign("content", "Hello World");
        //读取配置文件
        $config = Yaf_Application::app()->getConfig();
        //打印配置信息
        echo '<pre>';
        print_r($config);
        echo '</pre>';
    }
}

另外还需要一个视图文件,getView()->assign() 是给视图传递数据,视图文件保存到程序目录的 views 目录下建立的和控制同名的子目录内,默认为 phtml 扩展名。

<html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
        <?php echo $content;?>
    </body>
</html>

如果是Ajax请求, 可以关闭HTML视图输出,disableView() 会关闭视图模块,而 autoRender(false) 则只关闭视图输出:

 if ($this->getRequest()->isXmlHttpRequest()) {
     Yaf_Dispatcher::getInstance()->disableView();
     Yaf_Dispatcher::getInstance()->autoRender(false);
 }

路由使用与CLI执行

Yaf摒弃了0.1版本中的自定义路由器方式, 而采用了更为灵活的路由器和路由协议分离的模式。

路由协议事实上主要负责匹配我们预先定义好的路由协议, 意思就是我们只有一个路由器, 但我们可以有许多路由协议。

路由器主要负责管理和运行路由链,它根据路由协议栈倒序依次调用各个路由协议, 一直到某一个路由协议返回成功以后, 就匹配成功。路由注册的顺序很重要, 最后注册的路由协议, 最先尝试路由, 这就有个陷阱。

请注意。路由的过程发生派遣过程的最开始,并且路由解析仅仅发生一次。路由过程在何控制器动作(Controller, Action)被派遣之前被执行, 一旦路由成功, 路由器将会把解析出得到的信息传递给请求对象(Yaf_Request_Abstract object), 这些信息包括moduel、controller、action、用户params等。 然后派遣器(Yaf_Dispatcher)就会按照这些信息派遣正确的控制器动作。 路由器也有插件钩子, 就是routerStartup和routerShutdown, 他们在路由解析前后分别被调用。

默认的路由协议Yaf_Route_Static, 就是分析请求中的request_uri, 在去除掉base_uri以后, 获取到真正的负载路由信息的request_uri片段, 具体的策略是, 根据"/"对request_uri分段, 依次得到Module,Controller,Action, 在得到Module以后, 还需要根据Yaf_Application::$modules来判断Module是否是合法的Module, 如果不是, 则认为Module并没有体现在request_uri中, 而把原Module当做Controller, 原Controller当做Action。

Yaf_Route_Simple是基于请求中的query string来做路由的, 在初始化一个Yaf_Route_Simple路由协议的时候, 我们需要给出3个参数, m/c/a这3个参数分别代表在query string中Module, Controller, Action的变量名。路由设置可以在 Bootstrap 中进行,也可以在 Yaf_Application 实例化之后 run() 之前进行。

$router = Yaf_Dispatcher::getInstance()->getRouter();
$route = new Yaf_Route_Simple("m", "c", "a");
$router->addRoute("name", $route);
// $router->addConfig(Yaf_Registry::get("config")->routes);

Yaf_Route_Supervar和Yaf_Route_Simple相似, 都是在query string中获取路由信息, 不同的是, 它获取的是一个类似包含整个路由信息的request_uri。Yaf_Route_Map议是一种简单的路由协议, 它将REQUEST_URI中以'/'分割的节, 组合在一起, 形成一个分层的控制器或者动作的路由结果.

Yaf_Route_Map的构造函数接受俩个参数, 第一个参数表示路由结果是作为动作的路由结果,还是控制器的路由结果。默认的是动作路由结果,第二个参数是一个字符串, 表示一个分隔符, 如果设置了这个分隔符, 那么在REQUEST_URI中, 分隔符之前的作为路由信息载体, 而之后的作为请求参数。

Yaf_Route_Rewrite是一个强大的路由协议, 它能满足我们绝大部分的路由需求。如果这些还不能满足,那就用复杂点的 Yaf_Route_Regex,这是一个正则匹配路由。

http://domain.com/index.php/index/test      Yaf_Route_Static
http://domain.com/index.php?c=index&a=test  Yaf_Route_Simple
http://domain.com/index.php?r=/m/index/test Yaf_Route_Supervar

使用命令行(Cli模式)运行,为了更好的与web区分重新创建一个入口文件是比较好的做法。 Yaf_Request_Simple 特别的被用于测试,例如:CLI模式下模拟一些特殊的要求。

$app = new YafApplication(APP_PATH . "/conf/application.ini");
$app->getDispatcher()->dispatch(new Yaf_Request_Simple());

这样入口文件就完成了。接下来,你需要学会yaf命令行的调用方法。来一个示例:

php index.php request_uri="/daemon/start"

Yaf_Request_Simple的构造函数可以不接受任何参数, 在这种情况下, Yaf_Request_Simple会在命令行参数中, 寻找一个字符串参数, 如果找到, 则会把请求的request_uri置为这个字符串。CLI参数中指定的路径便是 Controller 的路由路径。在例子里指向/Controller/Daemon.php 中的 startAction()方法。

要使得yaf在命令行模式下运行, 有俩种方式, 第一种方式专门为用Yaf开发Contab等任务脚本设计的方式, 这种方式下, 对Yaf的唯一要求就是能自动加载所需要的Model或者类库, 所以可以简单的通过Yaf_Application::execute来实现。它的第一参数需要定义一个回调函数,也可以是一个类中的某个函数。

$application->execute("main", $argc, $argv);
$application->execute(array("Class","Method"), $argc, $argv);

后面的参数为一个可变列表,值为你希望传入的参数。综上所述,我们的另外一种入口文件可以写成:

$app = new YafApplication(APP_PATH . "/conf/application.ini");
$app->execute('callback', $avg1, $avg2 , ...);

如果需要通过bootstrap去初始化。只需要和web一样改为:

$app->bootstrap()->execute('callback', $avg1, $avg2 , ...);

异常处理

Yaf实现了一套错误和异常捕获机制, 主要是对常见的错误处理和异常捕获方法做了一个简单抽象, 方便应用组织自己的错误统一处理逻辑。 Yaf自身出错时候, 根据配置可以分别采用抛异常或者触发错误的方式来通知错误。在配置文件 appliation.dispatcher.throwException 打开的情况下, Yaf会抛异常, 或者通过Yaf_Dispatcher::throwException(true) 亦可以,否则触发错误。

那么对应的, 就有俩套错误处理方式可供应用选用。 在配置文件开启 application.dispatcher.catchException 或者可通过Yaf_Dispatcher::catchException(true)时,当Yaf遇到未捕获异常的时候, 就会把运行权限, 交给当前模块的Error Controller的Error Action动作, 而异常或作为请求的一个参数, 传递给Error Action。

在Error Action中可以通过$request->getRequest()->getParam("exception")获取当前发生的异常。

从Yaf1.0.0.12开始, 也可以通过[图片上传失败...(image-de3d6a-1656056564998)]

exception的参数的话, 也可以直接通过这个参数获取当前发生的异常。

有了这样的最终异常处理逻辑, 应用就可以在出错的时候直接抛出异常, 在统一异常处理逻辑中, 根据各种不同的异常逻辑, 处理错误, 记录日志,Error Action实现参考如下:

class ErrorController extends Yaf_Controller_Abstract {
    public function errorAction($exception) {
        assert($exception === $exception->getCode());
        $this->getView()->assign("code", $exception->getCode());
        $this->getView()->assign("message", $exception->getMessage());
    }
}

class ErrorController extends Yaf_Controller_Abstract {
    public function errorAction($exception) {
        switch($exception->getCode()) {
            case YAF_ERR_LOADFAILD:
            case YAF_ERR_LOADFAILD_MODULE:
            case YAF_ERR_LOADFAILD_CONTROLLER:
            case YAF_ERR_LOADFAILD_ACTION:
            //404
            header("Not Found");
            break;

            case CUSTOM_ERROR_CODE:
            //自定义的异常
            break;
        }
    }
}

class ErrorController extends Yaf_Controller_Abstract {
    public function errorAction() {
        $exception = $this->getRequest()->getException();
        try {
            throw $exception;
        } catch (Yaf_Exception_LoadFailed $e) {
            //加载失败
        } catch (Yaf_Exception $e) {
            //其他错误
        }
    }
}

定义插件

插件类是用户编写的, 但是它需要继承自Yaf_Plugin_Abstract,通过编写插件可以在yaf运行过程中实现自己的逻辑。对于插件来说,主要还是借助yaf提供的6个Hook来实现,只需要在插件类中定义和hook事件同名的方法,那么这个方法就会在该事件触发的时候被调用,这就是yaf插件的原理。 插件方法可以接受俩个参数, Yaf_Request_Abstract实例和Yaf_Response_Abstract实例。

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) { .... }
}

Yaf定义了6个Hook, 它们分别是:

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

插件要生效, 还需要向Yaf_Dispatcher注册, 那么一般的插件的注册都会放在 Bootstra中 进行。

class Bootstrap extends Yaf_Bootstrap_Abstract{
    public function _initPlugin(Yaf_Dispatcher $dispatcher) {
        $user = new UserPlugin();
        $dispatcher->registerPlugin($user);
    }
}

一般的, 插件应该放置在APPLICATION_PATH下的plugins目录, 这样在自动加载的时候, 加载器通过类名, 发现这是个插件类, 就会在这个目录下查找。 当然, 插件也可以放在任何你想防止的地方, 只要你能把这个类加载进来就可以。

yaf自动加载器与Medoo模块

Medoo 是采用了ORM (Object Relational Mapping) 设计模式,基于PDO数据对象(PHP Data Object)封装,轻量单文件实现易于使用,适用于所有PHP框架,如Laravel,Codeigniter,Yii,Slim和支持单例扩展或编写器的框架,支持各种常见和复杂的SQL查询,数据映射以及防止SQL注入。访问mysql数据库参考如下,只需要按配置实例化即可以使用。实例化后,可以通过使用pdo成员直接访问PDO对象$db->pdo。

require_once "Medoo.php";
use Medoo\Medoo;

class DBCONFIG {
    const CONF = [
        // required
        'database_type' => 'mysql',
        'database_name' => 'dbname',
        'server' => '192.168.0.242',
        'username' => 'root',
        'password' => 'xxx',

        // [optional]
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_general_ci',
        'port' => 3306,

        // [optional] Table prefix
        // 'prefix' => '',

        // [optional] Enable logging (Logging is disabled by default for better performance)
        // 'logging' => true,

        // [optional] MySQL socket (shouldn't be used with server and port)
        // 'socket' => '/tmp/mysql.sock',

        // [optional] driver_option for connection, read more from http://www.php.net/manual/en/pdo.setattribute.php
        'option' => [
            PDO::ATTR_CASE => PDO::CASE_NATURAL
        ],

        // [optional] Medoo will execute those commands after connected to the database for initialization
        'command' => [
            'SET SQL_MODE=ANSI_QUOTES'
        ]
    ];
}
$db = new Medoo(DBCONFIG::CONF);
$db = new Medoo\Medoo(DBCONFIG::CONF);
$rows = $db->select("users", "name");
$rows = $db->select("users", ["name", "role", "timestamp"]);

如果使用 SQLite 配置更简单,还可以使用 In-memory database(IMDB) 即内存数据库,SQLite 数据库通常是存储在磁盘文件中的。然而在有些情况下,我们可以让数据库始终驻留在内存中。最常用的一种方式是在调用sqlite3_open()的时候,数据库文件名参数传递":memory:"。相比传统的基于磁盘的数据库管理系统,IMDB速度快得多。

$database = new medoo([
    'database_type' => 'sqlite',
    'database_file' => 'my/database/path/database.db'
]);

$database = new Medoo([
    'database_type' => 'sqlite',
    'database_file' => ':memory:'
]);

配置信息也可以写到程序配置文件上

[sqlite]
database.database_type='sqlite'
database.database_file='e:\coding\Yeen\ci\yaf\database.db'

[memdb]
database.database_type='sqlite'
database.database_file=':memory:'

[mysql]
database.database_type='mysql'
database.database_name='dbname'
database.server='192.168.0.242'
database.username='user'
database.password='password'

;[optional]
database.charset='utf8mb4'
database.collation='utf8mb4_general_ci'
database.port=3306

;[optional] Table prefix
database.prefix='tbl_'

;[optional] Enable logging (Logging is disabled by default for better performance)
database.logging='true'

;[optional] MySQL socket (shouldn't be used with server and port)
;database.socket='/tmp/mysql.sock'

;[optional] driver_option for connection, read more from http://www.php.net/manual/en/pdo.setattribute.php
;database.option[] = PDO::ATTR_CASE "=" PDO::CASE_NATURAL

;[optional] Medoo will execute those commands after connected to the database for initialization
database.command[] = 'SET SQL_MODE=ANSI_QUOTES'

读取配置方法示例有两种,一是直接使用Yaf程序已经加载的配置,另一种是直接实例化一个 Yaf_Config_Ini 对象来读入配置文件。使用 Yaf_Config_Ini 可以指定读取的配置节点,如 [sqlite] 配置节点的内容。

$config = Yaf_Application::app()->getConfig()->toArray();
$db = new Medoo\Medoo($config['database']);
$rows = $db->select("users", ["name", "role", "timestamp"]);

// $conf = (new Yaf_Config_Ini('yaf.ini', "sqlite"))->toArray();
$conf = (new Yaf_Config_Ini('yaf.ini'))->toArray();
$database = $conf['product']['database'];
$db = new Medoo\Medoo($database);

在Yaf中使用Medoo,只需要将下载到Medoo.php放到程序目录下的library目录下即可,Yaf 会在实例化 Medoo 时自动加载它。注意 Medoo 使用了命令空间,自PHP 5.3开始支付命令空间,在使用时要使用use引入Medoo类或者在实例化时将命令空间写上 new Medoo\Medoo($config)。注意 use 引入的命令空间不被include引入的文件识别,所以即使在入口 index.php 引入了 Medoo 命令空间,想要在模型或控制器中使用 Medoo 则还是需要重新引入命令空间的。

composer就是用来解决自动加载的工具,有了自动加载基本就抛弃了require和include函数。一个项目中,这两个函数只可能出现一次,那就是require '../vendor/autoload.php'。这个工具根据配置文件 composer.json 的依赖项 require 和加载项 autoload 来完成自动加载任务。autoload 中又包含主要的两个选项 files 和 psr-4。files 就是需要 composer 自动加载的函数库,不含类。只要在 files 这个数组中将函数库的文件路径填写好即可。PSR-4 是PHP Standards Recommendation的简称,是FIG-PHP工作组推出的自动加载技术规范,它能够满足面向package的自动加载,它规范了如何从文件路径自动加载类,同时规范了自动加载文件的位置。psr-4 顾名思义,是一个基PSR-4自动加载规则的类库信息,只要在其后的配置项以 {"命名空间": "类实现文件路径"} 的方式写入类库信息即可。

php中对应的spl_autoload_register函数用来实现自动加载,如下实现的一个autoload.php,在实例化时,PHP遇到没有定义的类就会执行 spl_autoload_register 注册的自动加载函数,函数接收到类命令空间信息后再引入指定目录下的类文件。

function classLoader($class)
{
    $path = str_replace('\\', DIRECTORY_SEPARATOR, $class);
    $file = __DIR__ . '/src/' . $path . '.php';

    if (file_exists($file)) {
        require_once $file;
    }
}
spl_autoload_register('classLoader');

Yaf为了方便在一台服务器上部署的不同产品之间共享公司级别的共享库, 支持全局类和本地类两种加载方式。 全局类是指, 所有产品之间共享的类, 这些类库的路径是在 php.ini 配置项 ap.library 设置的。当然,如果PHP在编译的时候, 支持了 with-config-file-scan-dir 那么也可以写在单独的 ap.ini 中设置。而本地类是指, 产品自身的类库, 这些类库的路径是通过在产品的配置文件中, 通过ap.library配置的。 在Yaf中, 通过调用 Yaf_Loader的registerLocalNamespace方法来申明哪些类前缀是本地类即可。

Yaf运行时配置项参考,其中 yaf.library yaf.use_namespace yaf.use_spl_autoload 这三个配置是和类自动加载相关的。

选项名称              默认值  可修改范围       更新记录
yaf.environ          product PHP_INI_ALL     环境名称, 当用INI作为Yaf的配置文件时, 这个指明了Yaf将要在INI配置中读取的节的名字
yaf.cache_config     0       PHP_INI_SYSTEM  是否缓存配置文件只针对INI配置文件生效, 打开此选项可在复杂配置的情况下提高性能
yaf.name_suffix      1       PHP_INI_ALL     在处理Controller, Action, Plugin, Model的时候, 类名中关键信息是否是后缀式, 比如UserModel, 而在前缀模式下则是ModelUser
yaf.name_separator   ""      PHP_INI_ALL     在处理Controller, Action, Plugin, Model的时候, 前缀和名字之间的分隔符, 默认为空, 也就是UserPlugin, 加入设置为下划线_, 则判断的依据就会变成:"User_Plugin", 这个主要是为了兼容ST已有的命名规范
yaf.forward_limit    5       PHP_INI_ALL     forward最大嵌套深度
yaf.library          NULL    PHP_INI_ALL     全局类库的目录路径
yaf.use_namespace    0       PHP_INI_SYSTEM  开启的情况下, Yaf将会使用命名空间方式注册自己的类, 比如Yaf_Application将会变成Yaf\Application
yaf.use_spl_autoload 0       PHP_INI_ALL     开启的情况下, Yaf在加载不成功的情况下, 会继续让PHP的自动加载函数加载, 从性能考虑, 除非特殊情况, 否则保持这个选项关闭

Yaf应用配置中关于自动加载的配置项有三个

application.library               本地类库目录路径
application.library.directory     本地类库目录路径
application.library.namespace     以逗号分隔的本地库命名空间前缀

在配置项 yaf.use_spl_autoload 关闭的情况下, Yaf Autoloader在一次找不到的情况下, 会立即返回, 而剥夺其后的自动加载器的执行机会。

Yaf类的自动加载规则, 都是一样的: Yaf规定类名中必须包含路径信息, 也就是以下划线 _ 分割的目录信息。Yaf将依照类名中的目录信息, 完成自动加载,例如, 在没有申明本地类的情况下,Yaf将在类库目录中寻找类定义文件,类库路径在 php.ini 的配置项 ap.library 中指定,默认路径是程序目录下的 library 子目录。如 Foo_Dummy_Bar 这样的类对应了 library/Foo/Dummy/Bar.php。

如果通过 registerLocalNamespace 方式注册注册本地类,下面申明凡是以Foo和Local开头的类, 都是本地类,

 $loader = Yaf_Loader::getIgnstance();
 $loader->registerLocalNamespace(array("Foo", "Local"));

那么对于刚才的例子, 将会在程序配置文件中指定的类库路径,即 application.ini 中指定的 ap.library 目录下寻找Foo_Dummy_Bar。

分析源代码可以得到Yaf_Loader自动加载策略有以下几个要点

* 1)yaf.library和application.library匀未配置时,Yaf_Loader::$_library及Yaf_Loader::$_global_library都将设置为[application.directory]/library;故不管是否配置application.library.namespace或者Yaf/Loader::registerLocalNamespace()是否注册本地命名空间前缀,加载类文件时,自动到[application.directory]/library目录查找类并加载。
* 2)如果配置了application.library时,但未配置application.library。namespace时或者未通过Yaf/Loader::registerLocalNamespace()注册本地命名空间前缀,不管yaf.library是否配置都到yaf.library中加载相应类文件。
* 3)如果配置了application.library和application.library.namespace,且类名中包含配置的命名空间前缀,则到application.library加载相应的类文件,否则到yaf.library中加载相应类文件。
* 4)Yaf内部中加载文件时,类名中有”_”会转换为目录分隔符。

Yaf在自启动的时候, 会通过SPL注册一个自己的Autoloader, 出于性能的考虑, 对于框架相关的MVC类, Yaf Autoloader只以目录映射的方式尝试一次。但是要注意的一点是, 从2.1.18开始, Yaf支持在PHP脚本中触发对Controller的自动加载, 但是因为Controller的定位需要根据Module路由结果来判断, 这就造成了 在Bootstrap或者RouteStarrup之前, 无法确定. 所以, 对于Controller的加载, Yaf将只会尝试去加载默认Module的Controller, 也就是只在"{项目路径}/controllers" 目录下寻找。

具体的目录映射规则如下,后缀或者前缀可以通过php.ini中ap.name_suffix来切换:

类型      后缀或者前缀   映射路径
控制器    Controller    默认模块下为{项目路径}/controllers/, 否则为{项目路径}/modules/{模块名}/controllers/
数据模型  Model         {项目路径}/models/
插件      Plugin        {项目路径}/plugins/

而对于非框架MVC相关的类, Yaf支持全局类和自身类的两种加载方式, 并且Yaf支持大小写敏感和不敏感两种方式来处理文件路径。

应用SQLite数据与前端框架结合

SQLite 是一个软件库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是在世界上最广泛部署的 SQL 数据库引擎,特别是在小型设置上。SQLite 源代码不受版权限制。在windows平台上php一般自带了 sqlite.dll 扩展,配置文件中打开就好,在Ubuntu系统上可以执行以下命令安装,视php版本选择。一般安装后会自动配置,可以查看 Additional .ini 是否有 pdo_sqlite.ini,有则表明已经自动加载了。

sudo apt-get install php7.0-sqlite
sudo apt-get install php5.6-sqlite

SQLite 数据库的数据类型简单:

存储类 描述
NULL    值是一个 NULL 值。
INTEGER 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。
REAL    值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。
TEXT    值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。
BLOB    值是一个 blob 数据,完全根据它的输入存储。

SQLite 的存储类稍微比数据类型更普遍。INTEGER 存储类,例如,包含 6 种不同的不同长度的整数数据类型。如果对 SQLite 系统不熟悉,可以考虑使用一些数据库管理工具如 Navicat 之类。SQLite 官方也提供了命令工具,可以用来做查询调试。

为了方便开发,可以使用 php 内置的 Server,执行以下命令即可在本地运行一个服务器 通过localhost即可以访问,如果要在局域网其它主机上访问,可以在80端口前指定IP地,localhost 或IP最好选一个,避免服务器接收不到请求,在 Windows 平台还可以使用 start 命令打开页面。注意修改php配置,如果Medoo使用了PDO方式访问mysql数据库,请确保打开配置文件中的 extension=php_pdo_mysql.dll。在 Linux 服务器上要确保目录读写权限打开。

php -S localhost:80 -t e:\coding\Yeen\ci\yaf
start http://localhost && php -S localhost:80 -t e:\coding\Yeen\ci\yaf

将 Medoo 类文件拷贝到 library 目录后,就可以开始正式写程序,实现自己的控制器和视图了,那么这里就结合 vue + bootstrap 等写个例子程序,这个例子代码可以直接替换默认的 Index.php 控制器。

// use Medoo\Medoo;
class IndexController extends Yaf_Controller_Abstract {
    public function indexAction() {
        $db = new Medoo\Medoo(DBCONFIG::CONF);
        $rows = $db->select("users", ["name", "role", "timestamp"]);
        $this->getView()->assign("content", "Hello World");
        $this->getView()->assign("rows", $rows);
    }
}

PDO方法参考

PDO::beginTransaction — 启动一个事务
PDO::commit — 提交一个事务
PDO::__construct — 创建一个表示数据库连接的 PDO 实例
PDO::errorCode — 获取跟数据库句柄上一次操作相关的 SQLSTATE
PDO::errorInfo — 获取错误信息
PDO::exec — 执行一条 SQL 语句,并返回受影响的行数
PDO::getAttribute — 取回一个数据库连接的属性
PDO::getAvailableDrivers — 返回一个可用驱动的数组(了解即可)
PDO::inTransaction — 检查是否在一个事务内(了解即可)
PDO::lastInsertId — 返回最后插入行的ID或序列值
PDO::prepare — 创建SQL的预处理,返回PDOStatement对象
PDO::query — 用于执行查询SQL语句,返回PDOStatement对象
PDO::quote — 为sql字串添加单引号
PDO::rollBack — 回滚一个事务
PDO::setAttribute — 设置属性
$stmt = $pdo->query('select * from user limit 2');
$row = $stmt->fetch();
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容