CodeIgniter 高級(jí)技巧

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.phpset_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):前端大牛

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末岔激,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子是掰,更是在濱河造成了極大的恐慌虑鼎,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键痛,死亡現(xiàn)場(chǎng)離奇詭異炫彩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)絮短,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門江兢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人丁频,你說(shuō)我怎么就攤上這事杉允∪忧叮” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵夺颤,是天一觀的道長(zhǎng)似谁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)突那,這世上最難降的妖魔是什么雀久? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮寥裂,結(jié)果婚禮上嵌洼,老公的妹妹穿的比我還像新娘。我一直安慰自己封恰,他們只是感情好麻养,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诺舔,像睡著了一般鳖昌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上低飒,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天许昨,我揣著相機(jī)與錄音,去河邊找鬼褥赊。 笑死糕档,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拌喉。 我是一名探鬼主播速那,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼尿背!你這毒婦竟也來(lái)了端仰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤残家,失蹤者是張志新(化名)和其女友劉穎榆俺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坞淮,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茴晋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了回窘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诺擅。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖啡直,靈堂內(nèi)的尸體忽然破棺而出烁涌,到底是詐尸還是另有隱情苍碟,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布撮执,位于F島的核電站微峰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏抒钱。R本人自食惡果不足惜蜓肆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谋币。 院中可真熱鬧仗扬,春花似錦、人聲如沸蕾额。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诅蝶。三九已至退个,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秤涩,已是汗流浹背帜乞。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筐眷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓习柠,卻偏偏與公主長(zhǎng)得像匀谣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子资溃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 是什么 如果你知道yum武翎、apt-get、npm溶锭、bower等命令中的一種或者多種宝恶,那么,你也能很快知道compo...
    旱魃一樣閱讀 3,120評(píng)論 0 9
  • Composer Repositories Composer源 Firegento - Magento模塊Comp...
    零一間閱讀 3,956評(píng)論 1 66
  • Awesome PHP 一個(gè)PHP資源列表趴捅,內(nèi)容包括:庫(kù)垫毙、框架、模板拱绑、安全综芥、代碼分析、日志猎拨、第三方庫(kù)膀藐、配置工具屠阻、W...
    guanguans閱讀 5,753評(píng)論 0 47
  • ziadoz在 Github發(fā)起維護(hù)的一個(gè)PHP資源列表,內(nèi)容包括:庫(kù)额各、框架国觉、模板、安全虾啦、代碼分析蛉加、日志、第三方庫(kù)...
    Gundy_閱讀 6,285評(píng)論 4 192