2016年3月31日木曜日

【CakePHP】余計なヴァリデーションはしない(saveがなぜか失敗する場合)

CakePHPで開発中のシステムで、少し前まで問題なく動いていた新規データ登録フォームからのデータ登録ができなくなってしまっていた。

原因を検索したところ、以下のページがヒット:
cakePHPでsaveできない時

Model->save()の際にヴァリデーションが自動で行われる。
該当のモデルクラスのヴァリデーション設定に問題がないかどうかをチェック。

そういえば、複数のテーブルで同じ名前のフィールドがあったので、自作のModelクラスが共通で継承する抽象クラスにそのフィールド名のヴァリデーション設定を追加していた。
required を true にし、allowEmpty を false にしていたので、Model->save()の際のPOST送信値に該当の名前のデータがない場合ヴァリデーションエラーになってしまう。これが原因だった。新規登録が出来なくなっていた該当テーブルには継承した抽象クラスで設定したヴァリデーションに該当するフィールドがなかった。

教訓:ヴァリデーション設定は横着せずに。

2016年3月25日金曜日

【PHP】ファイルのアップロードについて

cakePHPを使っておなじみな感じのCMS(コンテンツマネジメントシステム)を作っています。
以前お世話になっていた会社の取り扱い商品の情報をバイヤー向けにWeb上で
どんどん発信したいという要求にこたえるもの。

ファイルアップロード処理についてメモ:

【大きなサイズのファイルのアップロードに失敗する場合】
■PHPのタイムアウト設定を疑う
これはおなじみのPHP記述で対応。
set_time_limit(300);

■PHPのアップロードファイルサイズ容量制限設定を疑う
参考 : http://d.hatena.ne.jp/knowledgetree/20110217/1297939617
「php_value upload_max_filesize」だけでなく「php_value memory_limit」
「php_value post_max_size」もセットで気を付けること。
今回はPHPスクリプトからの設定はうまく動かず、.htaccessに記述したらうまく動いた。



【ZIPをサーバー上で解凍する場合】
複数の画像をZIPで一括でアップロードしたいという場合。
たぶん常套手段はZIP解凍用のディレクトリに一時的に解凍して、
ファイル名なんかをチェックしながら本番ディレクトリに移動するというものだろう。

普通に1ファイルずつファイルをアップロードする時と処理を共通化させたいので
通常アップロードメソッドに処理を回したらなかなかうまくいかない。

原因は、move_uploaded_file()関数を使っていたからで、ZIP解凍でサーバー上に出現した
ファイルをこの関数で移動することはできない。
ZIP解凍で出現したファイルを移動する場合はrename()関数を使うこと。

2016年3月14日月曜日

【PHP】__DIR__ は使わない

お馴染みのcakePHPとMySQLとでシステムを作っています。商社のサイトで取扱商品の配布販促物(サムネイルやバナーなど)を表示・管理するシステム。

cakePHP3系を使おうと思ったんですがサーバーにインストールされているPHPのヴァージョンの関係で使えませんでした。

ファイルのインクルードをするとき、現在のファイルのディレクトリ名を取得するのに「__DIR__」という定数を使っていて、ZAMPPでは動いたんですがサーバーではエラーになりました。これ結構新しい定数なんですね。

 dirname(__FILE__) と書き直したら大丈夫でした。

同様に、ある読み込みファイルが画像だったらその幅と高さとを取得したいというとき、
exif_imagetype() 関数を使ってたんですが、これも古いPHPでは真っ白エラーになりました。
getimagesize() 関数のほうが重いらしいんですがこっちは動いたのでこっちで書き直しました。

古いサーバーにシステムを作る際はいろんな注意が必要になりますね。

2015年10月29日木曜日

【Android】メモリリークを防ぎたい 弱参照(WeakReference)を使おう

最近やっていたAndroid案件で、メモリリーク対策についてまた理解が進んだのでメモ。

主要な参考先:
http://tomokey.blogspot.jp/2011/05/android.html


コツ:
■内部クラスは static にする。
■「弱参照」という仕組みについて理解して、アクティビティやフラグメントといったライフサイクルのあるインスタンスを他のクラスに保持させるときはこの弱参照を使う。
■内部クラスから外側クラスにアクセスする必要がある場合、内部クラスのプロパティに外側クラスのインスタンスを持たせて、内部クラスのコンストラクタで外側クラスのインスタンスを渡す。この時、弱参照インスタンスとして保持しておく。
■処理の効率化のために作成した色々なクラスでも、プロパティにアクティビティやフラグメントを保持するときは弱参照経由にしておく。
■あとは、とにかくフラグメントやアクティビティを遷移するときは古いインスタンスを初期化していく。onResume()で準備したインスタンスはonPause()で片付けて、onStart()で準備したインスタンスはonStop()で片付ける。


---------------------------------
弱参照について。
通常、あるインスタンスへの参照を作成すると、それは「強参照」という強い参照になるらしい。
弱参照というのは弱い参照で、ガベージコレクションの対象になりやすい。
あるインスタンスについて、参照がひとつでも残っているとガベージコレクションの対象にならないという。
しかし、あるインスタンスについて、そこに残っている参照が弱参照のみだったら、それはGCの対象になるという。

アクティビティやフラグメントなど、ライフサイクルを持つものは、開発者が意図してないタイミングで破棄されたり再生成されたりするらしい。
しかしそうやってこっそり破棄された時、そのアクティビティやフラグメントへの参照(強参照)が残っていると、きちんと破棄されないでメモリを圧迫するという。
なので、これらへの参照は弱参照がいいのだということだ。

例:フラグメントクラスでアクティビティのインスタンスを持っておこうという時
//メンバ変数
private Activity activity;

//onStart() あたりで
× this.activity = getActivity();
○ this.activity = new WeakReference<Activity>(getActivity()).get();

弱参照とは、参照インスタンスである。
WeakReference クラスのインスタンス。
new WeakReference<型指定>(参照されるインスタンス) というコンストラクタで作成する。
このインスタンス自体は参照であってお目当てのインスタンスではない。
get() メソッドによって参照されているインスタンスを取り出す。


---------------------------------
インナークラスを持つフラグメントの見本
---------------------------------


public class MyFragment extends AbsFragmentWithViewFlipper{

    ///////////////////////////////////////////////////////////
    //フィールド
    ///////////////////////////////////////////////////////////

    //ボタン
    private Button btn;

    //ボタンのクリックリスナー
    private MyOnClickListener listener;


    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル:onStart()
     * フラグメントがユーザーに見えるように生成された
     * タイミングで呼び出される。
     *
     * アニメーションの開始など、表示に関わる初期化処理を行う。
     */
    @Override
    public void onStart(){
        super.onStart();

        //ボタン
        this.btn = (Button)getActivity().findViewById(R.id.btn);

        //クリックリスナー作成
        this.listener = new MyOnClickListener(this);

        //クリックリスナーをセット
        this.btn.setOnClickListener(listener);
    }//function


    ///////////////////////////////////////////////////////////
    //クラス内クラス
    ///////////////////////////////////////////////////////////
    /**
     * スイッチ操作時のリスナー
     */
    private static class MyOnClickListener implements OnClickListener{
        //外側クラスのインスタンス
        private MyFragment parent;

        //////////////////////////////
        /**
         * コンストラクタ
         */
        public MyOnClickListener(MyFragment fragment){
            //弱参照経由で外側クラスを受け取る
            parent = new WeakReference<MyFragment>(fragment).get();
        }//function

        //////////////////////////////
        /**
         * クリック時
         */
        @Override
        public void onClick(
            View view
        ){
            //外側クラスにアクセスする時は「parent」を使う

        }//function
    }//class
}//class

2015年8月13日木曜日

【cakePHP】ヴァリデーション時、UPDATEの場合であればプライマリキーのセットを忘れずに

cakePHPではデータベースを扱うMODELクラスにバリデーションの設定を書いておくことで入力内容の妥当性のチェックが容易にできる。

今回、バリデーションでしくじったケースがあったのでメモ。

モデルクラスによるバリデーションが行われるタイミングは主にふたつ。
■保存系:例・モデルクラスのインスタンスの save() が実行されるとき。
■明示的ヴァリデート系:例・モデルクラスのインスタンスの validates() を実行したとき。

save()の挙動は、新規レコードの追加(INSERT)と既存レコードの更新(UPDATE)とを兼ねるので、今書いている処理がINSERTなのかUPDATEなのかをしっかり意識して制御する必要がある。
主要な区別は、
■INSERT:モデルに渡される配列が、そのテーブルのプライマリキーの値を含んでいない。
■UPDATE:モデルに渡される配列が、そのテーブルのプライマリキーの値を含んでいる。


今回やってしまったエラーは以下のケース。
あるテーブルについて、レコードを新規追加する処理を追加した。
その際、そのテーブルを扱うモデルクラスについて、ヴァリデーションルールを追加した。
そのヴァリデーションルールは「isUnique」。そのテーブル内で値が重複していたらエラー。

