2013年1月20日日曜日

Fieldsetのcheckboxとradioのlabelについて #fuelphp

FuelPHPのFieldsetはFormを自動生成してくれる便利な機能のひとつです。
簡単な登録フォームを作る場合など便利だと思い、私も少し加工したパッケージを作ったことがあります。
Fieldsetの例としては以下がコントローラのソースです。

classes/controller/test.php
<?php

class Controller_Test extends Controller_Template
{
    public function action_index()
    {
        $fieldset = Fieldset::forge();

        $fieldset->add('name', 'Name')
                ->add_rule('required');

        $opt = array(
            '1' => 'Man',
            '2' => 'Woman',
        );
        $fieldset->add('gender', 'Gender',
                array(
                    'options' => $opt,
                    'type' => 'radio', 
                ));

        $opt = array(
            'PHP' => 'PHP',
            'Ruby' => 'Ruby',
            'Python' => 'Python',
        );
        $fieldset->add('lang', 'Lang',
                array(
                    'options' => $opt,
                    'type' => 'checkbox',
                ));

        $this->template->content = View::forge('test/index');
        $this->template->content->set_safe('form', $fieldset->build());
    }
}


これでView側(ここではview/test/index.php)は以下を記述するだけで登録フォームを生成してくれます。
<?php
    echo $form;

便利です。
populateやrepopulateを使うことでバリデーション後やモデルからデータを反映など実装してくれるので良いのですが、checkboxとradioを使った場合、現行バージョン(2013/01/19現在、1.4)ではソースを見ると以下のようになっています。
(langのcheckboxだけを抜粋)
<tr>
    <td class=""><label for="form_">Lang</label></td>
    <td class="">
        <input type="checkbox" id="form_lang_0" name="lang[0]" value="PHP" /> <label for="form_form_lang_0">PHP</label><br />
        <input type="checkbox" id="form_lang_1" name="lang[1]" value="Ruby" /> <label for="form_form_lang_1">Ruby</label><br />
        <input type="checkbox" id="form_lang_2" name="lang[2]" value="Python" /> <label for="form_form_lang_2">Python</label><br />
        <span></span>            
    </td>
<;/tr>

checkboxに紐づくはずのlabelのforが「form_form_xxx」となっています。
見た目は特に問題ですが、labelとcheckboxと紐付かないのはなかなか頂けません。
coreのプログラムを追ってたんですが、助言を頂いて1.5-devなら・・・ということで試してみました。

以下は1.5-devで実装した結果
<tr>
 <td class=""><label for="form_">Lang</label></td>
 <td class="">
  <input type="checkbox" id="form_lang_0" name="lang[0]" value="PHP" /> <label for="form_lang_0">PHP</label><br />
  <input type="checkbox" id="form_lang_1" name="lang[1]" value="Ruby" /> <label for="form_lang_1">Ruby</label><br />
  <input type="checkbox" id="form_lang_2" name="lang[2]" value="Python" /> <label for="form_lang_2">Python</label><br />
  <span></span>   
 </td>
</tr>

とりあえずはOK。確かになおってます。
今すぐどうにかしたい方はCoreの部分でdiffするとかやってみるといいかもしれませんね。




2013年1月9日水曜日

Sublime Text2の設定メモ

いろんなサイトを参考にしながら自分なり設定したことをメモ程度に。

ダウンロード
Sublime Text: The text editor you'll fall in love with

参考にしたサイトの紹介

この3つを見てもらえれば大抵の設定はできるので特に書く事もありませんが、自分の設定関係やらキーバインドやらをダラダラと。。。

Preferences - Settings User
{
 // フォントの設定
 "font_face": "Ricty",
 "font_size": 12.0,
 // 現在の行をハイライト
 "highlight_line": true,
 // Vimの有効化
 "ignored_packages":
 [
 ],
 "vintage_ctrl_keys": true,
 "vintage_start_in_command_mode": true,
 // ルーラの表示
 "rulers":
 [
  80,
  120
 ],
 // タブサイズ
 "tab_size": 4,
 // タブをスペースに変換
 "translate_tabs_to_spaces": true
}

