5月 082013
 

こんにちは。ファガイです。

本日はORMで詰まった所を書きます。軽く書くので、フォーマットはあまりきにしてません。

Ormのfindの挙動がおかしい

例として、Model_Site_MasterというModelがあるとします。

'site_id',
'user_id',
'category_id',
'name',
'url',
'description',

上記のようなカラムがあるとします。primary keyはsite_idとuser_idです。

その際にfuelphpのmodel app\model\site\master.phpにはprimary keyを設定していました。

protected $_primary_key = array('site_id', 'user_id');

そして、このModel_Site_Masterは$site_idのみの指定での更新があるとします。

どうにかfindで取ってきてsaveをしたかったのですが、結論的には取ってこれませんでした。メソッドを変えなきゃ厳しいのかもしれません。

なぜメソッドを変えないと無理なんじゃないかというと。

findメソッドで呼び出しているにもかかわらず、Modelオブジェクトが帰ってこない

一番最初の頃は以下としていました。

$site_id = 1;
$query = Model_Site_Master::query()->where("site_id", $site_id)->get_one();
$record = Model_Site_Master::find(array($site_id, $user_id));

一応これで取ってこれるのですが、2回queryを投げることになるのでコレは気持ち悪い、と。

そして、マニュアルを見てコレなら出来るかなと思った。

$site_id = 1;
$record = Model_Site_Master::find("all", array(
    'where' => array('site_id', $site_id)
));

コレで出来ると思ったら、大間違いだった。
Orm\Queryオブジェクトが返ってきた。

現状では出来なさそうなので、自分はprimary keyをsite_idだけにして、user_idが必要な場合はwhereで指定する形に変更。

一旦はこれで対応したんですが、もしかしたら1.6で改善されてるのかな・・・。

 Posted by at 5:18 PM
4月 232013
 

FuelPHPのUploadクラスを詳しくまとめてみる。

今回はFulephpのUploadクラスについてまとめています。多少のPHPの知識を知っているという認識で詳しく書こうと思います。

概要

FuelPHPのUploadクラスは、ファイルのアップロードを簡易的にしてくれるクラスです。しかしながら多少クセがあり、感覚がつかみにくいところがあります。(入力部分がブラックボックスすぎて)

基本ソース

■view部(わざとPHPは使用しない形で書いてます)

<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="upload_file">
    <input type="submit" name="submit" value="送信">
</form>

■sample.php


class Controller_Sample extends Controller
{
    public function action_index()
    {
        // 2016/03/16 追記
        // ver1.6.1以降、同じアクションでgetとpostを処理する場合は
        // ファイルがアップされる際のmethod判定が必要みたいです。
        if(Input::method() === 'POST') { 
            // 初期設定
            $config = array(
                'path' => DOCROOT.DS.'files',
                'randomize' => true,
                'ext_whitelist' => array('img', 'jpg', 'jpeg', 'gif', 'png'),
            );

            // アップロード基本プロセス実行
            Upload::process($config);

            // 検証
            if (Upload::is_valid())
            {
                // 設定を元に保存
                Upload::save();

                // 情報をデータベースに保存する場合
                Model_Uploads::add(Upload::get_files());
            }

            // エラー有り
            foreach (Upload::get_errors() as $file)
            {
                // $file['errors']の中にエラーが入っているのでそれを処理
            }
        }
    }
}

あれ?$_FILESはどこに行ったの?

自分はここで詰みそうになりました。$_FILES無いのにどうやって処理するんだよ!って。

結論をいうと、Upload::prosessメソッドに内包されていました。
prosessメソッドを使用すると自動的にクラス内の$files変数に入るようです。

例として、
自分で「サイト支援サイト.jpg」をアップし、Upload::process($config)を行ったあと、Upload::get_files()のvar_dumpを取ったもの。


array(1) {
  [0]=>
  array(10) {
    ["name"]=>
    string(28) "サイト支援サイト.jpg"
    ["type"]=>
    string(10) "image/jpeg"
    ["error"]=>
    bool(false)
    ["size"]=>
    int(472577)
    ["field"]=>
    string(11) "upload_file"
    ["file"]=>
    string(14) "/tmp/phpsh3Frt"
    ["errors"]=>
    array(0) {
    }
    ["extension"]=>
    string(3) "jpg"
    ["filename"]=>
    string(24) "サイト支援サイト"
    ["mimetype"]=>
    string(10) "image/jpeg"
  }
}

ちなみに、ファイルをアップしなかった場合は、array(0){}になってました。

で、じゃあfilesには何が入ってんの?

と、いう話になりますね。まあvar_dump取ったからほとんど書いてますけども。
表形式でまとめました。

Key 説明
field string フォームのinputタグに入っていたname名。nameに[]をつけていた場合は field:0となる。
name string アップロードしたファイル名
type string アップロードされたブラウザ上でのMIMEタイプ。
mimetype string よく分からなかったのですが、別途で判別出来る場合はそれで判別したMIMEタイプが入るようです。判別出来なかった場合はtypeと同じ値 になります。
file string アップロードされたファイルの場所
filename string アップロードされたファイル名
extension string ファイルの拡張子(自動的に小文字にはなりません)
size integer ファイルサイズ。単位はバイト。
error boolean エラーがあるかどうか
errors array エラーを連想配列に入れてある。array(‘error’ => エラーコード, ‘message’ => メッセージ)という形

また、Upload::save()を呼び出した後は、以下の2つのフィールドが追加されます。

Key 説明
saved_to string 保存される場所のパス
saved_as string 保存の際のファイル名

configに関して

configに関しては、日本語ドキュメントが充実しているの省きます。

FuelPHP 1.5 日本語ドキュメント Upload 設定

エラー文に関して

エラー文はcore/lang/en/upload.phpに書いてあります。
必要であれば、日本語化をしておきましょう。まだ自分は日本語にしてません・・・。(誰か日本語化してgistとかに上げてほしいな・・・チラッチラッ)

<?php

return array(
    'error_'.\Upload::UPLOAD_ERR_OK                     => 'The file uploaded with success',
    'error_'.\Upload::UPLOAD_ERR_INI_SIZE               => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
    'error_'.\Upload::UPLOAD_ERR_FORM_SIZE              => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
    'error_'.\Upload::UPLOAD_ERR_PARTIAL                => 'The uploaded file was only partially uploaded',
    'error_'.\Upload::UPLOAD_ERR_NO_FILE                => 'No file was uploaded',
    'error_'.\Upload::UPLOAD_ERR_NO_TMP_DIR             => 'Configured temporary upload folder is missing',
    'error_'.\Upload::UPLOAD_ERR_CANT_WRITE             => 'Failed to write uploaded file to disk',
    'error_'.\Upload::UPLOAD_ERR_EXTENSION              => 'Upload blocked by an installed PHP extension',
    'error_'.\Upload::UPLOAD_ERR_MAX_SIZE               => 'The uploaded file exceeds the defined maximum size',
    'error_'.\Upload::UPLOAD_ERR_EXT_BLACKLISTED        => 'Upload of files with this extension is not allowed',
    'error_'.\Upload::UPLOAD_ERR_EXT_NOT_WHITELISTED    => 'Upload of files with this extension is not allowed',
    'error_'.\Upload::UPLOAD_ERR_TYPE_BLACKLISTED       => 'Upload of files of this file type is not allowed',
    'error_'.\Upload::UPLOAD_ERR_TYPE_NOT_WHITELISTED   => 'Upload of files of this file type is not allowed',
    'error_'.\Upload::UPLOAD_ERR_MIME_BLACKLISTED       => 'Upload of files of this mime type is not allowed',
    'error_'.\Upload::UPLOAD_ERR_MIME_NOT_WHITELISTED   => 'Upload of files of this mime type is not allowed',
    'error_'.\Upload::UPLOAD_ERR_MAX_FILENAME_LENGTH    => 'The uploaded file name exceeds the defined maximum length',
    'error_'.\Upload::UPLOAD_ERR_MOVE_FAILED            => 'Unable to move the uploaded file to it\'s final destination',
    'error_'.\Upload::UPLOAD_ERR_DUPLICATE_FILE         => 'A file with the name of the uploaded file already exists',
    'error_'.\Upload::UPLOAD_ERR_MKDIR_FAILED           => 'Unable to create the file\'s destination directory',
);

Upload::registerに関して

Upload::registerメソッドは特定のメソッドの際にコールバックとして処理を追加することができます。これにより、バリデーションを追加したり、ファイルの保存先を拡張子別に変えたりすることができます。

Upload::register($event, $callback)
$eventにはvalidate(検証時),before(save()が行われる前),after(save()が行われた後)を指定できる。
コールバックには$fileが入る。

また、Upload::registerメソッドは基本的にprocessメソッドが呼ばれる前に行っておきましょう。(正確に言えば、validateを指定する場合はproccessより前で、beforeやafterを指定する場合はsaveより前に指定をしてください。)

Example – 拡張子によって保存先を切り替える

Upload::register('before', function (&$file) {
    if ($file['error'] == Upload::UPLOAD_ERR_OK)
    {
        switch($file['extension'])
        {
            case "jpg":
            case "png":
            case "gif":
                // 画像データであればimages/に入れる
                $file['saved_to'] .= 'images/';
            break;

            case "css":
                // cssファイルならcss/に
                $file['saved_to'] .= 'css/';
            break;

            case "js":
                // javascriptならjs/に
                $file['saved_to'] .= 'js/';
            break;

            default:
                // 他のファイルは通常の設定に従う
        }
    }
});

このようにすることで、動的に保存先を切り替えることが可能です。

また、こちらで定義された、$fileですが、上の方で説明した$filesの中身がそのまま入っています。

まとめ

・最初は迷ったけど、Uploadクラスは結構便利。

・コールバックが使える。

という感じですね。
慣れてしまえば、いろいろと利用出来そうです。ただ、Uploadされたファイルに限定されるので、URLで指定したファイルを動的に出来たら良かったかな・・・って感じです。

 Posted by at 5:50 PM
4月 162013
 

FuelPHPのOrm\Model_Softを使おう

こんばんは。ファガイです。
本日は、あんまり使われてる形跡がないOrmのModel_Softに関して解説。
Model_Softは1.4か1.5から入った・・・のかな。1.3では入ってなかったと思います。

Model_Softについて

Model_Softとは論理削除に対応したModelです。論理削除とは、物理削除とは異なり、データを残したまま削除することです。例えば、delete_flagと呼ばれるカラムを用意して、delete_flagが1の場合は読ませないようにします。
また、Model_SoftはOrm\Modelを継承しています。

Model_Soft Example

class Model_Sample extends \Orm\Model_Soft
{
    protected static $_properties = array(
        'id',
        'name',
        'createdAt',
        'updatedAt',
        'deletedAt',
    );
    protected static $_soft_delete = array(
        'deleted_field' => 'deletedAt',
        'mysql_timestamp' => true
    );
}

このような形となります。
$_soft_deleteには以下のプロパティが指定出来ます。

key default 解説
deleted_field deleted_at 削除時刻を入れるためのフィールド名
mysql_timestamp true mysqlタイムスタンプを使用するか

基本使用方法

Model_Softクラスは\Orm\Modelを継承しているため、Orm\Modelと同等に扱うことが可能です。
ただ、findやqueryで取得してくる値はdeleted_fieldがnullのものになります。

find_deleted,deleted – 論理削除のレコードを取得する

論理削除されたレコードを取得するにはfind_deletedまたはdeletedを使用します。find_deletedはdeletedのエイリアスのようなものなのでdeletedを使えばよいでしょう。

restore,undelete – 論理削除のレコードを復元する

論理削除のレコードを復元するには、restore()かundelete()を呼び出すだけです。undelete()はrestore()のエイリアスです。

Example

// 削除済みのデータを取得
$deleted = Model_Sample::deleted(1);

// 復元
$deleted->restore();

Model_Softを一部に使いたくないとき、使いたいとき

例えば、カテゴリ一覧を格納するだけのテーブルの場合、削除時刻など不要なものになります。また、逆にこの時だけModel_Softを利用したい。という時があるかもしれません。
そのような際には、$_disable_filterやメソッドを使用します。

■モデル全体にModel_Softの機能を使いたくない時

//そのクラス内に記述
protected static $_disable_filter = array(__CLASS__ => false);

falseの場合は使いたくない時。通常はデフォルトでtrueになる。

■一部に対して適用したい時

//メソッドをオーバーライドした上で、そのメソッドの中で
parent::disable_filter(); //使いたくないとき
parent::enable_filter(); //使いたいとき

まとめ

・論理削除はデータを管理する上で便利。(データが残るため)
・物理削除よりも速度が早い。
・だから使うといいと思うよ。

また、こちらの記事を書く前に私自身である程度クラスのコメントを和訳しておきました。

https://gist.github.com/fagai/5394102

便利かはわかりませんが、参考になればいいかな・・・。
最後に、find_all()とかdeleted_fieldも含めたfindもあれば良かったかな・・・。まぁModel_Softをそのまま拡張すれば簡単に出来るね!

 Posted by at 7:31 PM
4月 062013
 

こんばんは。ファガイです。

タイトルの通りなのですが、比較的簡単に実現したかったわけで。

とりあえず、比較的簡単に除きますか。(記事投稿時fuelphp1.5)
まずは、packages/oil/generate.phpの259行目付近

strpos($properties, "'id'") === false and $properties = "'id',\n\t\t".$properties.',';

これを、

!\Cli::option('no-id') and strpos($properties, "'id'") === false and $properties = "'id',\n\t\t".$properties.',';

こうします。

これでオプションとして–no-idをつければ出来ると思ってたら思わぬミスを発見。

packages/oil/generate/migration/actions.phpの70行目付近

$have_id or $field_str = "\t\t\t'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true),".PHP_EOL . $field_str;

え?orじゃなくてandじゃね・・・?
orじゃ$have_id付けた意味無いじゃん・・・ってことになった。なので、andに変更。

$have_id and $field_str = "\t\t\t'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true),".PHP_EOL . $field_str;

一応、idは付かなくなった。
しかしながらその下の行に直接array(‘id’)が書かれていた・・・。まあここはif判定やっちゃえばいいんだけども。。。もうちょっと使いやすくならないかなぁ。

追記

面倒なことに、$have_idの部分のorをandにしたらidが付かなくなった。
内部を確認した所、$have_idは全く効力を持っていなかった。idを付けてみたらidが2つ付いた形でmigrationが発行されてしまった。

・解決方法
generate.phpのline:589行目辺り

// We always pass in fields to a migration, so lets sort them out here.
$fields = array();
foreach ($args as $field)

この上に、

// idがあるかどうかの判定
$_split_array = explode(":", $args[0]);
!\Cli::option('no-id') and $_split_array[0] != "id" and array_unshift($args, 'id:int');

この判定を突っ込む。ここでidを振ってあるかどうかを確認する。
idを振ってなかったら振る。

あとは、action.phpの一部を変更。大体line:43

$name == 'id' and $have_id = true;

これの下部に以下を追加。

if($name == 'id') {
    continue;
}

以上!

とりあえずこれで自分は動くようになったので、これで一旦触りますかね・・・。

 Posted by at 2:30 AM