メトロスタイルとバウハウス

前回の記事 再びメトロあるいは、アールデコと Windows8 と iOS7 – Moonmile Solutions Blog で「バウハウス系統では?」というコメントを貰ったので、急遽チェック。「iOS 7」における、デザイン哲学のせめぎ合い « WIRED.jp と友人の話によれば、現在のWEBデザイナ(広義の意味ではプロダクトデザイナー?)にとって、デザイン学校では「バウハウス」の存在を学ぶのだそうで、なるほどその「系統」となれば、「フラットデザインと呼ばれるもの」は別な意味を持っている。

ちなみに、三井秀樹著「美の構成学」をざっと読んだ限りでは、バウハウスの発祥が、個々人の職人的な装飾の作り込み(アールデコやそれ以前に代表される美術的観点や個としての職人の腕に頼る装飾)から、大量生産や「プロダクト」という制作工程を前提とした、製品として作りやすいデザイン(おそらく「品質のばらつき」が少ない製品デザイン)を進めるうえで、何等かの基準でありそれを実現させるための思想なり教育なりを実現する発端となる。おそらく、ドイツ特有の「マイスター制度」から草案された、プロダクトデザイン以前と以後の境目になるのでは?と思っている。このあたりは、明日あたりに「バウハウスの本」が届くのでそれを読んでから。

で、その実際の発端はさておき、どうやらプロダクトデザインについてなんやかんややろうという象徴としての「バウハウス」という呼称があり、先の WIRED にある「スティーブ・ジョブズは、自分がもっとも強い影響を受けたもののひとつとしてバウハウスを見なしていた。」の下りはそれなのかな、と思うのだが、元ネタは何処にあるのかは知らない。どこかで「WEBデザインの本来の姿を取り戻す」という文も見たのだが、「本来のデザイン」自体が何なのか(本来は何をベースにしたかったが偽って何をつくっていたのか)の言及がなかったので、これもよくわからない。が、それらを別にしても、「バウハウス」という固有名詞は、iOS 7 のフラットデザインと、Windows 8 のメトロデザイン(と呼ばれていたもの)の源流であることは確からしい。

ちなみに、メトロスタイルの源流がバウハウス(Bauhaus)にあることの言及は、Ken Azuma : //build/ 参加日記 メトロスタイルデザイン にあって(infragisticsは NetAdvantage などの画面コンポーネントを作っている会社…がインドにある。デザインは別かも)、「モダンスタイル」という言葉が使われている。実際、「メトロデザイン」がやばくなった後には「モダンデザイン」あるいは「モダンスタイル」という用語が(英語では)使われているので、それはそれでOKかと。ただ「モダン」という用語自体が、日本ではあまりモダンに聞こえないので広告戦略として日本では広めていない。

さて、バウハウスを源流としたときに、その思想を取り入れるのか、それとも表層的なものだけを真似するのか、ということになるのだが「商業主義的」に考えれば、表層だけでOK。ただし、他人の言う(MicrosoftやAppleも含めて)、フラットデザインの原則や効用やら、人間工学やら最新のデザインやらという言葉は、すべて「広告的な意味あいを持つもの」として聞き流しておく必要がある。ってのが一番の前提。その上で、バウハウスやグリッドシステム、タイポグラフィを取り入れるとOKだし、実際バウハウスが発生したときの前後を学んでおくことは、有用ではないか?と思った次第。

ちょっと、現象学と認知学に話を移すと、

image

http://www.subblue.com/blog/2011/3/5/fractal_lab

例えば、フラクタルアートは文句なく美しいと感じる(感じる人が多いということで…)ことができるのは、アプリオリに「美しい」と感じるものがフラクタルで表現される「生物一般の惹かれるもの」であり、生物の根源にある式であるからに過ぎない。なので、因果関係が逆で「フラクタルが美しい」のではなく、「美しいのがフラクタルである」と言い換えることができる。では、必要十分条件として、フラクタルであればすべからく美しいのかどうか?は否であり、フラクタルを根底に持てば「美しくみられる確率が高くなる」という現象が発生するだけである。これは黄金比にも言えることで、黄金比にすればすべからく美しく見えるのかと言えば、そうは言えない。ただし、ある条件をクリアすることはできる。しかし、注意しておかなければいけないのは、フラクタルでないものがすべからく「美しくない」かというとそうではない。なぜならば「美しい」という現象(そう感じるという現象)自体が曖昧さを含み、同じものであっても時と場所が異なれば、それは「美しく」感じない場合もあるし、美しく感じない人もいる。なので、フラクタルという式自体には曖昧さがないものの、純粋なフラクタルの式を現象として「見せる」ことができるものに曖昧さがあり、更にそれを判断するための「美しさ」に対して曖昧さが含まれるゆえに、どちらも必要十分条件になりえない。ゆえに、「フラクタルアートは美しく感じることが多い」という言及にとどめておくのが望ましい。

これは、フラットデザインにも言えることで、フラットデザインにすればすべから使いやすく、すべからくWEBデザインの最先端(あるいは、メトロスタイルの最先端、iOS 7 アプリの最先端)になるかどうかは、必要十分条件ではない。勿論、フラットデザインという式が存在しないので(Windows 8 と iOS 7 という表層があるだけである)「フラットデザイン」自体の定義も曖昧なままである。しかし、曖昧でもOK。実際問題としては、フラットデザインは選択肢であって、時から離れた自由な存在ではないこと。もっと具体的に言えば「流行り」です。

それを踏まえたうえで、バウハウスという当時のプロダクトデザインの流行りと、それに対する各人の努力(偉そう…)に敬意を評し、過去から学ぶことによって舗装された高速道路を突っ走ることを感謝しつつ(羽生棋士曰く)、バウハウスとタイポグラフィの再入門ってことで。タイポグラフィは高校の頃にちょっと突き詰めたことがあるので知っているのですが、当時は紙への印刷が主流だったわけで、昨今のコンピューターディスプレイ表示では大きく異なるわけで、さらにタッチパネルを主流としたタブレットPC(iPhone, iPad なども含む)におけるタイポグラフィはまた異なるわけで。

そうそう、バウハウスの発端としては、当時は制作をする職人の違い(あるいは工程)に依存しない製品の品質を求める、という前提があるがゆえの「セリフなしの活字」だったり、平面構成や部品の標準化、部品自体の装飾の削除だったりするので、それを踏まえていないグリッドシステムの推奨やアイコンの作成、クロームへの言及は、ちょっと的はずれなのでは?と思ったり思わなかったり。いやいや、ただし、ポストモダン、ポスト構造主義としての「フラットデザイン」を追及するのは十分ありかと思います、ってことでちょっと「バウハウス」の本の到着待ちまで。

カテゴリー: 雑談 | 1件のコメント

再びメトロあるいは、アールデコと Windows8 と iOS7

