2016年12月16日金曜日

【JavaScript】非同期通信のためにPromiseをつかう

JavaScriptで非同期通信を利用する際に利用できるPromiseというのを勉強したのでメモ。

【参考URL】
http://qiita.com/koki_cheese/items/c559da338a3d307c9d88
https://www.htmlhifive.com/conts/web/view/study-room/async-programming-with-deferred

Promiseというのはデザインパターンのひとつらしい。
なのだけど、デザインパターンのWikipedia記事には記述がないのでデザインパターンということではないのかな。ただのライブラリ?

Promiseというのはオブジェクトである。
PHPやJSでは、関数を呼び出した際、リターンで値が戻ってこない限り次の処理に進めないので、まず素早くPromiseオブジェクトを返しておき、データの準備が整ったらそのオブジェクトを通してお目当ての値を取り出す、という仕組み。

■本来取得したい戻り値(WebAPIとの接続によって取得する値など)の代わりに「プロミスオブジェクト」という特別なオブジェクトをまず素早く返す。
■本来取得したい値を渡せる状態になったらそのプロミスオブジェクトを通して値を取り出す。
■Promiseオブジェクトのコンストラクタには、「成功時に実行する関数と失敗時に実行する関数とのふたつを引数に取る関数」を渡す。このコンストラクタにおいて、成功時のコールバックと失敗時のコールバックとの呼び出しを登録する。それぞれの実際の処理の内容をコンストラクタ内で定義するのではなく、あくまで呼び出されるタイミングを定義する。
■通信成功時の処理は呼び出し側のthen()に書く。
■通信失敗時の処理は呼び出し側のcatch()に書く。
■大抵の場合、非同期通信を使うだろうのでXMLHttpRequestインスタンスと組み合わせて使うことになると思う。Promiseコンストラクタでの「resolve(引数X)」と呼び出し側での「then(引数X)」とが対応するので、ここでXMLHttpRequestインスタンスをやり取りすると良さそう。


【サンプルコード】

index.php
<html>
<head>
<title>Promiseによる非同期処理テスト</title>
<script type="text/javascript" src="my_promise_ajax.js"></script>
<script src="https://www.promisejs.org/polyfills/promise-6.1.0.min.js"></script>
</head>

<style>
textarea{
width:600px;
height:300px;
}
</style>

<body>

<script type="text/javascript">
var promise = getContentFromUrlAsync('api01.php', '{"reward_point_bonus":{"initial":"10","limit_break":"15"}}');
promise.then(function(xmlHttp){
//非同期処理成功時の処理定義
//Promiseクラスのコンストラクタへの引数における
//「resolve」に相当する処理の内容をここで定義している。

//通信で受け取ったコンテンツ
var content = xmlHttp.responseText;

//テキストエリアにコンテンツを代入
var textarea = document.getElementById('textarea01');
textarea.value = content;
})
.catch(function(xmlHttp){
//非同期処理失敗時の処理定義
//Promiseクラスのコンストラクタへの引数における
//「reject」に相当する処理の内容をここで定義している。
alert('失敗01');
});
</script>


<h1>Promiseによる非同期処理テスト</h1>
<div>
<h2>01</h2>
<textarea id="textarea01"></textarea>
</div>
</body>
</html>

my_promise_ajax.js
////////////////////////////////////////////////////////////
//Promise関連
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
/**
 * Promiseインスタンスを返す関数。
 * 非同期処理を用いて同一ドメイン内APIから値を取得するために利用する。
 * この関数は汎用的に作っておき、個々の具体的な処理は呼び出し側のthen()で定義する。
 *
 * @param  string アクセスするURL(同一ドメイン内APIのURL)
 * @param  string 送信するJSON文字列
 * @return Promiseインスタンス
 */
function getContentFromUrlAsync(
api_url,
str_params_json
){
var promise = new Promise(function(resolve, reject) {
//AjaxのためのXMLHttpRequestインスタンスの作成
xmlHttp = new XMLHttpRequest();

///////////////////////
//コールバック登録。
//通信ステータスが更新されるたびに実行されるコールバック処理。
//resolve()、reject()に引数を渡してthen()、catch()で変数を使えるようにするため、
//このコールバックメソッドは外出しせずにここに無名関数として書く。
//こうすることでresolve()、reject()を引数を指定して呼び出せる。
xmlHttp.onreadystatechange = function(){
// 非同期の処理
// 処理が終わったら、resolve または rejectを呼ぶ
if(xmlHttp.readyState == 4){
//readyState : 4 通信完了

if(xmlHttp.status == 200){
//成功時
//promise.then() にXMLHttpRequestインスタンスを渡す
resolve(xmlHttp);
}else{
//失敗時
//promise.catch() にXMLHttpRequestインスタンスを渡す
reject(xmlHttp);
}//if
}//if
};
///////////////////////

//POST送信値の作成
var data = {
json : str_params_json
};

//XMLHttpRequestインスタンスでURLの値を取得
xmlHttp.open(
"POST",
api_url, //接続API(同一ドメイン内)
true
);

//サーバに対して解析方法を指定する(POST送信の場合必要)
xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

//戻り値の型を指定(JSON) 受け取り側で処理が楽になる
//→IEでは使用できないようなので不採用
//xmlHttp.responseType = 'json';

//open()で作成したリクエストを送信 POST送信値を渡す
xmlHttp.send(EncodeHTMLForm(data));
});
return promise;
}//function


//////////////////////////////
/**
 * 送信データをHTML形式にエンコードする。
 */
function EncodeHTMLForm(data){
var params = [];

for(var name in data){
var value = data[ name ];
var param = encodeURIComponent( name ) + '=' + encodeURIComponent( value );
params.push( param );
}//for

return params.join( '&' ).replace( /%20/g, '+' );
}//function


api01.php
<?php

$random_value_array = [
'太郎',
'次郎',
'三郎',
'四郎',
'五郎',
];

$json_array = [];
if(isset($_REQUEST['json'])){
$json_array = json_decode($_REQUEST['json']);
}//if

$test_array = [
'return_value' => $random_value_array[mt_rand(0, 4)],
'json'         => json_encode($json_array),
];

sleep (3);
echo(json_encode($test_array));

2016年5月14日土曜日

【Unity】WebAPIに接続してデータを取得する

PHPでもAndroidでも、過去の開発経験からいって、WebAPIに接続してデータを取得するという課題は必ずつきまとうし、また初回はつまづきが大きい。
でも一度やり方が分かってしまうと、あとは作っておいたライブラリファイルに処理を丸投げして終わりとか、コードのコピペ で終わりとか、さらりと出来てしまう。

なので、UnityでWebAPIに接続するサンプルを作っていた。

それなりに出来たのでGitHubにあげました。
https://github.com/ishiitakeru/Unity_WebApiConnectSample

主要なスクリプトはこれ
https://github.com/ishiitakeru/Unity_WebApiConnectSample/blob/master/WebApiConnectSample/Assets/script/ConnectWebApiSample.cs

説明。
WebAPIに接続して値をプロパティに格納する機能を持つのがWWWクラスのインスタンス。
POSTデータを送信するために使うのがWWWFormクラスのインスタンス。
WWWクラスをnewするときに WWWFormのインスタンスを引数に渡してやることでPOSTデータを送信する。

留意ポイントは、 WWWクラスのインスタンスは通信によって状態が変わるということ。
つまり、newした直後はWebAPIからのレスポンスがまだ受け取れていないので、取得した値を取り出せない。
しばらくして通信が完了すると、WWWインスタンスのプロパティから通信で手に入れたデータを取り出せる。
ということは、WWWインスタンスを生成したあと、その状態を時間経過に従って監視し続ける仕組みが必要。

そこで、コルーチンを使う。
コルーチンになっているメソッドでは、毎フレーム、yield return から次の yield returnまでが実行される。

つまり、コルーチンメソッドのすごいところは、 ひとつのメソッド内で時間経過で状態が変わることを取り込んで実現できるということだ。

そこで、WWWインスタンスによってWebAPIに接続するコルーチンメソッドは以下のような作りになるはず。

