開発メモ,主に補足by子連れ親父プログラマー

2011-09-18

CakePHP1.3で作る会員管理システム(16) ログイン認証

ログイン認証

会員がログインできるようにします。 最初にログインする画面とログインした後の会員用のメニュー画面のテンプレを作っておきます。 それぞれこんな感じで適当に。 会員ログイン画面 login.ctp のファイル名で。

<div class="members form">
<?php echo $this->Form->create('Member', array('action' => 'login'));?>

    <h2><?php __('ログイン'); ?></h2>
    <?php
        echo $this->Form->input('email',array('label' => array('text'=>'メールアドレス')));
        echo $this->Form->input('password',array('label' => array('text'=>'パスワード')));
    ?>
    <?php echo $this->Form->end(__('ログイン', true));?>
</div>
<div class="actions">
    <ul>
        <li><?php echo $this->Html->link(__('新規登録はこちら', true), array('action'=>'add')); ?></li>
    </ul>
</div>

会員メニュー画面 menu.ctp のファイル名で。

<div class="members index">
    <h2><?php __('会員メニュー'); ?></h2>
</div>
<div class="actions">
    <ul>
        <li><?php echo $this->Html->link(__('会員情報の更新', true), array('action'=>'edit')); ?></li>
        <li><?php echo $this->Html->link(__('退会手続き', true), array('action'=>'leave')); ?> </li>
        <li><?php echo $this->Html->link(__('ログアウト', true), array('action'=>'logout')); ?> </li>
    </ul>
</div>

ログアウトの完了画面も作っておきます。logout.ctp のファイル名で。

<div class="members index">
    <h2><?php __('ログアウトしました'); ?></h2>
</div>

簡単なやつでいいです。 で、コントローラ members_controller.php にログイン、ログアウトそれぞれのアクションを入れます。 ログインがこんな感じで。

    function login() {
        $this->Member->recursive = 0;
        $this->Session->destroy();
        if (!empty($this->data)) {
            if ($this->data['Member']['email']) {
                $member = $this->Member->findbyEmail($this->data['Member']['email'], array('id','password'));
                if ($member['Member']['password'] === $this->data['Member']['password']) {
                    $this->Session->write('Member.id', $member['Member']['id']);
                    $this->redirect(array('action'=>'menu'));
                } else {
                    $this->Session->setFlash(__('ログインIDまたはパスワードが違います', true));
                }
            }
        }
    }

入力された email の値で、 findbyEmail を使ってクエリーを投げます。 実行されたクエリーはこのようなものです。

SELECT `Member`.`id`, `Member`.`password` FROM `members` AS `Member` LEFT JOIN `types` AS `Type` ON (`Member`.`type_id` = `Type`.`id`) WHERE `Member`.`email` = 'your@email' LIMIT 1

結果のパスワードと入力されたパスワードの値を比較します。 認証OKだったら、 ‘Member.id’ というセッションに結果の id を入れます。 メニュー画面へリダイレクトします。

ログアウトは、

function logout() {
    $this->Session->destroy();
}
ただセッションを破棄するだけです。

メニューは、

function menu() {
    if (!$this->Session->check('Member.id')) {
        $this->redirect(array('action'=>'login'));
    }
}
Member.id というセッションがあるかどうかチェックします。

なければログイン画面にリダイレクトします。 function edit() にも、以下のチェックを入れておきます。

    function edit($id = null) {
        if (!$this->Session->check('Member.id')) {
            $this->redirect(array('action'=>'login'));
        }

        if (!$id && empty($this->data)) {
            $this->Session->setFlash(__('Invalid member', true));

2011-09-16

CakePHP1.3で作る会員管理システム(15) メール送信

メール送信

ここで会員登録時のメール送信を作ります。 Emailコンポーネントの使い方は本家サイトのドキュメントの通りです。

/app 直下の app_controller.php を開いて、

class AppController extends Controller {
}
となっている中に、
    var $components = array('Session', 'Email');
と入れます。

そして、以下のようなファンクションを入れます。

function _sendEmail($template, $subject, $to, $attachments, $from, $replyto, $fromName, $sendAs, $bcc) {
    if (!$from) {$from = 'your@email';}
    if (!$replyto) {$replyto = 'your@email';}
    if ($fromName) {$from = $fromName . " <$from>";}
    $this->Email->to = $to;
    $this->Email->bcc = $bcc;
    $this->Email->subject = $subject;
    $this->Email->replyTo = $replyto;
    $this->Email->from = $from;
    $this->Email->template = $template;
    if ($attachments) {$this->Email->attachments = $attachments;}
    $this->Email->sendAs = $sendAs;
    $this->Email->send();
}

smtp で送信する場合は、$this->Email->send() の部分を以下に書き換えます。

        $this->Email->smtpOptions = array(
            'port'=>'587',
            'timeout'=>'30',
            'host' => 'your.host.name',
            'username'=>'username',
            'password'=>'password'
        );
        $this->Email->delivery = 'smtp';
        $this->Email->send();
        $this->set('smtp-errors', $this->Email->smtpError);

gmailで送る場合は、smtpOptions のところを以下のようにします。

        $this->Email->smtpOptions = array(
            'port'=>'465',
            'timeout'=>'30',
            'host' => 'ssl://smtp.gmail.com',
            'username'=>'your@email',
            'password'=>'yourpassword'
        );

メールの文字コードは、デフォルトだとUTF-8で送られます。 charsetは以下で設定できますが、ISO-2022-JP にすると subject が文字化けするようです。

$this->Email->charset = "ISO-2022-JP";

from のメールアドレスは固定だし、text形式のメールしか送らない、というのであれば、 最初に設定した app_controller.php で、

    var $components = array(
        'Session', 
        'Email' => array(
            'from' => 'your@email',
            'sendAs' => 'text',
        ),
    );
というように書くこともできます。

こうすると、function _sendEmail() の中では指定する必要がありません。

//        $this->Email->sendAs = $sendAs;

つまり、var $components の方にどんどん指定していってしまうと、function _sendEmail() の方に書くことがなくなり、どんどん減っていきます。
自分の使いやすいと思うあたりに調節すればよいと思います。

メール文面のテンプレートは、まずレイアウトを

  • /app/views/layouts/email/html/default.ctp
  • /app/views/layouts/email/text/default.ctp
に入れます。

テキストメールの場合は、とりあえず、default.ctp に

<?php echo $content_for_layout; ?>
とだけ書いて保存します。

文面は

  • /app/elements/email/html/default.ctp
  • /app/elements/email/text/default.ctp
に入れます。

文面は普通に、確認画面の view.ctp と同じように作ればいいと思います。

以下の会員登録がありました。
-----------------------------------------------------
メールアドレス: <?php echo $member['Member']['email'] ."\n"; ?>
会員種別:<?php echo $types[$member['Member']['type_id']] ."\n"; ?>
誕生日:<?php echo $member['Member']['birthday']['year']; ?>年<?php echo $member['Member']['birthday']['month']; ?>月<?php echo $member['Member']['birthday']['day']; ?>日
好きな物:
<?php
if (!empty($member['Favorite'])) {
    foreach ($member['Favorite']['Favorite'] as $favorite) {
        echo "\t".$favorites[$favorite['id']]."\n";
    }
}
?>

こんな感じです。
こちらの elements の中の方のファイル名を上記 $this->Email->template に指定します。

ここではとりあえず、

/app/elements/email/text/member_add.ctp
としました。

で、コントローラ members_cntroller.php のアクション add のsaveしてる部分の下に

            } else {
                $this->Member->create();
                if ($this->Member->save($this->data)) {
                    $this->set('member', $this->data);
                    $attachments = array();
                    $this->_sendEmail('member_add', 'ユーザー登録のお知らせ', 'your@email', $attachments,'','','','text',array());
                    $this->Session->setFlash(__('The member has been saved', true));
                    $this->redirect(array('action' => 'complete'));
                } else {
                    $this->Session->setFlash(__('The member could not be saved. Please, try again.', true));
                }
            }
を入れます。
メールを受信して確認します。

CakePHP1.3で作る会員管理システム(14) checkbox の validate

checkbox の validate

動作がおかしい「好きな物」のチェックボックスを直していきます。

「好きな物」は Favorite モデルですが、あくまでここでは Member を登録している訳ですから Member モデルとして処理します。
今 add.ctp でこうなってる部分を、

        echo $this->Form->input('Favorite', array('multiple'=>'checkbox', 'label' => '好きな物'));
こうします。

        echo $this->Form->input('favorites', array('multiple'=>'checkbox', 'label' => '好きな物'));

するとなんと!

<input type="checkbox" id="MemberFavorites1" value="1" name="data[Member][favorites][]">

こういうソースが生成されます。
これはモデル Member です。

ということで Member モデルの方にルールを書きます。
/app/models/member.php の $validate が書かれている部分の 'birthday' の下に以下を追加します。

        'favorites'=>array(
            'custom' => array(
                'rule'=>array('multiple', array('min' => 1, 'max' => 3)),
                'message' => '好きな物は一つ以上選択してください'
            ),
        ),

次にコントローラに移動して、確認画面の時に validate するように members_controller.php を書き換えます。

if (!empty($this->data)) {
から始まる if 文の最初に、
$this->data['Favorite']['Favorite'] = $this->data['Member']['favorites'];
を入れます。

これで favorites を本来の Favorite に入れ直しています。 で、

if ($this->data['Member']['mode'] == 'confirm') {
から始まる mode="confirm" の部分の if 文を以下のように変えます。

if ($this->data['Member']['mode'] == 'confirm') {
    $this->Member->set($this->data);
    if ($this->Member->validates($this->data)) {
        $this->set('member', $this->data);
        $this->render('confirm');
    } else {
        $this->Session->setFlash(__('エラーがあります', true));
    }
} elseif ($this->data['Member']['mode'] == 'back') {//

これで正しくエラーチェックが動くようになりました。

あとまだ、好きな物チェックボックスの確認画面がおかしいので、
今 confirm.ctp の

    echo $this->Form->input('Favorite', array('type'=>'hidden'));
こうなっているところを、
    foreach ($member['Member']['favorites'] as $k => $v){
        echo $this->Form->input("Member.favorites.$k", array('type'=>'hidden'));
    }
このようにします。

これで生成されるHTMLの hidden の部分が、

<input id="MemberFavorites0" type="hidden" value="1" name="data[Member][favorites][0]">
<input id="MemberFavorites1" type="hidden" value="3" name="data[Member][favorites][1]">
のように出力されるようになります。

戻るボタンクリックでも値が保持されています。
今まで保存されていなかった members_favorites テーブル にもデータが保存されています。

CakePHP1.3で作る会員管理システム(13) checkbox、 戻るボタン、年月セレクト、完了画面

checkbox、 戻るボタン、年月セレクト、完了画面

次に、add.ctp 入力フォームの、Favorite(好きな物)が multiple セレクトになってるのを直します。
日本では複数選択にセレクトなんてほとんど使われないですよね。
こうなっているのを、

        echo $this->Form->input('Favorite');
こうします。

        echo $this->Form->input('Favorite', array('multiple'=>'checkbox', 'label' => '好きな物'));

これでとりあえずチェックボックスになるんですが、
どうも一つのチェックで一行になっているので収まりが悪いです。
横に並べたいですね。
生成されたHTMLを確認すると、以下のようになっています。

<div class="input select">
    <label for="FavoriteFavorite">好きな物</label>
    <input type="hidden" id="FavoriteFavorite" value="" name="data[Favorite][Favorite]">
    <div class="checkbox">
        <input type="checkbox" id="FavoriteFavorite1" value="1" name="data[Favorite][Favorite][]">
        <label for="FavoriteFavorite1">apple</label>
    </div>
    <div class="checkbox">
        <input type="checkbox" id="FavoriteFavorite2" value="2" name="data[Favorite][Favorite][]">
        <label for="FavoriteFavorite2">orange</label>
    </div>
    <div class="checkbox">
        <input type="checkbox" id="FavoriteFavorite3" value="3" name="data[Favorite][Favorite][]">
        <label for="FavoriteFavorite3">grape</label>
    </div>
</div>

チェックボックス一つにつき一つの div で囲まれています。
css を書くのもしんどいので、jQuery で処理することにします。
jQuery をダウロードして解凍しておいてください。

/app/webroot/js 以下に jquery.js としてコピーしておきます。
で、さらに /app/webroot/js 以下に、新規に util.js という名前でファイルを作ります。

中身は、こんな感じで。

$(document).ready(function() {
    $('div.checkbox').css('display','inline');
    $(':checkbox').css('float','none');
});

周りを囲んでいる div を inline 表示に変えて、左に float しているチェックボックスの float を解除しているだけです。

で、 add.ctp の一番上に、

<?php echo $javascript->link(array('jquery','util'), false); ?>
を追加します。

さらに、members_controller.php の 上の方に $helpers を追加します。

<?php
class MembersController extends AppController {

    var $name = 'Members';
    var $helpers = array('Javascript');

    function index() {
        $this->Member->recursive = 0;
        $this->set('members', $this->paginate());
    }

ブラウザで確認してみると、チェックボックスが横に並んでいます。
これはあくまでその場しのぎの付け焼刃ですので、本来はきちんとデザイナーさんに css を書いてもらいましょう。

ついでなので、さきほどのJavascriptの util.js の $(document).ready(function() {});の中に、以下を追加しておきます。

    $('#back').click(function(){
        $('#MemberMode').val('back');
        $("#MemberConfirm").submit();
    });

解説:
1行目:id="back" のボタンをクリックしたら
2行目:id="MemberMode" の valueを "back" にして(hidden で追加した mode の value です)
3行目:id="MemberConfirm" のフォームをサブミットする(確認画面の form です)

confirm.ctp の一行目に以下を追加します。

<?php echo $javascript->link(array('jquery','util'), false); ?>

members_controller.php の前回追加した if ~ else の中に以下の elseif を追加します。{} の中は空でいいです。

            if ($this->data['Member']['mode'] == 'confirm') {
                //・・・
            } elseif ($this->data['Member']['mode'] == 'back') {

            } else {
                //・・・
            }

これで、確認画面から入力画面への「戻る」ボタンが動くようになります。
いろいろエラーが出ているかもしれませんが、たいした問題ではないので気にしないで進みます。

あとは、誕生日の入力フォームが洋風で気に入らないので、変更します。

今こうなってるのを、

        echo $form->input('birthday');
以下に変更します。

        echo $this->Form->input('birthday',array('dateFormat'=>'YMD','maxYear'=>date('Y'),'minYear'=>date('Y')-100,'monthNames'=>false, 'label' => array('text'=>'誕生日')));

最後、一応公開側は完了画面を出すことにするので、「登録されました」みたいな適当なビューを complete.ctp という名前で作っておきます。

<div class="members form">
    <h2><?php __('会員登録'); ?></h2>

    <p>登録されました。</p>
</div>

そして、members_controller.php の add アクションの save の後の、

                    $this->redirect(array('action' => 'index'));
となっているのを、
                    $this->redirect(array('action' => 'complete'));
に変更し、さらに、
function complete() {
}
を追加しておきます。
ブラウザで完了画面が表示されることを確認します。

2011-09-15

CakePHP1.3で作る会員管理システム(12) 確認画面その2

確認画面その2

ではビューについて、順番に整理していきます。

confirm.ctp から管理メニュー部分

<div class="actions">
....
</div>
で囲まれたところをざくっと削除します。いらないので。

Birthday(誕生日)のところ、

<?php echo $member['Member']['birthday']; ?>
こうなっているのを、以下に変更します。

<?php echo $member['Member']['birthday']['year'] .'/' . $member['Member']['birthday']['month'] .'/'. $member['Member']['birthday']['day']; ?>

同じく “hidden” の Birthday 部分、

echo $this->Form->input('birthday', array('type'=>'hidden'));
これを以下に変更します。

echo $this->Form->input('birthday.year', array('type'=>'hidden'));
echo $this->Form->input('birthday.month', array('type'=>'hidden'));
echo $this->Form->input('birthday.day', array('type'=>'hidden'));

Type(会員種別) のところ、

<?php echo $this->Html->link($member['Type']['name'], array('controller' => 'types', 'action' => 'view', $member['Type']['id'])); ?>
こうなっているのを、以下に変更します。

<?php echo $types[$member['Member']['type_id']]; ?>

次に「好きな物」に関して、

<div class="related">
....
</div>
ではさまれたの関連テーブル部分をごっそり削除し、その代わりにメインのテーブルの Img2 の表示部分(tr で囲まれた一角)の下に以下を追加します。

  <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('好きな物'); ?></dt>
  <dd<?php if ($i++ % 2 == 0) echo $class;?>>
   <?php if (!empty($member['Favorite']['Favorite'])):?>
   <ul>
    <?php foreach ($member['Favorite']['Favorite'] as $favorite): ?>
     <li><?php echo $favorites[$favorite['id']];?></li>
    <?php endforeach; ?>
   </ul>
   <?php endif; ?>
    
  </dd>

members_controller.php に戻って、 function add() の、一番下の

  $types = $this->Member->Type->find('list');
  $favorites = $this->Member->Favorite->find('list');
  $this->set(compact('types', 'favorites'));
3行を、一番上に持ってきます。

こうなります。

 function add() {
  $types = $this->Member->Type->find('list');
  $favorites = $this->Member->Favorite->find('list');
  $this->set(compact('types', 'favorites'));
  
  if (!empty($this->data)) {
   if ($this->data['Member']['mode'] == 'confirm') {
    $this->Member->set($this->data);

ブラウザで確認します。
ここまでで、Type(会員種別)、Birthday(誕生日)、好きな物 が表示されるようになったはずです。
ボタン「送信」をクリックで、一覧画面に戻り、

The Member has been saved
と表示されればOKです。membersテーブルのデータは保存されています。

2011-09-14

CakePHP1.3で作る会員管理システム(11) 確認画面その1

確認画面その1

まず、確認画面のビューを作ります。 bake で自動生成した、 view.ctp がそのまま使えそうなので、 view.ctp を開いてから「名前を付けて保存」で confirm.ctp を作成します。

今回確認画面はhidden方式でいくので、

<div class="members view">
となっている所の、直下にフォームを作ります。

<?php echo $this->Form->create('Member', array('id'=>'MemberConfirm')); ?>

こんな感じです。 ただの

$this->Form->create('Member');
だけでもいいっちゃいいんですが、そうすると、以下のような add の画面と全く同じ id でフォームタグが生成されてしまうので、
<form id="MemberAddForm" accept-charset="utf-8" method="post" action="/~myname/cake/members/add">
これはなんとなく気持ち悪いので、
array('id'=>'MemberConfirm')
を指定して、確認画面用のフォームの id を指定しておきます。

で、その下に add.ctp のフォーム部分、

<?php
    echo $this->Form->input('email');
    echo $this->Form->input('password');
    echo $this->Form->input('type_id');
    echo $this->Form->input('birthday');
    echo $this->Form->input('img1');
    echo $this->Form->input('img2');
    echo $this->Form->input('Favorite');
?>
をごっそりそのままコピーして貼り付けます。

hidden にするので

array('type'=>'hidden')
を一個一個全部に指定して、一応全体を div で囲んでおきます。 こんな感じになりました。

<div class="members view">
<?php echo $this->Form->create('Member', array('id'=>'MemberConfirm'));?>
<div style="display:none;">
<?php
    echo $this->Form->input('email', array('type'=>'hidden'));
    echo $this->Form->input('password', array('type'=>'hidden'));
    echo $this->Form->input('type_id', array('type'=>'hidden'));
    echo $this->Form->input('birthday', array('type'=>'hidden'));
    echo $this->Form->input('img1', array('type'=>'hidden'));
    echo $this->Form->input('img2', array('type'=>'hidden'));
    echo $this->Form->input('Favorite', array('type'=>'hidden'));
?>
</div>

<h2><?php  __('Member');?></h2>

ただ、アクションとしては同じ add で処理するので、なにかで識別するようにしないと「今は確認画面だ!」というのが分かりません。

ということで、 mode = ‘confirm’ というのを作り出して、これを add.ctp (フォーム入力画面)の方に、 hidden で入れておきます。

add.ctp 側

<div class="members form">
<?php echo $this->Form->create('Member');?>
<div style="display:none;">
    <?php echo $this->Form->hidden('mode', array('value'=>'confirm')); ?>
</div>

    <h2>
        <?php __('Add Member'); ?></h2>

confirm.ctp 側はvalueなしで。以下を追加。

    echo $this->Form->input('mode', array('type'=>'hidden','value'=>''));

これで、

  • add → confirm へは mode = ‘confirm’ で、
  • confirm → complete へは mode = ” で
行く事になります。 最後に、ボタンを追加しておきます。

<div class="submit">
    <?php echo $this->Form->button('戻る', array('id'=>'back')); ?>
</div>
<?php echo $this->Form->end('送信'); ?>

これはどっか適当に下の方に入れておきます。 一応ただの id=”back” ボタンをつけておきます。

あと、view.ctp との違いで、そっちには id がありますが、こっちにはまだ id は決まっていない、存在していないので、以下の id に関する表示部分を削除しておきます。

        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Id'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $member['Member']['id']; ?>
             
        </dd>

同様に、 Created と Modified も要らないので削除しておきます。

        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Created'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $member['Member']['created']; ?>
             
        </dd>
        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Modified'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $member['Member']['modified']; ?>
             
        </dd>

ここでもうそろそろ画面を見たいのでコントローラをいじります。

/app/controllers/members_controller.php を開いて、function add() の以下の部分、

        if (!empty($this->data)) {
            $this->Member->create();
            if ($this->Member->save($this->data)) {
                $this->Session->setFlash(__('The member has been saved', true));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The member could not be saved. Please, try again.', true));
            }
        }
この1行目からのif文の中にさらにもう一個if文を入れて、もとからあった部分はelseのあとにつっこみます。

構造としてはこんな感じ。

if (!empty($this->data)) {
    if () {
     
    } else {
    // もとからあった部分
    }
}

で、その新しいif文を以下のようにします。

    function add() {
        if (!empty($this->data)) {
            if ($this->data['Member']['mode'] == 'confirm') {
                $this->Member->set($this->data);
                $this->set('member', $this->data);
                $this->render('confirm');
            } else {
                $this->Member->create();
                if ($this->Member->save($this->data)) {
                    $this->Session->setFlash(__('The member has been saved', true));
                    $this->redirect(array('action' => 'index'));
                } else {
                    $this->Session->setFlash(__('The member could not be saved. Please, try again.', true));
                }
            }
        }

これで確認画面が出るはずなのでブラウザで /members/add を開いて適当に入力して進むと・・・
ぼこぼこにエラーが出てますが、入力したEmailの値とかは表示されてるはずです。
エラーは配列とかの問題だけなので今は気にしなくていいです。

2011-09-13

CakePHP1.3で作る会員管理システム(10) レイアウトその2

レイアウトその2

続いて、一般の人が登録したり、ログインして編集する画面(公開側)のレイアウトを作ります。 通常公開側はデザイナーさんがカッチリデザインしてくるものですから、どっちみち最終的にそのデザインに合わせて調整することになると思います。 ということで、ここでは admin 側をそのまま流用してファイルだけ作っておくことにします。

まず、先ほど作った

/app/views/layouts/admin.ctp
をそのまま同じ場所にコピーして、member.ctp にリネームします。
/app/views/layouts/member.ctp

そして左メニューの部分を以下のように編集します。

            <div class="actions">
                <h3><?php __('メニュー'); ?></h3>
                <ul>
                    <li><?php echo $this->Html->link(__('登録情報変更', true), array('controller'=>'members','action' => 'edit'));?></li>
                    <li><?php echo $this->Html->link(__('退会', true), array('controller' => 'members', 'action' => 'delete')); ?> </li>
                    <li><?php echo $this->Html->link(__('ログアウト', true), array('controller' => 'users', 'action' => 'logout')); ?> </li>
                </ul>
            </div>

member.ctp のヘッダー部の以下の部分、

<head>
    <?php echo $this->Html->charset(); ?>
    <title>
        <?php echo $title_for_layout; ?>
    </title>
    <?php
        echo $this->Html->meta('icon');

        echo $this->Html->css('cake.generic');

        echo $scripts_for_layout;
    ?>
</head>
cssを指定しているところを以下のように書き換えます。

        echo $this->Html->css('member');

これで出力されるソースは

<link href="/~myname/cake/css/member.css" type="text/css" rel="stylesheet">
となります。

css の置き場所は /app/webroot/css/ ですので、

/app/webroot/css/cake.generic.css
をそのまま同じ場所にコピーして、member.css にリネームします。

/app/webroot/css/memmber.css

最後に、admin の場合と、member の場合で、自動でレイアウトが切り替わるように設定します。

まず、前回 member_controller.php に入れた、

    function beforeFilter() {
        $this->layout = 'admin';
    }
を削除しておきます。

そして、/app 直下に app_controller.php を配置します。

cd Sites/cake
cp cake/libs/controller/app_controller.php app/

今コピーした、/app/app_controller.php に以下を追加します。

<?php
/**
 * Application level Controller
 *
 * This file is application-wide controller file. You can put all
 * application-wide controller-related methods here.
 *
 * PHP versions 4 and 5
 *
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @package       cake
 * @subpackage    cake.cake.libs.controller
 * @since         CakePHP(tm) v 0.2.9
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 */

/**
 * This is a placeholder class.
 * Create the same file in app/app_controller.php
 *
 * Add your application-wide methods in the class below, your controllers
 * will inherit them.
 *
 * @package       cake
 * @subpackage    cake.cake.libs.controller
 * @link http://book.cakephp.org/view/957/The-App-Controller
 */
class AppController extends Controller {

    function beforeFilter(){
        if (!empty($this->params['admin'])) {
            $this->layout = "admin";
        }else{
            $this->layout = "member";
        }
    }

}

これで admin の時と admin でない時でレイアウトが切り替わります。

2011-09-11

CakePHP1.3で作る会員管理システム(9) レイアウトその1

レイアウトその1

で、ついでなので管理画面っぽく、レフトメニューがあって作業を選べるようなページレイアウトに変更します。

/cake/libs/view/layouts/default.ctp がもともとのレイアウトファイルなので、これを開いて参考にしながら、自分の admin 用のレイアウト admin.ctp を作ります。 それは /app/views/layouts に保存します。

/app/views/layouts/admin.ctp

今まで add.ctp などをいじってきて気がついていると思いますが、フォームなどのメイン部分の下に、左メニュー部分が Actions として入っています。

つまり全体としては、こんな構造になっていて、

<div id="header"></div>
<div id="content">
    <div class="members form"></div>
    <div class="actions"></div>
</div>

各ページのテンプレート($content_for_layout の中に入る部分)が、
    <div class="members form"></div>
    <div class="actions"></div>
になっている、という訳です。

ということで、このactionsを分離して、レイアウトの方に入れてしまいます。

add.ctp から

<div class="actions">
 <h3><?php __('Actions'); ?></h3>
 <ul>

  <li><?php echo $this->Html->link(__('List Members', true), array('action' => 'index'));?></li>
  <li><?php echo $this->Html->link(__('List Types', true), array('controller' => 'types', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Type', true), array('controller' => 'types', 'action' => 'add')); ?> </li>
  <li><?php echo $this->Html->link(__('List Favorites', true), array('controller' => 'favorites', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Favorite', true), array('controller' => 'favorites', 'action' => 'add')); ?> </li>
 </ul>
</div>
この部分をカットし、admin.ctp (もともとはdefault.ctp)の $content_for_layout の下ににコピーします。

<?php
/**
 *
 * PHP versions 4 and 5
 *
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @package       cake
 * @subpackage    cake.cake.libs.view.templates.layouts
 * @since         CakePHP(tm) v 0.10.0.1076
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 */
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <?php echo $this->Html->charset(); ?>
 <title>
  <?php __('CakePHP: the rapid development php framework:'); ?>
  <?php echo $title_for_layout; ?>
 </title>
 <?php
  echo $this->Html->meta('icon');

  echo $this->Html->css('cake.generic');

  echo $scripts_for_layout;
 ?>
</head>
<body>
 <div id="container">
  <div id="header">
   <h1><?php echo $this->Html->link(__('CakePHP: the rapid development php framework', true), 'http://cakephp.org'); ?></h1>
  </div>
  <div id="content">

   <?php echo $this->Session->flash(); ?>

   <?php echo $content_for_layout; ?>

   <div class="actions">
    <h3><?php __('Actions'); ?></h3>
    <ul>
     <li><?php echo $this->Html->link(__('List Members', true), array('action' => 'index'));?></li>
     <li><?php echo $this->Html->link(__('List Types', true), array('controller' => 'types', 'action' => 'index')); ?> </li>
     <li><?php echo $this->Html->link(__('New Type', true), array('controller' => 'types', 'action' => 'add')); ?> </li>
     <li><?php echo $this->Html->link(__('List Favorites', true), array('controller' => 'favorites', 'action' => 'index')); ?> </li>
     <li><?php echo $this->Html->link(__('New Favorite', true), array('controller' => 'favorites', 'action' => 'add')); ?> </li>
    </ul>
   </div>
  </div>
  <div id="footer">
   <?php echo $this->Html->link(
     $this->Html->image('cake.power.gif', array('alt'=> __('CakePHP: the rapid development php framework', true), 'border' => '0')),
     'http://www.cakephp.org/',
     array('target' => '_blank', 'escape' => false)
    );
   ?>
  </div>
 </div>
 <?php echo $this->element('sql_dump'); ?>
</body>
</html>

こんな感じになります。

そして、<div class="actions"> の中を書き換えます。

   <div class="actions">
    <h3><?php __('メニュー'); ?></h3>
    <ul>
     <li><?php echo $this->Html->link(__('会員管理', true), array('controller'=>'members','action' => 'index'));?></li>
     <li><?php echo $this->Html->link(__('担当者アカウント管理', true), array('controller' => 'users', 'action' => 'index')); ?> </li>
     <li><?php echo $this->Html->link(__('ログアウト', true), array('controller' => 'users', 'action' => 'logout')); ?> </li>
    </ul>
   </div>

で、一旦このレイアウトを確認します。 /app/controllers/members_controller.php を開いて、 var $name = 'Members'; の下に、

class MembersController extends AppController {

    var $name = 'Members';

    function beforeFilter() {
        $this->layout = 'admin';
    }
とレイアウトの指定を追加します。(上記5〜7行目)

ブラウザで見てみます。

CakePHP1.3で作る会員管理システム(8) フォームの見た目を変える

フォームの見た目を変える

ということで無事 members の会員登録部分が動くようになりました。 すぐに気づくことですが、デフォルトのテンプレートの見た目が気に入らないです。 会員の一覧画面はともかく、 /members/add の登録画面とか。 見た目の整形は最後にまとめてやっても構わない訳ですが、なんだかこのまま作業するのも気が重い。 ここでちょっといじりたい、というのが人情でしょう。 とりあえず、アクション add の場合で言うと、今自動生成されたテンプレートはこうなっています。

 <fieldset>
  <legend><?php __('Add Member'); ?></legend>
 <?php
  echo $this->Form->input('email');
  echo $this->Form->input('password');
  echo $this->Form->input('type_id');
  echo $this->Form->input('birthday');
  echo $this->Form->input('img1');
  echo $this->Form->input('img2');
  echo $this->Form->input('Favorite');
 ?>
 </fieldset>

fieldset とか legend が気に入らないですね。 ということで、本家サイトのドキュメントにあるとおり、デフォルトのテンプレートをコピーして、編集します。

9.3 Modify default HTML produced by "baked" templates

先に、/app/vendors/shells/templates/ 以下に、自分が作るテンプレート用のディレクトリを作っておきます。 ここでは

/app/vendors/shells/templates/mytheme
としました。

そして、

cd Sites/cake/cake/console/templates/default
cp -r views /Users/myname/Sites/cake/app/vendors/shells/templates/mytheme/views
として、デフォルトのテンプレートを自分のところへコピーします。

上記 views の中にある form.ctp を開いて、

 <fieldset>
  <legend><?php printf("<?php __('%s %s'); ?>", Inflector::humanize($action), $singularHumanName); ?></legend>
<?php
  echo "\t<?php\n";
  foreach ($fields as $field) {
   if (strpos($action, 'add') !== false && $field == $primaryKey) {
    continue;
   } elseif (!in_array($field, array('created', 'modified', 'updated'))) {
    echo "\t\techo \$this->Form->input('{$field}');\n";
   }
  }
  if (!empty($associations['hasAndBelongsToMany'])) {
   foreach ($associations['hasAndBelongsToMany'] as $assocName => $assocData) {
    echo "\t\techo \$this->Form->input('{$assocName}');\n";
   }
  }
  echo "\t?>\n";
?>
 </fieldset>
こうなっているところを、
 <h2><?php printf("<?php __('%s %s'); ?>", Inflector::humanize($action), $singularHumanName); ?></h2>
<?php
  echo "\t<?php\n";
  foreach ($fields as $field) {
   if (strpos($action, 'add') !== false && $field == $primaryKey) {
    continue;
   } elseif (!in_array($field, array('created', 'modified', 'updated'))) {
    echo "\t\techo \$this->Form->input('{$field}');\n";
   }
  }
  if (!empty($associations['hasAndBelongsToMany'])) {
   foreach ($associations['hasAndBelongsToMany'] as $assocName => $assocData) {
    echo "\t\techo \$this->Form->input('{$assocName}');\n";
   }
  }
  echo "\t?>\n";
?>
とします。

fieldset と legend を削除して、h2 に変えたってだけです。 再度 cake bake でビューを作り直します。

前回同様yes-noで進んでいくと、今度は、以下のようにテンプレートが選べるメニューが出ます。

---------------------------------------------------------------
You have more than one set of templates installed.
Please choose the template set you wish to use:
---------------------------------------------------------------
1. mytheme
2. default
Which bake theme would you like to use? (1/2) 
[1] > 1
自分が作ったテンプレートを選びます。

/app/views/members/add.ctp を確認すると、

 <h2>
  <?php __('Add Member'); ?></h2>
 <?php
  echo $this->Form->input('email');
  echo $this->Form->input('password');
  echo $this->Form->input('type_id');
  echo $this->Form->input('birthday');
  echo $this->Form->input('img1');
  echo $this->Form->input('img2');
  echo $this->Form->input('Favorite');
 ?>
自分用のテンプレートが適用されています。


CakePHP1.3で作る会員管理システム(7) Validate

Validate

ということで無事 members の会員登録部分が動くようになりました。
ブラウザから、

http://localhost/~myname/cake/members

を開いて、いろいろクリックしたりしてみます。

で、会員の新規登録

http://localhost/~myname/cake/members/add

をやってみると、custom エラーになって登録ができません。

最初に bake でモデルを作った時に、バリデーションの設定で、email と password に custom をセットして、その内容を入れていないのが原因です。
ということで validate をちゃんと作ります。

/app/models/member.php の、 今こうなっているところを

  'email' => array(
   'custom' => array(
    'rule' => array('custom'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'email' => array(
    'rule' => array('email'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),

以下のように書き換えます。

  'email' => array(
   'custom' => array(
    'rule' => array('isUnique'),
    'message' => 'このメールアドレスは既に登録されています',
    'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'email' => array(
    'rule' => array('email'),
    'message' => 'メールアドレスを正しく入力してください',
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    'message' => 'メールアドレスを入力してください',
   ),
  ),

パスワードの部分は、以下のように書き換えます。

  'password' => array(
   'custom' => array(
    'rule' => array('custom', '/^[a-zA-Z0-9\_\-]{6,10}$/i'),
    'message' => 'パスワードは6文字以上10文字以内で入力してください',
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    'message' => 'パスワードを入力してください',
   ),
  ),

ソース中に日本語が入りましたので文字コードをutf-8に設定して保存します。
会員の新規登録が出来ることを確認します。


http://localhost/~myname/cake/members/add

2011-09-09

CakePHP1.3で作る会員管理システム(6) bakeする(controller,view)

bakeする(controller,view)

次に bake のメニューから、 C を選んでコントローラを作ります。

> C
---------------------------------------------------------------
Bake Controller
Path: /Users/myname/Sites/cake/app/controllers/
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Favorites
2. Members
3. MembersFavorites
4. Types
5. Users
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > 2

モデルの時と同様の選択画面になるので、

2. Members
を選択します。

---------------------------------------------------------------
Baking MembersController
---------------------------------------------------------------
Would you like to build your controller interactively? (y/n) 
[y] > y

とりあえずYESですね。

Would you like to use dynamic scaffolding? (y/n) 
[n] > n

スキャフォルドをするかどうかですが、しないのでNOです。

Would you like to create some basic class methods 
(index(), add(), view(), edit())? (y/n) 
[n] > y

indexとかaddとかの基本のアクションを作るかどうかですね。ここでYESです。

Would you like to create the basic class methods for admin routing? (y/n) 
[n] > y

adminルーティングを使うかどうかです。これはYESです。

Would you like this controller to use other helpers
besides HtmlHelper and FormHelper? (y/n) 
[n] > n
Would you like this controller to use any components? (y/n) 
[n] > n
Would you like to use Session flash messages? (y/n) 
[y] > y

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:
 Members
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y

その後はそのままです。 /app/controllers/members_controller.php が作られました。

次に、 V を選んでビューを作ります。

> V
---------------------------------------------------------------
Bake View
Path: /Users/myname/Sites/cake/app/views/
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Favorites
2. Members
3. MembersFavorites
4. Types
5. Users
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > 2

やはり2番のMembersを選んで進みます。

Would you like bake to build your views interactively?
Warning: Choosing no will overwrite Members views if it exist. (y/n) 
[n] > y

ここはYESにします。

Would you like to create some CRUD views
(index, add, view, edit) for this controller?
NOTE: Before doing so, you'll need to create your controller
and model classes (including associated models). (y/n) 
[y] > y

ここもとりあえずYESにします。

Would you like to create the views for admin routing? (y/n) 
[n] > y

admin 用のviewを作るかどうかです。これもYESにします。

/app/views/ 以下に members というフォルダーが作られ、 index.ctp 他の各アクション用ビューが自動生成されます。 登録フォームの view は以下のようになりました。

<div class="members form">
<?php echo $this->Form->create('Member');?>
 <fieldset>
  <legend><?php __('Add Member'); ?></legend>
 <?php
  echo $this->Form->input('email');
  echo $this->Form->input('password');
  echo $this->Form->input('type_id');
  echo $this->Form->input('birthday');
  echo $this->Form->input('img1');
  echo $this->Form->input('img2');
  echo $this->Form->input('Favorite');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit', true));?>
</div>
<div class="actions">
 <h3><?php __('Actions'); ?></h3>
 <ul>

  <li><?php echo $this->Html->link(__('List Members', true), array('action' => 'index'));?></li>
  <li><?php echo $this->Html->link(__('List Types', true), array('controller' => 'types', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Type', true), array('controller' => 'types', 'action' => 'add')); ?> </li>
  <li><?php echo $this->Html->link(__('List Favorites', true), array('controller' => 'favorites', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Favorite', true), array('controller' => 'favorites', 'action' => 'add')); ?> </li>
 </ul>
</div>

users についても同じようにしてコントローラとビューを作ります。

2011-09-05

CakePHP1.3で作る会員管理システム(5) bakeする(model)

bakeする(model)

続いてbakeしてモデル、コントローラ、ビューを作ります。
作るんですが、その前にマスターにデータを入れておきます。
テーブルの types と favorites は、値が入ってるだけのマスターテーブルです。
今回これの管理機能は提供しません。
ということで最初にテーブルにデータを入れておきます。
別に普通にSQLのinsert文で入れておくだけです。

INSERT INTO types (id, name) VALUES (1, '一般');
INSERT INTO types (id, name) VALUES (2, '専門');
INSERT INTO favorites (id, name) VALUES (1, 'apple');
INSERT INTO favorites (id, name) VALUES (2, 'orange');
INSERT INTO favorites (id, name) VALUES (3, 'grape');

で、最初にmembersテーブルのモデルから作ります。

cd Sites/cake/app
cake bake
と打つと、
Welcome to CakePHP v1.3.11 Console
---------------------------------------------------------------
App : app
Path: /Users/myname/Sites/cake/app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> 
と表示されて入力待ちになるので、 M を入れます。
> M
---------------------------------------------------------------
Bake Model
Path: /Users/myname/Sites/cake/app/models/
---------------------------------------------------------------
Possible Models based on your current database:
1. Favorite
2. Member
3. MembersFavorite
4. Type
5. User
Enter a number from the list above,
type in the name of another model, or 'q' to exit  
[q] >
となるのでここで2番を選びます。

あとは対話しながら選択していくだけです。

A displayField could not be automatically detected
would you like to choose one? (y/n) 
> y
displayField が自動で見つけられなかった、自分で選ぶかい? と聞かれるのでyesとすると、
1. id
2. email
3. password
4. type_id
5. birthday
6. img1
7. img2
8. created
9. modified
Choose a field from the options above:  
> 2
members テーブルのカラム名が一覧されるので、2番の email を選択します。

Would you like to supply validation criteria 
for the fields in your model? (y/n) 
[y] > y
続いてバリデーションの設定をする。
Field: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - uuid
29 - Do not do any validation on this field.
... or enter in a valid regex validation string.
  
[29] > 
というように、カラム id について1から29までのルールが表示されるので、その中から適用するルールを選びます。 [] 内に表示されているのはcakephpが推測する値です。 一回選ぶ度に、
Would you like to add another validation rule? (y/n) 
[n] > 
と、追加のルールがあるかどうか聞いてくるので、ルールは複数選べます。 とりあえず、
id:
29
email:
7, 10, 19
password:
7, 19
type_id:
20
birthday:
8
img1:
29
img2:
29
created:
29
modified:
29
と、番号を選択して進みます。

Would you like to define model associations
(hasMany, hasOne, belongsTo, etc.)? (y/n) 
[y] > 
関連づけをするかどうかの確認です。YESです。
One moment while the associations are detected.
---------------------------------------------------------------
Please confirm the following associations:
---------------------------------------------------------------
Member belongsTo Type? (y/n) 
[y] > y
Member hasAndBelongsToMany Favorite? (y/n) 
[y] > y
Would you like to define some additional model associations? (y/n) 
[n] > n
とすると、
---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       Member
DB Table:   `members`
Validation: Array
(
    [email] => Array
        (
            [custom] => custom
            [email] => email
            [notempty] => notempty
        )

    [password] => Array
        (
            [custom] => custom
            [notempty] => notempty
        )

    [type_id] => Array
        (
            [numeric] => numeric
        )

    [birthday] => Array
        (
            [date] => date
        )

)

Associations:
 Member belongsTo Type
 Member hasAndBelongsToMany Favorite
---------------------------------------------------------------
Look okay? (y/n) 
[y] > 
とでるので、yesすると、
Baking model class for Member...

Creating file /Users/myname/Sites/cake/app/models/member.php
Wrote `/Users/myname/Sites/cake/app/models/member.php`
モデルが作られました。

最後にtestも作っておきます。

SimpleTest is not installed. Do you want to bake unit test files anyway? (y/n) 
[y] > y

You can download SimpleTest from http://simpletest.org

Baking test fixture for Member...

Creating file /Users/myname/Sites/cake/app/tests/fixtures/member_fixture.php
Wrote `/Users/myname/Sites/cake/app/tests/fixtures/member_fixture.php`
Bake is detecting possible fixtures..

Creating file /Users/myname/Sites/cake/app/tests/cases/models/member.test.php
Wrote `/Users/myname/Sites/cake/app/tests/cases/models/member.test.php`

作成された /app/models/member.php の中味はこんな感じです。

class Member extends AppModel {
 var $name = 'Member';
 var $displayField = 'email';
 var $validate = array(
  'email' => array(
   'custom' => array(
    'rule' => array('custom'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'email' => array(
    'rule' => array('email'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'password' => array(
   'custom' => array(
    'rule' => array('custom'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'type_id' => array(
   'numeric' => array(
    'rule' => array('numeric'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'birthday' => array(
   'date' => array(
    'rule' => array('date'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
 );
 //The Associations below have been created with all possible keys, those that are not needed can be removed

 var $belongsTo = array(
  'Type' => array(
   'className' => 'Type',
   'foreignKey' => 'type_id',
   'conditions' => '',
   'fields' => '',
   'order' => ''
  )
 );

 var $hasAndBelongsToMany = array(
  'Favorite' => array(
   'className' => 'Favorite',
   'joinTable' => 'members_favorites',
   'foreignKey' => 'member_id',
   'associationForeignKey' => 'favorite_id',
   'unique' => true,
   'conditions' => '',
   'fields' => '',
   'order' => '',
   'limit' => '',
   'offset' => '',
   'finderQuery' => '',
   'deleteQuery' => '',
   'insertQuery' => ''
  )
 );

}
よさそうな感じですね。

あとは同様にusersテーブルについても作っておきます。関連づけはなしですね。

class User extends AppModel {
 var $name = 'User';
 var $displayField = 'name';
 var $validate = array(
  'username' => array(
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'password' => array(
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
 );
}


CakePHP1.3で作る会員管理システム(4) schemaを作る

schemaを作る

ここでついでなので schema を作っておきます。 自分は今 app フォルダーの中にいるので、そのまま

cake schema generate -f
と打ちます。
Welcome to CakePHP v1.3.11 Console
---------------------------------------------------------------
App : app
Path: /Users/myname/Sites/cake/app
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------
Generating Schema...
Schema file: schema.php generated
とメッセージが出て終了。

/app/config/schema/schema.php というファイルが生成されます。 中味はこんな感じ。

class AppSchema extends CakeSchema {
 var $name = 'App';

 function before($event = array()) {
  return true;
 }

 function after($event = array()) {
 }

 var $favorites = array(
  'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
  'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
  'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
 );
 var $members = array(
  'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
  'email' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'password' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'type_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'),
  'birthday' => array('type' => 'date', 'null' => true, 'default' => NULL),
  'img1' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'img2' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL),
  'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL),
  'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'type_id' => array('column' => 'type_id', 'unique' => 0)),
  'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
 );
 var $members_favorites = array(
  'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
  'member_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'),
  'favorite_id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'index'),
  'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'member_id' => array('column' => 'member_id', 'unique' => 0), 'favorite_id' => array('column' => 'favorite_id', 'unique' => 0)),
  'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
 );
 var $types = array(
  'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
  'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
  'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
 );
 var $users = array(
  'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
  'username' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'password' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'name' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'email' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
  'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
 );
}
これを作っておけば、環境が変わって、データベースがMySQLからPostgreSQLに変わったりしても、すぐにデータベースを構築できます。

その場合は、

cake schema create
と打ちます。

例えばこいつをPostgreSQLの環境で構築したデータベースはこうなりました。

cake_db=# ¥d
                    List of relations
 Schema |           Name           |   Type   |  Owner
--------+--------------------------+----------+----------
 public | favorites                | table    | postgres
 public | favorites_id_seq         | sequence | postgres
 public | members                  | table    | postgres
 public | members_favorites        | table    | postgres
 public | members_favorites_id_seq | sequence | postgres
 public | members_id_seq           | sequence | postgres
 public | types                    | table    | postgres
 public | types_id_seq             | sequence | postgres
 public | users                    | table    | postgres
 public | users_id_seq             | sequence | postgres
(10 rows)

cake_db=# ¥d users
                                  Table "public.users"
  Column  |          Type          |                     Modifiers

----------+------------------------+----------------------------------------------------
 id       | integer                | not null default nextval('users_id_seq'::regclass)
 username | character varying(100) | not null
 password | character varying(100) | not null
 name     | character varying(100) | default NULL::character varying
 email    | character varying(100) | default NULL::character varying
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)

cake_db=# ¥d members
                                    Table "public.members"
  Column  |            Type             |                      Modifiers
----------+-----------------------------+------------------------------------------------------
 id       | integer                     | not null default nextval('members_id_seq'::regclass)
 email    | character varying(100)      | not null
 password | character varying(100)      | not null
 type_id  | integer                     | not null
 birthday | date                        |
 img1     | character varying(255)      | default NULL::character varying
 img2     | character varying(255)      | default NULL::character varying
 created  | timestamp without time zone | not null
 modified | timestamp without time zone | not null
Indexes:
    "members_pkey" PRIMARY KEY, btree (id)
    "type_id" btree (type_id)

cake_db=# ¥d favorites
                                 Table "public.favorites"
 Column |          Type          |                       Modifiers
--------+------------------------+--------------------------------------------------------
 id     | integer                | not null default nextval('favorites_id_seq'::regclass)
 name   | character varying(100) | not null
Indexes:
    "favorites_pkey" PRIMARY KEY, btree (id)

cake_db=# ¥d types
                                 Table "public.types"
 Column |          Type          |                     Modifiers
--------+------------------------+----------------------------------------------------
 id     | integer                | not null default nextval('types_id_seq'::regclass)
 name   | character varying(100) | not null
Indexes:
    "types_pkey" PRIMARY KEY, btree (id)

cake_db=# ¥d members_favorites
                            Table "public.members_favorites"
   Column    |  Type   |                           Modifiers
-------------+---------+----------------------------------------------------------------
 id          | integer | not null default nextval('members_favorites_id_seq'::regclass)
 member_id   | integer | not null
 favorite_id | integer | not null
Indexes:
    "members_favorites_pkey" PRIMARY KEY, btree (id)
    "favorite_id" btree (favorite_id)
    "member_id" btree (member_id)

MySQL からPostgreSQL へデータベースが変わるなんてことはないかもしれないし、後でやっても構わない訳ですが、作っておけば後々便利ということでせっかく今ターミナルを開いてる訳ですから schema だけは作っておいて損はないだろうと思います。
データベースは MySQL のままでも、開発用環境を移す時にコマンド一発でデータベースを作れてしまうのでとても楽ですね。

ちなみに、

cake schema create
の実行は、まず既存のテーブルを削除してから、新規にテーブルを作成する、という処理になるので、テーブルが存在していないのに、「削除しますか?」 で「はい」と返事すると、だーっとエラーが出ます。 が、特に問題はありません。
Welcome to CakePHP v1.3.11 Console
---------------------------------------------------------------
App : app
Path: /Users/myname/Sites/cake/app
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------

The following table(s) will be dropped.
favorites
members
members_favorites
types
users
Are you sure you want to drop the table(s)? (y/n) 
[n] > y
Dropping table(s).

ここでWarning発生!

The following table(s) will be created.
favorites
members
members_favorites
types
users
Are you sure you want to create the table(s)? (y/n) 
[y] > 
Creating table(s).
favorites updated.
members updated.
members_favorites updated.
types updated.
users updated.
End create.

CakePHP1.3で作る会員管理システム(3) データベース接続

データベース接続

データベースに接続するための設定を行います。
設定ファイルは /app/config/database.php.default を書き換えて、/app/config/database.php にリネームするだけなので、エディターで開いて編集すればいいのですが、ここでは console の cake コマンドを使ってやってみます。
大元の /cake/console の中にあるのがcakeコマンドです。
自分のhomeディレクトリから、

cd Sites/cake/cake/console
./cake
と打てば
Welcome to CakePHP v1.3.11 Console
---------------------------------------------------------------
Current Paths:
 -app: app
 -working: /Users/myname/Sites/cake/app
 -root: /Users/myname/Sites/cake
 -core: /Users/myname/Sites/cake

Changing Paths:
your working path should be the same as your application path
to change your path use the '-app' param.
Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp

Available Shells:
 acl [CORE]                             i18n [CORE]                            
 api [CORE]                             schema [CORE]                          
 bake [CORE]                            testsuite [CORE]                       
 console [CORE]                         

To run a command, type 'cake shell_name [args]'
To get help on a specific command, type 'cake shell_name help'
と表示され、とりあえず動くのが分かります。

で、一応このcakeコマンドにパスを通しておきます。

vim .profile
としてvim エディターを起動し、
export PATH=/Users/myname/Sites/cake/cake/console:$PATH
の一行を追加します。
ターミナルに戻って、
. .profile
して、
printenv PATH
で、パスを確認します。
/Users/myname/Sites/cake/cake/console:
と入っていればOK.

基本、自分の app フォルダーの中で作業するのがいいです。

cd Sites/cake/app
cake bake
別の場所にいる場合は、 app の場所を指定します。今自分がhomeにいるんだったら、
cake -app Sites/cake/app bake
とすると、
Welcome to CakePHP v1.3.11 Console
---------------------------------------------------------------
App : app
Path: /Users/myname/Sites/cake/app
---------------------------------------------------------------
Your database configuration was not found. Take a moment to create one.
---------------------------------------------------------------
Database Configuration:
---------------------------------------------------------------
Name:  
[default] > 
というように対話式の設定画面になりますので、必要な項目を入力します。
---------------------------------------------------------------
The following database configuration will be created:
---------------------------------------------------------------
Name:         default
Driver:       mysql
Persistent:   false
Host:         localhost
User:         cake_user
Pass:         ********
Database:     cake_db
Encoding:     utf8
---------------------------------------------------------------
という感じに、データベースの設定を入れて[y]すると、自動で /app/config/database.php が作られます。

ブラウザで http://localhost/~myname/cake/ を開いてみると

Your database configuration file is present.
Cake is able to connect to the database.
と出てるはずです。


CakePHP1.3で作る会員管理システム(2) CakePHPの設置

CakePHPの設置

本家サイトからダウンロードした CakePHP を設置します。
「Download」の画像をクリックするとgithub のページに飛ぶので、そちらから、 1.3.11.zip — 1.3.11 をクリックします。

ダウンロードしたファイルを解凍すると、 cakephp-cakephp-3b830a4 という名前のフォルダーが出来るので、これを cake とリネームしてローカル環境のドキュメントルート以下にコピーします。
MacOSXの場合は、いわゆる「サイト」フォルダー

/Users/myname/Sites
以下ということになります。

Windows の場合は、apache をどこにインストールしたかにもよるんでしょうが、その htdocs 以下、ということです。例えば、

c:¥Program Files¥apache・・・・¥htdocs
になるかと思います。
本番リリースと同じ構造にしておく、という手もありますが、開発用のローカル環境ではなにもいじらない方がやりやすいんじゃないですかね。
なにしろ、まっさらの状態でも本体をダウンロードしてきてそのまま始められる訳ですから。

で、 http://localhost/~myname/cake/ をブラウザから開きます。

Warning (512): /Users/myname/Sites/cake/app/tmp/cache/ is not writable [CORE/cake/libs/cache/file.php, line 267]
Warning (512): /Users/myname/Sites/cake/app/tmp/cache/persistent/ is not writable [CORE/cake/libs/cache/file.php, line 267]
Warning (512): /Users/myname/Sites/cake/app/tmp/cache/models/ is not writable [CORE/cake/libs/cache/file.php, line 267]
Notice (1024): Please change the value of 'Security.salt' in app/config/core.php to a salt value specific to your application [CORE/cake/libs/debugger.php, line 694]
Notice (1024): Please change the value of 'Security.cipherSeed' in app/config/core.php to a numeric (digits only) seed value specific to your application [CORE/cake/libs/debugger.php, line 698]
という3つの Warning と2つの Notice が出ますが、とりあえず動いてるってことでOKでしょう。

一つ一つ対応していきます。

/app/tmp/cache を書き込み可能にします。
ついでに /app/tmp も書き込み可能にするので、/appに移動して、

chmod -R 0777 tmp
とします。 ブラウザをリロードします。

引き続き、/app/config/core.php を開いて、

 Configure::write('Security.salt', 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi');
 Configure::write('Security.cipherSeed', '76859309657453542496749683645');
の2つの部分の値を書き換えます。

これでOKのはずですが、もし、php が 5.3 で、しかも php.ini に default timezone の設定をしていなかったりすると、

Warning (2): strtotime() [function.strtotime]: It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'Asia/Tokyo' for 'JST/9.0/no DST' instead [CORE/cake/libs/cache.php, line 597]
というような激しいエラーが出ますので、その場合は、 /app/config/core.php の、
 //date_default_timezone_set('UTC');
となっているところを、
 date_default_timezone_set('Asia/Tokyo');
としておきます。
できれば php.ini の方を設定することをおすすめします。

最後、ついでなので、以下の一行のコメントアウトを外しておきます。

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


CakePHP1.3で作る会員管理システム(1) データベースの作成

データベースの作成

いろんなやり方があるとは思いますが、まずはデータベースを作っておくのがいいと思います。
今回はMySQLで作りました。

CREATE DATABASE cake_db CHARACTER SET utf8;
GRANT all ON cake_db.* TO cake_user@localhost IDENTIFIED BY 'password';

users は管理側の管理者アカウントです。
管理側はAuthコンポーネントを使って認証するので、テーブル名を users にしておきます。
他の名前でも可能ですが、users が Auth コンポーネントのデフォルトなのでそうしておいた方が面倒がなくてよいです。

CREATE TABLE users (
 id INT(11) AUTO_INCREMENT,
 username VARCHAR(100) NOT NULL,
 password VARCHAR(100) NOT NULL,
 name VARCHAR(100),
 email VARCHAR(100),
 PRIMARY KEY (id)
);

membersは会員の情報を入れるテーブルです。
本当はここにstatusフィールドを入れて、仮登録・本登録の処理を入れたかったのですが、今回は見送りました。

CREATE TABLE members (
 id int(11) AUTO_INCREMENT,
 email VARCHAR(100) NOT NULL,
 password VARCHAR(100) NOT NULL,
 type_id INT(11) NOT NULL,
 birthday DATE,
 img1 VARCHAR(255),
 img2 VARCHAR(255),
 created DATETIME,
 modified DATETIME,
 PRIMARY KEY (id),
 KEY type_id (type_id)
);

typesは会員種別のマスターです。
一般会員、とか特別会員、とかのデータを入れます。

CREATE TABLE types (
 id int(11) AUTO_INCREMENT,
 name VARCHAR(100) NOT NULL,
 PRIMARY KEY (id)
);

favoritesは会員の「好きな物」の選択値を入れます。
「好きな物」でも「趣味」でも何でもいいんですが、要は複数選択の場合の処理を入れたいのです。

CREATE TABLE favorites (
 id int(11) AUTO_INCREMENT,
 name VARCHAR(100) NOT NULL,
 PRIMARY KEY (id)
);

members_favorites は会員情報と好きな物を関連づけるためのテーブルです。
多対多の関係になるのでいわゆる HABTM の連結になります。

CREATE TABLE members_favorites (
 id int(11) AUTO_INCREMENT,
 member_id INT(11) NOT NULL,
 favorite_id INT(11) NOT NULL,
 PRIMARY KEY (id),
 KEY member_id (member_id),
 KEY favorite_id (favorite_id)
);

2011-09-03

OGタグ用のサムネイル

テスト
インゲン

OSX で MacPorts を使って、CoffeeScript を試してみる

WEB+DB PRESS Vol.64 の記事で面白そうだったので早速試してみた。
記事では CoffeeScript のコンパイラをインストールするのに、まず Node.js と npm が必要とあり、
その Node.js のインストールには nvm を使ってみる、とあるが、面倒なので port でやってみる。
$ port search node*
とすると、以下の4つが見つかるので、
nodejs @0.4.11 (devel, net)
    Evented I/O for V8 JavaScript

nodejs-devel @0.5.4 (devel, net)
    Evented I/O for V8 JavaScript

nodejuice @1.5.0 (www)
    A web development tool to autorefesh the browser on changes.

npm @1.0.26 (devel)
    node package manager

Found 4 ports.
$ sudo port install nodejs
$ sudo port install npm
でよさそうだ。
が、
$ port search coffee*
とすると、
coffee-script @1.1.2 (lang)
    a language that compiles into JavaScript
こう出るので、もしかすると
$ sudo port install coffee-script
だけでもよかったのかもしれない。いずれにしろ、これで
$ coffee -v
CoffeeScript version 1.1.2
というように入ったので、記事の通りに
hello = ->
  console.log "Hello, World!"

hello()
を hello.coffee として保存し、

コンパイル。
$ coffee -c hello.coffee 
Node.js から実行。
$ node hello.js
Hello, World!
CoffeeScript から直接実行。
$ coffee hello.coffee 
Hello, World!
生成された js ファイルは、
(function() {
  var hello;
  hello = function() {
    return console.log("Hello, World!");
  };
  hello();
}).call(this);
となっている。
うまくいったようだ。
WEB+DB PRESS Vol.64

ブログ アーカイブ

このブログを検索

Powered by Blogger.

ラベル

php (17) jQuery (13) OSX (10) MySQL (8) Javascript (7) Postgres (7) port (7) apache (6) Java (3) Smarty (2) html (2) pear (2) FCKEditor (1) XAMPP (1) css (1) git (1) perl (1) ruby (1)

Facebookバナー