ぼちぼちとmetro stryleについて書き綴ろうと思っていたら、WWDC 2013でiOS7の発表があったのでそれに絡めて…と言いますか、iOS7で噂されていた「フラットデザイン」が気になって、Windows 8 のように「影なしのアイコンデザイン」かつ「グリッドシステムに固執したユーザーインターフェース」だったらどうしようか、と心配していたところなのです。本来のフラットデザイン、グリッドシステム、メトロ(地下鉄の標識)のデザインについては、そろそろ Metro Design について考えてみよう – Moonmile Solutions Blog あたりで書き綴っているので、それはさておき。いや、「意匠問題」のところと「コンテンツ主導」のところは関係がありますが、ざっと、1時間ほどで書き下してみたいと思います。

iOS 7 Release Date

Apple iPhone 4S

いわゆるホームページ(だっけ?)を眺めると、アイコンがフラットになっているように見えますが、もともとiOS 5,6 のアイコンはデフォルトでグラスよりが掛けられているのと、背景に影がつくようになっているので、iOS 7の処理がそれを外しているだけです。XCode 5 で作れるコントロールも、(おそらくは)XCode 4と同等のもの(下位互換)は用意されるでしょうから、OS が担う標準コントロールの処理が変わった、というところです。なので、いままでのアプリをそのまま iOS 7 に載せれば、「フラットデザイン風」になる可能性は高いかと。そういう意味では、Windows 8 のデザインの追随だったり、既に Android に見られるようなインターフェースがあったり、jQuery UI で作れるような Web サイトのデザインだったりの、寄せ集めな雰囲気は否めません。なので、先駆者としての Mac デザイン?はさておき、ユーザーのニーズに応えました、ってところでしょうか。Microsoft であれ Apple であれ資本主義を元にする会社であるので、本来のデザインの先進さやユーザビリティよりも発売後の一般受けが優先するのでしょう。もちろん、卵と鶏の関係になるので、どちらが先という訳ではないのですが、この「フラットデザイン」に関しては(それが、本来のフラットデザインではないにせよ)、Microsoft よりも Apple のほうが一歩リードしたままのように見えます。

■デザインの歴史は繰り返し、アールデコへ

Windows 8 のメトロデザインが発表された頃から感じていたのが「アールデコ」の匂いです。美術史を紐解けば、アールヌーヴォーの後のアールデコ、アールヌーヴォーがミューシャが代表する(とはいえ、ミューシャ自身はアールヌーヴォーの後期)「ごてごてとした装飾」の時代から一転、機能的なデザインのアールデコへと移ります。流線型を代表とする「機能美」のデザイン感覚ですね。

現在において「デザイン」という用語や用法は、美術的/芸術的な視点よりも、機能美のような「そぎ落とす」という意味で使われることがあります。あるいは、ユーザーが何かを使うときに「それが何であるか」を主張するための装飾を付け加えることを意味しています。前者は、ソフトウェアの設計であったり(ユーザーインターフェースではない機能設計とか)部品の生産効率のためのデザインという形で使われ、後者はノーマンなどの言うユーザビリティのデザインを示しています。他にも、アニメのロボットのデザインのような「架空ではあるが、それらしいデザインを組み込む」というスタイルや、デコレーションされた携帯のような「過剰的な装飾」である「過剰」な部分(≒自己主張?)をしたデザインという言い方もあります。

アールヌーヴォーの椅子の足には、いわゆる「装飾過剰」な猫の足が使われます。マッキントッシュはどちらかと言えば、アールデコの機能美の世界ですね。「スタイリッシュな」という形容詞が使われます。この椅子に関していえば、どちらが、優れているという訳ではありません、どちらもその時代の「ニーズ」にあわせた、あるいは、その時代のニーズに合ったものが残った、あるいはニーズを創り出した椅子になり、歴史的に言えば、どちらも「優れて」います。

アールヌーヴォー自体、それ以前の写実主義からポスターへの移り変わりなど前史があるわけで、前史があるがゆえのその反動的なデザインの仕方と言えます。なので、時代的には揺り返しが来るし、昨今ミュシャ展がずっと開かれている(商業的に一般ユーザーに受け入れられやすい。あるいは版権的にやりやすい?)のもあり、そういう大衆の流れがあります(本来は、大衆/分衆という仮想を動かすわけですが)。

ディスプレイの解像度とCPUのパワーが有り余ってきて、3Dのアイコンが過剰になってきたり、場合によっては3Dアイコン自体がリアルタイムの3Dレンダリングで動いたり(ということはなかったのですが)という未来もあったわけですが、「装飾」が「過剰」であると判断した Windows 8 のデザインチームが「メトロスタイル」を打ち出したと同様に、Apple のデザイン部門は、市場のニーズにあわせて「フラットデザイン」?に舵を切った、と言えます。なので、どちらが UI 的に優れているのか(3Dデザインとフラットデザイン)というのは「商業主義」の範疇の議論でしかありません。

■フラットすぎるWindows8と3D寄りのiOS7

私が Windows 8のデザインで解せないのは「フラットすぎるアイコン」と「青紫の利用」です。タブレットを主体とする(元は Window Phone だし)Windows 8 は、マウスポインタをかざしてアクションをする「だけ」ではダメなので、なんらかの「視覚的な」アクションがないと、それはボタンと分かりません。まあ、所詮「道具」ですから、慣れればなんということはないのですが(実際、私はスタート画面のアイコンは押さずに、すべてキーボード入力の検索になっています。Surface RTでは、自作アイコンを作って、それと分かりやすく区別化しています)、

 

鈴木英人か、わたせせいぞうのデザインですか?と思ったのは私だけ…ではないハズ…って80年代ポップスのデザインですね。覚えていますか?

装飾を切り詰めてコンテンツに集中するためのデザインかつデザインセンスとされていますが、かなりあやしいところがあります。

image

iOS6 の次には、長岡秀星が突き詰める「スーパーリアリズム」(当時は、エアーブラシで多焦点画像を描いていた)と思いきや、まあ、ちょっと3D感を残したフラットデザインな訳で、意味合いてきには Winodws XP の頃に流行ったフラットボタンコントロールの発想に似ています。ボタンが出っ張っているのではなくて、ちょっと凹みぎみのボタンですね。ちょうど iPhone のホームボタンに似ています。

あと気になるのは「青紫」という色なのですが、12色環の「赤紫」と「青紫」を使ったということことなのでしょうか?iOS 7では、iTunesが赤紫色になっています。紫自体が不安色なので、ちょっと気になるところです。

 

■機能については

Apple、「iOS 7」を発表――2013年秋にリリース – ITmedia Mobile を参照ってことで。iOS 7 的には XCode 5 を入れて試してみないといけない。今度はネットワーク廻りもきっちりと抑えていきたいので。

カテゴリー: 雑談 | 5件のコメント

Windowsストアアプリ開発入門 Visual C# 2012編が出来ました

