2007/12/25 火曜日

警告「Cannot modify header information」にはまる

このエントリをはてなブックマークに追加茘Cannot modify header information障のはてなブックマーク被リンク数
Filed under: PHP, cakePHP — akky @ 10:59:04

Webの記事を参考にコピペでページを作成すると下の警告が出てしまう。

( ! ) Warning: Cannot modify header information - headers already sent by (output started at /usr/local/corpjoin/app/controllers/users_controller.php:66) in /usr/local/corpjoin/cake/libs/controller/components/request_handler.php on line 135
Call Stack
# Time Function Location
1 0.0017 {main}( ) ../index.php:0
2 0.0377 dispatcher->dispatch( ) ../index.php:82
3 0.1971 dispatcher->_invoke( ) ../dispatcher.php:245
4 0.1971 dispatcher->start( ) ../dispatcher.php:256
5 0.1972 requesthandlercomponent->startup( ) ../dispatcher.php:298
6 0.1972 requesthandlercomponent->setajax( ) ../request_handler.php:120
7 0.1972 header ( ) ../request_handler.php:135

警告のメッセージで探してみると、/usr/local/corpjoin/app/controllers/users_controller.phpの66行目(要はエラーが出ている場所)から後ろにスペースもしくは改行があるのではないか。ということ。

はい。その通りでした。安易なコピペが思わぬ警告につながってしまった。

2007/12/18 火曜日

cakePHPのAjaxヘルパーがGETメソッドを呼ぶ前に検査を行なう

このエントリをはてなブックマークに追加cakePHPAjax若GET<純若九罎祉茵のはてなブックマーク被リンク数
Filed under: PHP, cakePHP — akky @ 19:08:48

AjaxヘルパーのobserveFormを用いた更新を行なう前に、フォームの値を検査して画面の更新を制御したい時の方法です。具体的には、フォームの値がない(空白)の場合は更新をしたくなかった。

実装はHTMLのformタグのonSubmitイベントでfalseを返却してsubmitを中断させる動きのcakePHP版。view部分のソースがこれで、

   $opt = array(
        "url" => "/hogeapli/result", // 反映するURL
        "update" => "result", // 反映先のdivタグのID
        "frequency" => "1", // 値変更の監視間隔
        "with" => "document.searchForm.serialize()", // 送信するフォーム
        "condition" => "searchFormSubmit()" // 検査を行なうJavaScriptのメソッド
      ); // 送信するフォーム
  echo $ajax->observeForm("searchForm",$opt);

optionのconditionに検査を実装したJavaScriptの関数を指定しておく。JavaScriptでは画面を更新したいときはtrueを返し、更新したくないときはfalseを返す実装をしておく。コードがこれ。

