FuelPHPでRedisを使用する

FuelPHPには、標準でRedisクラスが用意されている為、特別な事をする必要はなく、そのままRedisへアクセスが可能です。

例によって、ソート済みセット型を使用した、ランキング風のサンプルで試しました。

以下は、ユーザー名と年齢のリストをソート済みセット型に追加し、要素を取得するサンプルになります。

<?php
class Controller_Redistest extends Controller
{
    public function action_index()
    {
        $redis = Redis::instance('default');

        $staff = array(
                     'staff1' => 30,
                     'staff2' => 25,
                     'staff3' => 42,
                     'staff4' => 60,
                     'staff5' => 38
                 );

        foreach ($staff as $name => $age) {
            // 年齢をスコアとして、スタッフ名をメンバーとして登録
            $redis->zadd('mysort', $age, $name);
        }

        // 年齢の昇順リスト
        $list = $redis->zrange('mysort', 0, count($staff));

        // 年齢の降順リスト
        $rlist = $redis->zrevrange('mysort', 0, count($staff));

        // 昇順の場合の staff4 の順位(ランクが0から始まる為、1を足す)
        $rank = $redis->zrank('mysort', 'staff4') + 1;

        // 降順の場合の staff4 の順位(ランクが0から始まる為、1を足す)
        $rrank = $redis->zrevrank('mysort', 'staff4') + 1;
        ...
    }
}

Redis::instanceにセットする名前は『fuel/app/config/db.php』の『redis』に設定した名前になります。

'redis' => array(
        'default' => array(
                'hostname' => '127.0.0.1',
                'port'     => 6379
        )
),

PHPでRedisを試す

危険なほどのスピードを持つというKVS『Redis』。
redisドキュメント日本語訳を見て頂くと、Redisの持っている機能の概要が書いてあります。

インストール

自分の環境(CentOS 6.2)では、yumでインストールが可能でした。

# yum install redis
# cp /etc/redis.conf /etc/redis.conf.bak

ソースからインストールする場合の手順と起動方法について、以下のページに記載があります。
http://redis.io/download

$ wget http://redis.googlecode.com/files/redis-2.4.15.tar.gz
$ tar xzf redis-2.4.15.tar.gz
$ cd redis-2.4.15
$ make

設定ファイル

yumでインストールを行った場合、設定ファイルは『/etc』配下に設置されます。

# cp /etc/redis.conf /etc/redis.conf.bak

起動

redisを起動します。
(デフォルトでは『127.0.0.1:6379』がオープン)

# /etc/rc.d/init.d/redis start
# chkconfig redis on

次に、インストール手順に従い接続テストを行い、setとgetが問題なく動作する事を確認します。

$ redis-cli
redis 127.0.0.1:6379> set foo bar
OK
redis 127.0.0.1:6379> get foo
"bar"
redis 127.0.0.1:6379> exit

これで、redisを試す環境が出来ました。

PHPからの接続

http://redis.io/clientsを見ると、様々な言語のクライアントリストが表示されます。
今回は『Predis』で、簡単な接続のみ試しました。

$ wget -O predis.zip https://github.com/nrk/predis/zipball/v0.7
$ unzip predis.zip

PHP側では、伸張したディレクトリ内に存在する『autoload.php』をrequireします。
下記が、getとsetのサンプルです。

<?php

require 'nrk-predis-4bc6f58/autoload.php';

Predis\Autoloader::register();

// 接続
$redis = new Predis\Client('tcp://127.0.0.1:6379');

// 値をセット
$redis->set('foo', 'bar');

// 値を取得
echo $redis->get('foo');

コマンドリファレンスを参考に、後は以下の形式で実行するだけです。

$redis->{コマンド}(パラメータ...);

例)
$redis->hset('myhash', 'name', 'myname');
$redis->hget('myhash', 'name');

FuelPHPのRestコントローラ

FuelPHPにはRestコントローラというものがあります。
使い方は簡単で、『Controller_Rest』を継承したコントローラを作成し、その中に処理を入れていくだけで、以下のフォーマットの出力が可能になります。
(core/classes/format.phpを見ると、jsonpやらyamlやらもあるようです)

コントローラに追加していくメソッド名と、アクセスする為のURLは以下のようになります。

メソッド名)
  public function {HTTPメソッド}_{任意の名前}

URL)
  /{コントローラ名}/{任意の名前}.{フォーマット}

例)
public function get_list
  -> /xxx/list.xml
  -> /xxx/list.json

サンプルと出力内容

公式ドキュメントのサンプルは、以下のようになっています。

<?php
class Controller_Test extends Controller_Rest
{
    public function get_list()
    {
        $this->response(array(
            'foo' => Input::get('foo'),
            'baz' => array(
                1, 50, 219
            ),
            'empty' => null
        ));
    }
}

上記に『xml』『json』でアクセスした結果が以下になります。

xml

/test/list.xml?foo=bar