苦節半年…ってことで、ようやく出来がりました。ひと目でわかるシリーズのWindows ストアアプリ版です。諸々の状況をかんがみて、C#版を先に出して、1か月後に同じ内容でVisual Basic版が出る予定です(ただいま校正中)。

image

日経BP書店|商品詳細 - ひと目でわかるWindowsストアアプリ開発入門 Visual C# 2012編
Amazon.co.jp: ひと目でわかるWindowsストアアプリ開発入門 Visual C# 2012編: 増田 智明: 本

Amazonでは、まだ予約受付中になっていますが。日経BP書店では既に買えるようです。今回は、DRMフリーの電子書籍版(PDF形式)も発売しますので、Surface RTに入れて(あるいはiPadに入れて)閲覧することも可能です。

目次をざざっとみるとわかるのですが、先発の本と内容がだぶらないようにしてあります。主にファイル回りを中心に細かく作っていきます。デザインの話とか画像を扱うところとかは、先発の本にお任せしておいて、フォロー(というかWindows 8+XAMLで変更された部分ってかなり多いので)というか、あまり網羅せずに解説をしています。そのあたりは、従来の「ひと目シリーズ」と同じで、ひとつのアプリを作っていくという形式で。

image

サンプルコードは、日経BPさんのページからダウンロードできるようになります(現在予約中なので、まだダウンロードできない模様)。

書籍で作成する「ひと目アルバム」は、Windowsストアからダウンロードが可能です。
http://apps.microsoft.com/windows/ja-JP/app/57d39fc9-7623-406f-9cc7-3c0b4171f33e
 
プロジェクトテンプレートの「グリッドアプリケーション」を使って、アイテムの追加や削除までサポートします。マイピクチャーやSkyDriveから画像の取り込み、メールアプリへの相互共有(共有コントラクト)までできるので、自作ツールの一歩としてはかなりのところまで踏み込めるかと。

image

な訳で、VB版の校正/原稿に戻ります…ぶくぶくぶく。

そうそう、6月の.NETラボ勉強会 では、この本のネタ元のお話と書籍を2冊持って行きますので、お楽しみに。COD 2012JP で詳しく話せなかったファイル廻りの詳細など。

カテゴリー: 書籍, WinRT | Windowsストアアプリ開発入門 Visual C# 2012編が出来ました はコメントを受け付けていません

COD2013JP の発表資料

Community Open Day 2013 の資料、数日前に準備しようと思っていたものの(デモプログラム自体は随分前に準備…というか、それ用に考えていたのですが)、プレゼン資料が出来上がったのは、前日に orz

去年は、Acer w500 に Windows 8 Pro を入れて、デバッグログのデモンストレーション(らしきもの)をやりましたので、今回は「自前で作るアプリ」というのをテーマに(勝手に自分でテーマ)、サンデープログラミング的に Surface RT を使おうッ!!! という話です。Windows 8 ストアアプリ/Surface RTは初心者だけど、Surface RT自体は上級者的に使い倒したいッ!!! ってことで、50分の中に4つのデモを入れています。

  • ホームグループを利用して、Surface RTで動画を流す。
  • 仮想環境を利用して、Surface RTで Kindle を動かす。
  • Surface RT で Jail Break してしまう。
  • 私家版アプリを作って、自分だけで楽しむ。

ってな具合です。詳細は明日(5/11) cod.ms の東京会場で。

資料はこちら サンデープログラミング SurfaceRT

カテゴリー: 勉強会 | COD2013JP の発表資料 はコメントを受け付けていません

[CakePHP] RESTを使ってViewをXML形式で返す

REST ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/development/rest.html
湘南社中テクニカルブログ: <b>Server</b>CakePHPでRESTのJSON APIを作成する
http://blog.shonanshachu.com/2012/01/cakephprestjson-api.html
Cakephp 2.x の REST API を作成してみる ≪ Hello My World
http://plmin.us/blog/2013/01/29/cakephp-2-x-rest-api/
CakePHP2.0で配列をXMLに変換し出力する | Code Life
http://code-life.net/?p=1096

簡単に言えば、HTTPのURLを指定して、戻り値をXMLやJSONで取得する。XML-PRCの場合は、送るときにXMLデータをPOSTするのだが、RESTの場合はURLを使う。いわゆる ASP.NET MVCのWeb APIと同じ。POSTやPUTなどを使うが、手軽のはPOSTのほう。メソッド名さえ揃えてしまえば、POSTオンリーでもよい気がする。

CakePHP には、もともとRESTの機能を持っていて、XMLで返すことが簡単にできる…はずなのだが、結構苦労する。というのも、

REST ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/development/rest.html

を見ると、REST の方法には、

  • _serialize を使う方法
  • xml/ フォルダを作る方法

と2つ載っているものの、_serialize の方法は動かない。正確に言うと、内部データを XML に変換しているときにこけてしまう…う~ん、これがバグなのかどうかはわからない。なので、xml/ フォルダに index.ctp 等の View を作って対応する。
新しい View を作ると便利なのが、元の View(ブラウザで開くときのview)を残しておくことができることで、_serialize を使うと、Controller 自身を書き換えてしまうことになるので、ブラウザからちょいちょいと修正したいときに不便。なので、ブラウザ用の view は残したままで、REST 用の view≒XMLを返す view を新しく追加する、という方式のほうが保守的によい。ただし、セキュリティ的にはブラウザ用の view もロックをしないといけない。データの更新機能は管理者レベルにしたいことが多いので、実運用の場合は別途ロックを掛ける必要がある。

■app/Config/routes.php を書き換える

app/Config/routes.php に Router::parseExtensions(); を追加する。Router::mapResources(‘recipes’); のようにテーブル名をマッピングするようにも書いてあるのだが、これがなくても動く。ちなみに、ドキュメントでは「require CAKE … よりも前に」となっているが、後ろでも動作する。が、先頭に書いておくのがベター。

//Router::mapResources('Stores');	// 無くてもok
Router::parseExtensions('xml');
...

■app/Controller/AppController.php を書き換える

本来は、RequestHandler を個別のControllerに入れるべきなのだが、数が多いので AppController に入れてしまう。こうすると全てのコントローラーとビューで xml/ フォルダへの routes が有効になる。

class AppController extends Controller {
	public $components = array('RequestHandler');
}

■xml/index.ctp を作る

View/Stores/ に xml というフォルダを作って、その中に index.ctp などを新しく作る。

+ View
 + Stores
  - index.ctp
  - view.ctp
  - edit.ctp
  - add.ctp
  + xml
   - index.ctp
   - view.ctp

こうすることで、http://localhost/Stores/ を呼び出したときは View/Stores/index.ctp を、http://localhost/Stores.xml を呼び出したときは View/Stores/xml//index.ctp が呼び出される。多分、xml は小文字(フォルダー名と同じ)にしないとダメだと思う。実験環境は windows 上なので、XML でも xml で通るのだが、Unix 環境だと区別すると思われる。

View/Stores/xml/index.ctp

$items = array('items'=
	array('item'=> $stores));
