symfonyで学ぶMVCにのっとったリファクタリング入門
こんにちは。小川です。
本日はPHPユーザ会主催の設計勉強会が開催されていたのですが、応募期間に間に合わなかったので、鬱憤を晴らすためにブログを書いてみました。
1週間ほど前からsymfonyの公式ブログで、コントローラ(symfonyだとactions)にロジックを詰め込んでいるようなコードを、ストーリー形式でリファ クタリングしていくという記事が5回に渡って紹介されていました。
symfony | Web PHP Framework | Blog Category | Call the expert
こちらの「A refactoring story」というのがその記事になります。
僕が最初にMVCフレームワークを使ったときがそうだったのですが、MVCを理解していないとコントローラに全てのロジックを詰め込んでしまうようなコードを書いてしまいがちなのではないかと思います。皆さんはどうでしょうか。心当たりのあるかたはぜひこの機会に見直していきましょう。
この記事ではPart3から実際にリファクタリングに入っていきます。Part3のはじめに、こんな1文があります。
The Controller role is to get data from the Model and pass them to the View.
「コントローラの役割はモデルからデータを取得しビューへの橋渡しをすること」と述べられています。コントローラにはロジックを詰め込むべきではないのです。
それでは実際にリファクタリングしていく様子をみていきましょう。
今回は簡単なECサイトが題材として使われています。全体のコードやテーブル定義はPart1の記事に書かれています。
Call the expert: A refactoring story (Part 3/5)
<?php
// apps/frontend/modules/product/actions.class.php
public function executeIndex()
{
// get all available products
$criteria = new Criteria();
$criteria->add(ProductPeer::IS_IN_STOCK, true);
$criteria->addAscendingOrderByColumn(ProductPeer::PRICE);
$criteria->setLimit(10);
$this->products = ProductPeer::doSelectJoinCategory($criteria);
$this->getResponse()->setTitle('All products');
$this->getResponse()->addStylesheet('homepage.css');
return sfView::SUCCESS;
}
これは在庫のある商品を価格の昇順に10件取得して表示するというアクションです。まずはこのコードから直していくようです。
このコードはCriteriaを用いて条件を定義し検索するという処理をコントローラ内で全て行っていますが、こういったデータベースから取得するコードはモデルに書くべきです。
ということで以下のように書き直されました。
Call the expert: A refactoring story (Part 3/5)
<?php
// lib/model/ProductPeer.php
static public function getAvailableProducts()
{
$criteria = new Criteria();
$criteria->add(self::IS_IN_STOCK, true);
$criteria->addAscendingOrderByColumn(self::PRICE);
$criteria->setLimit(10);
return self::doSelectJoinCategory($criteria);
}
<?php
// apps/frontend/modules/product/actions.class.php
public function executeIndex()
{
$this->products = ProductPeer::getAvailableProducts();
$this->getResponse()->setTitle('All products');
$this->getResponse()->addStylesheet('homepage.css');
return sfView::SUCCESS;
}
どうでしょうか。上がモデル、下がコントローラになりますが、コントローラ側はだいぶすっきりしたのではないでしょうか。
例えば、同じ条件で商品をサイドバーに表示したい!という要望があったときどうでしょう。
その場合も上記のgetAvailableProductsメソッドを使えば同じ内容のデータがすぐに取得できて楽ですよね。
さて、getAvailableProductsメソッドをもう1度みてみましょう。Limit値が10固定になってしまっていますよね。このままでは、このメソッドを使ってる別の部分では5件だけ 取得したいといった要望がある場合に対応できません。これでは汎用性にかけるということで、以下のように修正されました。
Call the expert: A refactoring story (Part 3/5)
<?php
// lib/model/ProductPeer.php
static public function getAvailableProducts($max = 10)
{
$criteria = new Criteria();
$criteria->add(self::IS_IN_STOCK, true);
$criteria->addAscendingOrderByColumn(self::PRICE);
$criteria->setLimit($max);
return self::doSelectJoinCategory($criteria);
}
第一引数にLimit値を指定できるようになりました。デフォルト値に今までとおり10が入っていますので、既存の呼び出し部分を変更する必要はありません。
他にも例えば、特定の箇所でだけ価格の降順にしたいといった要望があった場合を想定して、自分なりに実装に手を加えてみました。
自分なり実装その1
<?php
// lib/model/ProductPeer.php
static public function getAvailableProducts($max = 10, $desc = false)
{
$criteria = new Criteria();
$criteria->add(self::IS_IN_STOCK, true);
$orderer = ($desc) ? 'addDescendingOrderByColumn' : 'addAscendingOrderByColumn';
$criteria->$orderer(self::PRICE);
$criteria->setLimit($max);
return self::doSelectJoinCategory($criteria);
}
これも同様にオプショナルな引数をつけることで簡単に対処してみました。