<?xml version="1.0" encoding="utf-8"?>
<xml>
  <foo>bar</foo>
  <baz>
    <item>1</item>
    <item>50</item>
    <item>219</item>
  </baz>
  <empty></empty>
</xml>

json

/test/list.json?foo=bar

{
    "foo":"bar",
    "baz":[1,50,219],
    "empty":null
}

CSV

csvの場合、Responseに対しに、以下のような配列で渡す必要があるようです。

<?php
...
    public function get_list()
    {
        $data = array(
                    array(1, 2, 3, 4),
                    array("aaa", "bbb", "ccc", Input::get('foo'))
                );
        $this->Response($data);
    }

『/test/list.csv?foo=bar』にアクセスすると、csvのダウンロードが始まり、出力されたcsvの中身は以下のようになっています。

"1","2","3","4"
"aaa","bbb","ccc","bar"

どうでしょう?Restコントローラ

FuelPHPでページキャッシュ

FuelPHPでページを丸々キャッシュする方法を調べたところ、『fuel-pagecache』というのを作っている方がいたので、試してみました。

fuel-pagecache)
https://github.com/xavividal/fuel-pagecache

以下のような動作になっています。

  • Templateコントローラで出力したコンテンツを静的ファイルに出力
  • mod_rewriteで静的ファイルに振り分け


1度キャッシュしてしまうと、その後はmod_rewriteでの振り分けになる為、当然ながらコンテンツ更新が行われてもキャッシュはクリアされません。
加えて、ページキャッシュの有効期限等も存在しない為、ページ更新時はページキャッシュを削除してやる必要があります。


以下、設定内容になります。

ファイル配置

githubよりfuel-pagecacheを取得し、以下にそれぞれ配置します。

  • fuel/app/config/fuel-pagecache.php
  • fuel/app/config/pagecache.php

Autoloader設定

『fuel/app/bootstrap.php』に以下を記述します。

Autoloader::add_core_namespace('Xvp');

Autoloader::add_classes(array(
    ...
    'Xvp\\Pagecache' => __DIR__.'/classes/pagecache.php'
));

キャッシュディレクトリ作成

ドキュメントルート直下にキャッシュ用ディレクトリを作成します。

$ mkdir {ドキュメントルート}/cache
$ chmod 777 {ドキュメントルート}/cache

Controller

ページキャッシュを行いたいTemplateコントローラにて、『before()』『after()』の設定を行い、キャッシュしたいaction内で『$this->pagecache->enableCache();』を呼び出します。

<?php
class Controller_Cachetest extends Controller_Template
{
    public function before()
    {
        parent::before();

        $this->pagecache = new Pagecache();
        $this->pagecache->setResponse($this->response);
        $this->pagecache->setRequest($this->request);
    }

    public function after($response)
    {
        $response = parent::after($response);

        if ($this->pagecache->isCacheable()) {
           $this->pagecache->cache($_SERVER['REQUEST_URI']);
        }

        return $response;
    }

    public function action_index()
    {
        $this->pagecache->enableCache();

        ...
    }
}

.htaccess設定

.htaccessにて以下の設定を行います。

RewriteRule ^/(.*)/$ /$1 [QSA]
RewriteRule ^$ cache/index.html [QSA]
RewriteRule ^([^.]+)/$ cache/$1/index.html [QSA]
RewriteRule ^([^.]+)$ cache/$1/index.html [QSA]

RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d

RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

上記設定後、『$this->pagecache->enableCache()』を設定したページにアクセスすると、cacheディレクトリに静的ファイルが出力され、次回以降のアクセスは静的ファイルが参照される事になります。
cacheディレクトリ内静的ファイルに、適当な文字列を記述するなどして、キャッシュに振り分けられてるかの確認が可能です。

ページャー(Pagination)のリンク表示数

FuelPHPでページャー(Pagination)に書いた通り、FuelPHP標準のPaginationでは、カレントページより前のリンク表示数は、『num_links』で設定した値 - 1を表示するようになっています。(後はnum_linksで設定した数)

例)
f:id:BTT:20120619234852p:plain

これを、出来ればカレントページ番号を挟み、前後とも『num_links』で設定した数だけ、リンクを表示したいと思うのは自分だけでしょうか。

設定自体は、Paginationクラス(fuel/core/class/pagination.php)内page_linksに以下の修正を加えれば完了です。

$start = ((static::$current_page - static::$num_links) > 0) ? static::$current_page - (static::$num_links - 1) : 1;
↓
$start = ((static::$current_page - static::$num_links) > 0) ? static::$current_page - (static::$num_links - 0) : 1;

普通は、この修正だけでもコアを拡張?

FuelPHPでページャー(Pagination)

FuelPHPでページャー

FuelPHP標準のPaginationを使用したページャーを実装してみます。

想定バージョン) FuelPHP 1.3

CodeIgniterの標準のページャーは、offsetがURLに入り、残念な感じになっていましたが、FuelPHPではそんな事はないようです。

今回は、ページャーのデザインとして、下記サイトのCSSを使用させて頂きました。

[使えるCSSテクニックVol.2] CSS を使った見栄えの良いページャー | バシャログ。

日本語設定

まずは、前後リンクの日本語設定を行います。

$ vi fuel/app/lang/ja/pagination.php

pagination.phpに下記を記述します。

<?php
return array(
    'previous' => '前へ',
    'next'     => '次へ',
);

Controller

Controllerでは、ページャー関連の設定をPaginationクラスに渡し、ページャーを生成します。(設定にページ下部参照)
データ取得時のlimit、offsetは下記で取得が可能です。

  • Pagination::$per_page
  • Pagination::$offset
<?php
class Controller_Welcome extends Controller
{
    public function action_index()
    {
        $view = View::forge('welcome/index');

        $config = array(
            'pagination_url' => 'pager/index',
            'total_items'    => DB::count_records('users'),
            'per_page'       => 5,
            'uri_segment'    => 3,
            'num_links'      => 5,
            'template' => array(
                'wrapper_start'           => '<ul class="pager"> ',
                'wrapper_end'             => '</ul>',
                'page_start'              => '',
                'page_end'                => '',
                'previous_start'          => '<li class="prev">',
                'previous_end'            => '</li>',
                'previous_inactive_start' => '<li class="prev">',
                'previous_inactive_end'   => '</li>',
                'previous_mark'           => '<< ',
                'next_start'              => '<li class="next">',
                'next_end'                => '</li>',
                'next_inactive_start'     => '<li class=next">',
                'next_inactive_end'       => '</li>',
                'next_mark'               => ' >>',
                'active_start'            => '<li><em>',
                'active_end'              => '</em></li>',
                'regular_start'           => '<li>',
                'regular_end'             => '</li>'
            )
        );
        Pagination::set_config($config);

        $view->set_safe('pager', Pagination::create_links());

        $data = DB::select()->from('users')
                            ->limit(Pagination::$per_page)
                            ->offset(Pagination::$offset)
                            ->execute()
                            ->as_array();

        return $view;
    }
}

Template

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
* {
    margin: 0;
    padding: 0;
    font-style: normal;
    list-style: none;
}
body {
    font: 70% Arial, Helvetica, sans-serif;
}
ul.pager {
    margin: 10px;
}
ul.pager li {
    float: left;
    margin-right: 5px;
    border: 1px #3366FF solid;
    font-weight: bold;
}
ul.pager li.prev,
ul.pager li.next {
    border: none;
}
ul.pager li a {
    position: relative;
    display: block;
    padding: 3px 8px;
    color: #3366FF;
}
ul.pager li a:link,
ul.pager li a:visited {
    text-decoration: none;
}
ul.pager li a:hover,
ul.pager li a:active {
    background-color: #AADDFF;
    text-decoration: none;
}
ul.pager li em {
    display: block;
    padding: 3px 8px;
    background: #3366FF;
    color: #FFFFFF;
}
</style>
</head>
<body>

<?php echo $pager ?>

</body>
</html>

出力内容

以下のようなページャーが出力されます。
f:id:BTT:20120619171811p:plain
HTMLは以下のようになります。

<ul class="pager">
  <li class="prev"><< 前へ </li>
  <li><em>1</em></li>
  <li><a href="http://hoge.com/welcome/index/2">2</a></li>
  <li><a href="http://hoge.com/welcome/index/3">3</a></li>
  <li><a href="http://hoge.com/welcome/index/4">4</a></li>
  <li><a href="http://hoge.com/welcome/index/5">5</a></li>
  <li><a href="http://hoge.com/welcome/index/6">6</a></li>
  <li class="next"><a href="http://hoge.com/welcome/index/2">次へ >></a></li>
</ul>

Pagination設定内容

Paginationクラスに渡す設定一覧になります。(templateのみ配列形式)
※num_linksは、カレントページ番号の前が『num_linksで設定した値 - 1』個表示されるようです。(num_linksが5の場合、カレントページの前は4つリンクが表示される)

config

pagination_url 生成するURL
total_items 対象件数
per_page 1ページ当たり表示件数
num_links カレントページの前後それぞれに表示するリンク数
uri_segment ページ番号パラメータを挿入する位置
current_page ページ番号指定?
template HTMLタグ設定(以下参照)

config.template

wrapper_start/wrapper_end ページャー全体を囲むタグ
page_start/page_end ページ番号全体を囲むタグ
previous_start/previous_end previousリンクを囲むタグ
previous_inactive_start/previous_inactive_end previousリンク無効時のタグ
previous_mark previous文言の前に出力
next_start/next_end nextリンクを囲むタグ
next_inactive_start/next_inactive_end nextリンク無効時のタグ
next_mark next文言の後に出力
active_start/active_end 選択ページのページ番号を囲むタグ
regular_start/regular_end 選択ページ以外のページ番号を囲むタグ


num_linksの前の個数が気になる...