$xml = Xml::fromArray(array('response'=>$items));
echo $xml->asXML();

XML形式で返すように asXML メソッドを使う。Xml::fromArray へ配列を渡す形式は、

XML ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/core-utility-libraries/xml.html

を参照。CakePHP の 1.x と 2.x で渡すときの形式が違っているので注意が必要。おそらく _serialize の方法がダメなのは、ここを間違っていると思われ。

返す結果は、以下のように items タグの中に item が複数ある形式になる。「$stores」のところは、Controller で set している名前に随時変更する。

<response>
 <items>
  <item>
   <Store>
    <ID>1</ID>
    <SellingCompanyID>1</SellingCompanyID>
    <AreaGroupID>26</AreaGroupID>
...
   <Store>
  <item>
    <item>
...
    </item>
 </items>
</response>

view の場合は、$store を元に単数で返せば ok

<!--?php $item = array('item'=--> $store);
$xml = Xml::fromArray(array('response'=>$item));
echo $xml->asXML();

■実行してみる

□http://localhost/Stores の場合

□http://localhost/Stores.xml の場合

□http://localhost/Stores/view/1 の場合

□http://localhost/Stores/view/1.xml の場合

index.ctp と view.ctp 自体はコピーアンドペーストで作れるので、適当なスクリプトを作って増産しておけばよい。編集機能(edit/add と DELETE)の場合は、むやみに使われないようにフォーム認証(BASIC認証)と合わせて作ることになるので、別途 C# のクライアントを作って試してみる。

~~~

追記

元のコントローラーを使っているのでページング機能が有効になっています。なので、デフォルトでは最初の20件しか取れません。このデータ自体は1万件以上あるので、すべてを取ってきてもダメ、できれば limit を指定したいところですね。全ページ数を取得するかどうかはさておき、「http://localhost/Stores/index/page:2.xml」のようにすれば、2ページ目も取得できます。

指定ページ以上を取得しようとして page:999.xml のようにすると、404 のエラーが発生するので、例外が出ている模様。このあたりを気を付ければ全件を取得できる?

カテゴリー: CakePHP | [CakePHP] RESTを使ってViewをXML形式で返す はコメントを受け付けていません

[CakePHP] シンプルな認証を実装する(認証の中盤)

[CakePHP] シンプルな認証を実装する(認証の前半) – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4855

なところでフォーム認証ができたので「こんにちは、○○さん」だけ作ってみる。
下記のように、ログイン状態を表示して login/logout へのリンクもつける。

□ログインしていない状態
20130504_01

□ログイン状態

20130504_02

こんな風に、ログイン状態とリンクを表示させる。

■AppController.php を変更する

class AppController extends Controller {

    public $components = array(
        'Session',
        'Auth' => array(
        	// ログイン後に /Posts/index へジャンプ
        	'loginRedirect' => array('controller' => 'posts', 'action' => 'index'),
        	// ログアウト後に /Pages/home へジャンプ
			'logoutRedirect' => array('controller' => 'pages', 'action' => 'display', 'home'))
		);

    public function beforeFilter() {
    	// 各コントローラーの index と view を有効にする
        $this->Auth->allow('index', 'view');
        // 認証コンポーネントをViewで利用可能にしておく
        $this->set('auth',$this->Auth);
    }
}

*.ctp で直接表示することも考えたのだが、Controller 経由でないと Auth コンポーネントを扱えないので、VView で扱えるように AppController に $this->set(‘auth’, $this->Auth ); という形で View からも Auth を使えるようにしてしまう。スピード的にはどうなんだろう、と思うところもあるけれど、こうするとすべての view からログイン関係がアクセス可能になるので、コードが楽になる。

■View で login/logiout の表示を制御する

app/Posts/index.ctp

<div>
	<?php if ($auth->loggedIn()) : ?>
	<?php echo h($auth->user('username')); ?> さん、こんにちは <a href=&quot;/cakeu/Users/logout&quot;>logout</a>
	<?php else: ?>
	<a href=&quot;/cakeu/Users/login&quot;>login</a>
	<?php endif ?>
</div>
<div class=&quot;posts index&quot;>
	<h2><?php echo __('Posts'); ?></h2>
	<table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot;>
...

実際は「こんにちは○○さん」のところは、View のコンポーネントとして用意したほうがいいと思う。そうしないと、あちこちに View に同じコードが散らばるし、あとからフォーマットを変えようと思ってえらいことになってしまう。login 状態による分岐なので、Controller でレンダリングする方法もあるのだが、「こんにちは」の部分とか、login, logout へのリンクは view の範疇になるので、Controller に押し込むとちょっと変な形になりそう。ってことで View に。

ログインしているかどうかは $this->Auth->loggedIn() でしらべて、 $this->auth->user(‘username’) でユーザ名を拾ってくる。username 自体はデータベースの列名そのままなので、ニックネームとかロールとかも拾える。

そんな訳で続きは、ユーザ名やロールによって処理を分ける方式を。

カテゴリー: CakePHP | 1件のコメント

[CakePHP] シンプルな認証を実装する(認証の前半)

認証(ログインとログアウト)
http://book.cakephp.org/2.0/ja/tutorials-and-examples/blog-auth-example/auth.html#id3

のところを追加していく。チュートリアルではいきなり、AppController を書き換えてしまっているが、一度ログイン認証を実装してから、beforeFilter あたりを追加したほうが分かりやすいかも。実は「ログインした状態」を判別するのと、「ログインしたユーザーが操作ができるか」というのを判別するのとは別のことになる。例えば、ログインユーザーの名称をページに表示するために「ログインしているかどうか」の状態は必要かもしれないが、「ログインしたユーザーが操作できるか」は必要ない。操作が可能かどうかの判別は必要なのは、操作を拒否するときに限られるので。

公開するアクションの作成
http://book.cakephp.org/2.0/ja/core-libraries/components/authentication.html#id20

を見ると、全体が拒否(deny)の状態から、徐々に許可(allow)していくパターンになる。本来は、このほうが良いのだが、一部分だけ管理したい(denyしたい)場合がある。この場合は、$this->Auth->allow(); ですべてを許可してから、部分的に $this->Auth->deny(…); していくのだと思う。

[CakePHP] シンプルな認証を実装する(準備) – Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4837

の続きとして、フォーム認証から実装しよう。

チュートリアルの通りに、User に対して認証を使ってみる。

■User にログイン/ログアウトを実装する

app/Controller/AppController.php に $components を追加する。

class AppController extends Controller {

    public $components = array(
        'Session',
        'Auth' );
}

こうすることで、$this->Session と $this->Auth が使えるようになる。

app/Controller/UsersController.php に、login と logout を追加する。

