2012年9月26日水曜日

[CakePHP]$form->create()の第一引数

このドキュメントみても$form->create()の第一引数にnullを入れることで、
そのformは特定のモデルを使用しないようにすることができると書いているが、
 本当のところはfalseを入れなくてはだめみたい。
    <?php
        echo $form->create(false,array(
            'type'=>'post',
            'action'=>'mkHtml'
        ));
        echo $form->input('goodsUrl',array('id'=>'goodsUrl'));
        echo $js->submit("作成",array(
            'url'=>'mkHtml',
            'update'=>'#data'
        ));
        echo $form->end();
        echo $js->writeBuffer(array('inline'=>false));
    ?>
てviewに書くと、出力されるhtmlは以下のようになる。
<input name="data[goodsUrl]" type="text" id="goodsUrl" /> 
もし、$form->create()の第一引数にfalseを設定しなかったら以下のように
モデルが配列の最初のレベルに来る。
<input name="data[theme][goodsUrl]" type="text" id="goodsUrl" />
 現在のコントローラーのデフォルトのモデルは"theme"。

 ※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[SVN]無料のSVNサーバ Assembla

いつもはノートPCで開発しているけど、たまにはデスクトップの端末で
開発をしてみたい、と思い、ソースコードをサーバに置こうと考えた。

無料でsvnのレポジトリが作れてしまう、以下のサービスを使ってみた。

https://www.assembla.com

今のところ非常に使いやすい。

やり方は、現在のSVNのサーバを以下のようにコマンドラインでエクスポート

svnadmin dump 【リポジトリロケーション】 > 【バックアップファイル名】
 
 出力された【バックアップファイル名】はzip圧縮する。
 
でassemblaのプロジェクトの管理画面のImport/Exportタブから
zipファイルをアップロードする。
 
何分か待つ。
 
画面がrunninng..的な画面でとまったように見えるけど、
しばらく経つと、数字が上がりはじめる。
 
 
【気になる点】
色んなブログで無料で無期限で使える(容量制限はあるみたい)
と書いてあるけど、本当に無期限なのかな。
assemblaのサイトに説明が見つからなかった。 
 
 【注意点】
移行した後、他の端末でchckoutする場合は元の端末と同じURLとなるように
checkoutする。外部サービスのcallbackURLとして設定しているから。 

2012年9月22日土曜日

[Error記録]Twitter Oauthログイン時のエラー

今朝最初にログインしようとしたとき、こんなエラーが出た。


で、修正も何もせずにすぐに同じようにログインしようとしたら、
正常にログインできた。

なんなんだろ、request_tokenがTwitter側からうまく帰ってくる前に処理が進んじゃってるからかな。


 【2012/10/03追記】
