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();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章