Preferences - Key Bindings User
[
    // モードの切替を行わずに改行
    {
        "keys": ["ctrl+o"], "command": "run_macro_file",
        "args": {"file": "Packages/Default/Add Line.sublime-macro"}
    },
    {
        "keys": ["ctrl+O"], "command": "run_macro_file",
        "args": {"file": "Packages/Default/Add Line Before.sublime-macro"}
    },
    // INSERT MODEから抜ける
    {
        "keys": ["j", "j"], "command": "exit_insert_mode",
        "context":
        [
            { "key": "setting.is_widget", "operand": false },
            { "key": "setting.command_mode", "operand": false }
        ]
    },
    // タブ切替
    { "keys": ["ctrl+tab"], "command": "next_view" },
    { "keys": ["ctrl+shift+tab"], "command": "prev_view" },
    {
        "keys": ["g", "t"], "command": "next_view",
        "context":
        [
            { "key": "setting.is_widget", "operand": false },
            { "key": "setting.command_mode" }
        ]
    },
    {
        "keys": [ "G", "T" ], "command": "prev_view",
        "context":
        [
            { "key": "setting.is_widget", "operand": false },
            { "key": "setting.command_mode" }
        ]
    },
    // Command Modeでのカーソル移動
    {
        "keys": ["0"], "command": "move_to", "args": { "to": "bol", "extend": false },
        "context":
        [
            { "key": "setting.is_widget", "operand": false },
            { "key": "setting.command_mode" }
        ]
    },
    {
        "keys": ["9"], "command": "move_to", "args": { "to": "eol", "extend": false },
        "context":
        [
            { "key": "setting.is_widget", "operand": false },
            { "key": "setting.command_mode" }
        ]
    },
    // INSERT MODEでのカーソル移動
    {
        "keys": ["ctrl+h"], "command": "set_motion",
        "args": { "motion": "vi_move_by_characters_in_line", "motion_args": { "forward": false, "extend": true }},
        "context":
        [
            { "key": "setting.command_mode", "operand": false },
            { "key": "setting.is_widget", "operand": false }
        ]
    },
    {
        "keys": ["ctrl+l"], "command": "set_motion",
        "args": { "motion": "vi_move_by_characters_in_line", "motion_args": { "forward": true, "extend": true }},
        "context":
        [
            { "key": "setting.command_mode", "operand": false },
            { "key": "setting.is_widget", "operand": false }
        ]
    },
    {
        "keys": ["ctrl+k"], "command": "set_motion",
        "args": { "motion": "move", "motion_args": { "by": "lines", "forward": false, "extend": true }},
        "context":
        [
            { "key": "setting.command_mode", "operand": false },
            { "key": "setting.is_widget", "operand": false }
        ]
    },
    {
        "keys": ["ctrl+j"], "command": "set_motion",
        "args": { "motion": "move", "motion_args": { "by": "lines", "forward": true, "extend": true }},
        "context":
        [
            { "key": "setting.command_mode", "operand": false },
            { "key": "setting.is_widget", "operand": false }
        ]
    }
]

今のところインストールしているパッケージは

  • SideBarEnhancements ・・・サイドバーの拡張
  • Emmet ・・・Zencodingの次期バージョン?進化版?
  • SublimeLinter ・・・リアルタイムで文法チェック
  • Additional PHP Snippets ・・・PHP用のスニペット
  • SCSS ・・・Sass、Scssのシンタックス
  • ConvertToUTF8 ・・・Shift_JISに対応させるプラグイン


とこんなかんじです。
まだ使いこなせて感、満載ですがとりあえずVimっぽい動きとPHPを書く準備はできました。




2012年12月18日火曜日

似てるけど似てないシリーズ #phpadvent2012

この記事はPHP Advent Calendar 2012の18日目です。
昨日はkiou oubaさんのフレームワークとは一体何だったのかでした。

私はオレオレFWとまではいきませんがずっと素書きでPHPを書いてきてましたが、今年からフレームワークも使ってみようと思ってFuelPHP、CakePHPとFWの勉強をしていました。
そこで勉強になったことやFWの良いところFWによる違い何かを書こうと意気込んでたんですが業務に追われてしまい、何も準備できずに今日になってしまいました。
Advent Calendarの流れ的にもよかったのかなと思って後悔してます。
来年またチャンスがあれば書けるように頑張ります!


準備してたネタとは違いますが、PHPの初歩的な部分を書いてみました。
7日目にはじかみさんがIntegerの上限を超えてゆけでも書かれてましたが、PHP.net。PHPのマニュアル的なサイトですがたまにはこれ見て勉強するのもいいですよね。
私も夏ごろにこのスライド見てPHP.netいろいろ見てみました。
https://t.co/e4nGnfAe
これはFukuoka.PHPという福岡でやってるPHPの勉強会で@akase244さんが発表されたスライドです。とても面白いスライドなので是非これも見て下さい!!

私が調べてみたのはPHPには似てるけど動きは全く別物だったり、似てるけど・・やっぱり一緒、みたいな関数。

PHP.netにも関数エイリアスというページがあって is_int()is_integer() の例のように is_integer() のページには「is_int()のエイリアス」とだけ書かれてます。
http://php.net/manual/ja/aliases.php

どっちがいいのかはわかりませんが、基本的にここに書いてあるのは同じ動作をします。

しかし、前述したように似てるけど全く別物というのもあります。
初心にもどった気分で読んでもらえれば嬉しいです。


まず

echo と print の違い


同じ文字列を出力する関数(ドキュメントには関数ではないと書かれてますが)
PHP始めたころはどっちで書けばいいんだろうとか気になってたこともあります。
<?php
    echo 'Hello, World!";
    print 'Hello, World!';
?>
出力される結果に違いはありませんが、echo では複数の文字列をカンマ区切りで記述することができます。
<?php
    echo 'Hello', 'World';    // OK
    print 'Hello', 'World';   // NG
?>
また、echo では返り値がないのに対し print は返り値で常に1を返します。
echo の方が返り値がない分、早いらしいです。
これだけ見ると echo のほうが良いのかなって思います。
けっこうどのサンプルも echo で書かれたりしてるのでやっぱり echo なのかな。


2つ目に

print_r と var_dump の違い


変数の値を確認したい場合に使う関数(何かとお世話になってます)
<?php
    $a = array(1, "2", true, array("a", "b"));

    print_r($a);
    var_dump($a);
?>

これは出力結果がかなり違ってます。
print_r の場合
Array
(
    [0] => 1
    [1] => 2
    [2] => 1
    [3] => Array
        (
            [0] => a
            [1] => b
        )

)

var_dump の場合
array
  0 => int 1
  1 => string '2' (length=1)
  2 => boolean true
  3 =>
    array
      0 => string 'a' (length=1)
      1 => string 'b' (length=1)

print_r では内容をただ出力している感がありますが、var_dump では型まで出力してくれます。
PHPでは明示的な型指定がない分、条件式とかでハマることが多々あるので個人的には var_dump のほうが好きです。
ちなみに var_dump のほうは変数を複数指定して出力することができます。
<?php
    var_dump($a, $b);
?>


3つ目に

シングルクォーテーションとダブルクォーテーションの違い


関数ではありませんが変数に文字列を格納する場合、どちらで囲むか。
一般的に使い分けをしているのかどうかわかりませんがこれも違うもので
<?php
    $name = 'Hayashida';

    $single = '私は $name です。';
    $double = "私は $name です。";

    echo $single;
    echo $double;
?>

出力結果は
シングルクォーテーションの場合
私は $name です。
ダブルクォーテーションの場合
私は Hayashida です。

シングルクォーテーションはそのまま出力するのに対し、ダブルクォーテーションは変数を展開してくれます。
上記の例では半角スペースをいれてますが、{ } で囲むことで確実に変数と認識させることができます。
ということはこんなこともできる。
<?php
    $closure = function($val) {
        return str_replace('a', '@', $val);
    };

    echo "私は {$closure($name)} です。";
?>

出力結果は
私は H@y@shid@ です。

文字列の変数に置換する場合などは sprintf とか使うことが多いと思いますが
知っておくと便利かもしれません。


4つ目に

and と && の違い


元々VBをやってたおかげ?で、PHPを始めたころハマったことあります。
条件式などを書く場合ですが、PHPの演算子には優先順位があってそれを知らないと意外なところでバグがでるかも。
http://php.net/manual/ja/language.operators.precedence.php
<?php
    $x = true and false;
    $y = true && false;

    var_dump($x, $y);
?>
二つの評価値(true or false)を比較して変数に代入。
なんてことをする場合、上記のように書くと結果が違います。というか意味が違います。

結果は
boolean (true)
boolean (false)

$x がなぜ true ??
理由はマニュアルに書いていますが、なぜかというと and より = のほうが優先度が高いからです。
$x の場合、$x という変数に true を代入し、その結果を and false としても意味がないものになってしまいます。
and と使う場合は $x = (true and false) とするのが正。
$y のほうは、 && のほうが = より優先度が高いからこのままでも良いのです。

or と || も同じようなことが言えます。


まとめ

取り留めも無い記事になってしまいましたが、今年は勉強会にも参加させてもらったりしてフレームワークやらPSRやらいろいろ勉強になりました。
来年はもう少しましな記事をかけるように頑張ろう。



明日はTakayuki Miwaさんです。よろしくお願いします。




2012年11月20日火曜日

Controller_HybridのafterでTemplateかRestか

特殊な例かもしれませんが、もともとController_Templateで作っていたコントローラをController_Hybridに変えたときにちょっと手間取ったのでメモ。

サンプルとして良いのか悪いのかわからないですが、
もともとのソースは
class Controller_category extends Controller_Template
{
    public function after($response)
    {
        $this->template->title = 'カテゴリ';
        return parent::after($response);
    }

    public function action_index()
    {
        $categories = Model_Category::find()->get();

        $this->template->content = View::forge('category/index');
        $this->template->content->set('categories', $categories);
    }
}

こんなかんじ。
afterの処理でテンプレートに対して値をセットしています。

これをController_Hybridに単純に変えて実装してみました。
class Controller_category extends Controller_Hybrid
{
    public function after($response)
    {
        $this->template->title = 'カテゴリ';
        return parent::after($response);
    }

    public function action_index()
    {
        $categories = Model_Category::find()->get();

        $this->template->content = View::forge('category/index');
        $this->template->content->set('categories', $categories);
    }

    public function get_list()
    {
        $categories = Model_Category::find()->get();

        $data = array();
        foreach ($categories as $category) {
            $data[$category->id] = $category->name;
        }

        $this->response($data);
    }
}

変更したところはController_Hybridにした点とget_listというRest用のアクションメソッド。
しかし、これだとController_Templateを使うような画面は良くても、Controller_Restを使うようなRESTfulな処理で正しく出力してくれません。
というかエラーになります。

エラーの内容は
ErrorException [ Warning ]: Attempt to assign property of non-object

Controller_Restを使ってるから$this->templateなんてないよ、的なエラー。
調べてみると、ないよー的なエラーではなくController_Hybridを使ってRESTfulな処理をしている場合、$this->templateには"template"という文字列がセットされています。
(Controller_Hybridでなくてもそうかもしれません)
これでは確かにエラーになります。

afterの中でどうにかしてTemplateなのかRestなのか判定したい場合は、
public function after($response)
{
    if (! $this->is_restful())
        $this->template->title = 'カテゴリ';

    return parent::after($response);
}

これでOK。
Controller_Hybridの中でも使われているメソッドになりますが、これで処理を判定することができます。

もしくは
public function after($response)
{
    if ($this->template instanceof View)
        $this->template->title = 'カテゴリ';

    return parent::after($response);
}

これでも判定はできています。
どういうやり方がいいか、模索中ではありますがどちらが良いのでしょうかね。
というかこれでいいのかな?と思いもします。

何か良い方法あれば教えてください。


2012年11月18日日曜日

routes.phpの設定(メモ)

ほんとうにメモ程度に。
ルーティングの設定でroutes.phpをさわってみました。


<掲示板の例>
app/classes/controller/bbs.php
class Controller_Bbs extends Controller_Template
{
    // 掲示板一覧
    public function action_index() { }
}

app/classes/controller/bbs/article.php
class Controller_Bbs_Article extends Controller_Template
{
    // 記事一覧
    public function action_index($bbs_id = null) { }

    // 記事書き込み
    public function action_post($bbs_id = null) { }
}

まず掲示板一覧のアドレス
http://localhost/fuelphp/bbs/

これはいい。
けど、記事一覧のアドレス
http://localhost/fuelphp/bbs/article/<bbs_id>

掲示板のIDを渡すようにしたいけどなんか格好わるい。。。
掲示板のIDというより記事のIDに見えるし。

さらに記事投稿のアドレス
http://localhost/fuelphp/bbs/article/post/<bbs_id>

変だ。
毎回、$bbs_idをチェックするのも面倒だし。


そこでapp/config/routes.phpに以下を追加
'bbs/:bbs_id/article(/:any)?' => 'bbs/article$2',
app/classes/controller/bbs/article.php
class Controller_Bbs_Article extends Controller_Template
{
    protected $bbs_id = null;

    public function before()
    {
        parent::before();
        $this->bbs_id = $this->param('bbs_id');
    }

    // 記事一覧
    public function action_index() { }

    // 記事書き込み
    public function action_post() { }
}


これで、記事一覧のアドレス
http://localhost/fuelphp/bbs/<bbs_id>/article/

記事投稿のアドレス
http://localhost/fuelphp/bbs/<bbs_id>/article/post/


コードもURLも最初に比べるとすっきりしたかな。



2012年11月16日金曜日

FuelPHPのfieldsetでボタンやINPUTを並べて表示

まず参考記事

ほとんど見よう見まねですが自分なりにFieldsetを拡張してみました。
やりたかったのは1つでボタンとかINPUTを並べて表示したい。てだけです。

最終的なイメージはこんなかんじ

参考記事と同様にパッケージ化してみました。Githubに置いてるのでバグとかあれば教えてください。


使い方は簡単です。
まず、Githubからパッケージをダウンロードしてpackagesフォルダに配置します。
https://github.com/hayashida/Fieldsetplus

つぎに、app/config/config.phpを修正
'always_load' => array(
    'packages' => array(
        'fieldsetplus',
    ),
),

準備はこれだけです。
あとはController側。
public function action_create()
{
    $fieldset = Fieldsetplus::forge();

    $fieldset->add_group(
        'name',     // name
        '名前',     // label
        array(
            array('name1', '姓', array(), array(array('required'))),
            array('name2', '名', array(), array(array('required'))),
        )
    );

    $fieldset->add('created_at', '作成日')
            ->add_rule('required')
            ->add_rule('valid_date');

    $fieldset->add_group(
        'buttons',  // name
        '',  // label
        array(
            array('submit', '', array('type' => 'submit', 'value' => '登録')),
            array('reset', '', array('type' => 'reset', 'value' => 'やり直し')),
        )
    );

    $this->template->title = 'Create';
    $this->template->content = View::forge('user/create');
    $this->template->content->set_safe('fieldset', $fieldset->build());
}

並べたい項目を add_group を使って定義します。
引数は順に、グループ名・ラベル・フィールド情報となっています。
ラベルを空白に設定すると1行(<td colspan="2">)として表示します。
フィールド情報は一見、難しそうに見えますが Fieldset の add と同じです。
Fieldset - Classes - FuelPHP Documentation

View側のソースは従来のFieldsetと同じ
<?php echo $fieldset; ?>

これでOK。
出力した結果はページの上に貼ってる通りに表示されるはずです。
自動生成されたソースはこんなかんじになります。
<form action="" accept-charset="utf-8" method="post">
<table>
    <tr>
        <td class="">名前*</td>
        <td class=""><label for="form_name1">姓</label> <input type="text" required="required" id="form_name1" name="name1" /> <label for="form_name2">名</label> <input type="text" required="required" id="form_name2" name="name2" />  <span></span> </td>
    </tr>
    <tr>
        <td class=""><label for="form_created_at">作成日</label>*</td>
        <td class=""><input type="text" required="required" id="form_created_at" name="created_at" /> <span></span> </td>
    </tr>
    <tr>
        <td class="field_groups " colspan="2">
            <input type="submit" value="登録" id="form_submit" name="submit" /> <input type="reset" value="やり直し" id="form_reset" name="reset" /> 
        </td>
    </tr>
</table>
</form>




2012年11月13日火曜日

FuelPHP1.4のインストール

自分の環境だけ?
oilコマンドを使用してFuelPHPをインストールしようとするとエラーになりました。
$ php oil create <project_name>

あれ?と思ってサイトからダウンロードして今度は
$ cd <project_name>
$ php oil refine install

でも結果は同じ。。。
エラー結果を見てみると
Fatal error: Uncaught exception 'Fuel\Core\PhpErrorException' with message 'date(): It is not safe to rely on the system's timezone settings.

timezoneが設定されてない的なエラー。

要はこれみたいですね。
madroom project: FuelPHP1.4とconfigファイル

1.4になってからconfigの初期設定がcoreのほうに移動してるみたいです。
さらにdefault_timezoneがnullとなっています。これが原因?みたいです。

以下、config.phpの違い(抜粋:コメント部分が1.3)
/**
 * index_file - The name of the main bootstrap file.
 */
'index_file' => false, // 'index.php',

/**
 * DateTime settings
 */
'default_timezone' => null, // 'UTC'

'security' => array(
    /**
     * Encoding mechanism to use on htmlentities()
     */
    'htmlentities_flags' => ENT_QUOTES, // 追加

    /** 
     * Wether to encode HTML entities as well
     */
    'htmlentities_double_encode' => false, // 追加
),

/**
 * Controller class prefix
 */
'controller_prefix' => 'Controller_', // 追加

'routing' => array(
    /**
     * Wether to strip the extension
     */
    'strip_extension' => true, // 追加
),

ただ書き出しただけですが、追加を除くと変更となっているのは index_file と default_timezone だけみたいです。

追加されてる内容は追々調べてみようかなと思います。

気になるは 'controller_prefix' 。。。
どういう用途なんだろう。