----------------------------------------------------------------------
private IEnumerator コルーチンメソッド名(){

    //WebAPIにPOSTデータを送信するための WWWForm インスタンス
    WWWForm my_www_form = new WWWForm();
    //キーと値とのセットを登録
    my_www_form.AddField("key", "value");

    //WWWクラスのインスタンス作成
    WWW my_www = new WWW(
        "http://WebAPIのURL", //WebAPIのURL
        my_www_form  //POST送信値をセットしたWWWFormインスタンス
    );

    //↑ここまでは一度だけ実行されれば良い
    //↓ここからは通信によるデータ取得が完了するまで繰り返し実行される必要がある
    while(true){
        //1フレームで、yield return から次の yield return まで実行される。
        yield return null;

        //WWW.isDone プロパティによって通信が完了したかどうかを調べられる。
        if(my_www.isDone == true){
            //通信完了
            //WWWインスタンスからデータ取り出し
            Debug.Log("Apiレスポンス : " + my_www.text);
            break;

        }else{
            //まだ通信が完了してない
        }//if
    }//while
 }//function
----------------------------------------------------------------------

コツ :
■WWWインスタンスは一度作ればいいので毎フレーム繰り返されるループの外で作っておく。
■WWW.isDone プロパティの確認を毎フレーム繰り返されるループ内で行う。

Androidの場合、別スレッドで通信処理を開始して、データ取得が終わったらそれを検知するコールバックメソッドからデータを取り出して、みたいにかなり複雑なことをやった記憶がある。

[Androidの場合]
■通信用にメインの処理とは別の非同期処理スレッドを作成し、通信はそこでしなくてはならない。
■通信完了を検知するコールバックメソッドを使い、通信で手に入れたデータをそこで取り出さないといけない。

UnityはAndroidに作りが似ているのかなと思ったけど、どうも、WebAPIとの通信においては非同期スレッドもコールバックメソッドも使わない(あるいは意識しないで良い)ということみたい。

その代わり、毎フレーム「yield return 」から「yield return 」まで繰り返し実行されるコルーチンという仕組みをきちんと理解する必要があるようですな。

【Unity】コルーチンに登場するIEnumeratorとはなんぞや

Unityには処理をフレームごとに分割するコルーチン(Coroutine)という仕組みがある。

理解するためのキーワードが IEnumerator というクラス。
これは、yield return 文とセットで使われる。
これは、Listなどのコレクションに関連するクラスだが、コレクションそのものではなく、JavaでいうIterator のように「元となるコレクションがまずあり、それを反復処理するときに使う」クラス。

コルーチンのためのメソッドというものがまず作られる。
これは、返り値がIEnumeratorになるメソッド。

------------------------------------------------------------------------
using UnityEngine;
using System.Collections;

public class CoroutineTest : MonoBehaviour {
    //コルーチンで書き換える文字列
    private string text_to_show;

    //////////////////////////////
    /**
     * スタート
     */
    void Start () {
        //コルーチンをスタート
        StartCoroutine(
            SampleCoroutine()
        );
    }//Start

    //////////////////////////////
    /**
     * コルーチンのテスト
     * このメソッドはUnityで追加されたスクリプト(MonoBehaviour継承クラス)内に書かれる想定
     */
    private IEnumerator SampleCoroutine() {
        int i = 0;
        while(true){
            this.text_to_show = "test " + i;
            i++;
            //コンソールに出力
            Debug.Log("text_to_show : " + this.text_to_show);

            //2秒待つ
            //n秒待たせるには、yield return 文でnew WaitForSeconds(n)を戻させなくてはならない。
            yield return new WaitForSeconds(2);
        }//while

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

while(true) があるから無限ループのように見える、もしくは (yield) return のところで処理が終わってそうに見える。
実際には、yield return のところで処理が呼び出し元に帰る。
そして、このメソッドの返り値であるIEnumeratorのインスタンスがMoveNext()を実行すると、前回 yield return した続きから処理が再開され、次の yield return まで処理が走る。
Unityの場合、この MoveNext() は明示的には書かれず、Unityが見えないところで自動で実行する。
その仕組が、Unityの持つ「StartCoroutine(コルーチンメソッド)」という命令。
これは、Unity によって毎フレーム「MoveNext()」が実行されるというもの。

IEnumerator とは、Iterator のような、コレクションを反復処理する際に使う反復子というインターフェイスの一種。
MoveNext() というメソッドを持っているのが特徴。

IEnumerator というのはつまり、元になるコレクションを持ってて(IEnumerator がコレクションから取り出されるとも言える)、MoveNext() によって次の値次の値とアクセスできるということ。
で、IEnumerator を返り値としてyield return するメソッドというのは、MoveNext()によって次の値を返す(次のyield returnの戻り値を返す)コレクションであると見ればいい。

IEnumeratorの元(持ち主)に2つの系統があると考える。
■Listなどのコレクションインスタンス
■yield return を持ち、IEnumerator を戻り値とするメソッド(コルーチン)

以下の変数とメソッドとは、似たようなことをやろうとしている。

------------------------------------------------------------------------
変数 test_list(コレクションのインスタンス)
------------------------------------------------------------------------
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;

public class IEnumeratorTest01 : MonoBehaviour {
    void Start () {
        //IEnumerator のインスタンス(コレクション変数)
        List<string> test_list = new List<string>();
        test_list.Add("test01");
        test_list.Add("test02");
        test_list.Add("test03");

        //コレクションからIEnumerator を取得
        IEnumerator test_ienumerator = test_list.GetEnumerator();
        while (test_ienumerator.MoveNext()) {
            Debug.Log(test_ienumerator.Current);
        }//while
    }//Start
}//class

------------------------------------------------------------------------
メソッド TestIenumeratorMethod()
------------------------------------------------------------------------
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;

public class IEnumeratorTest02 : MonoBehaviour {
    void Start () {
        //IEnumerator のインスタンス(yield return を持つメソッドからの戻り値)
        IEnumerator test_ienumerator_by_method = this.TestIenumeratorMethod("test");
        while (test_ienumerator_by_method.MoveNext()) {
            Debug.Log(test_ienumerator_by_method.Current);
        }//while

    }//Start

    //////////////////////////////
    /**
     * IEnumeratorの元となるコレクションに相当するメソッド
     */
    public IEnumerator TestIenumeratorMethod(string str){
        yield return str + "01";
        yield return str + "02";
        yield return str + "03";
    }//function
}//class

------------------------------------------------------------------------
IEnumerator.Current コレクションの中での現在地のオブジェクトを返す。
IEnumerator.MoveNext() boolean を返す。コレクションの現在地(Current)を次の要素に移動させ、次の要素がある場合はtrueを、ない場合はfalseを返す。

2016年5月10日火曜日

【ECcube3】さくらのレンタルサーバーの深い階層に設置したECcubeに独自ドメインを割り振ったところ、URL解決がややこしくなった

さくらのレンタルサーバーにはECキューブ(3系)のインストール機能がある。

マルチドメインで設定したディレクトリにECキューブをインストールした。

○○.sakura.ne.jp/△△/html

みたいになるように。

独自ドメインの移管の関係で独自ドメインを割り当てるのが遅れそうだったため、○○.sakura.ne.jp 以下のURLでECキューブサイトの構築作業をし、独自ドメインの操作ができるようになり次第URLを入れ替えればいいやと思っていた。

こんな感じ。

前:
http://○○.sakura.ne.jp/△△/html

後:
http://○○.com


ところが、独自ドメインを割り当てても、独自ドメインでアクセスした場合に正常に動作しない。

色々やったところ、

\app\config\eccube\path.yml

に記述されている内容を変更したら一応動くようになった。
「root」だとか「○○_urlpath」だとかの設定は、URLの定数設定らしいので、それらを変更した。
ただし、「○○_realdir」という名前の項目はURLではなくサーバー内のパス指定らしいのでこれらは変更してはいけないらしい。


path.yml の変更で一応は動くようになったんですが、URLに「index.php」が必須になってしまった。

独自ドメイン割り当て前の管理画面(例)
http://○○.sakura.ne.jp/△△/html/admin

独自ドメイン割り当て後の管理画面(例)
http://○○.com/index.php/admin

のように。
TOPページも「index.php」を省略するとエラーになってしまう。
.htaccess に DirectoryIndex を設定したりしたのだが、エラーが取れない。

どうも、mod_rewrite の設定が関係しているらしいのだけど、一旦保留。

独自ドメインのネームサーバーの設定が反映されない

作成しているWebサイトに独自ドメインを割り当てるネームサーバーの設定でちょっとつまづいたのでメモ。

ネームサーバーの設定を変更し、数時間経って設定が反映されたはずなのに以前と同じ表示のまま変更されない。

たまたま同じURLにスマホでアクセスしたら、ネームサーバー設定変更後のファイルが表示された。
ところが作業用PCでアクセスすると表示が昔のまま。

そこで、ブラウザのキャッシュを削除したらきちんと新しいファイルが表示された。

DNSの設定の話だからサーバーサイドのファイルだけが問題で、ローカルのキャッシュが影響を及ぼすとは思っていたなかったので気づくのが遅れた。

2016年4月26日火曜日

【ECcube3】追加したブロックにスクリプトを割り当てたい カゴの中身変数など

(この記事でのECcubeのヴァージョンは3.0.8 です。)

ECcube3で、「ロゴ」「カゴの中」「ログイン」のみっつのブロックをひとつにまとめたヘッダーブロックを新規に作成したいとする。

しかし、管理画面から新規作成したブロック内では、カゴの中ブロックで使われている「Cart」など、プログラムが用意しているであろう高機能な変数が使えない。

自ら追加したブロックにおいて、通常は使えないECcube変数などを使えるようにするにはどうするか。
自分が追加したブロックに対応するスクリプト(コントローラー)が追加でき、コントローラーで作成した変数をView(twigファイル)に渡すことができればよい。

ECcube2系では、データベースの「dtb_bloc」テーブルの「php_path」が鍵だった。
以前仕事でECcube2系をカスタマイズした時につけておいたメモがあったのでコピペしておく。

-------------------------------------
ECキューブのディフォルトのページには、ページのクラスとその拡張クラスとが紐付いていて、いろいろPHPで複雑なことが出来る。

大雑把に言って4つのPHPファイルがワンセットになってる。
■HTML側のPHP(URLに対応するもの)
 例:products/list.php
■テンプレートファイル
 例:data/Smarty/templates/default/products/list.tpl
■DATA側、ECキューブ標準の処理の書かれたクラスファイル
 例:data/class/pages/products/LC_Page_Products_List.php
■DATA側、ユーザーが拡張処理を書くための拡張クラスファイル
 例:data/class_extends/page_extends/products/LC_Page_Products_List_Ex.php

これらのファイルがあらかじめいろいろな処理をして、テンプレートで使える変数や配列を作っている。
管理画面から新規に作成したページで、他のページが使っているECキューブの変数が使えないのは、ECキューブ標準ページはこれらのファイルによってあらかじめ処理を経てきているから。
なので、新規追加ページでECキューブのいろいろな変数を使いたい場合、上記のクラスファイルや拡張クラスファイルを同様に追加して、処理を書いておかないといけない。


で、
そういう感じでPHPで色々なことを仕込んだ、ブロックも作りたい。

【方法】
管理画面からブロックを新規作成。

\public_html\frontparts\bloc に、block_任意の名前.php というファイル(※)を作成。
\public_html\data\class_extends\page_extends\frontparts\bloc に、LC_Page_FrontParts_Bloc_任意の名前_Ex.php という拡張クラスファイルを作成。その処理の内容は、同ディレクトリにある他のクラスを参考に。
このクラスファイルを、このHTMLディレクトリ側のPHPから呼び出す。

データベース管理画面に入り、「dtb_blocテーブル」を開く。
追加したブロックの「php_path」コラムに、「frontparts/bloc/block_任意の名前.php」と入力。(※)で作成したファイル。

LC_Page_FrontParts_Bloc_任意の名前_Ex.phpに、処理を書く!
-------------------------------------


今回はECcube3系で、フレームワークが変わってしまっているので同じ手法は使えない。だが似たような方法でできるはず。

うまくいったのでやり方をメモる。

-----------------
1.データベースのレコードの書き換え
管理画面からブロックを追加すると「dtb_block」テーブルに該当のレコードが増えている。
「logic_flg」のフィールドの値を1にすると、対応するクラスを読み込みに行くらしい。対応するクラスは何らかの命名規則があるはずで自動的に導き出されるはず。

-----------------
2.ブロックのためのコントローラーを作成
\src\Eccube\Controller\Block に、自分が追加したブロックのためのコントローラーを作成する。名前はおそらく、ブロックのファイル名をキャメル表記にしてControllerをつけたもの。

【例】
ブロックのファイル名 : my_header
→ブロックのコントローラーファイル名 MyHeaderController.php
→ブロックのコントローラー名 MyHeaderController

既存のブロックのためのコントローラーファイルを参考にして処理を書く。
indexという名前のファンクションが必要らしい。

ログインブロックのように、
public function index(Application $app, Request $request)
という、引数がふたつある関数にする場合、

use Symfony\Component\HttpFoundation\Request;
の指定が必要なので注意。

戻り値は
$app->render(
'Block/対応するブロックのファイル名.twig',
twigファイル(View)に渡す連想配列
)
にするらしい。

-----------------
3.コントローラープロバイダーの編集
\src\Eccube\ControllerProvider\FrontControllerProvider.php
を編集。
72行目あたりからブロックのための設定が書かれているのが見つかるので、自分のブロックのための記述を追加。

こんな感じ。
$c->match('/block/my_header', '\Eccube\Controller\Block\MyHeaderController::index')->bind('block_my_header');

これで、スクリプトにエラーがなければ動く(コントローラーで用意してViewに渡した変数がtwigファイル内で使える)はず。

2016年4月20日水曜日

【ECcube3系】ディフォルトテンプレートを複製する

ECキューブでECサイトをまた作らなくてはならず、作業しています。
さくらのレンタルサーバーを使っている環境で、さくらの管理画面からECキューブを楽にインストールできる機能があったのでインストールまではスムーズにできました。

ところがECキューブのバージョンが3.0.8で、過去に使ったことのある2系とはいろいろがらりと違う。

テンプレートの構造も違うようなので過去のECキューブサイトで取っておいたバックアップをあてはめることも出来ないらしい。

なのでディフォルトのテンプレートを複製してテンプレート枠を増やし、また一から独自に編集しようかと思ったんだけど、テンプレートの増やし方が分からないし、ECキューブ3系のデザインテンプレートには無料のものがない。

でも出来ました。

管理画面
>オーナーズストア
>テンプレート
>テンプレート一覧
>ディフォルトのテンプレートの「…」をクリックしてダウンロード
>「default.tar.gz」がダウンロードされる

この default.tar.gz をアップロードすればいいかというと、そうではないところが罠である。
一旦この default.tar.gz を解凍する。
すると

default
├app
└html

という構造のディレクトリになって解凍される。
default ディレクトリに入って、「app」「html」のふたつを選択し、zipに圧縮する。

これを「テンプレート>アップロード」からアップすればよい。
「テンプレート名」と「テンプレートコード」はよく分からなかったので両方アルファベットのみで名付けておきました。たぶん「テンプレート名」には日本語文字使っても大丈夫なのではないかと思います。

2016年4月10日日曜日

【C# .NET Framework】C#の勉強を始めました

エンジニア職で転職活動をしていて、Unityでのゲーム開発を仕事にできたらいいなあと思っています。
ただ、いままでUnityを使ったこともなければゲームで使うような C系の言語もやったことがないので、すこしずつ勉強を始めました。

まずはUnityで使う言語であるC#を勉強しようということで入門書を購入しコードを書きながら読み進める。

やっぱりなにか作らないと勉強もはかどらないなということで、複数の画像をひとつのウィンドウに同時に表示する画像ビューワーのWindowsのアプリケーションを作っていました。

まだプロトタイプですが今日作った分を GitHubにアップしました。
https://github.com/ishiitakeru/MultiImagesViewer/blob/master/MultiImagesViewer/MultiImagesViewer.cs

■ドラッグアンドドロップによって、そのディレクトリ内の複数の画像を横に並べて連続的に表示する。
■設定によって表示画像数を2~4枚から選択できる。
■ウィンドウのタイトル部分に開いているディレクトリのパスを表示する。
■矢印キーの左右で画像を次・前と順次入れ替えられる。

手応えとしては、C#はJavaに似てて、.NET Framework はAndroidに似てますね。

この練習用のアプリ、あと追加したい機能:
■矢印キーの上下でディレクトリ移動が出来る。

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() 関数のほうが重いらしいんですがこっちは動いたのでこっちで書き直しました。

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