Posts tagged function

CakePHP 1.2 の saveAll その2

3

CakePHP 1.2 の saveAll その1 では同一モデルへの複数レコードを saveAll で保存しました。今回はアソシエーションのモデルのデータを saveAll で保存する方法です。

アソシエーションのモデルのデータを保存

モデル
user.php

< ?php
class User extends AppModel {
    var $name = 'User';
    var $hasMany = array('Comment');
}
?>

comment.php

< ?php
class Comment extends AppModel {
    var $name = 'Comment';
    var $belongsTo = array('User');
}
?>

コントローラ
users_controller.php

function add() {
    if (!empty($this->data)) {
        $this->cleanUpFields();
        $this->User->create();
        if ($this->User->saveAll($this->data)===false) {
            $this->Session->setFlash('保存に失敗しました);
        } else {
            $this->Session->setFlash('保存しました');
        }
        $this->redirect(array('action'=>'index'), null, true);
    }
}

ビュー
users/add.ctp

< ?php echo $form->create('User');?>
< ?php echo $form->input('User.name');?>
< ?php echo $form->input('Comment.body'); ?>
< ?php echo $form->end('Submit');?>

CakePHP 1.2 の saveAll その1 の同一モデルへの複数レコードの保存よりも使い道は多いのではないかと思います。

追記
このエントリーは CakePHP 1.2.0.6311 beta で検証しています。

CakePHP 携帯専用サイトを作成する

6

CakePHP で携帯用のページを作成する際に CakePHP 携帯用ビューを表示する | Shin x blog のページが大変参考になります。

しかし、/m/ のような URL ではなく携帯専用サイトにしたかったので下記のような方法で実装しました。なお、PC でアクセスしたときには /pc.html という静的なページを表示するようにしてあります。
また、PC、携帯の振り分けはユーザエージェントで行っています。

携帯用コンポーネント

app/controller/component/mobile.php を作成し、PEAR の Net_UserAgent_Mobile を使用しています。このコンポーネントでユーザエージェントを判定して PC だったら pc.html へリダイレクトします。

PEAR の Net_UserAgent_Mobile はサーバにインストールしてもいいのですが、今回は app/vendors/ ディレクトリに入れました。また、app/vendors/ に include_path を通すために、CakePHPガイドブック を参考に include_path_vendors.php を作成しました。

app/controller/component/mobile.php

vendor("include_path_vendors");
vendor("Net/UserAgent/Mobile");
class MobileComponent extends Object {
    function startup(&$controller) {
        $this->controller = $controller;
        $mobile = &Net_UserAgent_Mobile::factory();
        if ($mobile->isNonMobile()) {
            $this->controller->redirect("/pc.html");
        }
    }
}

今回は簡単に PC か携帯でアクセス振り分けているだけですが、画面の大きさやキャリアなどによって色々な処理の振り分けが考えられます。

携帯用ヘルパー

app/views/helper/mobile.php を作成して HTML 出力時に文字コードを Shift-JIS へ変換します。ヘルパーの afterRender メソッドを使用しています。

class MobileHelper extends Helper {
    function afterRender() {
        $out = ob_get_clean();
        $out = mb_convert_kana($out, "rak", "UTF-8");
        $out = mb_convert_encoding($out, "SJIS", "UTF-8");
        ob_start();
        echo $out;
    }
}

コントローラで携帯用コンポーネント、ヘルパーを使用する

app/app_controller.php で携帯用コンポーネントと携帯用ヘルパーを使用します。

var $components = array('Mobile');
var $helpers = array('Mobile');

app_controller.php で設定しておけば全てのコントローラで共通に読み込むので各コントローラにその都度書く必要がなくなります。DB の管理画面など PC 用のコントローラが必要な場合は各コントローラに書いた方がいいでしょう。今回は完全に携帯用にしています。PC 用の管理画面もあるのですが、サブドメインを変えて app ディレクトリも違うものを使用しています。

また、各コントローラ内で設定するタイトルの文字コードを変換しないといけないので、app_controller.php の beforeRenderメソッドでタイトルの文字コードを変換します。

function beforeRender() {
    $this->pageTitle = mb_convert_encoding($this->pageTitle, "SJIS", "UTF-8");
    parent::beforeRender();
}

以上で携帯専用のサイトが CakePHP で作成できました。

コントローラの afterFilter で文字コードを変換する方法

コントローラの afterFilter で文字コードを変換することもできます。

app/controller.php

function afterFilter() {
    parent::afterFilter();
    $out = ob_get_clean();
    $out = mb_convert_kana($out, "rak", "UTF-8");
    $out = mb_convert_encoding($out, "SJIS", "UTF-8");
    ob_start();
    echo $out;
}

この場合は、beforeRender でのタイトルの文字コード変換の処理が必要ないのと、携帯用ヘルパーは必要ありません。
PC 用のコントローラも作りたいときは携帯用コンポーネント、携帯用ヘルパーを使用する方法がよいかと思います。

.htaccess で error_reporting を設定するときの注意点

5

.htaccess で PHP が出力するエラー表示を制御するには E_ALL などの定数は使えないので、ビット値で指定する必要があります。

E_ALL & ~E_NOTICE を設定する場合
php.ini
error_reporting = E_ALL & ~E_NOTICE

php コード内
error_reporting(E_ALL ^ E_NOTICE);

.htaccess
php_value error_reporting 6135

error_reporting に設定する値は PHP: error_reporting – Manual を参照するといいでしょう。

定数
1 E_ERROR
2 E_WARNING
4 E_PARSE
8 E_NOTICE
16 E_CORE_ERROR
32 E_CORE_WARNING
64 E_COMPILE_ERROR
128 E_COMPILE_WARNING
256 E_USER_ERROR
512 E_USER_WARNING
1024 E_USER_NOTICE
6143 E_ALL
2048 E_STRICT
4096 E_RECOVERABLE_ERROR

ちなみに E_ALL & ~E_NOTICE の値が 6135 になる理由は E_ALL の値が 6143、E_NOTICE の値が 8 なので 6143-8=6135 になります。(または E_NOTICE と E_STRICT 以外の値を足した数が 6135 になります。

私は開発中は E_NOTICE も表示するようにしていますが、結構表示させない人が多いようで人のコードを修正するときなど Notice エラーが出て困ることがあります。E_NOTICE を表示するようにしておくと未定義の変数などバグの元になりやすいものを教えてくれるのでお勧めです。

また、開発終了して本番運用するときにはブラウザにエラーを出力しないように
error_reporting(0);
を設定しておくことを忘れずに!
その際も PHP のエラーはエラーログに記録されるようにしておかないのも忘れずに。

CakePHP JQuery ヘルパー

3

JQuery helper for CakePHP ( PQuery port ) at NGCoders
CakePHP から JQuery を簡単に使うことができます。

正確には PQuery ヘルパーかもしれません。PQuery は JQuery を PHP から簡単に使用するライブラリで PQuery ヘルパーと同じ開発者が開発しています。
参考:PQuery – PHP and JQuery at NGCoders

インストール

JQuery helper for CakePHP ( PQuery port ) at NGCoders から JQuery ヘルパーをダウンロードし、解凍した pquery.php を /app/views/helpers にコピーします。
そのほかに jquery.js ファイルも必要になります。こちらもダウンロードして /app/webroot/js にコピーします。

コントローラ

Pquery ヘルパーと Javascritp ヘルパーを使用します
var $helpers = array('Pquery', 'Javascript');

ビュー

jquery.js の読み込み
<?php echo $javascript->link('jquery.js'); ?>

使用例

トグルボタン

<div id='msg'>Message...</div>
<?php echo $pquery->link_to_function('toggle', $pquery->toggle('#msg'));?>

フォームで送信された内容により HTML を更新
入力したテキストを /controller/action/ に GET で送信し、id=idtoupdate に受け取った HTML を表示する

<?php echo $pquery->form_remote_tag(array('url'=>'/controller/action/','update'=>'#idtoupdate'));?>
<input type='text' name='field' />
<input type='submit' />

<div id='idtoupdate'></div>

pquery.php を見ると色々なメソッドがあります。使い方はソースを見ればすぐに分かると思います。

scriptaculous.js を使って並び替えを行う

2

JavaScript の読み込み

まず prototype.js と scriptaculous.js を読み込む

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="scriptaculous.js"></script>

ソートするリストを作成する

ここでのポイントはドラッグさせたい要素に drag_1 のように「共通の名前+アンダバー+数字」を付けることです。

<ul id="sortarea">
<li id="drag_1">A1</li>
<li id="drag_2">A2</li>
<li id="drag_3">A3</li>
<li id="drag_4">A4</li>
<li id="drag_5">A5</li>
<li id="drag_6">A6</li>
<li id="drag_7">A7</li>
<li id="drag_8">A8</li>
<li id="drag_9">A9</li>
</ul>

ソートする JavaScript コード

ソートするエリアとソートする要素を定義した後で実際にソートするための JavaScript コードを書く。このコードがソートさせたい要素よりも前に書くとエラーになるので注意。

<script type="text/javascript" language="javascript">
// <![CDATA[
Sortable.create("sortarea",{
    onUpdate:function(){
        new Ajax.Updater(
        'sortarea',
        '/path/to/phpcode',
            {
                asynchronous:true,
                evalScripts:true,
                parameters:Sortable.serialize("sortarea")
            }
        );
    }
});
// ]]>
</script>

onUpdate でソート結果をPHP に送りデータベースなりに反映させる。
データベースに反映などが必要なければ、onUpdate は不要です。

デモ

これを実際に使用した例が下記になります。

#sortarea { width: 350px; border: 1px solid #000; } #sortarea li { cursor: default; }

  • LIST1
  • LIST2
  • LIST3
  • LIST4
  • LIST5
  • LIST6
  • LIST7
  • LIST8
  • LIST9

HTTP_Request を使用してファイルをアップロードする方法

1

PEAR の HTTP_Request を使用してファイルをアップロードする方法です。

<form action='POST_URL' method='post' enctype='multipart/form-data'>
<input type='text' name='title'>
<textarea name='body'></textarea>
<input type='file' name='upload_file'>
<input type='submit'>
</form>

上記のような HTML でファイルをアップロードする場合と同じような処理をするには、下記のように HTTP_Request を使用します。

// POST パラメータ
$post_data = array(
    'title' => $title,
    'body'  => $body,
);
// アップロードパラメータ
$upload_file = array(
    'name' => 'file',
    'path' => '/path/to/file',
);
// アップロード
$rs = http_send(POST_URL, $post_data, $upload_file);

function http_send($url, $params, $upload_file=null) {
    $req = new HTTP_Request();
    $req->setMethod(HTTP_REQUEST_METHOD_POST);

    foreach ($params as $key => $val) {
        $req->addPostData($key, $val);
    }

    $req->setURL($url);
    if ($upload_file) {
        $res = $req->addFile($upload_file["name"], $upload_file["path"]);
        if (PEAR::isError($res)) {
            echo $res->getMessage();
            exit;
        }
    }

    if (!PEAR::isError($req->sendRequest())) {
        return $req->getResponseBody();
    } else {
    	return false;
    }
}

参考:PEAR :: Manual :: ファイルアップロード

CakePHP Security コンポーネントのまとめ

1

CakePHP の Security コンポーネント の動作を調べたのでまとめておきます。
この Security コンポーネントをうまく使用すればクロスサイトリクエストフォージェリ(CSRF) を防ぐことができるでしょう。

トークンの使用

フォームにワンタイムトークンを実装する方法です。

コントローラの beforeFilter メソッドでトークンをチェックするアクションを指定
function beforeFilter() {
    $this->Security->requireAuth('login');
}
ビューのフォーム内にトークンを設定

<?php echo $html->formTag(); ?>
トークンが hidden 属性で生成される

トークンが一致しない場合

requireAuth で指定したアクションに POST でアクセスがあるとセションに保存したトークンとフォームから送られてきたトークンが一致するかチェックします。またそのほかにトークンの有効期間もチェックします。有効期間は CAKE_SECURITY の設定により違います。

CAKE_SECURITY 有効期間
high 10分
medium 100分
low 300分

トークンが一致しないと SecurityComponent の blackHole メソッドが実行されます。このメソッドでは
header('HTTP/1.0 404 Not Found');
を出力して exit します。(画面は空白)

任意の処理を実行したい場合は blackHoleCallback でコールバック関数を指定します。
設定できるのは同じコントローラ内のアクションのみになります。

function beforeFilter() {
    $this->Security->blackHoleCallback = "securityError";
    $this->Security->requireAuth('login');
}

function securityError() {
	die("security error!");
}
トークンチェックをするアクションを複数指定するときはカンマでつなげる

$this->requireAuth('login', 'delete');

特定のコントローラからのポストのみ許可する

$this->Security->allowedControllers = array("users");
これを指定するとたとえトークンが一致しても許可のないコントローラからのポストの場合は blackHole メソッドを実行します。

特定のアクションからのポストのみ許可する

$this->Security->allowedActions = array("action");
これを指定するとトークンが一致しても許可のないアクションからのポストの場合は blackHole メソッドを実行します。

ポストのみ受け付けるようにする

$this->requirePost('login');
これはトークンとは違い、POST のみ許可して GET でのアクセスを不可にします。
/users/login/ のような URL でのアクセスも GET なので不可になります。
POST の処理だけを実行したいようなアクションに使用します。

CakePHP Textヘルパーの truncate を全角文字に対応させてみた

5

CakePHP の Text ヘルパーに truncate という指定された文字列を任意の長さに省略するメソッドがあります。
しかし、このメソッドは全角文字を考慮していないため全角文字に用いると文字化けすることがあります。
そこでこのメソッドを全角文字に対応させてみました。

function truncate($text, $length, $ending = '…', $exact = true) {
    if (strlen($text) < = $length) {
        return $text;
    } else {
        mb_internal_encoding("UTF-8");
        if (mb_strlen($text) > $length) {
            $length -= mb_strlen($ending);
            if (!$exact) {
            $text = preg_replace('/\s+?(\S+)?$/', '', mb_substr($text, 0, $length+1));
            }
            return mb_substr($text, 0, $length).$ending;
        } else {
            return $text;
        }
    }
}

直接 Text ヘルパーを修正するとバージョンアップなどのときに困るので、
cake/libs/view/helpers/text.php を app/views/helpers/mb_text.php にコピーしクラス名を
class MbTextHelper extends Helper{
として、truncate メソッドを上記のように修正しました。

使用するときはコントローラ内で、
var $helpers = array("MbText");
view で
echo $mbText->truncate("あいうえおかきくけこ", 5, "…", true);
とすると
あいうえ…
と表示されます。

CakePHP find や findAll の条件指定にちょっとだけ便利な postConditions

1

CakePHP の コントローラに postConditions というちょっとだけ便利なメソッドがありました。

cake/libs/controllers.php

function postConditions($data) {
    if (!is_array($data) || empty($data)) {
        return null;
    }
    $conditions = array();

    foreach($data as $model => $fields) {
        foreach($fields as $field => $value) {
            $conditions[$model . '.' . $field] = $value;
        }
    }
    return $conditions;
}

例えば検索をするフォームなどで User.name で検索するような場合

view で
input('User/name');
と指定すると$this->data は

Array
(
    [User] => Array
        (
            [name] => 山田
        )
)

この $this->data を postConditions に渡すと
$data = $this->postConditions($this->data);
$data は

Array
(
    [User.name] => 山田
)

となり、このまま find や findAll に渡すことができます。
$this->User->find($data);

参考:CakePHP マニュアル
7.2. コントローラの関数

GoogleMap 特定のキーワードで表示させる

4

Google AJAX Search API を使用してキーワードや住所から自分のサイトに Google Map を表示させるメモ

Google AJAX Search API KEY と Google Map API KEY を指定して JavaScript を読み込む

<script src="http://www.google.com/uds/api?file=uds.js&v=1.0&key=Google AJAX Search API KEY" type="text/javascript"></script>
<script src="http://maps.google.com/maps?file=api&v=2.x&key=Google Map API KEY" type="text/javascript"></script>

実際の表示部分。 q=キーワードで表示させる地図のキーワード(または住所)を指定する。

<script type="text/javascript">
<!--
var gls;
var gMap;

function OnLocalSearch() {
    if (!gls.results) return;
    var first = gls.results[0];
    var point = new GLatLng(parseFloat(first.lat), parseFloat(first.lng));
    var zoom = 15;
    gMap.addControl(new GSmallMapControl());
    gMap.addControl(new GMapTypeControl());
    gMap.setMapType(G_MAP_TYPE);
    gMap.setCenter(point, zoom);
    var marker = new GMarker(point);
    gMap.addOverlay(marker);
    GEvent.addListener(marker, "click", function() {
        marker.openInfoWindowHtml(html);
    });
}

function load() {
    gMap = new GMap2(document.getElementById("map"));
    gMap.addControl(new GSmallMapControl());
    gMap.addControl(new GMapTypeControl());
    gMap.setCenter(new GLatLng(0, 0));
    gls = new GlocalSearch();
    gls.setCenterPoint(gMap);
    gls.setSearchCompleteCallback(null, OnLocalSearch);
    var q = "横浜ランドマークタワー";
    gls.execute(q);
}
//-->
</script>

<body onLoad="load()">
<div id="map" style="width: 410px; height: 320px"></div>
</body>

各 API KEY の取得は下記ページから
Sign-up for an AJAX Search API Key – Google AJAX Search API – Google Code
Sign Up for the Google Maps API – Google Maps API – Google Code

Go to Top