class UsersController extends AppController {
/**
 * ログイン
 */
public function login() {
    if ($this->request->is('post')) {
        if ($this->Auth->login()) {
            $this->redirect($this->Auth->redirect());
        } else {
            $this->Session->setFlash(__('Invalid username or password, try again'));
        }
    }
}

/**
 * ログアウト
 */
public function logout() {
    $this->redirect($this->Auth->logout());
}
...

app/View/Users/login.ctp を新規作成する。

<div class=&quot;users form&quot;>
<?php echo $this->Session->flash('auth'); ?>
<?php echo $this->Form->create('User'); ?>
    <fieldset>
        <legend><?php echo __('Please enter your username and password'); ?></legend>
        <?php echo $this->Form->input('username');
        echo $this->Form->input('password');
    ?>
    </fieldset>
<?php echo $this->Form->end(__('Login')); ?>
</div>

app/Model/User.php で、保存時にパスワードをハッシュ化する。

App::uses('AppModel', 'Model');
App::uses('AuthComponent', 'Controller/Component');	// コンポーネントを追加
/**
 * User Model
 *
 */
class User extends AppModel {
/**
 * 保存時にパスワードをハッシュ化する
 */
	public function beforeSave($options = array()) {
	    if (isset($this->data[$this->alias]['password'])) {
	        $this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
	    }
	    return true;
	}
}

$this->Auth->login() では、ユーザー名とパスワードをチェックするのだが、パスワードはハッシュ値を使っている。なので、Model/User でパスワードを保存するときにハッシュ化しておく。これ、もともとのテーブルがハッシュ化してあればそれがいいんだけど、直書きの場合とか別な暗号化を行っている場合はどうなるんだろうか?ってのを調査しないと。

多分、

カスタム認証オブジェクトの作成
http://book.cakephp.org/2.0/ja/core-libraries/components/authentication.html#id5

あたりを使えばいけると思うのだが。

■試しに実行してみる

これを実行してみて http://localohst:81/cakeu/users/ にアクセスしてユーザーの一覧を表示させようとすると。

な風に、/users/login に飛ばされる。これは正しい操作で、Auth をインストールした時点で、ログインしていないユーザーは全ページのアクセスを禁止されているので、こうなる。デフォルトの設定として、ログインできていない場合は、/users/login に飛ばされるようになっているので、こんな動きになります。

で、通常の場合は、

  • ログインしていない状態でも Post の一覧(index)が見たい。
  • ログインしていない状態でも User の Add ができたい。
  • ログインしたら、なんらかのページにジャンプする。
  • ログアウトしたら、なんらかのページにジャンプする。

というのが必要な訳で、これを CakePHP のチュートリアルのように書き換えます。

■ログインしていない状態の設定

/Controller/AppController.php の beforeFilter を書き換えます。

class AppController extends Controller {

    public $components = array(
        'Session',
        'Auth' => array(
        	// ログイン後に /Posts/index へジャンプ
        	'loginRedirect' => array('controller' => 'posts', 'action' => 'index'),
        	// ログアウト後に /Pages/home へジャンプ
			'logoutRedirect' => array('controller' => 'pages', 'action' => 'display', 'home'))
		);

    public function beforeFilter() {
    	// 各コントローラーの index と view を有効にする
        $this->Auth->allow('index', 'view');
    }
}

Auth->allow メソッドは、公開するメソッドを羅列するので、この場合は「全コントローラーに対して index と view が有効になる」という設定になります。なので、Model ごとに細かい設定をすることも必要になり、User の add メソッドだけ有効(ユーザーを追加する)にするためには、/Controller/UsersController.php に beforeFilter を追加する必要があります。

/Controller/UsersController.php の beforeFilter を追加する。

class UsersController extends AppController {

	public function beforeFilter() {
	    parent::beforeFilter();
	    // ユーザー自身が登録できるようにする
	    $this->Auth->allow('add');
	}

これで、ログインしていないユーザーは、

  • /Users/
  • /Users/view
  • /Users/add
  • /Users/login
  • /Users/logout
  • /Posts/
  • /Posts/view

にアクセスできるようになります。login, logout はデフォルト値なので、これが変更になったときは別途設定が必要。

■改めてアクセスしてみる

今度は http://localohst:81/cakeu/users/ にアクセスしてユーザーの一覧が表示できます。

「List Posts」ボタンを押すと、/posts/ が表示できます。

編集しようと思って「Edit」ボタンを押すと、ログインの画面に強制的にジャンプします。

■ログインユーザーを作ってアクセスする

/Posts/edit などがアクセス禁止になっていることが分かったので、/users/add で、ログインユーザーを作ってアクセスしてみる。

Role は一般ユーザーということで「Users」としておきます。今回の users テーブルでは Role は自由に設定できるので、想定として「Administrators(管理者)」と「Users(制限ユーザー)」のようなイメージにしておけば ok かと。ブログの場合は「Author」とか「Editor」を使えばわかりやすい。

submit すると、/users/ へジャンプする。

この状態では、まだログインはしていないので、あらためて、/users/login にアクセスするか、アクセス禁止の Edit ボタンを押して、ログインの画面にジャンプ。

うまくログインができると、/pages/home にジャンプする。実はこのページはルートのページになるので、http://localhost:81/cakeu/ にアクセスした時と同じページがでる。

※実は、ここに落とし穴があって、ログインしていないユーザーが http://localhost:81/cakeu/ のようにルートにアクセスするとログイン画面に飛ばされる。SNS の場合にはそれでもいいんだけど、ブログのような場合は、ページ一覧 /posts/ に飛んで欲しかったりするよね。そんな場合は、Controller/AppController.php で Auth のところで authError を追加すればいいはずなんですが…うまくいきませんね。これは後で調査。

■ユーザー情報を使う

ログインしたことは確認できたので、ログイン情報を使って定番の「こんにちは○○さん」を出してみます。これはチュートリアルにないので、オリジナル的に…ってのは次で。

カテゴリー: CakePHP | [CakePHP] シンプルな認証を実装する(認証の前半) はコメントを受け付けていません

[CakePHP] シンプルな認証を実装する(準備)

シンプルな認証と承認のアプリケーション ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/tutorials-and-examples/blog-auth-example/auth.html

を利用して、フォーム認証(普通のユーザー名とパスワードを使ったログイン)を実装する。

というか、チュートリアルのままで動かしてみるというテスト。

  1. テーブルを作る。
  2. cake bake して、CRUD を作る。
  3. ログイン認証部分を作る。
  4. ロールとユーザーでアクセスを制御する。

の流れになる。

■必要なテーブル

テストするための最低限のテーブルを用意する。

ブログチュートリアル ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/tutorials-and-examples/blog/blog.html

□posts テーブル

/* まず、postsテーブルを作成します: */
CREATE TABLE posts (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(50),
    body TEXT,
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);
/* それから、テスト用に記事をいくつか入れておきます: */
INSERT INTO posts (title,body,created)
    VALUES ('タイトル', 'これは、記事の本文です。', NOW());
INSERT INTO posts (title,body,created)
    VALUES ('またタイトル', 'そこに本文が続きます。', NOW());
INSERT INTO posts (title,body,created)
    VALUES ('タイトルの逆襲', 'こりゃ本当にわくわくする!うそ。', NOW());

□users テーブル

CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50),
    password VARCHAR(50),
    role VARCHAR(20),
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);

