FuelPHPのテンプレートコントローラでPC/スマホの出しわけ

FuelPHPでテンプレートコントローラを使用している場合に、PCとスマホでビューを出し分ける方法を検討。

ベースとなるテンプレートは

  • スマホ : views/template_sp.php
  • スマホ以外 : views/template.php

スマホからアクセスされた場合、parent::before()を実行する前に、$templateを上書きする。

<?php

class Controller_Blog extends Controller_Template
{
    public function before()
    {
        if (Agent::is_smartphone()) {
            $this->template = 'template_sp';
        }
        parent::before();
    }

    public function action_index()
    {
        $view = View::forge('pc/blog/index');
        if (Agent::is_smartphone()) {
            $view = View::forge('mb/blog/index');
        }
        $this->template->title = 'Blog &raquo; index';
        $this->template->content = $view;
    }
}

UAでのスマホ判定はこちら

上記の場合、ファイル名やパスの修正が入った場合、コツコツ修正する必要がある為、無理があるか。。。

FuelPHP + RedisでTwitterもどき

元ネタはこちら。

ケーススタディ — redis 2.0.3 documentation

上記ページの内容をFuelPHPで実装しました。
※一部実装を変えている部分があります。

ソースはこちら。

https://github.com/mmat/fuelphp-redis-tweet

ユーザー登録ページ

入力チェックを通過したユーザー名/passwordを文字列型にセット後、
ログイン中を表す期限付きのキーを発行し、Cookieにセットします。

Controller
// app/classes/controller/user.php
<?php

class Controller_User extends Controller
{
    public function before()
    {
        parent::before();
        $this->redis = Redis::instance();
    }

    public function action_add()
    {
        $view = View::forge('user/add');

        $form = Fieldset::forge();
        $form->validation()->add_callable(new MyValidation());
        $form->add('username', 'ユーザー名', array('max_length' => 16))
             ->add_rule('required')
             ->add_rule('max_length', 16)
             ->add_rule('duplicate_user');
        $form->add('password', 'パスワード', array('type' => 'password', 'maxlength' => 16))
             ->add_rule('required')
             ->add_rule('min_length', 8)
             ->add_rule('max_length', 16);
        $form->add('submit', '', array('type' => 'submit', 'value' => '作成'));

        $form->repopulate();

        if ($form->validation()->run()) {

            $input = $form->validation()->validated();

            // ユーザー登録
            $uid = $this->redis->incr('global:nextUserId');
            $this->redis->set(sprintf('uid:%d:username', $uid), $input['username']);
            $this->redis->set(sprintf('uid:%d:password', $uid), md5($input['password']));
            $this->redis->set(sprintf('username:%s:uid', $input['username']), $uid);

            // Cookieをセット
            $authkey = md5(uniqid(rand(), true));
            $this->redis->setex(sprintf('auth:%s', $authkey), 3600, $uid);
            Cookie::set('authkey', $authkey);

            Response::redirect('user/add_comp');

        } else {
            $view->set_safe('errors', $form->validation()->show_errors());
        }

        $view->set_safe('html_form', $form->build(Uri::create('user/add')));

        return $view;
    }

    public function action_add_comp()
    {
        // ログインチェック
        $authkey = Cookie::get('authkey');
        $uid = $authkey ? $this->redis->get(sprintf('auth:%s', $authkey)) : null;
        if (!$uid) {
            Response::redirect('user/add');
        }

        return View::forge('user/add_comp');
    }
}
Custom Validator

ユーザー名の重複チェック用のバリデーションです。

// app/classes/myvalidation.php
<?php

class MyValidation
{
    public static function _validation_duplicate_user($username)
    {
        $redis = \Redis::instance();

        if ($redis->get(sprintf('username:%s:*', $username))) {
            return false;
        }

        return true;
    }
}
Template
// app/views/user/add.php
<?php if (isset($errors)): ?>
<?php echo $errors ?>
<?php endif ?>

<?php echo $html_form ?>

// app/views/user/add_comp.php
ユーザー登録完了<br />
<a href="<?php echo Uri::create('top') ?>">TOPへ</a>

ログインページ

ログインを通ったユーザーに対し、ログイン中を表す期限付きのキーをセットし、Cookieを発行します。
(元のページでは、もう少しだけ込み入った事をしています)

Controller
app/classes/controller/login.php
<?php

class Controller_Login extends Controller
{
    public function before()
    {
        parent::before();
        $this->redis = Redis::instance();
    }

    public function action_index()
    {
        $view = View::forge('login/index');

        // フォーム
        $form = Fieldset::forge();
        $form->add('username', 'ユーザー名', array('maxlength' => 20))
             ->add_rule('required');
        $form->add('password', 'パスワード', array('type' => 'password', 'maxlength' => 20))
             ->add_rule('required');
        $form->add('submit', '', array('type' => 'submit', 'value' => 'ログイン'));

        $form->repopulate();

        if (Input::post()) {
            if ($form->validation()->run()) {
                $input = $form->validation()->validated();

                $uid = $this->redis->get(sprintf('username:%s:uid', $input['username']));

                if ($uid && md5($input['password']) == $this->redis->get(sprintf('uid:%d:password', $uid))) {
                    $authkey = md5(uniqid(rand(), true));
                    $this->redis->setex(sprintf('auth:%s', $authkey), 3600, $uid);
                    Cookie::set('authkey', $authkey);
                    Response::redirect('top');
                } else {
                    $view->error_flg = true;
                }
            } else {
                $view->error_flg = true;
            }
        }

        $view->set_safe('html_form', $form->build(Uri::create('login')));

        return $view;
    }
}
Template
// views/login/index.php
<?php if (isset($error_flg)): ?>ログインに失敗しました<br /><?php endif ?>

<?php echo $html_form ?>

<a href="<?php echo Uri::create('user/add') ?>">ユーザー登録はこちら</a>

ログアウト処理

ログアウトは単純に、ログイン中を表すキーを削除し処理します。
(削除後、ログインページへリダイレクト)

Controller
// app/classes/controller/logout.php
<?php

class Controller_Logout extends Controller
{
    public function before()
    {
        parent::before();
        $this->redis = Redis::instance();
    }

    public function action_index()
    {
        $authkey = Cookie::get('authkey');

        if ($authkey) {
            $this->redis->del(sprintf('auth:%s', $authkey));
        }

        Response::redirect('login');
    }
}

TOPページ

ログイン後のページでは『タイムライン』『全体のタイムライン』『フォロー一覧』『フォロワー一覧』を表示します。

ポスト

メッセージをポストした場合、文字列型にpost_idをキーにメッセージをセットし、その時点のフォロワーに対し、post_idを紐付けます。
紐付けにリスト型を使用し、毎回先頭に追加する事により、時間でソートする必要が無くなっています。
リスト型に格納されたメッセージの取得は、レンジを指定し取得します。

フォロー

フォロー情報は、1つのキーに対し同じ値が入らないようになっているセット型を使用します。

下のサンプルでは、手抜きをしてトップページのコントローラにフォロー/リムーブ処理を入れ、GETで処理を行なっていますが、非同期処理にする事により、よりそれっぽくなるんではないでしょうか。

Controller
// app/classes/controller/top.php
<?php

class Controller_Top extends Controller
{
    public function before()
    {
        parent::before();
        $this->redis = Redis::instance();

        // ログインチェック
        $authkey = Cookie::get('authkey');
        $this->uid = $authkey ? $this->redis->get(sprintf('auth:%s', $authkey)) : null;
        if ($this->uid) {
            $this->redis->expire(sprintf('auth:%s', $authkey), 3600);
        } else {
            Response::redirect('login');
        }
    }

    public function action_index()
    {
        $view = View::forge('top/index');

        // フォーム
        $form = Fieldset::forge();
        $form->add('message', 'メッセージ', array('type' => 'textarea', 'rows' => 5))
             ->add_rule('required')
             ->add_rule('max_length', 140);
        $form->add('submit', '', array('type' => 'submit', 'value' => '送信'));

        if ($form->validation()->run()) {
            // ポストされたメッセージをセット
            $postid = $this->redis->incr('global:nextPostId');
            $message = preg_replace('/\n|\t/s', ' ', Input::post('message'));
            $post = sprintf("%s\t%d\t%s", $this->uid, time(), $message);
            $this->redis->set(sprintf('post:%d', $postid), $post);

            $this->redis->lpush(sprintf('uid:%d:posts', $this->uid), $postid);
            $followers = $this->redis->smembers(sprintf('uid:%d:followers', $this->uid));
            foreach ($followers as $fid) {
                $this->redis->lpush(sprintf('uid:%d:posts', $fid), $postid);
            }

            $this->redis->lpush('global:timeline', $postid);
            $this->redis->ltrim('global:timeline', 0, 1000);
        } else {
            $form->repopulate();
            $view->set_safe('errors', $form->validation()->show_errors());
        }

        $view->set_safe('html_form', $form->build(Uri::create('top')));

        // フォローしている
        $following = array();
        $members = $this->redis->smembers(sprintf('uid:%d:following', $this->uid));
        if ($members) {
            foreach ($members as $member_uid) {
                $following[$member_uid] = $this->redis->get(sprintf('uid:%d:username', $member_uid));
            }
        }
        $view->following = $following;

        // フォローされている
        $followers = array();
        $members = $this->redis->smembers(sprintf('uid:%d:followers', $this->uid));
        if ($members) {
            foreach ($members as $member_uid) {
                $followers[$member_uid] = $this->redis->get(sprintf('uid:%d:username', $member_uid));
            }
        }
        $view->followers = $followers;

        // ツイート取得
        $messages = array();
        $timeline = $this->redis->lrange(sprintf('uid:%d:posts', $this->uid), 0, 100);
        if ($timeline) {
            foreach ($timeline as $postid) {
                $messages[] = $this->_get_post($postid);
            }
        }
        $view->messages = $messages;

        // 全体のツイート取得
        $gmessages = array();
        $global_timeline = $this->redis->lrange('global:timeline', 0, 100);
        if ($global_timeline) {
            foreach ($global_timeline as $postid) {
                $gmessages[] = $this->_get_post($postid);
            }
        }
        $view->gmessages = $gmessages;

        return $view;
    }

    public function action_follow($follow_uid = null)
    {
        if ($follow_uid && $this->redis->get(sprintf('uid:%d:username', $follow_uid))) {
            $this->redis->sadd(sprintf('uid:%d:following', $this->uid), $follow_uid);
            $this->redis->sadd(sprintf('uid:%d:followers', $follow_uid), $this->uid);
        }

        Response::redirect('top');
    }

    public function action_remove($remove_uid = null)
    {
        if ($remove_uid && $this->redis->get(sprintf('uid:%d:username', $remove_uid))) {
            $this->redis->srem(sprintf('uid:%d:following', $this->uid), $remove_uid);
            $this->redis->srem(sprintf('uid:%d:followers', $remove_uid), $this->uid);
        }

        Response::redirect('top');
    }

    private function _get_post($postid)
    {
        list($uid, $unixtime, $message) = explode("\t", $this->redis->get(sprintf('post:%d', $postid)));
        $username = $this->redis->get(sprintf('uid:%d:username', $uid));
        $post_info = array(
            'uid'           => $uid,
            'username'      => $this->redis->get(sprintf('uid:%d:username', $uid)),
            'time'          => date('Y-m-d H:i:s', $unixtime),
            'message'       => $message,
            'following_flg' => $uid != $this->uid && $this->redis->sismember(sprintf('uid:%d:following', $this->uid), $uid) ? true : false
        );

        return $post_info;
    }
}
Template
// views/top/index.php
<a href="<?php echo Uri::create('logout') ?>">ログアウト</a><br />

<?php if (isset($errors)): ?>
<?php echo $errors ?>
<?php endif ?>
<?php echo $html_form ?>

<?php if ($messages): ?>
<h2>タイムライン</h2>
<hr />
<?php foreach ($messages as $m): ?>
<?php echo $m['username'] ?><a href="<?php echo Uri::create('top/remove/'.$m['uid']) ?>">[フォロー解除]</a>&nbsp;
<?php echo $m['time'] ?><br />
<?php echo $m['message'] ?>
<hr />
<?php endforeach ?>
<?php endif ?>

<?php if ($followers): ?>
<h2>フォローされている</h2>
<ul>
<?php foreach ($followers as $uid => $username): ?>
<li><?php echo $username ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>

<?php if ($following): ?>
<h2>フォローしている</h2>
<ul>
<?php foreach ($following as $uid => $username): ?>
<li><?php echo $username ?><a href="<?php echo Uri::create('top/remove/'.$uid) ?>">[フォロー解除]</a></li>
<?php endforeach ?>
</ul>
<?php endif ?>

<?php if ($gmessages): ?>
<h2>全体のタイムライン</h2>
<?php foreach ($gmessages as $m): ?>
<?php echo $m['username'] ?>
<?php if ($m['following_flg']): ?>
<a href="<?php echo Uri::create('top/remove/'.$m['uid']) ?>">[フォロー解除]</a>
<?php else: ?>
<a href="<?php echo Uri::create('top/follow/'.$m['uid']) ?>">[フォロー]</a>
<?php endif ?>&nbsp;
<?php echo $m['time'] ?><br />
<?php echo $m['message'] ?>
<hr />
<?php endforeach ?>
</ul>
<?php endif ?>

FuelPHPのクエリビルダを表にまとめた

※Ver1.2の情報なので最新バージョンと合わない部分があるかもしれません...

タイトルそのまま。FuelPHP1.2のクエリビルダ関連を表にまとめました。

SELECT // SELECT * FROM... \DB::select()
// SELECT `hoge`, `fuga` FROM... \DB::select(column1, column2...) \DB::select_array(array(column1, column2...))
// SELECT `hoge` AS `h`, `fuga` AS `f` FROM... \DB::select(array(column1, alias), array(column2, alias)...) \DB::select_array(array(column1, alias), array(column2, alias)...)
DISTINCT // SELECT DISTINCT `hoge` FROM... distinct(true)
FROM // SELECT * FROM `hoge` from(table)
// SELECT * FROM `hoge` AS `h` from(array(table, alias))
JOIN // SELECT * FROM `hoge` INNER JOIN `fuga` ON (`hoge.id` = `fuga.hoge_id`) join(table)->on(column1, '=', column2)
// SELECT * FROM `hoge` LEFT JOIN `fuga` ON (`hoge.id` = `fuga.hoge_id`) join(table, type)->on(column1, '=', column2) ※ type : 『INNER』 『LEFT』 『RIGHT』 ...
// SELECT * FROM `hoge` AS `h` INNER JOIN `fuga` AS `f` ON (`h.id` = `f.hoge_id`) join(array(column, alias))->on(column1, '=', column2)
WHERE // WHERE `foo` = `bar` where(column, value)
// WHERE `foo` != `bar` where(column, op, value) ※ op : 『=』『!=』『>』『<』 ... ※opが!=でvalueがNULLの場合、クエリは自動的にIS NOT NULLに
// WHERE `hoge` IN (`foo`, `bar`) where(column, 'in', array(value1, value2...)) where(column, 'not in', array(value1, value2...))
// WHERE `hoge` LIKE `%fuga%` where(column, 'like', value)
// WHERE `hoge` BETWEEN `1` AND `10` where(column, 'between', array(value1, value2))
// AND `hoge` = `fuga` and_where(column, value)
// OR `hoge` = `fuga` or_where(column, value)
// (`hoge` = `fuga` OR `foo` = `bar`) where_open() ->where(column, value) ->or_where(column, value) ->where_close()
// OR (`hoge` = `fuga` AND `foo` = `bar`) or_where_open() ->where(column, value) ->and_where(column, value) ->or_where_close()
HAVING // HAVING `hoge` = `fuga` having(column, op, value) ※ op : 『=』『!=』『>』『<』 ...
// AND HAVING `hoge` = `fuga` and_having(column, op, value) ※ op : 『=』『!=』『>』『<』 ...
// OR HAVING `hoge` = `fuga` or_having(column, op, value) ※ op : 『=』『!=』『>』『<』 ...
// HAVING (`hoge` = `fuga` AND `foo` = `bar`) having_open() ->having(column, op, value)) ->and_having(column, op, value)) ->having_close()
// AND (HAVING (`hoge` = `fuga` OR `foo` = `bar`)) and_having_open() ->having(column, op, value)) ->or_having(column, op, value)) ->and_having_close()
// OR (HAVING (`hoge` = `fuga` AND `foo` = `bar`)) or_having_open() ->having(column, op, value)) ->and_having(column, op, value)) ->or_having_close()
ORDER BY // ORDER BY `hoge` order_by(column)
// ORDER BY `hoge` ASC order_by(column, 'asc')
// ORDER BY `hoge` DESC order_by(column, 'desc')
GROUP BY // GROUP BY `hoge` group_by(column)
LIMIT // LIMIT 10 limit(value)
OFFSET // OFFSET 10 offset(value)
INSERT // INSERT INTO `hoge` (`foo`, `fuga`) VALUES (`bar`, `buz`); \DB::insert(table)->set(array( column1 => value1, column2 => value2... ))->execute();
\DB::insert(table)->columns(array( column1, column2... ))->values(array( value1, value2... ))->execute();
UPDATE // UPDATE `hoge` SET `foo` = `bar` WHERE `fuga` = `buz`; \DB::update(table) ->value(column, value) ->where(WHERE参照) ->execute();
\DB::update(table)->set(array( column1 => value1, column2 => value2... )) ->where(WHERE参照) ->execute();
DELETE // DELETE FROM `hoge` WHERE `foo` = `bar`; \DB::delete(table) ->where(WHERE参照) ->execute();

クエリビルダでCOUNTを取りたい場合はこんな感じ

$result = DB::select(DB::expr('COUNT(*) as count'))->from('users')->execute();
$result_arr = $result->current();
$count = $result_arr['count'];

FuelPHPのSimpleAuthでACL(Access Control List)

FuelPHPのSimpleAuthのACLの概要は、以下のようになっています。

  • ロールを設定ファイルで管理
  • ユーザーではなく、グループに対しロールがひもづく

以下、設定例になります。

config

$ vi app/config/simpleauth.php

設定対象は『groups』と『roles』になります。

<?php
    ...
    // グループの設定
    'groups' => array(
        /**
         * グループID => array(     // グループIDはusersテーブルのgroupカラム
         *     'name' => '識別名',
         *     'role' => 'ロール名'
         * )
         */
        1 => array(
            'name' => 'Users',
            'roles' => array('user')
        )
    ),

    // 権限設定
    'roles' => array(
        /**
         * ロール名 => array(
         *   'アクセス先(任意の文字列)' => '権限(任意の文字列)'
         * )
         */
        'user' => array(
            'blog' => array('read')
        ),

        /**
         * ワイルドカード
         *
         * ワイルドカードは未ログインユーザーもあてはまる為、
         * 必要であれば、ログインチェックを別途行う必要があります
         */
        '#' => array(
            'comments' => array('read')
        ),

        /**
         * ロール名での指定
         */
        'user' => false,  // 指定のロール名は全てアクセス不可
        'admin' => true,  // 指定のロール名は全てアクセス許可
    ),

Controller

controllerでは『has_access』を使用し、権限チェックを行います。

<?php
    ...
    // blogのread権限があるかどうか
    if (Auth::has_access('blog.read')) {
        // OK
    } else {
        // NG
    }

    // まとめて記述したい場合
    // 下記の場合、read/write/delete全ての権限があるユーザーがOKになります
    if (Auth::has_access('blog.[read,write,delete]')) {
        // OK
    } else {
        // NG
    }


SimpleAuthを使ったログイン機能はこちらを参照

FuelPHPのテーマクラスのサンプル

Fuelphpのテーマクラスのサンプルです。
『active』を変更する事により、テーマの切り替えが可能です。

サンプル

Config

app/config/theme.php
<?php
return array(
    'active'        => 'mytheme1',
    'fallback'      => 'default',
    'paths'         => array(APPPATH.'views'),
    'assets_folder' => 'themes',
    'view_ext'      => '.php'
);

あらかじめテーマ用のディレクトリを作成します。

$ mkdir app/views/mytheme1
$ mkdir app/views/default

Controller

app/classes/controller/sample.php
<?php
class Controller_Sample extends Controller
{
    public function before()
    {
        $this->theme = \Theme::instance();
    }

    public function action_index()
    {
        $this->theme->set_template('homepage')->set('title', 'sample');
        $this->theme->set_partial('header', 'header')->set('name', 'hoge');
        $this->theme->set_partial('footer', 'footer')->set(array(
            'foo' => 'val1',
            'bar' => 'val2'
        ));
    }

    public function after($response)
    {
        if (empty($response) or ! $response instanceof Response) {
            $response = \Response::forge($this->theme->render());
        }
        return parent::after($response);
    }
}

Template

app/views/mytheme1/homepage.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?php echo $title ?></title>
</head>
<body>

<?php echo $partials['header'] ?>
...
<?php echo \Theme::instance()->asset->img('myicon.png') ?>
...
<?php echo $partials['footer'] ?>

</body>
</html>
app/views/mytheme1/header.php
...
<?php echo $name ?>
...
app/views/mytheme1/footer.php
...
<?php echo $foo ?>
...
<?php echo $bar ?>
...

諸々の説明

Config

active アクティブなテーマ名
fallback アクティブなテーマが存在しない場合のテーマ
paths テーマ検索パス
assets_folder assetフォルダ名
view_ext テンプレートの拡張子
info_file_name
require_info_file
info_file_type

テンプレートのベースパスは、pathsで指定したディレクトリ内のactive名、fallback名のディレクトリになり、まずactive内でテンプレートを探し、ない場合にfallback内のテンプレートを表示します。

デフォルトでは『DOCROOT/themes/default』を参照するようになっている為、サンプルではapp/viewsに変更しています。

また、テーマクラスに関する設定は、設定ファイル(theme.php)以外に、instance()実行時またforge()実行時に指定が可能です。
例)

<?php
..
    function before()
    {
        ...
        $this->theme = \Theme::instance(
            'custom',
            array(
                'active'        => 'mytheme1',
                'fallback'      => 'default',
                'paths'         => array(APPPATH.'views'),
                'assets_folder' => 'themes',
                'view_ext'      => '.php'
            )
        );
        ...
    }

テンプレート/パーツの読み込み

『set_template('テンプレート')』を使用し、ベースとなるテンプレートを読み込みます。
テンプレートで使用する変数は『set』で渡します。

$this->theme->set_template('homepage')->set('title', 'sample');

ヘッダーやフッター等の共通パーツは『set_partial('セクション名', 'テンプレート')』を使用します。

$this->theme->set_partial('footer', 'footer')->set(array(
    'foo' => 'val1',
    'bar' => 'val2'
));

『set_partial()』で読み込んだパーツは、テンプレート側で以下のように出力が可能です。

<?php echo $partials['footer'] ?>

Asset

テーマクラスを使用した場合、Assetの呼び出しは以下のように行います。

<?php echo \Theme::instance()->asset->img('myicon.png') ?>

サンプルでは『'assets_folder' => 'themes'』を設定している為、『DOCROOT/themes/mytheme1』がベースになり、サンプルは以下のようなタグが出力されます。

<img src="http://hoge.com/themes/mytheme1/img/myicon.png" alt="" />

※通常のAsset同様、『img』『css』『js』の各ディレクトリ配下のファイルを呼び出し

よくわからない点

  • set_chrome()
  • info_file_name/require_info_file

FuelPHPのAgentクラスと拡張

FuelPHPのAgentクラスは、下記URLからブラウザ情報を取得し、これを元にプラットフォーム等の判定を行っています。

http://browsers.garykeith.com/stream.asp?Lite_PHP_BrowsCapINI

上記URLへアクセスは最初に1度のみで、それ以降はキャッシュとして保持されます。
(アクセス先URL、ファイル保存先はhttp://docs.fuelphp.com/classes/agent/config.html:設定ファイルで変更可能)

これを元に判定されたユーザーエージェント情報は、以下の関数で取得する事になります。(諸々の使い方は公式ドキュメント参照)

accepts_charset($charset = 'utf-8')

指定charsetがHTTP_ACCEPT_CHARSETに含まれるか

accepts_language($language = 'en')

指定languageがHTTP_ACCEPT_LANGUAGEに含まれるか

browser()

ブラウザ名(Firefox,IE,Chrome...)

platform()

プラットフォーム(Win95,Win98,WinNT...)

version()

ブラウザのバージョン

charsets()

HTTP_ACCEPT_CHARSET一覧

languages()

HTTP_ACCEPT_LANGUAGE一覧

properties()

プロパティ一覧

property($property = null)

指定プロパティの値

is_mobiledevice()

モバイル判定(スマホ含む)

is_robot()

ロボット判定



上記の関数を見てもらうと分かりますが、現状ではスマホの判定を行う関数がないようですので、Agentクラスを拡張し、スマホ判定の関数を追加してみたいと思います。

まずは『fuel/app/classes/agent.php』を用意します。

<?php
class Agent extends Fuel\Core\Agent
{
    public static function _init()
    {
        parent::_init();

        $sp_list = array(
                       'iPhone',
                       'iPod',
                       'Android',
                       'IEMobile',
                       'dream',
                       'CUPCAKE',
                       'blackberry9500',
                       'blackberry9530',
                       'blackberry9520',
                       'blackberry9550',
                       'blackberry9800',
                       'webOS',
                       'incognito',
                       'webmate'
                   );

        $pattern = '/'.implode('|', $sp_list).'/i';
        static::$properties['x_issmartphone'] = preg_match($pattern, static::$user_agent) ? true : false;
    }

    public static function is_smartphone()
    {
        return static::$properties['x_issmartphone'];
    }
}

※スマホのUAリストはこちらを参考にさせて頂きました。

次に『fuel/app/bootstrap.php』に対し、Agentを追加します。

Autoloader::add_classes(array(
        // Add classes you want to override here
        // Example: 'View' => APPPATH.'classes/view.php',
        'Agent' => APPPATH.'classes/agent.php'
));

上記の設定が完了すれば、以下の呼び出しが可能になります。

if (Agent::is_smartphone()) {
    // スマホ用処理
}

Redisのバックアップ

Redisのバックアップ(スナップショット?)の取得方法のメモ。

bgsaveでデータベースを保存、lastsaveで最終保存日時を確認後、データベースファイルをコピー。

$ redis-cli bgsave
$ redis-cli lastsave

lastsaveはUNIXTIMEで返ってくる為、要変換。

例)
$ date --date "@`redis-cli lastsave`" +"%Y/%m/%d %H:%M"

lastsaveで、データベースがファイルに保存されている事を確認し、データベースファイルをコピー。

# cd /var/lib/predis/
# cp dump.rdb dump.rdb.YYYYMMDD

※データベースファイルのファイル名と配置パスは、redis.confの『dbfilename』と『dir』を参照。

参考)
http://my.safaribooksonline.com/book/databases/9781449311353/4dot-redis-administration-and-maintenance/id3064347
http://redis.shibu.jp/commandreference/control.html#command-LASTSAVE