function searchFormSubmit(){
  var ret = false;
  for each( var e in document.forms.searchForm.elements ){
    if ( e.nodeName =="INPUT" ){
      if ( e.value != "" ){
        ret = true;
      }
    }
  }

  return ret;

こうすることで、フォーム内の値の変更を検知し、画面更新をする前に、JavaScriptのsearchFormSubmitが実行され、戻り値に従って更新の継続/中断を制御する。

2007/12/2 日曜日

app/views/pages/home.thtml で helperを使う方法

このエントリをはてなブックマークに追加app/views/pages/home.thtml сhelper篏帥号のはてなブックマーク被リンク数
Filed under: PHP, cakePHP — akky @ 11:35:51

home.thtmlでJavaScriptヘルパーを使おうとおもったら、

Notice: Undefined variable: javascript in /usr/local/corpjoin/app/views/pages/home.thtml

とエラーになってしまった。

ぐぐって、CakePHPではまったこと 10(home)にあった。

home.thtmlでヘルパー(html以外の)とか使いたい場合は、 cake/libs/controller/pages_controller.phpを app/controller/pages_controller.phpにコピーしてvar $helpers;を定義すればよいです。

らしい。

2007/9/20 木曜日

AJAXヘルパーのobserveField

このエントリをはてなブックマークに追加AJAX若observeFieldのはてなブックマーク被リンク数
Filed under: cakePHP — akky @ 22:25:52

先日はAJAXヘルパーのobserveFormを使って、フォーム内の任意の項目が更新されたタイミングで非同期な検索を行うことをやり、今回は、observeFieldを使ってフォーム内の特定のフィールドが更新されたタイミングで検索を行わせてみました。observFieldの使い方はobservFormと一緒で、フォームのidを指定していた第1引数をフィールドのidにするだけ。ラクチン。フィールドがhiddenでもOKなのね。

今回、observFiledを使うことになったのは、DragZoomControlからインスパイァーを受けるの続きの話で、マウスでGoogleMap上の範囲を指定させ、指定された始点/終点(長方形の対角の2点)を使って検索を行う機能。mousedown,mousemove,mouseupのイベントを使い範囲指定させるのは上手くいったので、mouseupの最後で始点/終点の座標をフォームに設定してsubmit。検索結果を画面に出したかった。
当初はJavaScriptでsubmitさせようとしたのだけど、検索の一部と結果表示を既存のobsevFormを使っている部分を共有したかったので、方式を統一する意味でもobservFiledを使う事にした。始点X,始点Y,終点X,終点yの順番でフォームに書き込んでいき、終点yをobservFiledで監視しておくと、マウスでの範囲選択終了を契機にして、検索が行われ結果表示までつなげていく。

observFormの使い方についてはhttp://puyo2.upper.jp/cake/CakePHPのHelperを使う〜Helperの使用法とかんたんAJAX〜(pdf)を参考にしてください。

2007/9/4 火曜日

cakePHPとUnitテスト

このエントリをはてなブックマークに追加cakePHPUnit鴻のはてなブックマーク被リンク数
Filed under: cakePHP — akky @ 23:33:39

cakePHPを使った開発でUnitテストの組み方。今回はリンクだけ

2007/9/3 月曜日

pagination.phpを使ったページングに条件を追加する

このエントリをはてなブックマークに追加pagination.php篏帥c若吾潟違>散菴遵のはてなブックマーク被リンク数
Filed under: cakePHP — akky @ 15:04:23

http://bakery.cakephp.org/articles/view/paginationのpagination.phpを使ってページングさせている状態でロジックで必要な情報をGETパラメータに追加する方法とajaxを使って特定のdivタグの中を更新する方法です。

画面の動き

コードの説明の前に画面の構成です。

+-----------------------------------------------+
| +--------------+                               |
| |検索条件      |   +-------------------------+ |
| +--------------+   |  結果結果 (id="result") | |
| +--------------+   |                         | |
| | googlemap    |   |                         | |
| |              |   |                         | |
| |              |   |                         | |
| |              |   |                         | |
| |              |   |                         | |
| |              |   |                         | |
| +--------------+   +-------------------------+ |
+-----------------------------------------------+

拙い絵ですが、画面レイアウトです。簡単た仕様と想定している使い方は

  1. 検索条件のフォームへ$ajax->observeFormを使い検索結果がajaxで更新されるようにする
  2. 検索結果はpagination.phpでページングしておく
  3. 検索条件のテキストボックスに入力
  4. $ajax->observeFormがサーバへPOSTして、得られたコンテンツを検索結果のdivタグに反映
  5. 検索結果のページナビゲーションを使ってページの移動を行なう

ロジックに必要な情報をGETパラメータに追加

ここで一番悩んだのは、フォームからPostするが、ページナビゲーションはGETで移動先のページへ遷移するために、POSTされた時はフォームへ入力された検索条件値がリクエストURLには含まれていないということ。そのため、pagination.phpが出力するナビゲーションにも検索条件値がふくまれていない。最初はコントローラで$this->dataに格納されている必要値を$this->params[’url’]に追加したが、app/controller/components/pagination.phpの中でURLの末尾に’/’が付けられてしまうためボツ。app/views/helper/pagination.phpのコード追いかけていくと、viewの先頭で呼ぶPaginationHelper::setPaging($paging) の中で$this->params[’url’]を保存しておき、PaginationHelper::_generateUrl($page=NULL,$pageDetails=NULL)がこの中身を使ってGETのURLを組み立てているのが判った。

これが判れば、コントローラの中でPOSTされた値を$this->dataから必要な値を$params->l;paramsへ写しておけばよい。PaginationHelper::_generateUrlがポストされた値も含めてGETのURLを組み立ててくれる。最後は、コントローラでPOSTとGETの両方をみてWHEREの条件を組み立てればよい。

  /*
   * GETパラメータ及びPOSTされたデータからWHERE句の条件を作る
   *
   * @param paramName パラメータ名。
   * @param condition WHERE句の条件。sprintfで値を代入させる。
   *
   * @return 値がある場合は WHERE句の条件。値が無い場合はnull
   */
  function buildWhere($paramName,$condition){
    $result  = null;
    // GETの場合

    if (isset($this->params['url'][$paramName]) &&
        $this->params['url'][$paramName] != ''
       ) {
      $param_var = $this->params['url'][$paramName];
      // ページングにパラメータとして渡すためにurlパラメータに追加
      $this->params['url'][$paramName]=urlencode($param_var);
      // WHERE句を作成
      $result = sprintf($condition,mb_convert_encoding(urldecode($param_var),"EUC-JP","UTF-8"));
    }
    // エリア指定 POSTの場合

    if (isset($this->data['Hogemodel'][$paramName]) &&
        $this->data['Hogetable'][$paramName] != ''
       ) {
      // WHERE句を作成
      $result = sprintf($condition, $this->data['Hogemodel'][$paramName]);
      // ページングにパラメータとして渡すためにUTF-8に変換してURLエンコードした値をurlパラメータに追加
      $this->params['url'][$paramName]=urlencode(urlencode(mb_convert_encoding($this->data['Hogemodel'][$paramName],"UTF-8","EUC-JP")));
    }

  function index(){
    $param_var = $this->buildWhere("name","name= '%s'");
    if ( $param_var != null ) {
      array_push($where_params,$param_var);
    }
    // 配列に格納したWHEREの条件をANDで結合
    $where = implode(" AND ", $where_params);
    list($order,$limit,$page) = $this->Pagination->init( 〜
  以下省略
  }

ajaxを使って特定のdivタグの中を更新

コントローラがPaginationComponent::initを呼ぶときの第3引数の連想配列にキーに’ajaxDivUpdate’、値にdivタグ名を入れる。

list($order,$limit,$page) = $this->Pagination->init(
$where
,Array()
,array(’ajaxDivUpdate’ => ‘result’) // prototyle.jsが更新するdivタグのidを指定
);

2007/8/30 木曜日

本番環境とテスト環境でのURLの差異を吸収する

このエントリをはてなブックマークに追加医鴻医сURL綏違後のはてなブックマーク被リンク数
Filed under: cakePHP — akky @ 18:57:28

cakePHPでは開発環境はDocumentRootの下に一式展開するのだが、本番環境をセットアップするときには余計なファイルを見られないようにapp/webrootをWebサーバのDocumentRootに設定せよ。と CakePHP プログラマーズ リファレンスガイドに書かれている。これを素直に実行すると開発環境と本番環境ではURLが異なってしまう。まさか、本番環境へリリースするときにわざわざ書き換えるなんてことはしたくない。でも、それを何とかしないと開発が行い難い。特に困ったのがCSS。app/views/layout/default.thtml に読ませるCSSを指定したときに、相対パスだと読める(たどり着ける)ページとたどりつけないページが出てきて、不統一っぷりが格好悪い。

そんな問題を解決してくれるのがHTMLヘルパー。よく使うのはHtmlHelper::link。内部へのリンクはこれを使い、”/コントローラ名/〜”と書くことで出力されるHTMLをヨロシクやってくれるので、作り手が環境を気にする必要がない。上のCSSの例だとdefault.htmlに

<?php echo $html->css(”common”); ?>

とするとapp/webroot/css/common.css を読み込むようにhref属性を作ってくれる。

これを知らなかったとには、試しに本番環境へアップロードしたらNotFoundになるのでワザワザソースを書き換えていた。そのときは、「こんなんじゃ使いモノにならないじゃん」とも思った。他の人も悩んでいると思い、ぐぐってみるとhttp://li-pton.com/wordpress/php/cakephp_virtual_host.htmlにバーチャルホストで解決させるという案があった。最初に見たときは「成る程!!」とも思ったが、これは、これで面倒だとも思った。

HTMLヘルパーで解決するのが(思想的にも)スマートと思う。URLを書くたびにHTMLヘルパーを使ったコーディングになってしまうのは、それはそれで、如何なものかとも思うが、現実的な方法かな。

2007/8/29 水曜日

pagination.phpを使ったページング

このエントリをはてなブックマークに追加pagination.php篏帥c若吾潟阿里呂討淵屮奪マーク被リンク数
Filed under: cakePHP — akky @ 22:19:17

cakePHPでページングをするです。http://bakery.cakephp.org/articles/view/paginationを見て3つのソースファイルをダウンロードしてviewとcontrollerを変更します。

簡単な説明

コントローラへの変更

<?php
class PostsController extends AppController
{
    var $name = 'Posts'; // for PHP4 installs
    var $components = array ('Pagination'); // http://bakery.cakephp.org/articles/view/67  からダウンロードしたコンポーネントを使う宣言
    var $helpers = array('Pagination'); //  http://bakery.cakephp.org/articles/view/68 からダウンロードしたヘルパーを使う宣言

    function index() {
        $criteria=NULL; // WHERE句に使う条件
        list($order,$limit,$page) = $this->Pagination->init($criteria); // コンポーネントの初期化
        $data = $this->Post->findAll($criteria, NULL, $order, $limit, $page); // コンポーネントで初期化された条件を使ってDBから読み込み

        $this->set('data',$data); 
    }
}
?>

viewを変更

<h1>Paginated Posts Index</h1>
<table>
<?php
$pagination->setPaging($paging); // Initialize the pagination variables ??よくわからんです。
$th = array (
            $pagination->sortBy('id'),
            $pagination->sortBy('title'),
            $pagination->sortBy('created')
); // Generate the pagination sort links
echo $html->tableHeaders($th); // Create the table headers with sort links if desired

foreach ($data as $output)
{
    $tr = array (
        $output['Post']['id'],
        $html->link($output['Post']['title'], "/Posts/View/{$output['Post']['id']}"),
        $output['Post']['created']
        ); // 1行分のtrを作成
    echo $html->tableCells($tr,array('class'=>'altRow'),array('class'=>'evenRow'));
}
?>
</table>
<? echo $this->renderElement('pagination'); // Render the pagination element http://bakery.cakephp.org/articles/view/69 からダウンロードしたelementを使ってページ番号のリンクを作成
 ?>

その他、判ったこと

  • app/webroot/img/nav/arrowleft.gifとapp/webroot/img/nav/arrowright.gifを用意すると次ページ、前ページを意味するイメージが表示される。変更したい場合は /app/views/elements/pagination.thtml を編集すればよい
  • 1ページに表示する件数を指定するにはコントローラで $this->Pagination->init($criteria) 前で $this->Pagination->show=件数 とする
  • 勝手にajaxとして動作する場合はコントローラーで $this->Pagination->ajaxAutoDetect=false とする。判断は /app/controllers/components/pagination.php の_checkAjax()が行なっている
次のページ »