※規約にしたがって、複数形「posts」と「users」で用意すると、$useTable しなくて済む…が、既存のテーブルを使っても可能。

※列名を created, modified としておくと、自動で作成日時、更新日時が入る。手作業でやる場合は別途調査。

■Config/database.php を書き換える

cake bake がデータベースに接続できるように detabase.php を書き換えておく。

class DATABASE_CONFIG {
	public $default = array(
		'datasource' => 'Database/Mysql',
		'persistent' => false,
		'host' => 'localhost',
		'login' => 'user',
		'password' => 'password',
		'database' => 'test_database_name',
		'prefix' => '',
		'encoding' => 'utf8',
	);
}

■cake bake all で MVC を作成する

Consolecake bake all で Post と User の MVC を作成する。

※面倒がなければ、phpunit のひな形も作成しまえばok。

■IE で確認

http://localhost:81/cakeu/Posts
http://localhost:81/cakeu/Users

にアクセスをして確認(フォルダ名とポートは適宜変更)


この状態で、自由に Post と User が編集できる状態になる。

■できあがった MVC

cake bake all すると同じものができるけど、あとから参照できるの自動生成される MVC ファイルを貼り付けておきます。ソースコードを見ると、$this->request->is(‘post’) な REST の部分とか、$this->Session->setFlash なセッション部分とかが生成されていて、御影上ややこしくなっているけど、マスターテーブルの編集なんかはこれで十分なので、そのまま活用してもよいと思う。

□Model/Post.php

<?php
App::uses('AppModel', 'Model');
/**
 * Post Model
 *
 */
class Post extends AppModel {

}
&#91;/code&#93;
<p>
※リンクとかしてないので空っぽ
</p>
<p>
□Controller/PostController.php
[code lang="php"]
<?php
App::uses('AppController', 'Controller');
/**
 * Posts Controller
 *
 * @property Post $Post
 */
class PostsController extends AppController {

/**
 * index method
 *
 * @return void
 */
	public function index() {
		$this->Post->recursive = 0;
		$this->set('posts', $this->paginate());
	}

/**
 * view method
 *
 * @throws NotFoundException
 * @param string $id
 * @return void
 */
	public function view($id = null) {
		if (!$this->Post->exists($id)) {
			throw new NotFoundException(__('Invalid post'));
		}
		$options = array('conditions' => array('Post.' . $this->Post->primaryKey => $id));
		$this->set('post', $this->Post->find('first', $options));
	}

/**
 * add method
 *
 * @return void
 */
	public function add() {
		if ($this->request->is('post')) {
			$this->Post->create();
			if ($this->Post->save($this->request->data)) {
				$this->Session->setFlash(__('The post has been saved'));
				$this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The post could not be saved. Please, try again.'));
			}
		}
		$users = $this->Post->User->find('list');
		$this->set(compact('users'));
	}

/**
 * edit method
 *
 * @throws NotFoundException
 * @param string $id
 * @return void
 */
	public function edit($id = null) {
		if (!$this->Post->exists($id)) {
			throw new NotFoundException(__('Invalid post'));
		}
		if ($this->request->is('post') || $this->request->is('put')) {
			if ($this->Post->save($this->request->data)) {
				$this->Session->setFlash(__('The post has been saved'));
				$this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The post could not be saved. Please, try again.'));
			}
		} else {
			$options = array('conditions' => array('Post.' . $this->Post->primaryKey => $id));
			$this->request->data = $this->Post->find('first', $options);
		}
		$users = $this->Post->User->find('list');
		$this->set(compact('users'));
	}

/**
 * delete method
 *
 * @throws NotFoundException
 * @param string $id
 * @return void
 */
	public function delete($id = null) {
		$this->Post->id = $id;
		if (!$this->Post->exists()) {
			throw new NotFoundException(__('Invalid post'));
		}
		$this->request->onlyAllow('post', 'delete');
		if ($this->Post->delete()) {
			$this->Session->setFlash(__('Post deleted'));
			$this->redirect(array('action' => 'index'));
		}
		$this->Session->setFlash(__('Post was not deleted'));
		$this->redirect(array('action' => 'index'));
	}
}

□Model/User.php

<?php
App::uses('AppModel', 'Model');
/**
 * User Model
 *
 */
class User extends AppModel {

}
&#91;/code&#93;
<p>
※Post と同じようにリンクとかしてないので空っぽ
</p>
※外部キーを使うと、Post と User がリンクするようになる
</p>
<p>
□Controller/UserController.php
[code lang="php"]
<?php
App::uses('AppController', 'Controller');
/**
 * Users Controller
 *
 * @property User $User
 */
class UsersController extends AppController {

/**
 * index method
 *
 * @return void
 */
	public function index() {
		$this->User->recursive = 0;
		$this->set('users', $this->paginate());
	}

/**
 * view method
 *
 * @throws NotFoundException
 * @param string $id
 * @return void
 */
	public function view($id = null) {
		if (!$this->User->exists($id)) {
			throw new NotFoundException(__('Invalid user'));
		}
		$options = array('conditions' => array('User.' . $this->User->primaryKey => $id));
		$this->set('user', $this->User->find('first', $options));
	}

/**
 * add method
 *
 * @return void
 */
	public function add() {
		if ($this->request->is('post')) {
			$this->User->create();
			if ($this->User->save($this->request->data)) {
				$this->Session->setFlash(__('The user has been saved'));
				$this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
			}
		}
	}

/**
 * edit method
 *
 * @throws NotFoundException
 * @param string $id
 * @return void
 */
	public function edit($id = null) {
		if (!$this->User->exists($id)) {
			throw new NotFoundException(__('Invalid user'));
		}
		if ($this->request->is('post') || $this->request->is('put')) {
			if ($this->User->save($this->request->data)) {
				$this->Session->setFlash(__('The user has been saved'));
				$this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
			}
		} else {
			$options = array('conditions' => array('User.' . $this->User->primaryKey => $id));
			$this->request->data = $this->User->find('first', $options);
		}
	}

/**
 * delete method
 *
 * @throws NotFoundException
 * @param string $id
 * @return void
 */
	public function delete($id = null) {
		$this->User->id = $id;
		if (!$this->User->exists()) {
			throw new NotFoundException(__('Invalid user'));
		}
		$this->request->onlyAllow('post', 'delete');
		if ($this->User->delete()) {
			$this->Session->setFlash(__('User deleted'));
			$this->redirect(array('action' => 'index'));
		}
		$this->Session->setFlash(__('User was not deleted'));
		$this->redirect(array('action' => 'index'));
	}
}

以下は、View

□View/Posts/index.ctp

