CodeIgniter 是個(gè)很傳統(tǒng)的 PHP 框架覆山,小巧玲瓏,盡管與 Laravel 等新興框架相比端幼,缺乏優(yōu)雅礼烈,但它簡(jiǎn)單、容易上手婆跑、易掌控
下面記錄一下此熬,我在用 CodeIgniter(以下簡(jiǎn)稱 CI)過(guò)程中,摸索或查閱到的一些技巧
先做一些約定:
-
dirApp
表示 application 目錄
一洽蛀、改造 Controller 方法名摹迷,加上 Http Method 前綴
該方法為本人原創(chuàng),受 YII郊供、Laravel 等框架啟發(fā)
CI 支持自定義一些類峡碉,重定義其本身的行為,創(chuàng)建 dirApp/core/MY_Router.php
驮审,重寫 set_method
方法鲫寄,如下:
<?php
class MY_Router extends CI_Router
{
public function set_method($method)
{
$verb = isset($_SERVER['REQUEST_METHOD'])
? strtolower($_SERVER['REQUEST_METHOD'])
: '';
$this->method = $verb . ucfirst($method);
}
}
改造之后,對(duì)于 Http 請(qǐng)求:
-
get /vendor/list
疯淫,將會(huì)執(zhí)行controllers/Vendor::getList
方法 -
post /vendor/list
地来,將會(huì)執(zhí)行controllers/Vendor::postList
方法 -
put /vendor/list
,將會(huì)執(zhí)行controllers/Vendor::putList
方法 - 以此類推
而改造之前熙掺,這些方法就沒有區(qū)分未斑,都會(huì)執(zhí)行 controllers/Vendor::list
方法。很不幸币绩,list
是 php 的關(guān)鍵字蜡秽,你還不能用 list
作為方法名
二、修復(fù) CI Route 不能帶 Url 參數(shù)的行為
該方法思路來(lái)自網(wǎng)絡(luò)缆镣,后經(jīng)本人優(yōu)化芽突,原文鏈接太久忘了,就不放了
假設(shè)我定義一個(gè)這樣的 route
:
$route['error/(:num)'] = 'site/error/index?code=$1';
CI 竟然不支持帶 Url 參數(shù)董瞻,會(huì)把整個(gè) index?code=$1
識(shí)別為方法名寞蚌,從而找不到正確的方法
為了糾正這一行為,仍然重寫 dirApp/core/MY_Router.php
的 set_method
的方法,代碼如下:
<?php
class MY_Router extends CI_Router
{
public function set_method($method)
{
$parts = explode('?', $method, 2);
if (count($parts) == 2) {
$t = $_SERVER['QUERY_STRING'];
$q = $parts[1] . ($t != '' ? '&' : '') . $t;
parse_str($q, $_GET);
}
$this->method = $parts[0];
}
}
代碼原理很簡(jiǎn)單挟秤,就是通過(guò) explode
將 Url 參數(shù)從 $method
分離出來(lái)壹哺,將前面的部分賦值給 $this->method
,并用 parse_str
將 Url 參數(shù)煞聪,賦值到 $_GET
一斗躏、二都是重寫 set_method
逝慧,若要具備兩者的功能昔脯,代碼則應(yīng)改為:
<?php
class MY_Router extends CI_Router
{
public function set_method($method)
{
$parts = explode('?', $method, 2);
if (count($parts) == 2) {
$t = $_SERVER['QUERY_STRING'];
$q = $parts[1] . ($t != '' ? '&' : '') . $t;
parse_str($q, $_GET);
}
$verb = isset($_SERVER['REQUEST_METHOD'])
? strtolower($_SERVER['REQUEST_METHOD'])
: '';
$this->method = $verb . $parts[0];
}
}
三、設(shè)置應(yīng)用代碼目錄名稱
CI 默認(rèn)的應(yīng)用代碼目錄名為 application
笛臣,這個(gè)名字老長(zhǎng)了云稚,不喜,改為 web
沈堡,打開入口文件 index.php
静陈,搜索 application_folder =
,并改寫如下:
$application_folder = 'web';
后文用
web
目錄诞丽,指代application
目錄
四鲸拥、給 CI 插上 Composer 的翅膀
Composer 是現(xiàn)代化 PHP 框架的標(biāo)配,而 CI 很傳統(tǒng)僧免,默認(rèn)沒有帶 Composer刑赶,也沒有命名空間,這很不方便懂衩,其實(shí)加上 Composer 與命名空間也很簡(jiǎn)單
1撞叨、首先需要安裝 Composer,這步不贅述浊洞,考慮到 GFW 的影響牵敷,最好按照 https://pkg.phpcomposer.com/
上的說(shuō)明,設(shè)置中國(guó)鏡像
2法希、在根目錄枷餐,創(chuàng)建 composer.json
,寫入:
{
"autoload": {
"psr-4": {
"web\\": "web/"
}
}
}
應(yīng)根據(jù)需要修改
psr-4
中的內(nèi)容苫亦,我這里用的是web
作為 application 的名稱
3毛肋、然后在根目錄執(zhí)行 composer dump
4、在 index.php
末尾部分著觉,最后一句 require_once BASEPATH.'core/CodeIgniter.php';
之前添加:
require_once 'vendor/autoload.php';
5村生、除了 Controller 與 core/MY_*,應(yīng)用程序目錄 web
里的其他文件饼丘,開頭應(yīng)帶上 namespace
聲明趁桃,例如文件 web\models\User.php
,開頭這樣聲明:
<?php
namespace web\models;
引用該 Model 也很簡(jiǎn)單,請(qǐng)用 PHP 的機(jī)制卫病,不要再用 CI 的 $this->load->model
油啤,如下:
<?php
use web\models\User;
// 或者
use web\models\User as UserModel;
同樣,應(yīng)該廢棄 CI 的 helpers
蟀苛,在 web
目錄下益咬,創(chuàng)建 util
子目錄,并用標(biāo)準(zhǔn)的 PHP 命名空間/面向?qū)ο髾C(jī)制規(guī)劃工具類
實(shí)際上帜平,引入 Composer 后幽告,幾乎可以廢棄所有的
$this->load->xxx
加載方法,改用標(biāo)準(zhǔn)的 PHP 類加載機(jī)制
6裆甩、每次上線前冗锁,應(yīng)執(zhí)行一次 composer dump -o
,以便優(yōu)化 Composer 的執(zhí)行速度
五嗤栓、為 Model 定義基類
為了繼承 CI 的遺產(chǎn)冻河,可以為 Model 定義一個(gè)基類,放在 web\core\BaseModel.php
里:
<?php
namespace web\core;
// load_class 已做了緩存茉帅,不會(huì)重復(fù)加載
load_class('Model', 'core');
class BaseModel extends \CI_Model
{
use \web\util\CI;
use \web\util\Db;
use \web\util\Instance;
...
}
web\util\CI
是一個(gè)很有用的 trait叨叙,用來(lái)在 model 里方便地獲取 CI 實(shí)例,這樣定義的:
<?php
namespace web\util;
/**
* 提供獲取 CI 實(shí)例的方法
*/
trait CI
{
protected static function ci()
{
$ci = &get_instance();
return $ci;
}
}
web\util\Db
用來(lái)方便地操縱數(shù)據(jù)庫(kù)堪澎,定義如下:
<?php
namespace web\util;
/**
* 提供一組訪問(wèn)數(shù)據(jù)庫(kù)的方法
*/
trait Db
{
protected static function db($name = 'default')
{
if (!isset(self::$_ciDbCache[$name])) {
$ci = &get_instance();
log_message('debug', "load db $name");
self::$_ciDbCache[$name] = $ci->load->database($name, true);
}
return self::$_ciDbCache[$name];
}
private static $_ciDbCache = [];
protected static function executeSql($sql, $data = null)
{
return self::db()->query($sql, $data);
}
}
web\util\Instance
用來(lái)提供單例模式擂错,定義如下:
<?php
namespace web\util;
/**
* 實(shí)現(xiàn)單例模式
*/
trait Instance
{
final public static function instance()
{
$className = get_called_class();
if (!isset(self::$_instanceList[$className])) {
self::$_instanceList[$className] = new static;
}
return self::$_instanceList[$className];
}
private static $_instanceList = [];
}
六、為 Controller 定義基類
Controller 也需要有一個(gè)共同的基類全封,以便定義一些公用的行為马昙,大致代碼如下(省略了一些本司商業(yè)邏輯,以免泄密 ??刹悴,只討論技術(shù)):
<?php
namespace web\core;
...
class BaseController extends \CI_Controller
{
/**
* 加載 Twig View
* @data 傳遞給 View 的數(shù)據(jù)
* @view 可選的 View 路徑行楞,一般不需傳,會(huì)自動(dòng)獲取
*/
protected function view($data = null, $view = null)
{
View::render($data, $view);
}
/**
* 輸出 { s: , data: } 格式的 json 數(shù)據(jù)
* @s 數(shù)字狀態(tài)碼土匀,默認(rèn) 200子房,表示成功
* @data 附加數(shù)據(jù),應(yīng)傳遞關(guān)聯(lián)數(shù)組就轧,不宜使用數(shù)值數(shù)組
* @jsonp 可選证杭,設(shè)置該參數(shù),則返回 JSONP 數(shù)據(jù)妒御;
* 支持 true 或字符串解愤,傳 true 時(shí),將自動(dòng)提取
* callback 參數(shù)(jquery 的默認(rèn)方式)作為 callback
*/
protected function json($s = 200, $data = null, $jsonp = false)
{
...
}
/**
* 帶可選子域名的重定向
* @uri 重定向的目的地址
* @keepReferrer 是否保留 document.referrer
* @domain 可選子域名乎莉,例如傳遞 www送讲,則重定向到 www.yourdomain.com/$uri
*/
protected function redirect($uri, $keepReferrer = false, $domain = null)
{
$url = $domain != null
? '//' . preg_replace('/^[^\.]*\./', $domain . '.', $_SERVER['HTTP_HOST']) . $uri
: $uri;
$keepReferrer
? die("<script>location = '$url'</script>")
: header("Location:$url");
}
/**
* 跳轉(zhuǎn)到錯(cuò)誤頁(yè) xxx
*/
protected function goError($code) {
$this->redirect('/error' . $code, true);
}
/**
* 跳轉(zhuǎn)到 404 頁(yè)
*/
protected function go404()
{
$this->goError(404);
}
/**
* 跳轉(zhuǎn)首頁(yè)
*/
protected function goHome()
{
$this->redirect('xxx', true);
}
/**
* 跳轉(zhuǎn)到登錄頁(yè)
*/
protected function goLogin()
{
$loginUrl = 'xxx';
$this->redirect($loginUrl, true);
}
/**
* 是否為 ajax 請(qǐng)求
*/
protected function isAjax()
{
return 'XMLHttpRequest' == @$_SERVER['HTTP_X_REQUESTED_WITH'];
}
/**
* 是否為 post 請(qǐng)求
*/
protected function isPost()
{
return 'POST' == @$_SERVER['REQUEST_METHOD'];
}
/**
* 當(dāng)前登錄的用戶 ID
*/
protected $uid = 0;
/**
* 當(dāng)前登錄的用戶實(shí)例
*/
protected $user = null;
/**
* 需要登錄
*/
protected function requireLogin()
{
...
}
}
再定義一個(gè) web\core\RequireLoginController
類奸笤,所有需要登錄的頁(yè)面,應(yīng)繼承自該類:
<?php
namespace web\core;
/**
* 所有需要登錄的頁(yè)面哼鬓,繼承自該類
*/
class RequireLoginController extends BaseController
{
public function __construct()
{
parent::__construct();
$this->requireLogin();
}
}
本來(lái)监右,
requireLogin
方法,是放在RequireLoginController
類的异希,但后來(lái)健盒,考慮到一些繼承自BaseController
的某個(gè)頁(yè)面,也可能需要登錄
七称簿、引入優(yōu)秀 ORM 庫(kù) Eloquent
該方法是一名喜歡 Laravel 的前同事總結(jié)的
Eloquent
是非常優(yōu)秀的 ORM 庫(kù)扣癣,將它引入 CI 也非常簡(jiǎn)單,github 也有現(xiàn)成的庫(kù)予跌,但這里手寫代碼搏色,也不復(fù)雜
以下示例善茎,使用 mysql 數(shù)據(jù)庫(kù)券册,其他請(qǐng)根據(jù)實(shí)際情況進(jìn)行修改
1、根目錄執(zhí)行 composer require illuminate/database
2垂涯、創(chuàng)建 web/libraries/Eloquent.php
烁焙,寫入代碼:
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
$runtimeDb = APPPATH . 'config/' . ENVIRONMENT . '/database.php';
$defaultDb = APPPATH . 'config/database.php';
if (is_file($runtimeDb)) {
require_once $runtimeDb;
} else {
if (is_file($defaultDb)) {
require_once $defaultDb;
} else {
exit('No database config file be found');
}
}
$capsule = new Capsule;
$ciToEloquentKeyMap = [
'hostname' => 'host',
'username' => 'username',
'password' => 'password',
'database' => 'database',
'dbdriver' => 'driver',
'dbprefix' => 'prefix',
'char_set' => 'charset',
'dbcollat' => 'collation',
'stricton' => 'strict',
];
foreach ($db as $k => $v) {
$t = [];
if (!isset($v['char_set']) or $v['char_set'] != 'utf8') {
$v['char_set'] = 'utf8';
}
foreach ($v as $mm => $nn) {
if (isset($ciToEloquentKeyMap[$mm])) {
$t[$ciToEloquentKeyMap[$mm]] = $nn;
} else {
$t[$mm] = $nn;
}
}
$t['driver'] = 'mysql';
$capsule->addConnection($t, $k);
}
$capsule->bootEloquent();
3、打開 index.php
耕赘,在 require_once 'vendor/autoload.php';
與 require_once BASEPATH.'core/CodeIgniter.php';
之間骄蝇,插入:
require_once 'web/libraries/Eloquent.php';
4、定義一個(gè)基類 web\core\EloquentModel
:
<?php
namespace web\core;
/**
* Eloquent Model 基類
*/
class EloquentModel extends \Illuminate\Database\Eloquent\Model
{
use \web\util\CI;
use \web\util\Instance;
protected $guarded = ['id'];
// 如果表中沒有 created_at updated_at 字段操骡,子類需要加
// public $timestamps = false;
}
更多用法九火,請(qǐng)參考 Eloquent
官方文檔
整篇完。歡迎轉(zhuǎn)載册招,轉(zhuǎn)載請(qǐng)注明出處:
簡(jiǎn)書作者:lip2up
微信公眾號(hào):前端大牛