この現象、朝PCを起動して初めてログインしようとすると発生するんだよな。
今日も発生したので、「戻る」を押して、もう一度「許可」を押したら発生した。
その際、
$json = $consumer->get($accessToken->key, $accessToken->secret, 'http://twitter.com/account/verify_credentials.json', array());
で得られる$jsonをログに出力してみたんだけど、その結果が、
 {"error":"Could not authenticate you.","request":"\/account\/verify_credentials.json?oauth_consumer_key=・・・・・

"Could not authenticate you."というメッセージがあった。

これで検索すると、Could not authenticate you
というページでtwitterのリクエスト先のURLにhttpsをつければよい、
といった意見があったので試してみよう。
またユーザ側のクライアントの設定で「常にSSLを使用する」、といった設定をしている 場合に発生するとのこと。 クライアントとはおそらくブラウザのこと。 こちらも試してみよう。

【2012/10/03追記②】

ちなみに、本日の追記の時は、一度失敗したCallbackURLをもう一度
ブラウザでたたいたので、シミュレーションだったのだが、先ほど、
お昼休みにPCを落として、またログインしてみたら発生して、
ログを見たら新たな発見があった。
2012-10-03 16:50:15 Debug: requestToken
2012-10-03 16:50:15 Debug: oauth_token=XXXXXXXXXXXXXXXXXXXXXXXXX&oauth_token_secret=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
2012-10-03 16:50:15 Debug: accessToken
2012-10-03 16:50:15 Debug: 
2012-10-03 16:50:15 Debug: Notice (8): Trying to get property of non-object in [C:\xampp\htdocs\rakuzon\app\controllers\users_controller.php, line 55]
2012-10-03 16:50:16 Debug: json
2012-10-03 16:50:16 Debug: {"error":"Could not authenticate you.","request":"\/account\/verify_credentials.json?oauth_consumer_key=
requestTokeをセッションから取得することはできているのだが、
accessTokenをTwitter側から取得できていない、ということがわかった。
つまり以下がうまくいっていないようだ。
$accessToken=$consumer->getAccessToken('http://api.twitter.com/oauth/access_token',$requestToken); 
【2012/10/04追記】
上記のURLをhttpからhttpsにしてもダメだった。
なんでだろうなー。

【2012/10/05追記】
何も修正していないのに、なぜか今日エラーが出ずにいけた。

2012年9月21日金曜日

[PHP]boolean のfalseはechoしても何も表示されない。

PHPのTRUEと FALSEについて 値を返すというけれど、なぜ「1」かブランク(何もなし)なのでしょうか?


このサイトによると以下とのことです。
どおりでなんにも表示されないと思った。


-----以下抜粋---------------------------------------------------------------------------
echo false;
とやると、falseがstring型へ自動型変換という手順を踏んでechoの対象になりますが
PHPの場合は falseをstring型へ変換すると ""(空文字列)になります。
(別の言語では"false"という文字列に変換されるものが多いですね)
よって、出力されません。
----ここまで----------------------------------------------------------------------------

[CakePHP]layoutってcontrollerがrenderする前に処理してないか?

※このpostは前半部分間違った推測をしています。
 後で本当の原因がわかったので、最後に追記として書いています。


全てのControllerで共通してdefault.ctpというlayoutを使っている。
そこには、もしAuth認証でログイン済みであれば、ユーザ名と
プロフィール画像を表示するようにしている。

つまり、全ての画面で共通なロジックだからだ。

もちろんlogoutでレンダーされる画面にもそのlayoutが適用
されるんだけど、なぜかlogout画面でもユーザ名と
プロフィール画面が表示されてしまう。
赤で囲った部分はログイン状態でなければ表示されないはずの部分。



仮説だが、
「layout内の処理はControllerのrender処理の前に行われる。」
のではないか。

ちなみに、ログアウト画面表示後、TOP画面等を表示すると、
ちゃんとユーザ情報などは表示しないでくれていた。
なので、logout()の処理自体はどこかで動いていて、セッションから
ログイン情報を消してくれているはず。
そして、layout内の<?php echo $content_for_layout ?>を赤枠の部分より
上に配置してみたが、相変わらず表示された。

つまり、viewの部分がlayoutより上にあったとしても、処理としてはlayoutが先に行われて
いるということではないか。

仕方がないので、layout内でlogout後かどうかを判別する仕組みを入れた。
コントローラー側は以下の3行目にログアウト後であるフラグを入れた。
ちなみに、ここで$this->session->write()を使わず$this->set()を使ったのは、
前者だと、おそらく次以降の遷移後にもフラグが立ちっぱなしになってしまうので。
setは一度のrenderでしか有効でない。
 public function logout(){
  $this->Auth->logout();
  $this->set('logoutFlg',true);
 }


つづいてlayout側。(抜粋)
2行目の&&以降で、フラグに値が入っているかを確認している。
入っていなければ、logout後ではないのでユーザ情報を表示する。
 <?php
  if(!empty($auth['User']['id']) && empty($logoutFlg)){
   echo $this->element('loginInfo');
  }else{
   echo $this->element('loginLink');
  }
 ?>

※本当はせっかくtrueが入るからempty()とか使いたくなかったけど、
logout後以外は絶対に値がはいらないから$logoutFlgみたいにbooleanで
判別しようとすると、エラーになっちゃうんだよね。

こうすれば、ログアウト画面のユーザ情報は消えてくれた。
つまり、まとめると、

「layout内の処理はControllerのrender処理の前に行われる。
ただし、layout内でController側で設定された変数が必要な場合は、
Controllerの処理を待って処理をする。」

あくまで仮説です。
誰か、本当のところを知っている人教えて下さい。


【追記】
すみません。書いた直後に原因が発覚しました。
上で書いたことと全然違ってました。

自分はAppControllerのbeforeFilterで
    function beforeFilter(){
       $this->Auth->allow('*');
       $auth=$this->Auth->user();
       $this->set('auth',$auth);
    }
としていました。
$this->Auth->logout()はsessionにあるユーザ情報は消しますが、
set()された$authは消しません。(auth.phpのソースコード参照)
なので、logoutアクションでrenderされる画面にユーザ情報が表示されて
しまったということです。
そこで、Authのユーザ情報の取り出しはview側(layout)にて行うように
修正したところ、上記で追加したlogoutFlg無しでも正常にlogout画面が
ユーザ情報無しで表示されました。

そもそも、AppControllerにAuthのユーザ情報の取り出し処理を入れていた理由は、
全画面でやるので、全viewのファイルに書くのは嫌だなと思っていたからです。
でも、layoutに書いておけば一箇所でよいので、layoutに移しました。









[CakePHP]viewで繰り返し使う部分を外だしする(elements)

elementsという機能を使えば、viewで度々使う部分を外部ファイルにして
呼び出して使うことができる。

たとえば、どの画面にも出ている「○○さんがログイン中」とか。

以下のサイトがわかりやすい。

CakePHPでビューからエレメントを呼び出す

[CakePHP]AppControllerと普通のControllerのcomponentの設定の関係

AppControllerで設定したcomponentを普通のControllerで重ねて設定すると
うまくマージしてくれないみたい。

以下のようにapp_controller.phpで設定するとする。
var $components = array('Auth','Session');
そして普通のControllerであるusers_controller.phpに以下のように設定するとする。
※まぁ、AppControllerで設定しているものを重ねて設定することはないのだけど、
 消し忘れた場合など。
var $components = array('Auth');
すると、以下のようなエラーが発生
Notice (8): Trying to get property of non-object [APP\controllers\users_controller.php, line 51]

 たぶん、コンポーネントの設定のマージがうまくいっていないんだろうな。
以下で指摘した点と同じ原因かもしれない。

[CakePHP]AppController でコンポーネント設定するとsessionコンポーネントが使えない。

なので、users_controller.phpの方のcomponentsの設定を消したらう動いた。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip


※追記:
この後、もう一度、users_controllerの方のcomponentの設定を戻してやっても
動いた。原因は違うところにありそうです。すみません。

[SyntaxHighlighter]右上の"?"を非表示にする

ずっときになっていた以下の赤で囲った右上の"?"。




 消す方法がわかりました。以下のサイトの中程に書いてあります。

Bloggerにソースコードをハイライトする SyntaxHighlighterを導入する

[SyntaxHighlighter]bloggerにソースコードをきれいに貼り付ける

ソースコードを行番号付きで、きれいに表示する このサイトを見てできました。

[SyntaxHighlighter]blogger mode だと<br/>タグが表示できない件

PHPのソースコードをブログに載せようとしたんだけど、
なぜか、ソース中の<br/>タグが表示されず、空行となってしまっていた。
そこで調べていると、英語だけど以下でblogger modeが悪さをしていることが
わかった。

syntaxhighlighter in blogger interpreting <br/> literally despite html encoding

 なので、以下の行をコメントアウトしたら、ちゃんと<br/>タグが表示されるようになった。

//SyntaxHighlighter.config.bloggerMode = true;

[API]各サービスのOauthを一括でやってくれる仕組みがあるのね

いつか試してみよう。

18.Opauth ライブラリで複数のソーシャルアカウントを試す/a>

2012年9月20日木曜日

[CakePHP]TwitterのOAuth認証とAuthコンポーネントを連携させる

Twitterのログイン認証を自分のサイトで利用できるAPIを使用し、
CakePHPのAuthコンポーネントを連携させてみた。
難しくて時間がかかってしまったので概念と実装方法を説明する。

 概念


まずは概念から。
流れは以下の三つの図の通り。図1→図3という時系列で処理が進んでいく。
APIを使用する側のサイトをConsumerと呼ぶらしい。
また、パソコンの絵はユーザの端末を表している。
(excelで図を作ったが、bmpファイルにしたらぼやけて汚くなっちゃった。
どうすればきれいな図の画像ファイルできるかな。)

図1



図2




 図3



 実装方法 

 

1.Twitter API https://dev.twitter.com のサイトで自分のサイトの情報を登録。


 自分のTwitterアカウントでログインしたら右上にマウスを持っていくと現れる「My Applications」を押す。次の画面で「Create a new application」を押す。

現れる入力画面で自分のサイトの情報を入力する。
「Callback URL」以外は適当でもよさそう。
Callback URLは上の概念の「⑦Callback URLにリダイレクト」でTwitter側から
リダイレクトされる際のURLである。
このURLにはAuthコンポーネントで使用するモデルのコントローラに
専用のアクションを用意し、そのURLを指定する。
私の場合はUsersコントローラにtwtrCallback()というアクションを作ったので、

http://127.0.0.1/rakuzon/users/twtrCallback

とした。まだ、開発用なので自IPアドレスを指定している。
一つ注意点として、localhosotは指定できないので127.0.0.1を指定する。
このサイト参照。 Twitter アプリケーションのコールバックURLにlocalhostを指定する

2.Cnsumer KeyとConsumer Secretを控えておく


システムがTwitterのAPIを使うための、「システムに一つ」のIDとパスワードのようなもの。
「My applications」画面から、自分が今登録したサイト名をクリックした画面で、
Detailsタブを参照することにより確認できる。
これでTwitter側で行う作業は終わり。

3.OAuthを使うためのCakePHP用のライブラリをダウンロード、インストール


Consuming OAuth-enabled APIs with CakePHP

上記でダウンロードできる。
ただ、CakePHP 1.X系を使用している場合は、
「get the version for the older CakePHP 1.x.」
というリンクからダウンロードする。

自分はcakephp-cakephp-1.3.15-9-gacd25c3.zipを使っているが、
最初2.X用のものをダウンロードし使ってみたが、動かなかった。

ファイルを解凍したら、app/vendros直下にOAuthというフォルダをフォルダごと
配置する。これでインストールは完了。

4.ソースコードを書く


コメント増量中。
コメント中の丸の数字は上記の概念図の丸の数字に対応している。
まずはAuthで使うモデルのコントローラ。
私の場合は、users_controller.php
<?php
//インストールしたライブラリを使用するためにこの1行を入れる。
App::import('Vendor','oauth',array('file'=>'OAuth'.DS.'oauth_consumer.php'));


class UsersController extends AppController {

    var $name = 'Users';
    var $components = array('Auth');

    function beforeFilter()
    {
        $this->Auth->allow('index', 'login', 'twtrCallback');

        //Authの認証で使うIDとパスワードはデフォルトでは'username'と'password'だが
     //それぞれaccess_token_keyとaccess_token_secreteに変更する。当然Usersテーブルにも同カラムを準備している。
        $this->Auth->fields = array('username' => 'access_token_key', 'password' => 'access_token_secret');
       
        //これをやらないと、app_controllerで行った設定が反映されないらしい。
        parent::beforeFilter();
    }
    //①で呼び出されるURL
    public function index() {
        $consumer=new OAuth_Consumer(
            '【2で控えたご自分のCnsumer Key】',
            '【2で控えたご自分のCnsumer Secret】');

        // ②、③ request_tokenを取得。
        //認証後、「http://127.0.0.1/rakuzon/users/twtrCallback」にリダイレクトする
        $requestToken=$consumer->getRequestToken(
        'http://twitter.com/oauth/request_token',
        'http://127.0.0.1/rakuzon/users/twtrCallback');


        // 認証後、アクセストークンを取得する際に必要なので保存
        $this->Session->write('request_token',$requestToken);

        // ④、⑤Twitterの認証ページにリダイレクト
        $this->redirect('http://twitter.com/oauth/authorize?oauth_token='
        .$requestToken->key);
    }

    //⑦のリダイレクト先URLとなるアクション。
    public function twtrCallback() {
        if (isset($this->params['url']['denied'])) {
            echo 'access denied';
            return;
        }
        $consumer=new OAuth_Consumer(
            '【2で控えたご自分のCnsumer Key】',
            '【2で控えたご自分のCnsumer Secret】');
        $requestToken=$this->Session->read('request_token');

        //access_token取得する。
        //⑧、⑨ access_tokenはユーザごとに払い出されるもの。
        $accessToken=$consumer->getAccessToken(
            'http://api.twitter.com/oauth/access_token',$requestToken);
        //⑩、⑪ access_tokenを使用し、ユーザ情報をTwitter側から引き出す。
        $json = $consumer->get($accessToken->key, $accessToken->secret, 'http://twitter.com/account/verify_credentials.json', array());
        $twitterData = json_decode($json, true);

        //⑫モデルに渡す。モデルでは保存が行われている。
        $this->User->update(
        Array(
            "user_id" => $twitterData['id_str'],
            "user_name" => $twitterData['screen_name'],
            "access_token_key" => $accessToken->key,
            "access_token_secret" => $accessToken->secret,
            "img_url" => $twitterData['profile_image_url']
            )
        );
       
        //⑬Authのlogin()処理のために型にaccess_token_keyとaccess_token_secretを入れてあげる。
        $user['User']["access_token_key"] = $accessToken->key;
        $user['User']["access_token_secret"] = $accessToken->secret;
       
        //⑬Authのlogin処理
        $this->Auth->login($user);

        $this->redirect('/main/index');
    }

    public function logout(){
        $this->Auth->logout();
    }

    public function login(){
    }
}

users/indexがユーザがまずログインするときにアクセスするURLとなる。
つまり、ログインボタンなどに設定するURLだ。
twtrCallback()がTwitterからのリダイレクト先のURLとなる。
概念の「⑦CallbackURLにリダイレクト」。
63~70行目はログインするごとにユーザの情報をアップデートする
ことを意味している。

つづいて全てのコントローラーが継承するapp_controller.php。

class AppController extends Controller {

    function beforeFilter(){
    //全ての画面でログイン状態のチェックを行うため、
    //authのユーザ情報を取得する。
        $auth=$this->Auth->user();
        $this->set('auth',$auth);
    }
}

なお、view側で$authという変数に値が入っているかどうかで
ログインしているかどうかを確認する。全画面でログイン状態確認を
行うのでapp_controllerで行っている。
$this->session->check('Auth');で最初チェックしようと思ったのだが、
なぜかlogout後もtrueが帰ってきてしまう。
値の中身は空だったけど、変数の型が残っててもtrueとなってしまうからかも。
このあたりあまり定かではないけど、 ログイン状態確認の処理は、
後々Cookieを利用した自動ログインの機能を
追加する予定で、大きいプログラムになりそうなので、view側でなく、
controller側にあったほうがいいかな、と思い、深追いしないでおく。


そして、モデル。user.php

<?php

class User extends AppModel {

    var $name = 'User';

    public function update($user_data)
    {
        $user = $this->find('first', array('conditions' => array('user_id' => $user_data['user_id'])));
        if($user) {
            $user_data['id'] = $user['User']['id'];
        }
        $this->create();
        $this->save(Array("User" => $user_data));
    }
}

9~12行目でDB中にあるuser_idとTwitterから引っ張ってきた情報のuser_idを
引き合わせて、同じユーザ情報にはきちんと上書きしてくれるようになっている。
user_idというのはTwitterでTwitterアカウント一つ一つに割り当てられる数字のID。

このモデルに対応するテーブルは以下。
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(64) NOT NULL,
  `user_name` varchar(64) NOT NULL,
  `access_token_key` text NOT NULL,
  `access_token_secret` text NOT NULL,
  `img_url` varchar(512) NOT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;


users_controller.phpの80行目で、ログイン後にmain/indexに飛ばしているが、
飛ばし先でどのようにauthを扱っているかを解説するためmain_controller.phpを載せる。

<?php
//3でインストールしたライブラリを使うための1行
App::import('Vendor','oauth',array('file'=>'OAuth'.DS.'oauth_consumer.php'));

class MainController extends AppController {

    public $name = 'Main';
    public $uses = array('Item','Tag','Theme');
   
    //Authを使うため。
    var $components = array('Auth');

    var $paginate = array(
            'Theme' => array (
            'limit' => 10,
            'order' => array(
                'theme.id' => 'asc'
            )
        ),
            'Tag' => array (
            'limit' => 10
        )
        );

    function beforeFilter(){

    //未ログイン状態でもindexにはアクセスを可能とするため。
        $this->Auth->allow('index');

        parent::beforeFilter();
    }

    function index(){
        //この辺はもともとあった処理
        $this->Theme->hasMany['Item']['limit'] = 5;
        $this->Theme->hasMany['Item']['order'] = 'Item.id asc';
        $this->Theme->unbindModel(array('belongsTo'=>array('User')),false);
        $themes = $this->paginate('Theme');
        $tags =   $this->paginate('Tag');
        $this->set('themes',$themes);
        $this->set('tags',$tags);
    }
}

11行目は全部のコントローラに設定するものなので本当はapp_controllerにもたせ
たかった。でもなぜかapp_controllerにもたせることができなかったので、各コントローラで
設定。ここ参照。[CakePHP]AppController でコンポーネント設定するとsessionコンポーネントが使えない。
28行目でindexを許容している。ログイン状態によって同じ画面でも出す部品を
変えようとしているだけだから、基本的に全部許容なんだよね。
だから、$this->Auth->allow(*);にしてアクセスさせない、登録系や管理系の画面だけ
$this->Auth->deny();で設定すればいい気もする。


つづいて、実際にTwitterのユーザ名やTwitterのプロフィール画像を表示するmain/index.ctp。
こいつは抜粋。

        <?php if(!empty($auth['User']['id'])): ?>
        <div>
            <img src = "<?php echo $auth['User']['img_url'];?>"/>
            <br/>
            <?php echo $auth['User']['user_name'];?>
        </div>
        <?php endif;?>


1行目でcontroller側で取り出したauthのユーザ情報があるか確認。
無ければ、未ログインと判断し、3行目のプロフィール画像や5行目のユーザ名は表示しない。
この部品は全画面に共通的に使おうと思う。

あとは、必要に応じてlogin.ctpやlogout.ctp等のviewも作成する。

5.課題


この実装だと、ブラウザを閉じてもう一度アクセス、ログインしようとすると、
また、Twitterのアプリケーション認証画面に飛ばされて承認ボタンを押す必要
が出てくる。
それだと面倒くさいのでCookieを使用して自動ログインを実装してもいいかもしれない。

6.参考にしたURL


CakePHP で OAuth 認証を使ったログイン認証・保持や会員データの保持・更新をするコード
[プログラミング][PHP][CakePHP][twitter]CakePHPでOAuth
CakePHPでOAuthを使ったログインと自サービスのログインの両方に対応させてみました。


※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2012年9月19日水曜日

[CakePHP]AppControllerでコンポーネント設定するとsessionコンポーネントが使えない。

 AppControllerにて下記のようにAuthコンポーネントを設定したところ、

var $components = array('Auth');

以下のようなエラーが出た。
Notice (8): Undefined property: UsersController::$Session [APP\controllers\users_controller.php, line 31]
Fatal error:  Call to a member function write() on a non-object in C:\xampp\htdocs\rakuzon\app\controllers\users_controller.php 
on line 31

以下のサイトによると、どうやらバグっているらしく、AppControllerにコンポーネトを設定すると
Controllerにで設定されたコンポーネントとマージされないらしい。

CakePHPのController->__mergeVars()でcomponentsがマージされてない?


仕方がないので、AppControllerでAuthとsession両方設定しよう。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2012年9月14日金曜日

[API]楽天の商品URLから、商品コードを取得するには

うわー、これでいけそう。

http://blog.fkoji.com/2012/03130824.html

 プログラムで商品ページのHTMLを取得し、
javascript内に記述されている商品コードを取得。

それができたら、商品検索APIで商品の詳細情報を取得することができるね。

検索とかだけでAPI使うのはつまらないから、
これができれば面白いことができそう。

2012年9月13日木曜日

[Excel]Excelで負の時間を表示させるには

Excelで時間計算をしていて、引き算等で負の値になってしまうことがあるんだけど、
『負 の日付または時間は####と表示されます。』というエラーが出て表示できない。
 
 EXCELで負の日付や時間をエラーにならずに表示する方法はありますか?表示形...

ただ、上記の紹介を参考に
 ツール→オプション→計算方法→ブックオプション→「1904年から計算する」にチェックすると、
負の時間も表示されるようになった。

ただし、日付を入力しているセルは4年と1日プラスされた日付となってしまうので、
修正が必要。

2012年9月12日水曜日

Thinkpad x61sメモリ1Gから4Gへ増設した。

 以下のサイトを参考に増設を行った。

Thinkpad X61sのメモリ増設

ほとんどこのサイトと同じ内容なので書く必要もないかもしれないが、
記念として書いておく。

ctrl+alt+del→タスクマネージャで事前のメモリの大きさを確認。


まず電源ケーブルと電池をはずす。そしてねじ(以下の赤丸部分)をはずしてメモリのふたを外す。



開いたらこんな感じ。



今回購入したメモリはこれ。



Amazonだとこちらね。


メモリの端っこから囲い込んでいるアームみたいなもの(赤丸部分)を外側に引っ張ると、
かちっと持ち上がってはずれる。



新しいのを入れるときは斜めからそっと入れ込めばカチッとアームがつかんでくれる。


PCをさっそく起動してメモリの大きさ確認。





おお!3G越えですね!
動きもサクサクしてます!!








[CakePHP]キーワード検索時にpaginateするために

テキストのフォームからキーワードの検索して その結果をpaginateしようとしていたが、
一番最初の結果は正常に表示されるが、 違うページに遷移しようとすると
エラーとなってしまっていた。 理由と対策は以下で説明されている。

 1.2・検索結果をpaginateする方法

 なぜか検索ワード等のポストで受けたパラメータは次の遷移に
引き継がれないとのこと。
 めんどくさい。。。キーワード検索結果でペイジネイション使う人多いと思うのに、
あまり説明されていない。

リンクのURLの最後に引数という形での検索であれば、
どうやら勝手に次の遷移にパラメータが引き継がれるみたいだね。



[CakePHP]tmp配下の管理の仕方がわからない

app/tmp配下は動的に生成されるキャッシュなので サブバージョンで管理したくない。

 Subversion利用の際、tmpディレクトリを外す

にしたがって設定したが、eclipse上だとどうしても「変化しているマーク 」がでてしまう。

しかもeclipseの「svn:ignoreに追加」がなぜdisableになっているのかもわからない。
これが押せればいけそうなんだけどな。
とりあえず、コミットする前にtmp配下のキャッシュは削除するようにするしか
ないかな。面倒くさいな。
※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[CakePHP]validationエラーを入力元の画面に出す方法

例えば、TOPページにある検索ボックスの未入力エラーのメッセージなんかは、
違うページに遷移するのではなく、TOPページのその検索ボックスのすぐそばに
メッセージを出したい場合、以下のようにする。

//in the themes_controller.php
    public function search(){
        if(!empty($this->data)){
            $this->Theme->set($this->data);
            if($this->Theme->validates()){
                //入力エラーが無いときの処理
            }else{
                $this->loadModel('Tag');
                $this->Theme->unbindModel(array('belongsTo'=>array('User')),false);
                $themes = $this->paginate('Theme');
                $tags =   $this->paginate('Tag');
                $this->set('themes',$themes);
                $this->set('tags',$tags);
                $this->render('/main/index');
            }
        }
    }

ポイント
※コードの下から説明していくよ。

・14行目のrenderでトップページを指定するのがポイント。
 ここで、redirectを使用してはだめ。redirectではvalidationエラーの情報を遷移先に
 引き継いでくれないため、エラーメッセージが表示されない。

・そして、9~13行目でTOPページを表示していたコントローラの処理(main_controller.phpの
 indexアクション)をそのままコピペ。これがないと、トップページでDBアクセスして取得する
 情報などが表示できない。(エラーとなる。)
 main_controllerのそのアクションを呼び出せれば楽なんだけどなぁ。
 どうやらそういう時は、コンポーネントという仕組みで処理を共通化するのがよいみたい。
 今度試してみよう。

・8行目で不足しているmodelをloadする。mainコントローラでは$usesに'Tag'が入っていたが、
 themeコントローラには入っていないため、ここでloadする必要がある。
 くわしくは[CakePHP]modelを二つ以上使用する場合の注意点参照。


【2012/09/13追記】
このやり方のだめなところを発見した。
一度入力エラーなしで遷移に成功し、その検索結果の画面にも 検索ボックスがあるとする。
そうすると、その検索結果画面で未入力チェックにひっかかると、そのままの画面ではなく、
main/indexに遷移してしまう。
遷移元のURL$this->referer();で判断できるかと思ったが、これをやると
検索結果画面のペイジネイションが維持できなくなる。3ページ目でひっかかったのに、
なぜか1ページ目で表示されるなどする。
結局、難しいので、全てエラー画面に遷移させてしまうことにする。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip



[CakePHP]便利なdebugコマンド

デバッグのために、処理中の変数の中身を見たい場合など、
今まではprintやprint_rを使用していた。
ただ、これだと、HTMLを描画する処理に入ってからでないと
表示させることができない。
例えばコントローラ内での変数の中身などを確認することが
できない。

以下のサイトで知ったが、debugコマンドであれば、
どこの処理であっても無理矢理画面に出力することが
できる。

[CakePHP] デバッグに便利な関数 debug


こんな感じで配列でもブラウザにきれいに表示してくれる。


※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[CakePHP]modelを二つ以上使用する場合の注意点

始め、コントローラにある$usesで使用するモデルを以下みたいに
設定してあげればよいと考えた。

$uses = array('Theme','Tag','Item');

しかし、これだと以下の問題が発生した。

・scaffoldの画面がおかしくなる。
 「New Tag」ボタンを押してたのに、なぜか「New Theme」画面に遷移したり。

・コントローラの頭で定義しなければ有効にならず、
 アクションの中で変更することができない。
 
 scaffoldの画面がおかしくなるため、アクションの中で動的に設定される
 ものにすれば大丈夫だろうと思って、アクション内で以下のように設定した。

$this->uses = array('Theme','Tag','Item');

ところが、やはり有効になっておらず目的のモデルが使用できなかった。

なので、調べていたところ以下のサイトでloadModelを使えばよいというのが
わかった。

Modelを2個以上読み込む場合は、$usesを使わない方がスピードアップする


$this->loadModel('Tag');

これならば、scaffoldの画面も壊れず、アクション内でも使用できる。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[CakePHP]save以外でのvalidate利用時の注意点

以下のサイトで知ったが、$this->モデル名->save();のときは自動的にvalidateが走るが、
それ以外のときは、モデルにデータをセットしてあげる必要がある。

CakePHPの練習:validates()をしようとしたらうまく動かなくて復習

以下の3行目参照。

    public function search(){
        if(!empty($this->data)){
            $this->Theme->set($this->data);
            if($this->Theme->validates()){ 
そんなこと聞いてねーよ。と思ってたけど、cookbookに書いてあったのね。

4.1.6 コントローラ(Controller)からデータのバリデーションを実行する 


※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2012年9月11日火曜日

[CakePHP]hasManyで関連先の取得レコードのソートや数制限する方法

アソシエイションのhasManyは使えないものと思っていたけど
この発見によりかなり使えるものになりそう。

例えば、Theme hasMany(1対多) Itemの関係である時、
検索時に一つのThemeにつらなる複数のItemの数や
順序について、コントロールできないと思っていた。

だけど、下記のページを見て、hasManyの属性に値を設定すれば
コントロールできることがわかった。

How to limit number of records from associated model

下記の3行目、4行目にlimitとorderを設定した。
//in the themes_controller.php
    public function search(){
        $this->Theme->hasMany['Item']['limit'] = 2;
        $this->Theme->hasMany['Item']['order'] = 'Item.id asc';
        $this->Theme->unbindModel(array('belongsTo'=>array('User')),false);
        if(!empty($this->data)){
            $skey =    $this->data['Theme']['content'];
            $data = $this->paginate('Theme',array('Theme.content like'=>"%".$skey."%" ));
            $this->set('data',$data);
        }
    }
すると、下記のようにItemの数が二つに制限されidの降順となった。
//検索された配列をprint_rした結果
[0] => Array
        (
            [Theme] => Array
                (
                    [id] => 1
                    [content] => 秋に読みたい本
                    [explanation] => 芸術の秋にはどんな本が合うでしょうか
                    [user_id] => 2
                    [created] => 2012-09-01
                    [modified] => 2012-09-01
                )

            [Item] => Array
                (
                    [0] => Array
                        (
                            [id] => 14
                            [code] => 4844362046
                            [theme_id] => 1
                            [rank] => 11
                            [created] => 2012-09-08
                            [modified] => 2012-09-08
                        )

                    [1] => Array
                        (
                            [id] => 13
                            [code] => 4844362046
                            [theme_id] => 1
                            [rank] => 10
                            [created] => 2012-09-08
                            [modified] => 2012-09-08
                        )

                )


hasManyとbelongsToの使い分けだが、まとめると現状以下のように理解している。

・hasMany
 1対多関係において、”1”側の項目で検索をする際、
 それにつらなる”多”側の取得結果にソートや数制限をしたい場合。

・belongsTo
 1対多関係において、”多”側の項目で検索をしたい場合。

hasManyとbelongsToは1対多の関係をどちら側から見るかの違いしかなく、
使いようで大体同じことができるんでしょ う、と最初思っていたが、
特徴がそれぞれあって、使い分ける必要があり、
わかれば便利に使えるということがわかってきた。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[CakePHP]paginateにunbindModel/bindModelを使用する方法

なぜかpginateだとunbindModelが効いていなかったのだけど、
下記のURLによると、第二引数にfalseを指定すればよいとのこと。
 defaultのtrueだと、1度Model操作を行っただけで、unbindModelが
の効果が外れてしまう。paginateの場合は、本番の検索の前に
カウントをしているのでそれで外れてしまうそうだ。
なので、第二引数にfalseを入れれば恒久的に有効になるため、
paginateでもunbindModelが有効になる。

CakePHPのbindModelとunbindModel

 しかし一つ疑問なのは、恒久的にunbindModelされたままで
問題起きないのかな。model側でアソシエイション設定する意味が
ない気がする。どこかで、やはりアソシエイションが復活するのかな。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[SVN]不要となった作業コピーの削除

Subversionの作業コピーって不要になったらどうやって消せばいいのかな
と思ってたんですけど、単純にWindowsのExploreから削除してしまえば
いいみたいですね。
下記の記載を信用しようと思います。

作業コピーの削除

[CakePHP]HABTMでの中間テーブルを使用する際の注意点

以下のサイトで示唆されているとおり、HABTMのアソシエイションは
中間的なモデルを作成し、そのテーブルから二つのテーブルへそれぞれ
belongsToを張り、モデル操作を行う際はこの中間モデルを通じて操作すれば
きちんとjoinをしたSQLが発行される。

SQLから考える Model::findの使い方

素晴らしいポストなのだが、一点疑問。
なぜ中間的なモデルのクラス名を「GroupUserRelation」としているのだろう。
下記で説明されている命名規則にしたがえば、「GroupsUser」でよいのではないか。

3.7.6.6 hasMany through (The Join Model)

「GroupsUser」とすれば、下記の2行目は不要なのではないか。
class GroupUserRelation extends AppModel {
   public $useTable = 'groups_users';
   public $belongsTo = array(・・・・ 

また、このモデルを使用するコントローラにて下記を設定しないと
「Warning (512): SQL Error: 1054: Unknown column」が発生する。
public $uses ='GroupsUser';

もしかしたら、groups_users_controller.phpだったら必要ないかもしれない。

※CakePHPのバージョンはcakephp-1.3.15-9-gacd25c3
【2012/09/11追記】
あるコントローラで、$usesを使って他のコントローラを指定すると、
scaffoldの動きがおかしくなるようです。
上記の例で言うと、例えばGroupsコントローラに
public $uses ='GroupsUser';

と設定すると、scaffolodにて
「New Group」ボタンを押すと、「New Groups User」画面に
遷移してしまいます。

また、usesにもともとのモデルであるGroupも指定すればよいのかと思い、
public $uses =array('Groups','GroupsUser');
と設定したところ「New Group」ボタンを押すと、「New Groups User」画面に
遷移しましたが、他のモデルを操作するボタンが消えてしまいました。
そこで、Groupモデルがアソシエイションを持つモデルを全て$usesで
指定したのですが、ボタンは現れませんでした。

結論としては、scaffoldの画面を維持しておきたいのなら、
モデルを持つ$コントローラのusesをいじるのではなく、
GroupsUserコントローラを使用して処理を記述するべき。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[CakePHP]Ctpファイルを EclipseでPHPファイルとして表示する

Ctpファイルを EclipseでPHPファイルとして表示する(CakePHP)

このページを参考にしてうまくできた。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2012年9月8日土曜日

[CakePHP]paginateで引数を検索条件にする

find等と同じように下記のようなURLで指定された引数を
paginateの検索条件にするには

http://localhost/themes/view/2

アクションの方で$this->paginate()の第二引数として、
検索条件を指定してあげればよい。
(下記16行目)
<?php
class ThemesController extends AppController {

 var $name = 'Themes';
 public $uses = array('item','theme');
 var $paginate = array(
         'item' => array ( 
   'limit' => 2,
   'order' => array(
    'item.id' => 'asc'
   )
  )
 );

 function view($param){
  $data = $this->paginate('item',array('theme.id' => $param));
  $this->set('data',$data);
 }

}
※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2012年9月6日木曜日

[CakePHP]sccaffoldを使いたいけどindex、view、edit、addなどのアクションの自作もしたい

CakePHPでsccaffoldを管理画面的に使いたいのだけど、
indexやviewやeditやaddなどのアクション名をユーザ向けのアプリで
カスタマイズして使いたい場合、どうするか。

下記を参考にした。
普通のアクションとscaffoldを混在させる
3.12.1 Creating a simple admin interface with scaffolding

まず、app/core/config/core.phpに下記の一行を追加。

Configure::write('Routing.prefixes', array('admin'));

そして、controllerに下記4行目を追加。 するとindex()がカスタマイズ可能となる。
<?php
class ThemesController extends AppController {
        var $name = 'Themes';
 var $scaffold = 'admin';
 public $autoRender = false;
 function index(){
  print "hello world";
 }
}


すると、 index()は自由にカスタマイズできるのに、
sccaffoldの画面は下記のURLにアクセスすることで、
今までどおりの動きをする。

http://【ドキュメントルート】/admin/controller/index

ちなみに「var $scaffold = 'admin';」はapp_controller.phpに
入れれば全てのcontrollerに適用される。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2012年9月5日水曜日

[CakePHP]scaffoldでidでなく名前を表示させるには

CakePHPで二つのmodelを連携(hasManyやbelongsTo等で)し、
scaffoldにて画面を自動生成した場合、外部キーにて結合された側の
情報は、デフォルトだと下のようにIDが表示される。
 この例はThemeモデルのbelongsToにUsersモデルを設定している状態で、
http://【ドキュメントルート】/themes にアクセスした場合。




これだとぱっと見、どのユーザかわからないので、名前を表示させる。
そのためには、user.phpに下記のように$displayFieldで指定してやる。
<?php
class User extends AppModel {
 var $name = 'User';
 var $displayField = 'username';

そうすると下記のように名前が表示されるようになる。



※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip