2012-07-28
■[php][Silex] フラットなPHPからSilexへ
追記
- DB接続時にcharset=utf8を指定
- bindValueで暗黙の型変換されないように変更
- Pimpleをサービスロケータとして使う場合の注意点を追加
- テンプレートとしてフラットなPHPからTwigで書いた場合を追加
前提
スクリプト、ファイル、DBの文字コードはすべてUTF-8で統一です。
また、最初に以下のMySQLのテーブルがあることを前提として記事を書いています。
- Database: MySQL
- user: myuser
- password: mypassword
CREATE TABLE `blog_db`.`post` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `body` text NOT NULL, `date` date NOT NULL, PRIMARY KEY ( `id` ) ) ENGINE = INNODB CHARACTER SET utf8
フラットなPHPからSymfony2へ ... にインスパイアされて
この記事は Symfony2 versus Flat PHP (current) - Symfony をベースに
Symfony2ではなくマイクロフレームワーク(Silex)を使ったパターンに書き換えたらどうなるかについて書いています。
また、姉妹記事としてフラットなPHPからSlimへというのも書いたのですが、フレームワークの話が出てくるまでの前半は共通です。
しかも、書いていて気づいたのですが、元記事のコードはそのままでは動きません。(えっ!!)
そのあたりをカバーするためにもとりあえず書いてみました。
参照: 日本語訳 Symfony2 vs フラットなPHP | Symfony2日本語ドキュメント
なぜ マイクロフレームワーク は単にファイルを開いてフラットな PHP を書くよりも良いのでしょうか?
マイクロフレームワークをご存知でしょうか? 一番有名なのは Ruby の sinatra だと思います。通常のMVCという考え方ではなく、どのリクエストメソッドでどのURIにアクセスされたかによって、レスポンスを用意するというシンプルな構成が特徴です。
PHPではSilex, Slimなど sinatraからインスパイアされて開発されているマイクロフレームワークがあります。
フラットなPHPを使うよりも早く、マイクロフレームワークを利用することでよりよいソフトウェアを開発できるということを、1ステップずつ説明していきたいと思います。
この記事では、最初にフラットな PHP でシンプルなアプリケーションを記述します。
フラットなPHPによる単純なブログ
フラットなPHPでざくっとブログの記事を表示するコードを書くと次のようになります
<?php $pdo = new PDO( 'mysql:host=localhost;dbname=blog_db;charset=utf8', 'myuser', 'mypassword', array(PDO::ATTR_EMULATE_PREPARES => false) ); $stmt = $pdo->query('SELECT id, title FROM post'); ?> <html> <head> <title>投稿の一覧</title> </head> <body> <h1>投稿の一覧</h1> <ul> <?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)): ?> <li> <a href="show.php?id=<?php echo htmlspecialchars($row['id'], ENT_QUOTES, 'utf-8') ?>"> <?php echo htmlspecialchars($row['title'], ENT_QUOTES, 'utf-8') ?> </a> </li> <?php endwhile; ?> </ul> </body> </html>
(元記事では、PDOすら使っていませんでしたが、さすがにこの時代にそれも無いかということでPDOを使っています。)
(元記事では エスケープせずにechoしてましたが、さすがにこの時代に(ry )
HTMLと混在させることができたり、HTMLの中での繰り返し処理はのようにブロックの閉じタグが分かりやすくなっていたりするところはPHPらしいところだと思います。
このようにサクッと書けるのはいいことなのですが、アプリケーションが大きくなってくるとメンテナンスが大変になってくることが想像できます。
次のような解決すべき問題があります。
- エラーチェックがない: データベースへの接続が失敗した場合はどうなるのでしょう?
- 体系化されていない: アプリケーションが複雑になってくると、この1ファイルはどんどんメンテナンスできなくなってきます。フォームの送信を行うコードや、メール送信するコードを追加したいときはどこに書いたらよいのでしょう?
- コードの再利用性が低い: 全てが1ファイルにまとまっているので、アプリケーションで新しく作成したページでこのコードの一部を再利用することができません。
note: ここで述べられていない他の問題として、データベースが MySQL に固定されてしまうということがあります。 よくある解決策として、何かしらのデータベースの抽象化を行うライブラリ(Doctrine, Propel, フレームワークが提供しているライブラリ)を使うことになります。 Silexであれば、DoctrineのDBALというライブラリを簡単に利用できるようになっています。 Slimはデータベースアクセスのためのライブラリは用意してくれていないのですが、 PDOを薄くカプセル化したライブラリを自前で用意したり、 「特定のデータベースに固定されてもいい」という判断も有りだと思います。
さぁ、これらの問題を解決していきましょう
表示部分(view)の分離
このコードは、HTML部分とアプリケーションの「ロジック」を分離することで、すぐに改善できますね。
<?php $pdo = new PDO( 'mysql:host=localhost;dbname=blog_db:charset=utf8', 'myuser', 'mypassword', array(PDO::ATTR_EMULATE_PREPARES => false) ); $stmt = $pdo->query('SELECT id, title FROM post'); // HTML部分のコードを読み込む require 'templates/list.php';
HTML部分は別のファイル (templates/list.php) に保存するようにしました。これは本来、テンプレート風の PHP 文法を使う HTML ファイルです。
<html> <head> <title>投稿の一覧</title> </head> <body> <h1>投稿の一覧</h1> <ul> <?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)): ?> <li> <a href="show.php?id=<?php echo htmlspecialchars($row['id'], ENT_QUOTES, 'utf-8') ?>"> <?php echo htmlspecialchars($row['title'], ENT_QUOTES, 'utf-8') ?> </a> </li> <?php endwhile; ?> </ul> </body> </html>
慣例によって、全てのアプリケーションのロジックを含むファイル「index.php」は「コントローラ」と呼ばれます。コントローラという用語は、使用する言語やフレームワークに関係なく、よく聞くことでしょう。コントローラは、あなたのコードにおける、ユーザからの入力を処理し、レスポンスを返す部分のことを指しています。
この場合、コントローラはデータベースからのデータを準備し、それからそのデータを提供するテンプレートをインクルードします。テンプレートとコントローラを分離させることによって、何か他のフォーマット (例えば JSON フォーマットの list.json.php) でブログのエントリをレンダリングする必要があった場合に、テンプレートファイルだけを簡単に変更することができます。
アプリケーション (ドメイン) ロジックの分離
今のところアプリケーションは1つのページしか含んでいませんが、2番目のページが同じデータベース接続、あるいは同じ投稿の配列を使用する必要がある場合はどうでしょうか?アプリケーションのコアの動作とデータアクセスの機能を mode.php という新しいファイルに分離するように、コードをリファクタリングしてみましょう。
<?php // model.php function get_database_connection() { $pdo = new PDO( 'mysql:host=localhost;dbname=blog_db;charset=utf8', 'myuser', 'mypassword', array(PDO::ATTR_EMULATE_PREPARES => false) ); return $pdo; } function close_database_connection(&$pdo) { $pdo = null; } function get_all_posts() { $pdo = get_database_connection(); $stmt = $pdo->query('SELECT id, title FROM post'); $posts = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $posts[] = $row; } close_database_connection($pdo); return $posts; }
Tip model.php というファイル名が使われているのは、アプリケーションのロジックとデータアクセスが 伝統的に「モデル」というレイヤーだからです。 うまく体系付けられたアプリケーションでは、「ビジネスロジック」を表すコードの大部分は、 モデル内に存在するべきです (コントローラに存在するのとは対照的に) 。 そしてこの例とは違って、モデルの一部分のみが実際にデータベースへのアクセスに関わることになります。
コントローラー(index.php)はさらにシンプルになります。
<?php require 'model.php'; $posts = get_all_posts(); require 'templates/list.php';
テンプレートもこの$postsを使うように修正しシンプルになります。
<html> <head> <title>投稿の一覧</title> </head> <body> <h1>投稿の一覧</h1> <ul> <?php foreach ($posts as $post): ?> <li> <a href="/show.php?id=<?php echo htmlspecialchars($post['id'], ENT_QUOTES, 'utf-8') ?>"> <?php echo htmlspecialchars($post['title'], ENT_QUOTES, 'utf-8') ?> </a> </li> <?php endforeach; ?> </ul> </body> </html>
さきほどまで$rowという変数を利用していましたが、$postになりました。名前が変わっただけでもコードは意図を表現できて読みやすくなるのがわかりますね。
この時点で、コントローラの唯一のタスクは、アプリケーションのモデルレイヤー(モデル)からデータを取り出し、そのデータをレンダリングするためにテンプレートを呼び出すことです。これは、モデル-ビュー-コントローラ(MVC)パターンのとても単純な例です。
レイアウトの分離
この時点でアプリケーションは、いくつかの有利な点を持つ3つの明確な部品(MVC)にリファクタリングされ、別のページでほとんど全てを再利用できる機会を得ます。
コードの中で再利用できない唯一の部分は、ページレイアウトです。レイアウトでは各ページで共通で利用される部分です。layout.php ファイルを新しく作成して、この問題に対応しましょう。
<html> <head> <title><?php echo $title ?></title> </head> <body> <?php echo $content ?> </body> </html>
次に、list.php をレイアウトを拡張するように修正します。
<?php $title = '投稿のリスト' ?> <?php ob_start() ?> <h1>投稿のリスト</h1> <ul> <?php foreach ($posts as $post): ?> <li> <a href="show.php?id=<?php echo htmlspecialchars($post['id'], ENT_QUOTES, 'utf-8') ?>"> <?php echo htmlspecialchars($post['title'], ENT_QUOTES, 'utf-8') ?> </a> </li> <?php endforeach; ?> </ul> <?php $content = ob_get_clean() ?> <?php include 'layout.php' ?>
ここで、レイアウトの再利用を可能にする方法を説明しましょう。残念なことに、これを可能にするために、いくつかの格好悪い PHP の関数 (ob_start() と ob_get_clean())をテンプレート内で使わなければならないことにお気づきだと思います。
(元記事ではob_end_cleanになってたのですが、それじゃ動かないっすよね...)
正直テンプレートを用意するのに毎回これを書くのはヒドイですよね。通常はフレームワークが提供しているテンプレートのライブラリや,Twig, Smarty, PHPTALなどのサードパーティーの優れたテンプレートエンジンを利用することになります。
ブログの「show (単独表示) 」ページを追加
ブログの「list (一覧表示)」ページは、より体系付けられて再利用可能なコードになるようリファクタリングされました。これを証明するために、id をクエリーパラメータとしてそれぞれのブログの投稿を表示する「show (記事の詳細表示)」ページを追加しましょう。
まず初めに、与えられた ID を元にそれぞれのブログの結果を取得する関数を model.php ファイルに追加する必要があります。
<?php // model.php function get_post_by_id($id) { $pdo = get_database_connection(); $sth = $pdo->prepare('SELECT id, date, title, body FROM post where id = :id'); $sth->bindValue(':id', $id, PDO::PARAM_INT); $sth->execute(); $post = $sth->fetch(PDO::FETCH_ASSOC); close_database_connection($pdo); return $post; }
次に、この新しいページのためのコントローラである show.php という新しいファイルを作ってください。
<?php require_once 'model.php'; $post = get_post_by_id($_GET['id']); require 'templates/show.php';
最後に、それぞれの投稿を表示するための templates/show.php という新しいテンプレートファイルを作ってください。
<?php $title = $post['title'] ?> <?php ob_start() ?> <h1><?php echo htmlspecialchars($post['title'], ENT_QUOTES, 'utf-8') ?></h1> <div class="date"><?php echo htmlspecialchars($post['date'], ENT_QUOTES, 'utf-8') ?></div> <div class="body"> <?php echo htmlspecialchars($post['body'], ENT_QUOTES, 'utf-8') ?> </div> <?php $content = ob_get_clean() ?> <?php include 'layout.php' ?>
2番目のページを作るのは、とても簡単で、重複したコードもありません。まだこのページには、フレームワークが解決できるさらにやっかいな問題があります。例えば、「id」クエリーパラメータが存在しなかったり不正な場合、ページがクラッシュする原因になります。このような問題では 404 ページを表示する方がよいですが、まだこれは簡単には実現できません。
それ以外の大きな問題として、それぞれのコントローラのファイルが model.php ファイルを含まなくてはならないということです。それぞれのコントローラファイルが、突然追加のファイルを読み込む必要に迫られたり、その他のグローバルなタスク(例えばセキュリティの向上など)を実行する必要が出た場合、どうなるでしょう。現状では、それを実現するためのコードは全てのコントローラのファイルに追加する必要があります。もし何かをあるファイルに含むのを忘れてしまった時、それがセキュリティに関係ないといいのですが…。
「フロントコントローラ」の出番
フロントコントローラを使うことでファイルの読込忘れが起こらないようにすることができます。これは、全てのリクエストが処理される際に通過する一つの PHP ファイルです。フロントコントローラによって、アプリケーションの URI は少し変更されますが、より柔軟になり始めます。
フロントコントローラなしの場合
- /index.php => ブログ一覧表示ページ (index.php が実行されます)
- /show.php => ブログ単独表示ページ (show.php が実行されます)
index.php をフロントコントローラとして使用した場合
- /index.php => ブログ一覧表示ページ (index.php が実行されます)
- /index.php/show => ブログ単独表示ページ (index.php が実行されます)
Tip URI の index.php という一部分は、Apache のリライトルール(あるいはそれと同等の仕組み)を使っている場合は、省略することができます。 この場合、ブログの単独表示ページの URI は、単純に /show になります。
フロントコントローラを使用する時は、一つの PHP ファイル(今回は index.php)が全てのリクエストをレンダリングします。ブログの単一表示ページでは、/index.php/show という URI で実際には、完全な URI に基づいてルーティングのリクエストに内部的に応える index.php ファイルが実行されます。ここで見たように、フロントコントローラはとてもパワフルなツールなのです。
フロントコントローラの作成
我々のアプリケーションに関して、大きな一歩を踏み出そうとしています。全てのリクエストを扱う一つのファイルによって、セキュリティの扱いや、設定の読み込み、ルーティングといったことを集中的に扱えるようになります。我々のアプリケーションでは index.php が、リクエストされた URI に基づいて、ブログの一覧表示ページあるいは単一表示ページをレンダリングするのに十分なぐらい洗練されている必要があります。
<?php // index.php // グローバルライブラリの読み込みと初期化 require_once 'model.php'; require_once 'controllers.php'; // ドキュメントルート以外に設置した場合のベースとなるアプリケーションのパス $base = '/path/application_root'; // リクエストを内部的にルーティング $uri = $_SERVER['REQUEST_URI']; if ($uri === ($base .'/index.php')) { list_action(); } elseif ( preg_match("#^{$base}/index.php/show#", $uri) && isset($_GET['id'])) { show_action($_GET['id']); } else { header('Status: 404 Not Found'); echo '<html><body><h1>ページが見つかりません</h1></body></html>'; }
コードの体系化のために、2つのコントローラ(以前の index.php と show.php)は、PHP の関数になり、それぞれは別のファイル controllers.php に移動されました。
<?php // controllers.php function list_action() { $posts = get_all_posts(); require 'templates/list.php'; } function show_action($id) { $post = get_post_by_id($id); require 'templates/show.php'; }
フロントコントローラとして、index.php は全く新しい役割を引き受けることになりました。それは、コアライブラリを読み込み、2つのコントローラ(list_action() と show_action() 関数)のうちの1つを呼び出せるようにアプリケーションをルーティングすることです。実際にこのフロントコントローラは、リクエストを取り扱いルーティングする MVCフレームワークのメカニズムによく似た見た目と動作をし始めています。
Tip フロントコントローラのもう一つの利点が、柔軟性のある URL です。 コードのたった1箇所だけを変更すれば、ブログ単一表示ページの URL を /show から /read に変更できることに注目してください。 以前は、ファイル全体の名前を変更する必要がありましたね。SilexやSlimなどのマイクロフレームワークではさらに柔軟に設定できます。
ここまで、アプリケーションを単一の PHP ファイルから、体系化されてコードの再利用ができる構造へと発展させてきました。これで幸せになれたらいいのですが、現実的に満足からは程遠いものでしょう。例えば、「ルーティング」システムは気まぐれで、一覧表示ページ(/index.php)が / (Apacheのリライトルールが追加されている場合)からでもアクセス可能であるべきだということを認識できません。また、ブログを開発する代わりに、コードの「アーキテクチャ」(例えばルーティングや呼び出すコントローラ、テンプレートなど)にたくさんの時間を費やしています。より多くの時間を、フォームの送信の扱い、入力のバリデーション、ロギングやセキュリティといったことに費やす必要があるでしょう。なぜこれら全てのありふれた問題への解決策を再発明しなければならないのでしょうか?
ライブラリを使って再開発を防ぐ
- Slimに興味がある方は => フラットなPHPからSlimへ
- Silex (Symfony Component と Pimple)に興味がある方はこのまま読み進めてください。
次にこの再開発をしなくて済むように Symfony Component の出番です。
ちょっと Symfony Component の Request と Response に手を出してみる
実際に Silex でWebアプリケーションを開発すると、Symfony Componentのライブラリを使うことになります。まず最初にこれらのライブラリのクラスをどのように見つけるのかを PHP が知っているようにする必要があります。これは、 Composerというパッケージ管理システムを使えば名前空間を利用したオートローダーが簡単に利用できます。これはSilexに限らず、SymfonyやBehatなどのライブラリなどでも同じです。
Composerを使って ResponseとRequesetを使うようにしてみましょう。
まずはComposerをコマンドラインからインストールします。
$ curl -s https://getcomposer.org/installer | php
// もし curlがインストールされていない場合は以下でもOK
$ php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
これで、composer.pharというファイルがダウンロードされます。次に、Symfony ComponentのHttpFoundationというRequestやResponseを扱うために用意されたコンポーネントをダウンロードするために、以下のように composer.jsonファイルを用意します。
{
"require": {
"symfony/http-foundation": "2.1.x-dev"
}
}
あとは、コマンドラインでinstallを叩くだけです。
$ php composer.phar install
Installing dependencies
- Installing symfony/http-foundation (dev-master)
Cloning 4ac6d1ef88798fbbdc7600b1859e62403e1f8c97
Writing lock file
Generating autoload files
これで、vendorディレクトリが作成され、そこにコンポーネントとautoload.phpが用意されます。
もし、追加で必要なコンポーネントがあれば composer.jsonに追加してインストールが行えます。
requireやuseなどの宣言を bootstrap.phpファイルとしてまとめて、フロントコントローラから読み込むようにしましょう。
<?php // bootstrap.php require_once 'vendor/autoload.php'; require_once 'controllers.php'; require_once 'model.php';
フロントコントローラでHttpFoundationコンポーネントを使うように書き換えてみます。
これまでアプリケーションをどのパスに設置するかを考慮していましたが、HttpFoundationコンポーネントがその部分を吸収してくれています。
<?php // index.php // グローバルライブラリの読み込みと初期化 require 'bootstrap.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; // リクエストを内部的にルーティング $request = Request::createFromGlobals(); $uri = $request->getPathInfo(); if ($uri === '/') { $response = list_action(); } elseif ($uri === '/show' && $request->query->has('id')) { $response = show_action($request->query->get('id')); } else { $html = '<html><body><h1>Page Not Found</h1></body></html>'; $response = new Response($html, 404); } // ヘッダーを返し、レスポンスを送る $response->send();
コントローラは、Response オブジェクトを返す責任を持つようになりました。これを簡単にするために、新しく render_template() 関数を追加しています。ちなみに、この関数は Symfony2 のテンプレートエンジンとちょっと似た動きをします。
この関数には読み込みたいテンプレートのパスと、テンプレートで使用する変数を配列で渡します。
// controllers.php <?php use Symfony\Component\HttpFoundation\Response; function list_action() { $posts = get_all_posts(); $html = render_template('templates/list.php', array('posts' => $posts)); return new Response($html); } function show_action($id) { $post = get_post_by_id($id); $html = render_template('templates/show.php', array('post' => $post)); return new Response($html); } // テンプレートをレンダリングするためのヘルパー関数 function render_template($path, $params) { extract($params, EXTR_SKIP); ob_start(); require $path; $html = ob_get_clean(); return $html; }
Symfony Component の HttpFoundation を使うことによって、アプリケーションはより柔軟で信頼できるものになりました。Request は HTTP リクエストに関する情報にアクセスするための信頼できる仕組みを提供します。具体的にいうと、getPathInfo() メソッドは整理された URI(常に /show で、/index.php/show ではない)を返します。そのため、もしユーザが /index.php/show にアクセスしたとしても、アプリケーションは show_action() によってリクエストをルーティングするインテリジェントさを持っています。
Response オブジェクトは、HTTP ヘッダーとコンテンツをオブジェクト指向のインタフェースを介して追加できるようにすることで、HTTP レスポンスを構成する際に柔軟性を提供しています。そして、アプリケーションのレスポンスがシンプルなために、この柔軟性はアプリケーションが成長するのに大きな利点があるのです。
データベースの設定を外に出す
MySQLが別のデータベースに変更になることはそれほど無いかもしれませんが、別のサーバーで動かすためにデータベース名、ユーザー名、パスワードが変更になるということはよくあることです。さらに、model.phpのテストコードを書こうとすると、テスト用のDB接続に切り替えることができません。これを柔軟に対応する方法を考えましょう。
本格的に複雑なアプリケーションを構築するためには 本格的な DI(Dependency Injection) ライブラリを利用するのですが、ここでは手軽に依存関係を入れておく入れ物(コンテナ)だけを用意してくれる Pimple を利用します。
参照: Pimple - A simple Dependency Injection Container for PHP 5.3
Pimpleは40行程度しかない小さなライブラリでPHP5.3以降で利用できる無名関数を活用したDIコンテナだけのライブラリです。
Pimpleのインストールは composer.jsonにpimpleを追加し、composer.phar update します。
{
"require": {
"symfony/http-foundation": "2.1.x-dev",
"pimple/pimple": "1.0.x-dev"
}
}
$ php composer.phar update
次にconfig.phpを用意し、データベースに関する設定をコンテナ(pimpleオブジェクト)に配列のように追加します。
// pimple <?php $container = new Pimple(); // database $container['db.config'] = array( 'host' => 'localhost', 'database' => 'blog_db', 'user' => 'myuser', 'password' => 'mypassword' );
このconfig.phpをbootstrap.phpで読み込みます。
<?php // bootstrap.php require_once 'vendor/autoload.php'; require_once 'config.php'; <= 追加 require_once 'controllers.php'; require_once 'model.php';
次にフロントコントローラで読み込んだコンテナを渡します。
<?php // index.php // グローバルライブラリの読み込みと初期化 require 'bootstrap.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; // リクエストを内部的にルーティング $request = Request::createFromGlobals(); $uri = $request->getPathInfo(); if ($uri === '/') { $response = list_action($container['db.config']); // <= databaseの設定を渡す } elseif ($uri === '/show' && $request->query->has('id')) { $response = show_action($request->query->get('id'), $container['db.config']); // <= databaseの設定を渡す } else { $html = '<html><body><h1>Page Not Found</h1></body></html>'; $response = new Response($html, 404); } // ヘッダーを返し、レスポンスを送る $response->send();
つぎに、controllers.phpにコンテナを引き渡すための修正を行います。
<?php // controllers.php function list_action($db_config) { $posts = get_all_posts($db_config); $html = render_template('templates/list.php', array('posts' => $posts)); return new Response($html); } function show_action($id, $db_config) { $post = get_post_by_id($id, $db_config); $html = render_template('templates/show.php', array('post' => $post)); return new Response($html); }
最後にコンテナから取得したデータベースの設定情報をmodel.phpで利用できるように修正します。
<?php // model.php function get_database_connection($db_config) { $pdo = new PDO( sprintf('mysql:host=%s;dbname=%s;charset=utf8', $db_config['host'], $db_config['database']), $db_config['user'], $db_config['password'], array(PDO::ATTR_EMULATE_PREPARES => false) ); return $pdo; } function close_database_connection(&$pdo) { $pdo = null; } function get_all_posts($db_config) { $pdo = get_database_connection($db_config); $stmt = $pdo->query('SELECT id, title FROM post'); $posts = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $posts[] = $row; } close_database_connection($pdo); return $posts; } function get_post_by_id($id, $db_config) { $pdo = get_database_connection($container); $sth = $pdo->prepare('SELECT id, date, title, body FROM post where id = :id'); $sth->bindValue(':id', $id, PDO::PARAM_INT); $sth->execute(); $post = $sth->fetch(PDO::FETCH_ASSOC); close_database_connection($pdo); return $post; }
これで、model.phpからデータベース設定のハードコーディングを追い出すことができました。しかし、PDOオブジェクトを毎回生成し毎回接続、終了を繰り返している部分が気になります。
そこで、無名関数を利用してサービスコンテナにmodelの関数を登録することを考えてみます。
まず、PDOオブジェクトの取得はPimpleのshareメソッドを利用して登録します。shareを使うことで何度呼ばれても同じPDOオブジェクトが返されます。
<?php // model.php $container['db.pdo'] = $container->share(function($c) { $db_config = $c['db.config']; $pdo = new PDO( sprintf('mysql:host=%s;dbname=%s;charset=utf8', $db_config['host'], $db_config['database']), $db_config['user'], $db_config['password'], array(PDO::ATTR_EMULATE_PREPARES => false) ); return $pdo; });
shareメソッドに渡す無名関数は引数としてコンテナ自身が渡されるので、内部でコンテナで定義したデータを利用することができます。以降は無名関数内で参照するコンテナ自身はPimpleオブジェクトである$containerと混乱しないように$cという名前(containerのc)で使うようにしています。
次に、get_all_posts関数を無名関数として登録してみましょう。Pimpleに無名関数を登録すると引数に自身のオブジェクトが渡されるので、これを利用してPDOオブジェクトの取得を行なっています。最初のget_all_posts関数より読みやすくなりましたね
<?php // model.php $container['model.all_posts'] = function($c) { $stmt = $c['db.pdo']->query('SELECT id, title FROM post'); $posts = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $posts[] = $row; } return $posts; };
これを呼ぶlist_action関数をあわせて修正します。
<?php // controllers.php function list_action($container) { $posts = $container['model.all_posts']; // <= コンテナから無名関数を実行 $html = render_template('templates/list.php', array('posts' => $posts)); return new Response($html); }
最後にget_post_by_id関数も登録します。普通に無名関数を登録しても、引数はコンテナ自身しか渡らないため、protectメソッドを使って$idを引数として渡すことができる無名関数を登録します。また、この場合コンテナ自身を無名関数の内部で利用できるようにするためにuseを使ってコンテナ自身を渡します。
<?php // model.php $container['model.post_by_id'] = $container->protect(function($id) use ($container) { $sth = $container['db.pdo']->prepare('SELECT id, date, title, body FROM post where id = :id'); $sth->bindValue(':id', $id, PDO::PARAM_INT); $sth->execute(); $post = $sth->fetch(PDO::FETCH_ASSOC); return $post; });
これを呼ぶshow_action関数も修正します。
<?php //controllers.php function show_action($id, $container) { $get_post_by_id = $container['model.post_by_id']; // <= コンテナから無名関数を取得 $post = $get_post_by_id($id); // <= 無名関数を引数$idを渡して実行 $html = render_template('templates/show.php', array('post' => $post)); return new Response($html); }
Pimpleで用意したコンテナを利用するように書き換えたことで、テスト時にPDOをテスト用のPDO_TESTに変更したいとしたい場合でもコンテナの内容を変更すれば簡単に差し替えることができるようになりました。
Note. ここではPimpleを利用してmodelを実装していますが、このようにコンテナとしてPimpleにあらゆるものをただ入れていくとすべての処理がPimpleに依存してしまいます。 このようにサービスロケータとしてPimpleを使うシンプルさだけに目を奪われず、本当に必要な知識を適切な場所に依存させるということも考えましょう
参照: PHPメンターズ -> Pimpleでシンプルに正しくDIを理解する
Silexで書き換える
ここまでの、Webアプリケーション開発を通してどのようにコードを分離してきたかを整理してみましょう。
アプリケーションへのアクセスとは"どのURIに、どのリクエストメソッドで、どのパラメータをもってアクセスされるか"ということです。
つまり、ルーティングによってどの処理を行うかが決定されるということだけなのです。
この部分に注目したのが、マイクロフレームワークです。マイクロフレームワークではルーティングごとに処理を定義するだけです。
では、これまで書いてきたコードをSilexで書き換えてみます。
まず、Silexをインストールします。composer.jsonを以下に書き換えてupdateするだけです。
{
"require": {
"silex/silex": "1.0.*"
},
"minimum-stability": "dev"
}
$ php composer.phar update
注目すべきは、vendor以下にsilexとその依存するコードがインストールされますが、これまで書いてきたコードはSilexと同じライブラリを使っているので、全くコードを修正しなくても今の状態でサンプルコードは動くということです。
では、本格的にSilexに書き換えていきます。
最初の一歩として1ファイルでとりあえず書いてみます。
とはいえ、難しくありません。完成形のコードを見てみましょう
<?php //index.php require_once __DIR__.'/vendor/autoload.php'; use Silex\Application; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $app = new Silex\Application(); //config.php'; $app['db.config'] = array( 'host' => 'localhost', 'database' => 'blog_db', 'user' => 'myuser', 'password' => 'mypassword' ); //model.php'; $app['db.pdo'] = $app->share(function($c) { $db_config = $c['db.config']; $pdo = new PDO( sprintf('mysql:host=%s;dbname=%s;charset=utf8', $db_config['host'], $db_config['database']), $db_config['user'], $db_config['password'], array(PDO::ATTR_EMULATE_PREPARES => false) ); return $pdo; }); $app['model.all_posts'] = function($c) { $stmt = $c['db.pdo']->query('SELECT id, title FROM post'); $posts = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $posts[] = $row; } return $posts; }; $app['model.post_by_id'] = $app->protect(function($id) use ($app) { $sth = $app['db.pdo']->prepare('SELECT id, date, title, body FROM post where id = :id'); $sth->bindValue(':id', $id, PDO::PARAM_INT); $sth->execute(); $post = $sth->fetch(PDO::FETCH_ASSOC); return $post; }); // controllers.php $app->get('/', function(Application $app, Request $request) { $posts = $app['model.all_posts']; $html = render_template('templates/list.php', array('posts' => $posts)); return $html; }); $app->get('/show', function(Application $app, Request $request) { $get_post_by_id = $app['model.post_by_id']; $post = $get_post_by_id($request->query->get('id')); if (!$post) { $app->abort(404); } $html = render_template('templates/show.php', array('post' => $post)); return $html; }); $app->error(function (\Exception $e, $code) { $html = '<html><body><h1>ページが見つかりません</h1></body></html>'; return new Response($html, $code); }); $app->run(); // テンプレートをレンダリングするためのヘルパー関数 function render_template($path, $params) { extract($params, EXTR_SKIP); ob_start(); require $path; $html = ob_get_clean(); return $html; }
1ファイルで書いていても、それなりに読みやすいとおもいます。
Silex化する前とのコードの違いはコンテナ($container)はSilexではSilexそのものがコンテナになっているため$appに書き換えている点とルーティングごとに処理を無名関数で登録している点です。
無名関数の内容は最初とほとんど変わっていません。戻り値がResponseオブジェクトを指定しなくても、ブラウザに返却したい文字列を返しているというぐらいです。
また、記事詳細を表示するときに該当するIDで記事が存在しなかった場合の処理もSilexが提供するabortメソッドで404として簡単に実装できていることもわかりますね。
あとは、適切にファイルに分けてrequireすれば良いのですが、テンプレートをレンダリングする処理が微妙な感じです。これだけグローバル関数として存在しています。テンプレートエンジンに置き換えることも簡単ですが、まずはにコンテナに無名関数として閉じ込めてしまいましょう。
<?php // template $app['template.render'] = $app->protect(function($path, $params) { extract($params, EXTR_SKIP); ob_start(); require $path; $html = ob_get_clean(); return $html; }); // controllers.php $app->get('/', function(Application $app, Request $request) { $posts = $app['model.all_posts']; $render = $app['template.render']; $html = $render('templates/list.php', array('posts' => $posts)); return $html; }); $app->get('/show', function(Application $app, Request $request) { $get_post_by_id = $app['model.post_by_id']; $post = $get_post_by_id($request->query->get('id')); if (!$post) { $app->abort(404); } $render = $app['template.render']; $html = $render('templates/show.php', array('post' => $post)); return $html; }); $app->error(function (\Exception $e, $code) { $html = '<html><body><h1>ページが見つかりません</h1></body></html>'; return new Response($html, $code); });
あと、もう少しです。ファイルを分割してみてください。
そうするとフロントコントローラは以下のようになるはずです
<?php //index.php require_once __DIR__.'/vendor/autoload.php'; $app = new Silex\Application(); require __DIR__.'/config.php'; require __DIR__.'/model.php'; require __DIR__.'/controllers.php'; $app->run();
シンプルになりましたね!
テンプレートエンジン Twig を使う
あと気になるところはどこでしょうか。それは、テンプレート部分の記述がフラットなPHPで実現しているため汚いというところです。Silex では Twig というとてもエレガントなテンプレートエンジンを簡単に導入できるようになっています。そこで Twig でテンプレートを書き換えてみましょう
参照:
- Homepage - Twig - The flexible, fast, and secure PHP template engine
- Documentation - Twig - The flexible, fast, and secure PHP template engine
Twig のインストール
PimpleやSilexと同じでcomposerで簡単にインストールしましょう。
以下のようにcomposer.jsonに追記します。
また、Twigに幾つかメソッドを追加するために、Symfony Componentのtwig-bridgeも入れておきます。(これでテンプレートでpath、urlというメソッドを使うことができるようになります)
{
"require": {
"silex/silex": "1.0.*",
"twig/twig": "1.*", <= 追加
"symfony/twig-bridge": "2.1.*" <= 追加
},
"minimum-stability": "dev"
}
あとはいつもの様にupdateを行います。
$ php composer.phar update
次にSilexにあるプロバイダーという拡張機能で、Twigを利用する準備を行います。具体的には以下のようなregisterメソッドで TwigServiceProviderとUrlGeneratorServiceProviderを登録します。
UrlGeneratorServiceProviderはTwigとは直接関係ありませんが後々必ず使うことになるので入れておきましょう。
<?php .... // twig $app->register(new Silex\Provider\TwigServiceProvider(), array( 'twig.path' => __DIR__.'/templates', )); $app->register(new Silex\Provider\UrlGeneratorServiceProvider());
ルーティングのレンダラーをTwigに変更する
次に一覧表示の処理をTwigを使うように変えてみましょう。
<?php // controllers.php $app->get('/', function(Application $app, Request $request) { $get_all_posts = $app['model.all_posts']; $posts = $get_all_posts; $render = $app['template.render']; return $app['twig']->render('list.html.twig', array('posts' => $posts)); //<= $app['twig']に変更 });
テンプレートエンジンを変えただけなので、Viewの部分をTwigに変えただけです。
同じように、詳細表示のコントローラーも変更します。
<?php $app->get('/show/{id}', function($id, Application $app, Request $request) { //<= idをパスから取得 $get_post_by_id = $app['model.post_by_id']; $post = $get_post_by_id($id); if (!$post) { $app->abort(404); } return $app['twig']->render('show.html.twig', array('post' => $post)); // <= $app['twig']に変更 }) ->bind('blog_show'); // <= このルーティングに'blog_show'という名前をつける
詳細表示側もTwigを使うように変更しました。また詳細表示へのリンクをテンプレートで行うときに、詳細表示のURLをハードコーディングしていたものを'blog_show'という名前をつけることで参照できるようにします。これがbindメソッド部分です。そしてこの機能がさきほど追加したUrlGeneratorServiceProviderの機能です。
また、前回までは記事idはGETパラメータで取得するようにしていましたが、Silexなどのマイクロフレームワークではパスから自由にパラメータとして取得することが簡単に記述できるようになっています。Silexの場合は{id}のように括弧でパラメータ名を指定することで無名関数で$idとして取得することができます。
これで、記事詳細のパスは /show/xxx と定義したことになり、xxxの部分を$idとして内部で扱うことができます。
テンプレートの作成 layout.html.twig
次にテンプレートを修正します。これまで利用してきたlayout.phpに対応するlayout.html.twigを作成します。
Twigではテンプレートの継承が行えます。つまり、このlayoutを継承したテンプレートを用意し、継承先で書き換えたい継承元の一部だけをコーディングすれば良いということになります。では、具体的に見てみましょう。
<!doctype html> <html> <head> <title>{% block title %}Default title{% endblock %}</title> </head> <body> {% block body %}{% endblock %} </body> </html>
Twigでは継承できる部分を {% block 名前 %}デフォルトの値{% endblock %} と定義します。
このレイアウトでは title と body ブロックが定義されています。これを継承したテンプレートで title と body を定義すればOKということです。
list.html.twig
次に一覧表示のテンプレートを用意しましょう。
{% extends "layout.html.twig" %}
{% block title %}投稿のリスト{% endblock %}
{% block body %}
<h1>投稿のリスト</h1>
<ul>
{% for post in posts %}
<li>
<a href="{{path('blog_show', {'id': post.id}) }}">
{{ post.title }}
</a>
</li>
{% endfor %}
</ul>
{% endblock %}
まず最初にどのレイアウトを継承するかという記述があります。これは extends で指定します。もし、異なるレイアウトを使いたい場合はここで新しいレイアウトファイルを指定するだけでレイアウトを変えることができます。
あとは上書きしたいblockを記述していくだけです。Twigの構文では{{ xxx }} で変数をエスケープしたものを出力できるのでとても読みやすいテンプレートになったことがわかります。また、オブジェクトのプロパティへや配列のアクセスも {{post.title}} のように記述できるのが特徴です。
また、さきほどのコントローラーでblog_showという名前をつけたパスをIDのパラメータを指定しつつテンプレートに埋め込むために、pathというメソッドで指定しています。これで、パスをハードコーディングする必要もありません。コントローラー側でURIが変わったとしてもルーティングにつけた名前が同じである限り自動的に解決してくれます。この機能がcomposerで追加したTwigBridgeコンポーエントが提供している機能です。
よくみると、ループ処理も for in というTwigの構文で書かれていたり、pathメソッドでのIDの指定の仕方がjson方式だったりとフラットなPHPとは異なる部分が多いですが、フラットなPHPよりも書きやすく読みやすいというのがわかっていただけるかと思います。
ここではshow.html.twigで用意したコードは書いていませんが、簡単ですので実際にテンプレートを用意して表示を試してみてください。
最後に
フラットなPHPからSilexに変化していく様子を見てきました。なぜフレームワークが便利なのかというのが見えてきたのではないでしょうか?とはいえ、フレームワークは銀の弾丸ではありません。このようにルーティングの処理は任せて、本来コーディングしたい部分に集中できるように助けてくれます。
今回はSilexで説明してきましたが、Silexには他にも良い感じの機能を提供してくれています。たとえばサービスプロバイダという拡張機能が用意されているため、テンプレートエンジンをTwigに変えたり、PDOではなくDoctrineのDBALを使うというのも簡単にできます。
参照:
また、Silexは小規模で複雑でないアプリケーションを開発するときには悩むことはそれほどありませんが、ある程度の規模や人数での開発になってくるばあいはそのために考慮したり開発を行う部分が増えてきます。そのため、Symfonyなどのフルスタックフレームワークで開発することをお勧めします。
これまで理解した知識を活かしつつ、さらにしっかりとした枠組み(フレームワーク)で開発が行うことができます。
次はSlim版も書く予定。たぶんこれよりももっと薄い内容になるはず。。疲れた書いた。
2012-07-07
■[php][Silex] これからのSilexのインストール方法
七夕ですね。BEAR.Sundayが楽しいですね。詳細についてはまだ発表されていませんが7/19(木)についに BEAR.Sunday Meetup #0 が開催されるようですよ。
今日はSilexのインストール方法について色々変更があったのでまとめておきます。
silex.pharは配布しないぜよ
詳しくはダウンロードページにまとめられていますが、これまで silex.phar の1ファイルをDLするだけ!が特徴だったのですが、これは今後配布されなくなります。
1ファイルだけで管理できるというのが大きかったのですが、それよりもデメリットのほうが大きいという判断だと思います。
たとえば、実行速度。pharファイルはアーカイブなので展開処理分オーバーヘッドです。以前試したところでは、Hello Worldを表示するレベルでもpharを使わなければ140%ぐらいのリクエスト処理数になりました。
あと、コア開発者側としては、pharを動かせずにissueやMLにいつも質問が飛び交っていてサポートするのが大変というのもあったかもしれません。
事実、pharに入っているSymfony Componentのバージョンが上がるだけでSilexのコアが変更されていなくてもsilex.pharも更新しなければならず面倒というのもあったと思います。
この問題はこれから説明するcomposerによるインストールでいっきに解決します。
とりあえず Silex のコードだけを入れる方法
composerはパッケージの依存関係を管理する方法です。最小構成のSilexをインストールするには以下のcomposer.jsonというファイルを用意します。
composer.json
{
"require": {
"silex/silex": "1.0.*"
}
}
そして、composer.pharをダウンロードしてきて、installを叩くだけ。
$ curl -s http://getcomposer.org/installer | php $ php composer.phar install
かんたんですよね。もし、twigも使うという場合は
{
"require": {
"silex/silex": "1.0.*",
"twig/twig": ">=1.8.0,<2.0-dev"
}
}
とtwig/twigの1行を追加し
$ php composer.phar update
を叩くだけ。これで最新のstable版が追加でインストールされます。
いままでのことを考えるとかなり便利だと思います。
本格的にSilexを使うためのたったひとつの方法
この記事を書いたのはこの方法について知って欲しかったからなのです。
Silexである程度大きなアプリを作るとなると、コントローラーを別ファイルにしたり、ロガー欲しいよねとか、いろいろあるわけですが、このあたりの準備となるディレクトリ構成などは自分で考えなくてはなりませんでした。もしくは、Silex-Kitchen-Editionという全部入りのコードをgithubからダウンロードしてくるというのが一般的でした。
大丈夫です。これからは良い感じのスケルトンが用意されました。
何も考えずに以下のコマンドを打てば作業は終わりです。
$ php composer.phar create-project fabpot/silex-skeleton ./silex $ mkdir ./silex/silex.log ./silex/cache $ chmod 0777 ./silex/silex.log ./silex/cache
これでコマンドを打ったディレクトリにsilexというディレクトリが作成され、そこにsilexのスケルトンファイルが用意されます。実際にはdoctrine/dbal以外はすべてインストールされます。
これだけでも便利ですよね。
あとはweb/index.phpがプロダクション環境用、web/index_dev.phpが開発用のコントローラになっているのはSymfony2っぽい感じですね。
また、コンソールから叩くための準備もされていて
$ php console
を叩くとmy-commandがサンプルとして用意されているのが見えます。
このスケルトンを利用すれば、最初の開発するまでの手順はかなり短縮化できると思います。
あとは composerがもう少し安定してくれれば...
おまけ
doctrine/dbalも使いたい場合は composer.json に
{
"name": "fabpot/silex-skeleton",
"require": {
"php": ">=5.3.3",
"silex/silex": "1.0.*",
"twig/twig": ">=1.8.0,<2.0-dev",
"monolog/monolog": ">=1.0.0,<1.2-dev",
"symfony/browser-kit": "2.1.*",
"symfony/class-loader": "2.1.*",
"symfony/config": "2.1.*",
"symfony/console": "2.1.*",
"symfony/css-selector": "2.1.*",
"symfony/finder": "2.1.*",
"symfony/form": "2.1.*",
"symfony/monolog-bridge": "2.1.*",
"symfony/process": "2.1.*",
"symfony/security": "2.1.*",
"symfony/translation": "2.1.*",
"symfony/twig-bridge": "2.1.*",
"symfony/validator": "2.1.*",
"doctrine/dbal": "2.3.*"
},
"minimum-stability": "dev",
"autoload": {
"psr-0": { "": "src/" }
}
}
と、最後にdoctrine/dbalを追加すればOKですよ。
2012-05-16
■[php][Silex]Silexを通して伝えたかったこと ~ PHPカンファレンス関西2012 ~
お詫び:
本当は月曜日にでもレポートを書こうと思っていたのですが、諸事情により*1このタイミングになりました。
カンファレンス全体を通して感じたこと
5/12に大阪で開催されたPHPカンファレンス関西に参加してきましたのでまずはざっくりと印象を箇条書き。
- 当たり前でも難しいタイムスケジュールがほぼ完璧だったんじゃないかと思います。すばらしいれす。
- Ustの機材や設定がとても気になったのでどこかで公開してほしい。
- 自分が知ってる里とは違う里の忍者、+くのいちが居た。
- 去年はPHPのコードがなかなか出てこないという不満がありましたが、今年はお腹いっぱいでした。
- ドラ娘の目の前の席でした。ごちそうさまでした。
- 「あれ、今日は忍者じゃないんですか?」ってほとんどの人に指摘された。ニンニン。
- 4Fにしか居なかったんだけど、各セッションの後の質疑応答で参加者からの質問があまりなかった。次回からは皆質問しようぜ。
- LTは神業
番外
- 無限もやしが売り切れるという矛盾を体験できた
- 和民のあやしげなサワーには青く光る氷が入っているが、そのスイッチON/OFFの仕組みに感動
- ひさしぶりに「チューハイのカルピス」というフレーズで注文した
Silexのお話
そしてSilexについて20分ほど時間をいただけたので話をしてきました。
まず、最初にお詫び。「たった9行で」と熱く語りましたが、ご指摘あったとおり、はい、8行です。
私の澄んだ心では前日はどう数えても9行だった(ry
また、当日のUstが公開されていたので以下に紹介
Silexの話を..といいながら前半5分ぐらいは"なぜフレームワークを使うのか"というSilexのSの字も無いお話でしたが言いたいことは前半でした。
すべてのフレームワークに精通する必要は全くありませんが、自分に合っているもの、またはチームやプロジェクトに適しているものを見極める必要はあります。
食べず嫌いにならずに、色々試してみてくださいね。PHPをプロ(仕事)として使うなら当たり前なことですが。
当日懇親会などで話していて感じたこと
キャッシュは大事
当日の講演の"笑けるほど速いPHP-Ninjaの裏側 WordPressの超高速化を支えるテクニック"でも指摘されていたように、Webアプリケーションでいうと、やはり"キャッシュ"をどこまでできるかが重要なポイントだと思います。
Symfony2がキャッシュを使わないと重いのは当たり前で、キャッシュを利用すれば YouPorn*2の実例紹介にあるようにSymfony2で1日1億PV+α捌くことだって可能なのです。
やりたいことによって必要な知識は全く異なる
大規模なアプリケーションを開発することとWPでプラグインを作成することとにおいて必要な知識は全く異なると思います。
でも、"PHPを使えるようになりたい"という視点からは両方とも同じに見えてしまうかもしれません。
少しずつステップアップして理解していくことも必要ですし、必要以上に知らなくてもいいんじゃないかと思います。
そういう意味でも@yandoさんの"PHP classの教室"のような講座があるカンファレンスはとても良いと思いました。
このあたりはまた言いたいことがあるので別途ブログに書きます。
まとめ
スタッフの皆さんをはじめ参加者の皆さんも本当に楽しい時間をありがとうございました!
また来年のこのときまでにレベルアップします!
2012-02-04
■[php][Silex][PHPTAL] SilexのExtensionからProvider への進化
かなり放置してしまっていたPHPTALのSilexエクステンションを更新しました。
Extensionという名前からProviderという名前に変わったのでその対応だけです。
brtriver/PHPTALServiceProvider ? GitHub
そして、SilexがExtensionからProviderへと名前を変えたのには意図があるということが最近把握できた気がするので一言。
Providerは2種類ある
Silexが提供しているProviderはServiceProviderというものです。
外部ライブラリやサービスを利用するための遅延読込、腐敗防止層的な役割を行います。
PHPTALServiceProviderもこの1種です。
また、Silexにはコントローラーを別ファイル化し、mountする仕組みが用意されていますがそのときに用意するコントローラーもProviderとして用意するようになりました。
たとえば、管理画面用のコントローラーを用意しAdminControllerProviderとして用意したりします。
Extensionのときはこのようなコントローラーはエクステンションの一部として考えておらずコントローラーアプリケーションの一部としてみなしていました。
[追記] 訂正。気になって調べなおしてたのですが、別ファイル化したコントローラーは以前でもエクステンションの一種として扱いっていました。ただ、アプリケーションの一部という位置づけだったのが現在はコントローラーの一部という位置づけになっています。
しかしSilexから見ればどちらもSilexに機能を提供してくれる何かという意味では同じですよね。
なので、Extensionという定義からより適切な名前を表すProviderという領域を定義しそこに閉じ込めたんだと思います。
名前大事。関心ごとを正しく整理すること大事。
2011-10-20
■[php][Silex]Silexの紹介記事を寄稿しました&さらに最新情報
Silex のことが書かれた最初の記事かも?
もうみなさんご存知ですよね。今月のWEB+DB PRESS Vol.65 に"PHPフレームワーク実践活用〜Symfony2,CakePHP,Silex〜"という特集があります。
今回、声を掛けていただき 6ページにまとめた Silex の紹介記事を寄稿しました。
http://gihyo.jp/magazine/wdpress/archive/2011/vol65
内容は、ただSilexの説明をするだけでは面白くないので、CSVファイルで用意した新着情報の一覧表示、詳細表示を行う小さなアプリケーションのサンプルを作り、そのソースコードでSilexの特徴を解説するという流れになっています。
実際にサンプルコードもダウンロードできるようになりますので、環境さえあればソースコードを一式展開するだけで試すことができるようになっています。
予想外の出来事とお詫び
記事を提出してほっとしていたら、Silexのエクステンションの仕組みが変更になり、最新版のSilexにアップデートしてしまうと動かなくなるということが発覚!
仕様変更の詳細は公式サイトのChangelogのページの「2011-09-22」を参照してください。
Changelog - Documentation - Silex - The PHP micro-framework based on Symfony2 Components
具体的にはインターフェースの名前が変更されました。
Twigエクステンションの場合
(旧) Silex\Extension\TwigExtension
(新) Silex\Provider\TwigServiceProvider
というわけで、もし silex.pharを最新版で使う場合は名前空間の指定を変更しなければなりません。
サンプルコードに入っているsilex.pharを使っている場合は変更しなくても動きます。
また、日本語ドキュメントにもまだ反映できていないのでこの点は申し訳ありませんがご注意いただければと思います。
ちょっとしたこぼれ話
10/15,16と大阪で PHPMatsuri2011 が開催され、Silexの開発者でもあるFabienさんが来日されていました。
せっかくなので、Silexはどこで実際に利用されているのかと聞いてみました。
すると、Silex, Twig, SwiftMailerなどの公式サイトは全てSilexで作られているとのこと。
やはりああいう感じのシンプルなページ構成のサイトを作るのにはぴったりなんですね。
PHPMatsuriではSilexのルーティングをハックしていたのですが、ハック後に動いているように見えるのですが、なぜか単体テストがレッドになってしまう現象に遭遇。
どうしても謎な挙動だったのですが、直接Fabienさんに見てもらって色々とアドバイスをもらうという超贅沢な経験もしつつ充実した2日でした。
PHPMatsuriについては、のちほど簡単にレポートをまとめます。にんにん。。
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |



