原文:YAF框架入门教程-简书

YAF框架试用

介绍与安装

Yaf框架是一个c语言编写的PHP框架,是一个以PHP扩展形式提供的PHP开发框架,相比于一般的PHP框架, 它更快,更轻便,内存占用率更低,就是本着对性能的追求,Yaf把框架中不易变的部分抽象出来,类如路由、自动加载、bootstrap、分发等,采用PHP扩展去实现,以此来保证性能。

Yaf优点

用c语言开发的PHP框架,相比原生的PHP,几乎不会带来额外的性能开销。
所有的框架类,不需要编译,在PHP启动的时候加载,并常驻内存。
更快的执行速度,更少的内存占用。
灵巧的自动加载。 支持全局和局部两种加载规则, 方便类库共享。

yaf缺点

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

linux 平台下安装上海步骤如,首先使用 wget 下载yaf源代码,再使用 phpize 去安装扩展。假如你的服务器上安装了多个版本php,那么需要告诉phpize要建立基于哪个版本的扩展。通过使用--with-php-config=指定你使用哪个php版本,whereis php-config 可以提供定位信息,安装扩展后根据extension_dir配置一下php.ini以加载yaf.so。php 7.0建议使用 yaf 3.0。

wget http://pecl.php.net/get/yaf-2.2.9.tgz
tar -zxf yaf-2.2.9.tgz
apt install php7.0-dev
phpize
./configure --with-php-config=/usr/bin/php-config
make
make install

使用 php 5.6 可以下载 yaf 2.3.3,windows平台有DLL库,下载后解压到php扩展目录下,并相应设置 php.ini 的 extension。

基本程序结构

基本程序目录结构如下,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 //插件目录

如果在application.ini中定义了模块功能,则需要在程序目录下modules目录下建立模块目录,再建controllers目录,测试中没有发现有大小写要求,但尽量保持目录字母大写一到致。一个简单的 index.php 入口程序可以实例化一个Yaf程序类,执行run()方法,bootstrap() 是可选的,它指示Yaf_Application去寻找 Bootstrap.php, 而这个文件中, 必须定义一个Bootstrap类, 而这个类也必须继承自Yaf_Bootstrap_Abstract。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开始, 也可以通过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 MedooMedoo($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 教程 - http://www.runoob.com/sqlite/sqlite-tutorial.html
SQLite Download Page - https://www.sqlite.org/download.html

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);
    }
}

视图展示了VUE的表单处理能力,代码稍有点长

<html>
    <head>
        <meta charset="utf-8">
        <title>Hello World</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" >
        <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
    </head>
    <body>
        <div id="app" class="container">
            <div class="jumbotron">
            <h1><?php echo $content;?></h1>
            <div class="table-responsive"><table class="table">
                <tr class="row" v-for="item in rows">
                    <td class="col-xs-1"><label class="btn btn-primary">{{item.name}}</label></td>
                    <td class="col-xs-1"><label class="btn btn-danger"  v-if="item.role=='administrator'">{{item.role}}</label></td>
                    <td class="col-xs-1"><label class="btn btn-danger"  v-if="item.role=='advanced'">{{item.role}}</label></td>
                    <td class="col-xs-1"><label class="btn btn-danger"  v-if="item.role=='operator'">{{item.role}}</label></td>
                    <td class="col-xs-1"><label class="btn btn-success" v-if="item.role=='agent'">{{item.role}}</label></td>
                    <td class="col-xs-1"><label class="btn btn-warning" v-if="item.role=='member'">{{item.role}}</label></td>
                    <td class="col-xs-1"><label class="btn btn-info"    v-if="item.role=='guest'">{{item.role}}</label></td>
                    <td class="col-xs-1"><label class="btn btn-info">{{item.timestamp}}</label></td>
                </tr>
            </table></div>
            </div>
            <div class="panel">

                <div class="well">
                    <p>单选框:</p>
                    <input type="radio" value="ABC" name="group" v-model="radio">
                    <input type="radio" value="XYZ" name="group" v-model="radio">
                    <label for="checkbox">{{ radio }}</label>
                </div>

                <div class="well">
                    <p>单个复选框:</p>
                    <input type="checkbox" value="true" id="checkbox1" v-model="listA">
                    <label for="checkbox1">{{ listA }}</label>
                    <input type="checkbox" value="false" id="checkbox2" v-model="listB">
                    <label for="checkbox2">{{ listB }}</label>
                </div>

                <div class="well">
                    <p>多个复选框:</p>
                    <input type="checkbox" id="vue" value="Vue" v-model="checkedNames">
                    <label for="vue">vue</label>
                    <input type="checkbox" id="bootstrap" value="Bootstrap" v-model="checkedNames">
                    <label for="bootstrap">Bootstrap</label>
                    <p>选择的值为: {{ checkedNames }}</p>
                </div>

                <div class="well">
                    <select v-model="selected" name="strawberry">
                    <option value="">单选选择</option>
                    <option value="vue">vue</option>
                    <option value="bootstrap">bootstrap</option>
                    </select>
                    <p>选择的是: {{selected}}</p>
                </div>

                <div class="well">
                    <select multiple v-model="multiple" name="strawberry">
                    <option value="">多选列表</option>
                    <option value="vue">vue</option>
                    <option value="bootstrap">bootstrap</option>
                    </select>
                    <p>选择的是: {{multiple}}</p>
                </div>

            </div>
        </div>
        <script>
        new Vue({
            el: '#app',
            data: {
                rows:<?php echo json_encode($rows,JSON_UNESCAPED_UNICODE);?>,
                radio : "XYZ",
                listA : "true",
                listB : "true",
                selected : '',
                multiple : ['vue','bootstrap'],
                checkedNames: []
            }
        })
        </script>
    </body>
</html>

AF + VUE + Bootstrap demo.jpg

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();
$rows = $stmt->fetchAll();

作者:坚果jimbowhy
链接:https://www.jianshu.com/p/1460d2296f19
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签: none