<div class="posts index">
	<h2><?php echo __('Posts'); ?></h2>
	<table cellpadding="0" cellspacing="0">
	<tr>
			<th><?php echo $this->Paginator->sort('id'); ?></th>
			<th><?php echo $this->Paginator->sort('title'); ?></th>
			<th><?php echo $this->Paginator->sort('body'); ?></th>
			<th><?php echo $this->Paginator->sort('created'); ?></th>
			<th><?php echo $this->Paginator->sort('modified'); ?></th>
			<th class="actions"><?php echo __('Actions'); ?></th>
	</tr>
	<?php foreach ($posts as $post): ?>
	<tr>
		<td><?php echo h($post&#91;'Post'&#93;&#91;'id'&#93;); ?>&nbsp;</td>
		<td><?php echo h($post&#91;'Post'&#93;&#91;'title'&#93;); ?>&nbsp;</td>
		<td><?php echo h($post&#91;'Post'&#93;&#91;'body'&#93;); ?>&nbsp;</td>
		<td><?php echo h($post&#91;'Post'&#93;&#91;'created'&#93;); ?>&nbsp;</td>
		<td><?php echo h($post&#91;'Post'&#93;&#91;'modified'&#93;); ?>&nbsp;</td>
		<td class="actions">
			<?php echo $this->Html->link(__('View'), array('action' => 'view', $post['Post']['id'])); ?>
			<?php echo $this->Html->link(__('Edit'), array('action' => 'edit', $post['Post']['id'])); ?>
			<?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $post['Post']['id']), null, __('Are you sure you want to delete # %s?', $post['Post']['id'])); ?>
		</td>
	</tr>
<?php endforeach; ?>
	</table>
	<p>
	<?php
	echo $this->Paginator->counter(array(
	'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
	));
	?>	</p>
	<div class="paging">
	<?php
		echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
		echo $this->Paginator->numbers(array('separator' => ''));
		echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
	?>
	</div>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('New Post'), array('action' => 'add')); ?></li>
		<li><?php echo $this->Html->link(__('List Users'), array('controller' => 'users', 'action' => 'index')); ?> </li>
		<li><?php echo $this->Html->link(__('New User'), array('controller' => 'users', 'action' => 'add')); ?> </li>
	</ul>
</div>

□View/Posts/view.ctp

<div class="posts view">
<h2><?php  echo __('Post'); ?></h2>
	<dl>
		<dt><?php echo __('Id'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'id'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Title'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'title'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Body'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'body'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Created'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'created'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Modified'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'modified'&#93;); ?>
			&nbsp;
		</dd>
	</dl>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('Edit Post'), array('action' => 'edit', $post['Post']['id'])); ?> </li>
		<li><?php echo $this->Form->postLink(__('Delete Post'), array('action' => 'delete', $post['Post']['id']), null, __('Are you sure you want to delete # %s?', $post['Post']['id'])); ?> </li>
		<li><?php echo $this->Html->link(__('List Posts'), array('action' => 'index')); ?> </li>
		<li><?php echo $this->Html->link(__('New Post'), array('action' => 'add')); ?> </li>
		<li><?php echo $this->Html->link(__('List Users'), array('controller' => 'users', 'action' => 'index')); ?> </li>
		<li><?php echo $this->Html->link(__('New User'), array('controller' => 'users', 'action' => 'add')); ?> </li>
	</ul>
</div>

□View/Posts/add.ctp

<div class="posts form">
<?php echo $this->Form->create('Post'); ?>
	<fieldset>
		<legend><?php echo __('Add Post'); ?></legend>
	<?php
		echo $this->Form->input('title');
		echo $this->Form->input('body');
	?>
	</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>

		<li><?php echo $this->Html->link(__('List Posts'), array('action' => 'index')); ?></li>
	</ul>
</div>

□View/Posts/edit.ctp

<div class="posts form">
<?php echo $this->Form->create('Post'); ?>
	<fieldset>
		<legend><?php echo __('Edit Post'); ?></legend>
	<?php
		echo $this->Form->input('id');
		echo $this->Form->input('title');
		echo $this->Form->input('body');
	?>
	</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>

		<li><?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $this->Form->value('Post.id')), null, __('Are you sure you want to delete # %s?', $this->Form->value('Post.id'))); ?></li>
		<li><?php echo $this->Html->link(__('List Posts'), array('action' => 'index')); ?></li>
	</ul>
</div>

□View/Users/index.ctp

<div class="users index">
	<h2><?php echo __('Users'); ?></h2>
	<table cellpadding="0" cellspacing="0">
	<tr>
			<th><?php echo $this->Paginator->sort('id'); ?></th>
			<th><?php echo $this->Paginator->sort('username'); ?></th>
			<th><?php echo $this->Paginator->sort('password'); ?></th>
			<th><?php echo $this->Paginator->sort('role'); ?></th>
			<th><?php echo $this->Paginator->sort('created'); ?></th>
			<th><?php echo $this->Paginator->sort('modified'); ?></th>
			<th class="actions"><?php echo __('Actions'); ?></th>
	</tr>
	<?php foreach ($users as $user): ?>
	<tr>
		<td><?php echo h($user&#91;'User'&#93;&#91;'id'&#93;); ?>&nbsp;</td>
		<td><?php echo h($user&#91;'User'&#93;&#91;'username'&#93;); ?>&nbsp;</td>
		<td><?php echo h($user&#91;'User'&#93;&#91;'password'&#93;); ?>&nbsp;</td>
		<td><?php echo h($user&#91;'User'&#93;&#91;'role'&#93;); ?>&nbsp;</td>
		<td><?php echo h($user&#91;'User'&#93;&#91;'created'&#93;); ?>&nbsp;</td>
		<td><?php echo h($user&#91;'User'&#93;&#91;'modified'&#93;); ?>&nbsp;</td>
		<td class="actions">
			<?php echo $this->Html->link(__('View'), array('action' => 'view', $user['User']['id'])); ?>
			<?php echo $this->Html->link(__('Edit'), array('action' => 'edit', $user['User']['id'])); ?>
			<?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $user['User']['id']), null, __('Are you sure you want to delete # %s?', $user['User']['id'])); ?>
		</td>
	</tr>
<?php endforeach; ?>
	</table>
	<p>
	<?php
	echo $this->Paginator->counter(array(
	'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
	));
	?>	</p>
	<div class="paging">
	<?php
		echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
		echo $this->Paginator->numbers(array('separator' => ''));
		echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
	?>
	</div>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('New User'), array('action' => 'add')); ?></li>
		<li><?php echo $this->Html->link(__('List Posts'), array('controller' => 'posts', 'action' => 'index')); ?> </li>
		<li><?php echo $this->Html->link(__('New Post'), array('controller' => 'posts', 'action' => 'add')); ?> </li>
	</ul>
</div>

※何回かテストしているので ID が 1 から始まってない。

※パスワードは、そのまま書き込まれている。これは後でハッシュ値にする。

□View/Users/view.ctp

<div class="posts view">
<h2><?php  echo __('Post'); ?></h2>
	<dl>
		<dt><?php echo __('Id'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'id'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Title'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'title'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Body'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'body'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Created'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'created'&#93;); ?>
			&nbsp;
		</dd>
		<dt><?php echo __('Modified'); ?></dt>
		<dd>
			<?php echo h($post&#91;'Post'&#93;&#91;'modified'&#93;); ?>
			&nbsp;
		</dd>
	</dl>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('Edit Post'), array('action' => 'edit', $post['Post']['id'])); ?> </li>
		<li><?php echo $this->Form->postLink(__('Delete Post'), array('action' => 'delete', $post['Post']['id']), null, __('Are you sure you want to delete # %s?', $post['Post']['id'])); ?> </li>
		<li><?php echo $this->Html->link(__('List Posts'), array('action' => 'index')); ?> </li>
		<li><?php echo $this->Html->link(__('New Post'), array('action' => 'add')); ?> </li>
	</ul>
</div>

□View/Users/add.ctp

<div class="posts form">
<?php echo $this->Form->create('Post'); ?>
	<fieldset>
		<legend><?php echo __('Add Post'); ?></legend>
	<?php
		echo $this->Form->input('title');
		echo $this->Form->input('body');
	?>
	</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>

		<li><?php echo $this->Html->link(__('List Posts'), array('action' => 'index')); ?></li>
	</ul>
</div>

□View/Users/edit.ctp

<div class="posts form">
<?php echo $this->Form->create('Post'); ?>
	<fieldset>
		<legend><?php echo __('Edit Post'); ?></legend>
	<?php
		echo $this->Form->input('id');
		echo $this->Form->input('title');
		echo $this->Form->input('body');
	?>
	</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>

		<li><?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $this->Form->value('Post.id')), null, __('Are you sure you want to delete # %s?', $this->Form->value('Post.id'))); ?></li>
		<li><?php echo $this->Html->link(__('List Posts'), array('action' => 'index')); ?></li>
	</ul>
</div>

この状態から、

認証(ログインとログアウト)
http://book.cakephp.org/2.0/ja/tutorials-and-examples/blog-auth-example/auth.html#id3

を追加していく。

カテゴリー: CakePHP | [CakePHP] シンプルな認証を実装する(準備) はコメントを受け付けていません

[CakePHP] セッションを利用する

もともと PHP には $_SESSION があるので、それを利用すればよいのだが、CakePHP は $this->Session というう形で用意されている。

セッション ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/core-libraries/components/sessions.html

残念ながら、$_SESSION と $this->Session と互換性はない模様。他のモジュールとのやり取りをするならば、$_SESSION を使うと良い。

■Model

Model はそのまま

■Controller

sess メソッドで id を保存しておいて、index で検索結果を出すという具合。

in Controller/TestController.php

	public function index($id=null) {
		if ($id==null) {
			// セッション内のIDを使う
			$id = $this->Session->read(&quot;Test.id&quot;);
			// session_start();
			// $id = $_SESSION['Test.id'];
		}
		if (isset($this->params['url']['id']))
			$id = $this->params['url']['id'];

		if ( $id != null ) {
			$this->set('Test',$this->Test->findAllById($id));
		} else {
			$this->set('Test',$this->Test->find('all'));
		}
	}

 

	// セッションを保存
	public function sess($id=null) {
		$this->Session->write(&quot;Test.id&quot;,$id);
		// session_start();
		// $_SESSION['Test.id'] = $id ;
		$this->redirect('.');
	}

/Test/sess/7 で URL を呼び出すと、/Test/index/7 と同じような呼び出しになる(内部で redirect している)。このときに、sess から index に渡すために $this->Session を使っている。このパターンだと、直接メソッド呼び出しをしたほうが安全か…まあ、URL が /Test/ になるので、それはそれで ok(アドレスが隠蔽化されるという意味で)。

$_SESSION[‘Test.id’] でも同じことができる。残念ながら、$_SESSION[‘Test.id’] と $this->Session->read(“Test.id”); は異なる値を示している。これは名前を「id」とかにしてもダメだった。

■View

View は同じ

■結果

/Test/sess/7 で呼び出すと、$this->Session に保存されて index で参照される。

カテゴリー: CakePHP | [CakePHP] セッションを利用する はコメントを受け付けていません

[CakePHP] トランザクションを利用する

トランザクションを利用する…が、WEB系の場合は、ワンコールのトランザクションしか使えないので、複数のコール/Web APIにまたがる場合は、2フェーズコミットの方法を取る。まあ、チケット予約とかのように普通はダーティコミットの方式がパフォーマンスがよい。最後にチェックを入れて、整合性がなければ/既に予約済みだとかの場合は、エラーにしてしまう方式。社内業務の場合は、それほどパフォーマンスを必要としない or 途中の入力を破棄されるほうが痛い場合は、一時入力を保持する方式でセッションを作る。

ここはワンコールのトランザクションで。

■Model

Model/AppModel.php を書き換え

class AppModel extends Model {
	var $db ;
	function begin()
	{
	    $db = ConnectionManager::getDataSource($this->useDbConfig);
	    $db->begin($this);
	}
	   
	function commit()
	{
	    $db = ConnectionManager::getDataSource($this->useDbConfig);
	    $db->commit($this);
	}
	   
	function rollback()
	{
	    $db = ConnectionManager::getDataSource($this->useDbConfig);
	    $db->rollback($this);
	}
}

コネクションが Model に付属するので、$this->useDbConfig も Model 単位になってしまうという罠があるので、多人数で使う場合には考慮が必要。ただ、大抵の場合はワンコネクションで済ませてしまうので、わざわざマルチコネクションを利用してない場合には特に問題にならない。

CakePHPで複数テーブルに対するトランザクションを使う場合 – takami_hirokiの日記
http://d.hatena.ne.jp/takami_hiroki/20101109/p1

のように ADO.NET 風に Transaciton クラスを別に用意したほうがよいかも。

■Controller

ためしに、delete で commit/rollback してみる。

in Controller/TestController.php

	public function delete($id=null) {
		if (isset($this->params['url']['id'])) $id = $this->params['url']['id'];
		if ( $id != null ) {

			// トランザクション開始
		    $this->Test->begin();
    		// 処理を書く
			// $this->Test->read(null, $id);
			$this->Test->delete( $id );
			
			// コミット or ロールバック
    		$this->Test->rollback();
    		// $this->Test->commit();
   		}
		$this->set('Test',$this->Test->find('all'));
	}

コネクションが $this->Test に属しているのだが、$this->Store でも同じ。ConnectionManager::getDataSource($this->useDbConfig); なので。

■View

View は同じ

■結果

削除はロールバックされて、更新されない。

※クエリを見ると、「BEGIN」しかないけど大丈夫なのか?動作的には問題ない。

カテゴリー: CakePHP | 2件のコメント