mirror http://develop.ddo.jp/symfony/book/jp/1.0/my_first_project_jp.html
私の最初の symfony プロジェクト
警告: このチュートリアルは symfonyの1.0.0かそれ以降のバージョンが対象です
symfonyを試してみたいですか? では、1時間で完全に動作するウェブアプリケーションを一緒に作成してみましょう。 まず、そのアプリケーションに名前をつけましょうか。 ブックセラー(bookseller)という名前はどうですか? そうですね、別の名前を。。weblogにしましょう!
ローカルホストに apache と PHP5 がインストールされてきちんと動作していることを想定してます。そして、SQLiteエクステンションも必要です、これはデフォルトのPHP5に含まれています。しかしながら、PHP 5.1.0であれば、手動で php.ini においてエクステンションが有効にする必要があります。
symfony のインストールとプロジェクトの初期化
手早く行うために、symfony のsandbox版を使いましょう (最終的なソースコードがダウンロードできます)。 それには必要な全てのライブラリが含まれた空のsymfonyプロジェクトで、既に基本的な設定が行われています。sandboxの他のインストールと比べた最大の利点は symfony ですぐに試用できることです。
ここからsf_sandbox.tgzを取得してください。そして、あなたのウェブルートディレクトリで解凍してください。添付されているREADMEファイルにさらに詳しくかかれてあります。最終的なファイル構造は次のようになります。
www/
sf_sandbox/
apps/
frontend/
batch/
cache/
config/
data/
sql/
doc/
lib/
model/
log/
plugins/
test/
web/
css/
images/
js/
sf_sandbox プロジェクトが frontend アプリケーションを含んでいることを表しています。sandboxを次のURLをたたくことでテストしてみましょう。
http://localhost/sf_sandbox/web/index.php/
次のようなコングラチュレーションページを見れますね。
別のフォルダーにsymfonyをインストールすることも、バーチャルホストやエイリアス設定のサーバーの設定を行ったウェブサーバーでセットアップすることもできます。symfony book には symfony のインストール、プロジェクト作成、そしてファイル構造についての詳細な説明が含まれています。
データモデルの初期化
ここで、weblogアプリが投稿記事やそれらに行われたコメントを操作します。sf_sandbox/config/ に schema.yml ファイルを作成し、次のデータモデルを貼り付けてください。
propel:
weblog_post:
_attributes: { phpName: Post }
id:
title: varchar(255)
excerpt: longvarchar
body: longvarchar
created_at:
weblog_comment:
_attributes: { phpName: Comment }
id:
post_id:
author: varchar(255)
email: varchar(255)
body: longvarchar
created_at:
この設定ファイルは YAML 構文を利用しています。とてもシンプルな言語でXMLのようなツリー構造をインデントによって表現することができます。さらに、XMLより読み書きが速いです。たった1つ、インデントは意味を持っていてタブは禁止されているので、スペースをインデントとして使うことを覚えておいてください。YAMLについてとsymfonyの設定については設定の章を参照してください。
このスキーマは weblog において2つのテーブルが必要とされている構造であるということを表しています。Post と Comment が生成される関連するクラスの名前です。ファイルを保存し、コマンドラインを開き、sf_sandbox/ ディレクトリで次のように入力してください。
$ php symfony propel-build-model
注意: symfonyコマンドを呼ぶときは プロジェクトのルート (sf_sandobx/) であることを確認してください。
複数のクラスが sf_sandbox/lib/model/ ディレクトリに作成されます。 これらは オブジェクト-リレーションマッピングのクラスで、リレーションデータベースにSQLのクエリーを書かずにオブジェクト指向でアクセスすることができます。symfony は Propel ライブラリをこの目的のために利用しています。これらのオブジェクトをモデルと呼んでいます(詳細はモデルの章を見てください)。
ここで、コマンドラインで次のように入力してください。
$ php symfony propel-build-sql
lib.model.schema.sql ファイルが sf_sandbox/data/sql/ に作成されます。このSQLクエリは同じテーブル構造でデータベースを初期化するときに利用されます。コマンドラインやウェブインターフェースでMySQLにデータベースを作成することができます(modelの章に書かれてあります)。幸いにも、symfony の sandbox はシンプルなSQLiteファイルで動作するように構成されています。そのため、データベースの初期化は必要ありません。デフォルトで、sf_sandbox プロジェクトは sf_sandbox/data/ディレクトリに置かれている sandbox.db と呼ばれるデータベースを使います。SQLファイルに基づくテーブル構造を作成するために、次のように入力します。
$ php symfony propel-insert-sql
注意: この時点で警告がでても気にしないでください。正常な動作です。 insert-sql コマンドは lib.model.schema.sql のテーブルを追加する前に、既存するテーブルを削除します。そして、この時点では削除するテーブルがないからです。
アプリケーションの足場(scaffolding)を生成する
weblogの基本的な機能は投稿記事とコメント記事の新規作成(Create)、取得(Retrieve)、更新(Update)そして削除(Delete)の CRUDです。symfonyにこれらを作成するために、スクラッチでsymfonyのコードを書く必要がありません。必要があれば修正するという足場(scaffolding)を生成するのです。symfonyは自動的にCRUDインターフェースを生成するためにデータモデルを解釈することができるのです。
$ php symfony propel-generate-crud frontend post Post
$ php symfony propel-generate-crud frontend comment Comment
$ php symfony clear-cache
On *nix systems, you will have to change some rights:
$ chmod 777 data
$ chmod 777 data/sandbox.db
ここでPostとCommentクラスを操作するための2つのモデル(postとcomment)を持っています。モジュールは通常、単一のページや同じような目的の複数のページを代表しています。あなたの新しいモジュールは sf_sandbox/apps/frontend/modules/ ディレクトリに作成され、次のようなURLでアクセスできます。
http://localhost/sf_sandbox/web/frontend_dev.php/post
http://localhost/sf_sandbox/web/frontend_dev.php/comment
自由に新しい投稿記事を作成したりしてみてください。
より詳しい足場についてとsymfony のプロジェクト構造(プロジェクト、アプリケーション、モジュール)についての説明を見てください。
注意: 上記のURLで、メインスクリプト - symfonyではフロントコントローラーと呼んでます - の名前がindex.php から frontend_dev.php に変更されています。この2つのスクリプトは同じアプリケーション(frontend)へアクセスします。しかし、環境が異なります。frontend_dev.phpでは、開発環境のアプリケーションへアクセスすることができ、開発環境においては、画面右上にあるデバッグツールバーのような開発ツールが提供されていています。そういうわけで、各ページのレスポンスはindex.phpという製品環境のレスポンス速度が改良されているコントローラーを使うよりも多少遅いでしょう。もし製品環境を使いたいなら、URL中の frontend_dev.php/ を index.php/ で置き換えてください。しかし、変更を確認するまえに、キャッシュをクリアすることを忘れないでください。
環境についてのページで詳細を見てください。
レイアウトを修正
2つのモジュール間をナビゲートするために、weblogアプリケーションはグローバルナビゲーションが必要です。
グローバルテンプレートである sf_sandbox/apps/frontend/templates/layout.php を編集し、
タグの内容を次のように変更します。[php]
<div id="container" style="width:600px;margin:0 auto;border:1px solid grey;padding:10px">
<div id="navigation" style="display:inline;float:right">
<ul>
<li><?php echo link_to('List of posts', 'post/list') ?></li>
<li><?php echo link_to('List of comments', 'comment/list') ?></li>
</ul>
</div>
<div id="title">
<h1><?php echo link_to('My first symfony project', '@homepage') ?></h1>
</div>
<div id="content" style="clear:right">
<?php echo $sf_data->getRaw('sf_content') ?>
</div>
</div>
いまいちな感じのデザインと内部タグでCSSを使っていたりすることは許してください。だって1時間は短いですから。。
ここまでできれば、次にページのタイトルを変更することができます。アプリケーションのビュー設定ファイル (sf_sandbox/apps/frontend/config/view.yml) を編集し、titleキーを表している行の位置で次のように変更します。
default:
http_metas: content-type: text/html
metas:
title: The best weblog ever
robots: index, follow
description: symfony project
keywords: symfony, project
language: en
ホームページ自体が変更される必要があります。デフォルトモジュールのデフォルトのテンプレートを使っていて、それはフレームワークの中にあり、アプリケーションディレクトリの中にはありません。それを上書きするためには、カスタム main モジュールを作成しなければなりません。
$ php symfony init-module frontend main
デフォルトで、indexアクションはデフォルトの congratulations 画面を表示します。変更するために、sf_sandbox/apps/frontend/modules/main/actions/actions.class.php を編集し、executeIndex()関数の内容を次のように空にしてしまいます。
[php]
public function executeIndex()
{
}
すばらしいウェルカムメッセージを表示するために sf_sandbox/apps/frontend/modules/main/templates/indexSuccess.php ファイルを編集します。
[php]
<h1>Welcome to my swell weblog</h1>
<p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p>
そして、symfonyにホームページがリクエストされたときに実行するアクションを指定しなければなりません。拡張するために、sf_sandbox/apps/frontend/config/routing.yml ファイルを編集し、ホームページルールを次のように変更します。
homepage:
url: /
param: { module: main, action: index }
ホームページを再度リクエストして確認してみましょう。
http://localhost/sf_sandbox/web/frontend_dev.php/
さぁ、あなたの新しいウェブアプリを使ってみましょう。新しいテストの投稿を行ってみたり、投稿記事にテストコメントをつけたりしてみましょう。
アクションからテンプレートにデータの受け渡し
簡単に早くできたでしょ?次に comment モジュールを post モジュールのなかに混ぜて投稿記事の下にコメントを表示してみましょう。
最初に、投稿されたコメントをテンプレートで表示するために利用できるようにする必要があります。symfony において、この種のロジックをアクション(action)内に書きます。sf_sandbox/apps/frontend/modules/post/actions/actions.class.php ファイルを編集し、executeShow() 関数に最後の4行を追加します。
[php]
public function executeShow()
{
$this->post = PostPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->post);
$c = new Criteria();
$c->add(CommentPeer::POST_ID, $this->getRequestParameter('id'));
$c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$this->comments = CommentPeer::doSelect($c);
}
Criteria と -Peer オブジェクトは Propel のオブジェクトリレーションマッピングの部品です。基本として、これら4行は現在の Post (URL中の id パラメーターによって決定される記事)に関連するコメントを取得するために Comment テーブルへのSQLクエリを操作しています。 アクション内の $this->comments 行によって $comments変数にテンプレートでアクセスすることができます。さて、最後に次のように追記することで、postを表示する sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php テンプレートを編集しましょう。
[php]
...
<?php use_helper('Text', 'Date') ?>
<hr />
<?php if ($comments) : ?>
<p><?php echo count($comments) ?> comment<?php if (count($comments) > 1) : ?>s<?php endif; ?> to this post.</p>
<?php foreach ($comments as $comment): ?>
<p><em>posted by <?php echo $comment->getAuthor() ?> on <?php echo format_date($comment->getCreatedAt()) ?></em></p>
<div class="comment" style="margin-bottom:10px;">
<?php echo simple_format_text($comment->getBody()) ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
このページは (format_date()、 simple_format_text()などの)symfonyによって提供される新しいPHP関数を使っています。そして、'helpers' を呼び出します。なぜなら、あなたに代わって通常はもっと時間がかかるコーディング作業を行ってくれます。最初の記事に新しいコメントを作成し、再度最初の記事をその数字をクリックするか直接入力して表示することで確認してください。
http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1
うまく動いていますね。 アクションからテンプレートにリンクするための名前の規則やテキストや日付のヘルパーについてはそれぞれの章を参照してください。
別のテーブルに関連するレコードを追加
コメントを追加するとき、関連する記事のIDを選択できます。これはあまりユーザーフレンドリーではありません。これを変更してみましょう。そして、ユーザーが追加したコメントに戻ってこれるようにします。
最初に、まだ手を加えていない fresh modules/post/templates/showSuccess.php テンプレートに次のように追加しましょう。
[php]
<?php echo link_to('Add a comment', 'comment/create?post_id='.$post->getId()) ?>
link_to()ヘルパーはcommentモジュールのcreateアクションを指し示すハイパーリンクを作成してくれます。それで、記事の詳細ページから直接コメントに追加することができます。次に、modules/comment/templates/editSuccess.phpを開き、次の行を
[php]
<tr>
<th>Post:</th>
<td><?php echo object_select_tag($comment, 'getPostId', array (
'related_class' => 'Post',
)) ?></td>
</tr>
以下のように変更します。
[php]
<?php if ($sf_params->has('post_id')): ?>
<?php echo input_hidden_tag('post_id',$sf_params->get('post_id')) ?>
<?php else: ?>
<tr>
<th>Post*:</th>
<td><?php echo object_select_tag($comment, 'getPostId', array('related_class' => 'Post')) ?></td>
</tr>
<?php endif; ?>
comment/createページでformはcomment/updateアクションを指し示しています。そしてこの comment/update は サブミットされたときに comment/show にリダイレクトします(そして、これはデフォルトの生成されたCRUDでの振舞いです)。weblogにとって、コメントを追加したあとにコメントの詳細が表示されることを意味します。コメント付きで記事を表示できたほうがより良いでしょう。それで、modules/comment/actions/actions.class.php を開き executeUpdate() 関数を探してください。created_at フィールドはアクションでは定義されていないことに注意してください。つまり、symfonyはcreated_atというフィールド名はレコードが生成されたときのシステム時間であるということを知っているのです。最終的なアクションのリダイレクトは正しいアクションを指し示すために修正されなければなりません。次のようにします。
[php]
public function executeUpdate ()
{
if (!$this->getRequestParameter('id', 0))
{
$comment = new Comment();
}
else
{
$comment = CommentPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($comment);
}
$comment->setId($this->getRequestParameter('id'));
$comment->setPostId($this->getRequestParameter('post_id'));
$comment->setAuthor($this->getRequestParameter('author'));
$comment->setEmail($this->getRequestParameter('email'));
$comment->setBody($this->getRequestParameter('body'));
$comment->save();
return $this->redirect('post/show?id='.$comment->getPostId());
}
ユーザーは新しいコメントを記事に追加することができ、その後に記事に戻ってくることができます。weblogが欲しかったでしょ? ほら、ここにweblogができました。
アクションについてはアクションの章をみてください。
フォームのバリデーション
訪問者はコメントを残すことができますが、データを空のままサブミットしたらどうなりますか? データベースが汚されてしまうでしょう。そうならないために、update.yml というファイルをsf_sandbox/apps/frontend/modules/comment/validate/ディレクトリ(ディレクトリも作成しなくてはなりません)に作成します。そして、次のように書き加えます。
methods:
post: [author, email, body]
get: [author, email, body]
fillin:
enabled: on
names:
author:
required: Yes
required_msg: The name field cannot be left blank
email:
required: No
validators: emailValidator
body:
required: Yes
required_msg: The text field cannot be left blank
emailValidator:
class: sfEmailValidator
param:
email_error: The email address is not valid.
注意: 各行の始めに4つの余分のスペースをコピーしないということに注意してください。なぜならYAMLパーサーはこの場合に失敗するかもしれないからです。このファイルの最初の文字は
methodsのmにしなければなりません。
フィルインアクティベーションはバリデーション失敗時にユーザーによって入力された値でフォームの再構築することを可能にしてくれます。名前の宣言はフォームの各入力のためのバリデーションルールをセットします。
それ自身で、もしエラーが発生すれば、コントローラーはユーザーをupdateError.phpテンプレートにリダイレクトするでしょう。 エラーメッセージと共に再度入力フォームを表示するほうが良いでしょう。そうするために、handleErrorUpdate 関数を modules/comment/actions/actions.class.php クラスのアクションに追加します。
[php]
public function handleErrorUpdate()
{
$this->forward('comment', 'create');
}
仕上げに、再びmodules/comment/templates/editSuccess.phpテンプレートを開き、上部に次の行を挿入します。
[php]
<?php if ($sf_request->hasErrors()): ?>
<div id="errors" style="padding:10px;">
Please correct the following errors and resubmit:
<ul>
<?php foreach ($sf_request->getErrors() as $error): ?>
<li><?php echo $error ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
これで強力なフォームが完成しました。
詳細についてはフォームバリデーションの章を参照してください。
URLの表記を変更
URLがどのように提供されているか気づいていますか? ユーザーや検索エンジンにもっと優しいものに作ることができます。記事のタイトルを投稿された記事のURLとして使ってみましょう。
問題は記事のタイトルはスペースなどの特別な文字を含むことができるということです。それらをエスケープするなら、URLは %20 のような醜いものになってしまうでしょう。それで、モデルを拡張し、Postオブジェクトにクリーンにするための新しい関数を追加したほうがよいでしょう。そうするために、 sf_sandbox/lib/model/ディレクトリにある Post.phpファイルを編集します。そして、次の関数を追加します。
[php]
public function getStrippedTitle()
{
$result = strtolower($this->getTitle());
// strip all non word chars
$result = preg_replace('/\W/', ' ', $result);
// replace all white space sections with a dash
$result = preg_replace('/\ +/', '-', $result);
// trim dashes
$result = preg_replace('/\-$/', '', $result);
$result = preg_replace('/^\-/', '', $result);
return $result;
}
次に postモジュールのためにpermalinkアクションを作成します。次の関数を the modules/post/actions/actions.class.php に追加します。
[php]
public function executePermalink()
{
$posts = PostPeer::doSelect(new Criteria());
$title = $this->getRequestParameter('title');
foreach ($posts as $post)
{
if ($post->getStrippedTitle() == $title)
{
break;
}
}
$this->forward404Unless($post);
$this->getRequest()->setParameter('id', $post->getId());
$this->forward('post', 'show');
}
postの一覧は各投稿記事のための表示するアクションの代わりにこのpermalinkアクションを呼ぶことができます。modules/post/templates/listSuccess.php の中で、表のid見出しを削除し、Titleセルを
[php]
<td><?php echo $post->getTitle() ?></td>
から次のように変更します。
[php]
<td><?php echo link_to($post->getTitle(), 'post/permalink?title='.$post->getStrippedTitle()) ?></td>
もう1段階作業をしてみましょう。sf_sandbox/apps/frontend/config/ ディレクトリにある routing.yml を編集し、最初に次のようなルールを追加します。
list_of_posts:
url: /latest_posts
param: { module: post, action: list }
post:
url: /weblog/:title
param: { module: post, action: permalink }
これで、アプリケーションにおいて再びナビゲーションが動作し、次のようなURLを見ることができます。
スマートなURLについてにさらに詳細があります。
フロントエンドをお掃除
さて、これがウェブログなら、誰もが投稿する権利があります。このことはあなたが考えていることとは正確には違いませんか?テンプレートを少しばかりお掃除しましょう。
modules/post/templates/showSuccess.php テンプレートで、'edit' リンクを取り除きましょう。
[php]
<?php echo link_to('edit', 'post/edit?id='.$post->getId()) ?>
同じことを、modules/post/templates/listSuccess.php テンプレートにも行い取り除きます。
[php]
<?php echo link_to('create', 'post/create') ?>
modules/post/actions/actions.class.php から次の関数も取り除く必要があります。
* executeCreate
* executeEdit
* executeUpdate
* executeDelete
これで大丈夫です。読者はもう投稿することはできません。
バックエンドアプリの生成
あなたが投稿記事を書くために、バックエンドアプリケーションを(sf_sandboxプロジェクトディレクトリから)コマンドラインから入力することで作成してみましょう
$ php symfony init-app backend
$ php symfony propel-init-admin backend post Post
$ php symfony propel-init-admin backend comment Comment
今回はadmin generator を利用します。これはCRUDジェネレーターよりも多くの機能とカスタマイズを可能とします。
フロントエンドアプリケーションのときと同じように、全体のナビゲーションのためにレイアウト(apps/backend/templates/layout.php)を編集します。
[php]
<div id="navigation">
<ul style="list-style:none;">
<li><?php echo link_to('Manage posts', 'post/list') ?></li>
<li><?php echo link_to('Manage comments', 'comment/list') ?></li>
</ul>
</div>
<div id="content">
<?php echo $sf_data->getRaw('sf_content') ?>
</div>
バックオフィスアプリケーションに次のように呼び出すことで開発環境でアクセスすることができます。
http://localhost/sf_sandbox/web/backend_dev.php/post
生成されたadminの最大の利点は設定ファイルでカスタマイズが簡単に行えることです。
backend/modules/post/config/generator.ymlを変更します。
generator:
class: sfPropelAdminGenerator
param:
model_class: Post
theme: default
fields:
title: { name: Title }
excerpt: { name: Exerpt }
body: { name: Body }
nb_comments: { name: Comments }
created_at: { name: Creation date }
list:
title: Post list
layout: tabular
display: [=title, excerpt, nb_comments, created_at]
object_actions:
_edit: ~
_delete: ~
max_per_page: 5
filters: [title, created_at]
edit:
title: Post detail
fields:
title: { type: input_tag, params: size=53 }
excerpt: { type: textarea_tag, params: size=50x2 }
body: { type: textarea_tag, params: size=50x10 }
created_at: { type: input_date_tag, params: rich=on }
Postテーブルの存在するカラムの中に注意してください、adminは nb_comments を探そうとします。関連するゲッターはまだ存在しません、しかし、sf_sandbox/lib/model/Post.php にただ次のように追加するだけです。
[php]
public function getNbComments()
{
return count($this->getComments());
}
これで、Postアドミニストレーションを更新すると次のようになります。
バックエンドアプリケーションへのアクセス制限
今のままではバックエンドアプリケーションへ誰もがアクセスできてしまいます。アクセス制限を掛ける必要があるでしょう。
apps/backend/modules/post/config/ に、次のようなsecurity.ymlファイルを追加します。
all:
is_secure: on
commentモジュールのために操作を繰り返します。これで、これらのモジュールにログインせずにアクセスできません。
しかし、ログインアクションが存在しません! 大丈夫です、簡単に追加できます。最初に、security モジュールスケルトンを作成します。
$ php symfony init-module backend security
この新しいモジュールはログインフォームとリクエストを操作するために使用されます。apps/backend/modules/security/templates/indexSuccess.php にログインフォームを作成するために編集します。
[php]
<h2>Authentication</h2>
<?php if ($sf_request->hasErrors()): ?>
Identification failed - please try again
<?php endif; ?>
<?php echo form_tag('security/login') ?>
<label for="login">login:</label>
<?php echo input_tag('login', $sf_params->get('login')) ?>
<label for="password">password:</label>
<?php echo input_password_tag('password') ?>
<?php echo submit_tag('submit', 'class=default') ?>
</form>
securityモジュールと対話するためのフォームによって呼び出されるログインアクション(login)を(apps/backend/modules/security/actions/actions.class.phpに)追加します。
[php]
public function executeLogin()
{
if ($this->getRequestParameter('login') == 'admin' && $this->getRequestParameter('password') == 'password')
{
$this->getUser()->setAuthenticated(true);
return $this->redirect('main/index');
}
else
{
$this->getRequest()->setError('login', 'incorrect entry');
return $this->forward('security', 'index');
}
}
main モジュールのために、indexアクションの中の元からあるコードを取り除きます。
[php]
public function executeIndex()
{
}
最後にすることは、securityモジュールをloginアクションが操作する標準のモジュールとして設定することです。そうするために、apps/backend/config/settings.yml設定ファイルを開き、次を追加します。
all:
.actions:
login_module: security
login_action: index
これで、投稿記事の管理画面へアクセスするために、ログインとパスワードを入力する必要があるでしょう。
詳細はセキュリティの章を参照してください。
結論
どうでしたか、1時間が経ちました。製品環境において両方のアプリケーションを利用できるようになり、これらで遊ぶことができます。
frontend: http://localhost/sf_sandbox/web/index.php/
backend: http://localhost/sf_sandbox/web/backend.php/
この時点でエラーが発生しているなら、アクションがキャッシュされた後に変更を加えたからかもしれません(キャッシュは開発環境では有効になっていません)。キャッシュをクリアするためには、コマンドラインで次のように入力するだけです。
$ php symfony cc
アプリケーションが快適にスムーズに動くのを見てください。おどろくほどクールでしょ?コードを自由に眺めたり、新しいモジュールを追加したり、ページデザインを変更してください。
そして、symfony Wikiであなたのsymfonyアプリケーションが動作していると点に触れることを忘れないでください!
original :http://www.symfony-project.com/tutorial/my_first_project.html