そのモデルでのヴァリデーションを使用している箇所は、今回作った新規追加だけではなく、既存の処理にもあった。
過去に作った既存の処理においては、UPDATEにおけるヴァリデーションであるのにも関わらず、プライマリキーを配列に含めないでチェックさせていた。これまではそれで問題なかった。
しかし、「isUnique」のヴァリデーションルールは、INSERT時とUPDATE時とで挙動が違うため、既存の処理側でエラーを起こしてしまった(エラーとしたくないケースの入力においてもエラーになってしまった)。


今回の教訓:
ヴァリデーションが必要な場合、INSERTかUPDATEかを意識して、UPDATEであればプライマリキーを忘れずにモデルに渡すこと。

【cakePHP】半角・全角・カタカナ・ひらがななどの混じった文字列でのあいまい検索

cakePHPで、データベースからの検索をする場合。
キーワード入力欄などで、いい加減な入力にも柔軟に対応したいという要望がある場合。

MySQLの機能をうまく使うと、以下の曖昧入力での検索が簡単にできるようになる。
■アルファベットが、半角/全角、大文字/小文字にかかわらず、検索可能
■数字が、半角/全角にかかわらず、検索可能
■平仮名、カタカナ、半角カナにかかわらず、検索可能


参考URL:
http://tutty.info/527
http://qiita.com/kazu56/items/108075fcb5e98c0d637a
https://www.softel.co.jp/blogs/tech/archives/1877


要点:
■「collate utf8_unicode_ci like」を使う
    select * from item where item_name collate utf8_unicode_ci like '%りんご%';

■cakePHPの場合、テーブル名やコラム名にバッククオーテーションがつくので、カッコで囲む。
    $this->find('all',[
        'conditions'=>['(name) collate utf8_unicode_ci like'=>'%hoge%'
    ]);

■データベースがUTF8でない場合はもう一段階変換を挟む。「convert(コラム名 using utf8)」という書式。

    select * from member where convert(namae using utf8) collate utf8_unicode_ci like '%サトウ%';



現在の案件だと、データベースの文字コードがUTF8ではなかったので、以下のような感じになった。

コントローラーにおける記述:
---------------------------------------------------
//検索結果
$items = array();
if(empty($this->request->data) == false){
    //検索条件のセット
    $condition = array();

    //会員名あいまい検索
    $condition['and'][] = array('(convert(Member.name using utf8)) collate utf8_unicode_ci like'=>'%'.$this->request->data['Member']['name'].'%');

    //~ここにさらに検索条件を追加する処理~

    //ページネイターを使っての検索実行
    $items = $this->paginate('Member', $condition);
}//if
//検索結果をセット
$this->set("items", $items);
---------------------------------------------------


【cakePHP】Paginator を使っての検索結果並び替え

cakePHPで作るシステムはたいていデータベースと連動する。

基本的な構造として、ひとつのDBテーブルあたり、以下の機能がワンセットで作られる。
■一覧:検索フォームと検索結果
■新規登録
■詳細&編集
■削除


検索結果ページにおいて、検索結果にページネーション機能をもたせたりいろいろな条件での並び替えをさせたかったりする。

cakePHPには「Paginator」という便利な機能があるので、一覧画面のためのDB検索にはこの機能を使う。
今回、ページネイターを使ってのレコードの並び替えをはじめて使ってみて便利だったのでメモを残す。

Viewにおいて、

<?php echo($this->Paginator->sort("並び替え対象モデル名.並び替え対象コラム名", "リンク文字列")); ?>

これが基本。
これで、並び替えのためのURLが仕込まれたリンクを作成することができる。
この場合、指定されたコラムでの並び替えになり、昇順・降順がトグルする(クリックするたびに昇順降順が入れ替わる)。

トグルするのが気色悪いという場合、昇順降順指定込みのリンクをワンセットで作っておくとよい。sort() の第三引数にオプションを設定できるのでここに書く。
こんな感じ。たいてい検索結果はテーブルで出すだろうからテーブルの一部を抜き出した感じのソースで。

------------------------
<th>
    会員ID
    <?php echo($this->Paginator->sort("Member.id", "▲", array('direction' => 'asc' , 'lock' => true))); ?>
    <?php echo($this->Paginator->sort("Member.id", "▼", array('direction' => 'desc', 'lock' => true))); ?><br>
</th>
------------------------