ECサイト運営開発記

PHPフレームワーク Laravelの使い方を中心とした通販サイトの開発日記

LaravelのORM機能 Eloquent の EagerLoading

LaravelのORM Eloquentには、Eager Loadingという機能がある。
モデル内に定義されたメソッドを元に自身以外の他テーブルからデータを引っ張りだしてくれる便利な機能。
例えば、以下の様なECサイトのテーブル構成でイメージしてみる。

商品テーブルにはimage_idというサムネイル用のIDが定義されており、画像テーブルのimage_idにリンクされている。
やりたいこととしては、商品情報と画像の情報を一度に取得するというもの。
モデルの定義は割と簡単で以下のように設定する。

<?php

// models/item.php
class Item extends Eloquent{

	public static $table = "item";
	public static $key = "item_id";
	public static $timestamps = false;

	// サムネイル
	function thumb(){
		return $this->belongs_to("Image","image_id");
	}
}

// models/image.php
class Image extends Eloquent{

	public static $table = "image";
	public static $key = "image_id";
	public static $timestamps = false;
}

で、問題はコントローラー側でどういう呼び出し方をするのか?
Eloquentでは、必ず、staticなwithメソッドからスタートさせなければならないルールがある。

<?php

/*
 コントローラー側の処理
*/

// すべての商品をサムネイルと一緒に取得
$itemList=Item::with("thumb")->get();

// 条件を付与
$itemList=Item::with("thumb")
->where("price",">",500)
->where("del_flg","=",0)
->get();

// 情報の呼び出し方
foreach($itemList as $value){
  echo $value->item_name." ".$value->price."円 :";
  echo $value->thumb->file_name;
}

// 出力
// 銅の剣 500円:thumb_ken.jpg

?>

といった具合な事を書く。ちなみに、以下が実行されるSQL

SELECT * FROM `item`;
SELECT * FROM `image` WHERE `item_image_id` IN (1,2,3);

SELECT * FROM `item` WHERE `price` > 500 AND `del_flg`=0; 
SELECT * FROM `image` WHERE `item_image_id` IN (1,3);

見ての通り、JOINなどでテーブル結合しているわけではなく、一度、商品テーブルからデータを取得した後、
image_idをIN句でくくって、取得するという方法が取られている。
これに関しては、まぁ、色々と議論の余地はあるんでしょうが、チャチャっと作りたい人には便利な機能かもしれません。

LaravelでSQLの実行結果が簡単にわかるプロファイラー機能

Laravel3.1になり、Bandle(プラグイン)として提供されていたSQLプロファイラーのAnbuが標準で組み込まれることになり、LaravelでもCakePHPのようなSQL実行結果が簡単にわかるようになったようです。このプロファイラーはCake同様、ページの下部に固定される形に表示されるわけですが、標準ではオフになっているので、有効にするには、以下のように設定する必要があります。

  • application/config/application.phpを開く
    • Profilerの項目をfalseからtrueに変更して保存
<?php 

/* 中略 */

	/*
	|--------------------------------------------------------------------------
	| Profiler Toolbar
	|--------------------------------------------------------------------------
	|
	| Laravel includes a beautiful profiler toolbar that gives you a heads
	| up display of the queries and logs performed by your application.
	| This is wonderful for development, but, of course, you should
	| disable the toolbar for production applications..
	|
	*/

	'profiler' => true,

DBに接続しているページを表示してみれば、下のような具合で、SQLの実行結果が確認できます。

LaravelのELOQUENTの便利なSETTERとGETTER

LaravelのORMには特定のフィールドに対して、ちょっとした処理を加えて、データを保存できたりする便利な
メソッドがあります。わかりやすいのは、パスワードを暗号化して保存したい場合。
$user->password="password" としてやるだけで、文字列が暗号化されてデータがセットされるみたいな。

方法は非上に簡単で、set_{フィールド名}でメソッドを作ってあげればいいだけ。あとは、そのメソッド内で暗号化なりなんなり好きにすればいいだけです。下のサンプルでは、LaravelのCrypterを使って暗号化するサンプルです。

<?php 

// models/user.php
use Laravel\Crypter;
use Laravel\Database\Eloquent\Model;

class User extends Eloquent {

	public static $table = "user";
	public static $key = "user_id";
	public static $timestamps = false;

	public function set_password($password){
	  $this->set_attribute("password", Crypter::encrypt($password));
	}
}

// コントローラー
// controllers/user.php
class User_Controller extends Base_Controller {
	public function action_add(){
	  $user=new User;
	  $user->name="nohohonx";
	  $user->password="hogehoge";
	  $user->save();
	}
}
?>

phpMyAdminなどで、userテーブルを見ると、暗号化されたU326DItRyQYN8Pb3Eu82iNshUMqwngf+Oh5o93hEみたいな文字列で埋まってるはずです。素晴らしい。また、逆にget_{フィールド名}のメソッドを定義することも可能で、テーブル上に存在しないフィールド名を使用しても問題ありません。下のサンプルは、日本語の日付フォーマットに整形されたデータを取得するサンプル。

<?php 

	public function get_format_registed_date(){
	  $datetime = date_create($this->get_attribute("registed_date"));
	  return date_format($datetime, "Y年m月d日");
	}

	// 整形されたデータが表示されます。
	echo $user->format_registed_date;

他にも苗字と名前を一緒に取得するためのメソッドを定義するなんてことも。

<?php 

class User extends Eloquent {

	public static $table = "user";
	public static $key = "user_id";
	public static $timestamps = false;

	public function get_full_name(){
	  return $this->get_attribute("last_name")." ".$this->get_attribute("first_name");
	}
}

// Yamada Tarou が出力される
$user=User::find(1);
echo $user->full_name;

他のフレームワークにも似たような機能はあるんでしょうが、実に素晴らしいです。

Laravelのバリデーションの日本語化など。

Webアプリでは欠かせない重要な機能。入力値の検証いわゆるバリデーション。もちろん、Laravelにも、バリデーション機能があるわけで。
一応、備忘録として、日本語化の方法なども含めて、簡単に残しておこうと思う。

  • 下準備
    • application/config/application.phpを編集
      • Application Languageをjaに設定。別にjpでもOK。
        • "language"=> "ja"
    • application/language/ディレクトリに ja という名前のディレクトリを作成
      • application/language/ja/にapplication/language/en/にあるvalidation.phpをそのままコピーする下準備は一応ここまで。
  • 以下、入力検証アクションのサンプル
<?php 

  // 入力検証アクション
  public function action_validate(){

    // 入力値
    // すべての入力値が連想配列で取得できる。
    $input = Input::all();

    // バリデーションルール
    $rules = array(
        "name"  => "required|min:5|max:60",
        "email"  => "required|email",
    );

    // 検証
    $validation = Validator::make($input, $rules);

    // 検証結果
    if ($validation->fails()){
        // エラーの場合
        Session::flash("action_message","入力値に問題があります。");
        Session::flash("action_errors",$validation->errors->all());
        return Redirect::back()->with_input();
    }else{
        echo "Success!!";
    }

  }
?>
    • 上のコードは何をやっているか?
      • $input = Input::all(); で入力値を連想配列で取得。
      • $rules = array(); で検証ルールを設定
      • Validator::make($input, $rules);でオブジェクトを作成。
      • $validation->fails()で失敗かどうか判定
      • 入力値に問題がある場合は呼び出し元へリダイレクト
        • return Redirect::back()->with_input(); はとても幸せになれる魔法のコードだと思ってる。
          • リダイレクトすると共に、入力値を引き継げるメソッドだから。
          • View内でInput::old("name")って呼び出すと、保持された入力値が表示できる。ただし、リロードすると消える。
  • 肝心の日本語化は?
    • 下準備の時点でコピーしたapplication/language/ja/validation.phpを見ればなんとなくわかるはず!
<?php 
return array(
	"accepted"       => "The :attribute must be accepted.",
	"active_url"     => "The :attribute is not a valid URL.",
	"after"          => "The :attribute must be a date after :date.",
	"alpha"          => "The :attribute may only contain letters.",
	"alpha_dash"     => "The :attribute may only contain letters, numbers, and dashes.",
);
?>
    • お好みの日本語でどうぞ
    • :attribute には HTMLフォームで指定した name 属性が勝手に入ることになる。
      • これも日本語にしたいって?
        • ご心配なく、同ファイル内に↓これを書けばいい。
<?php 

"attributes" => array(
	"name"=>"お名前",
	"email"=>"メールアドレス",
	"address"=>"住所",
)

?>
    • 簡単でしょ?

とりあえず、今日はこれまで。

LaravelのBladeテンプレート

PHPフレームワークLaravel」のコントローラーの簡単な使い方とBladeテンプレートについて、備忘録も兼ねて残しておく。
Laravelのインストール方法については、割愛。ダウンロードして、この辺を参考にすれば、基本どうにかなるので。

  • コントローラーを使うにあたり、下準備
    • application/controllers/ディレクトリに標準で、home.phpとbase.phpが入っている。
      • 文字通りHome_ControllerとBase_Controller。
      • 通常は、BaseControllerを継承して、新たにコントローラーを作成するらしいので、今回もそれに習う
  • 今回やりたい事
    • コントローラーを作成して、LaravelのBladeテンプレートを使いViewを表示する
    • まずは、新規にショッピングコントローラーを作成
      • ファイル名 application/controllers/shopping.php
<?php 
class Shopping_Controller extends Base_Controller {
  // とりあえず、何もない状態
  public function action_index(){
     echo "shopping controller!";
  }
}
?>
    • デフォルトレイアウトファイルを作成
      • application/views/layouts/default.blade.php という具合で 拡張子 *.blade.php で作成。
      • レイアウトテンプレートは以下のような感じ
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>@yield("page_title")</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{HTML::style("css/bootstrap/css/bootstrap.min.css")}}
{{HTML::script("js/jquery-1.7.2.min.js")}}
{{HTML::script("css/bootstrap/js/bootstrap.min.js")}}
</head>
<body>
<div class="container">
	@include("partials.header")
	@yield("content")
	@include("partials.footer")
</div>
    • Bladeテンプレートの基礎
      • {{ }}ブロック
        • 変数の出力
          • この中には、PHPコードが書ける
          • {{$a}}、{{10*3}}、{{$a+1}} など基本なんでもOK。
          • ちなみに、上のコードでは、Laravelのヘルパを呼び出してる。
      • @yieldは特定のセクションの出力結果を表示するもの
        • アクションViewファイル等で@section("section_name") @endsectionで囲まれた内容を @yieldで呼び出す
          • アクションViewファイル内で@section("page_title")ようこそホームページへ@endsection と記述し、@yield("page_title")とした場合、「ようこそホームページへ」と出力される。
      • @include("〜〜")は文字通り、見たままパーツの読み込みに使用する
        • @include("partials.header")は application/views/partials/header.blade.php を読み込む
    • アクションテンプレートの作成
      • レイアウトテンプレートと同様に application/views/shopping/index.blade.php という具合で 拡張子 *.blade.php で作成。
        • application/views/コントローラー名/アクション名.blade.php がわかりやすいかも。でも、基本的にどこに置いても自由
@layout("layouts.default")

@section("page_title")
カテゴリー一覧
@endsection

@section("content")
	<h2>カテゴリー一覧</h2>
	<ul>
	@forelse ($categoryList as $category)
		<li>
		<a href="<?php echo URL::to("shopping/category/"); ?><?php echo $category->category_id ?>/">
		<?php echo $category->category_name ?>
		</a>
		</li>
	@empty
		<li>カテゴリーが見つかりませんでした</li>
	@endforelse
	</ul>
@endsection
    • アクションViewテンプレートからレイアウトを指定する
      • @layout("layouts.default")
        • .(ドット)がディレクトリ区切り
        • application/views/layouts/default.blade.php
    • @section("content") 〜 @endsection がコンテンツ。
      • もちろん、"content" という名前で縛る必要はない。
    • @section("page_title")カテゴリー一覧@endsection
      • レイアウトテンプレートで@yield("page_title")で呼びだすと、カテゴリー一覧が出力される
    • ループ構文や条件分岐が可能
      • @forelse @empty @endforelse
        • Smartyでいう{foreaech}〜{foreachelse}〜{/foreach}
      • @if ($a==1) 〜 @endif
  • コントローラーファイル application/controllers/shopping.php を編集
    • 以下、shoppingコントローラーのindexアクションで全てのカテゴリー一覧を取得して、Bladeテンプレートを使い表示するコード
<?php 
class Shopping_Controller extends Base_Controller {

  // レイアウトテンプレートの指定
  public $layout = "layouts.default";

  // インデックスアクション
  public function action_index(){
    
    // Viewsの指定
    $view=View::make("shopping.index");
    $view->with("categoryList",Category::all());

    // 必ず、Viewを返却する
    return $view;
  }
}
?>
    • public $layout = "layouts.default"; でテンプレートファイルを指定する。
    • アクションメソッドの中で、Viewにデータをセットして、レンダリングする
      • $view=View::make("shopping.index");
        • Viewファイルがapplication/views/shopping/index.blade.php の場合
    • Viewにデータをセットする場合、$view->with("categoryList",Category::all());
      • Smartyでいうassign("key",$value);
    • アクションの最後に、$viewオブジェクトを返却すれば、レンダリング
      • return $view;

大筋ではこんな具合。Bladeテンプレートの詳細に関しては、

[ドキュメントとチュートリアル]
http://laravel.com/docs/views/templating
http://daylerees.com/2012/04/07/laravel-blade/

[電子書籍版]
http://daylerees.com/2012/03/31/release-code-happy/
などを参照。


MVCフレームワーク Laravel 3.0

FuelPHPを勉強してみたが、ORMがなんとなく、気に入らなかったので、他のフレームワークを探してみると、気になるフレームワークを発見。それがLaravel。なんて、読むのかは知らない。ララヴェル

公式サイトは以下のとおり。
http://laravel.com/

[概要]

  • Laravel is a clean and classy framework for PHP web development.
  • ZendFramework、CakePHPSymfony、CodeIgniterのどれかを使ったことがあれば、取っ付き易い。
    • インストールは非常に簡単で、公式サイトのこの辺を読めばなんとなくできる。
    • しかし、日本語情報はほぼ皆無。m-tagさんのダイアリーしか出てこない。
      • 英語の情報も洋書が一冊も見つからないような状態。歴史はかなり浅い?
  • Slimのようなマイクロフレームワークのような使い方もできる。
    • application/routes.php に記述
<?php 

/* 中略 */
Route::get('/', function()
{
    echo "Hello World!";
});
Route::post('/input/', function()
{
    echo "this is post!";
});

?>
  • Eloquentというシンプルで強力なORマッパーがある。
    • Relationに関しては、結構、パワフルな印象。
      • ただ、ちょっと余計なお世話な機能もある?
    • 標準でkey/valueデータストアのREDISも使える
    • 拡張で、MongoDBも使える
  • もちろん、キャッシュやセッション、ファイルのアップロードなどアプリケーション構築に必要な機能は搭載
    • 入力検証のバリデーション機能は、CodeIgniterライク
<?php 

$input = Input::all();
$rules = array(
    'name'  => 'required|max:50',
    'email' => 'required|email',
);

$validation = Validator::make($input, $rules);
if ($validation->fails()){
  return $validation->errors;
}

?>
    • オートローダーも付属
      • ディレクトリ単位で読み込み
<?php
Autoloader::directories(array(
    path('app').'extra',
));
?>

[Eloquentを利用したアプリケーションサンプル]

  • 目的:武器、防具、道具の3つのカテゴリー別の商品一覧を表示するだけ
  • まず、以下の様なMySQLデータベースを用意。
    • DB名:framework_db
      • 商品テーブル:item
      • カテゴリーテーブル:category
      • 商品とカテゴリーの関連テーブル(いわゆる中間テーブル):category_item
    • 設定ファイルに接続情報を記述
      • application/config/database.phpを編集
<?php

// 使用するデータベース
'default' => 'mysql',

/* 中略 */ 
'connections' => array(
  'mysql' => array(
    'driver'   => 'mysql',
    'host'     => '127.0.0.1',
    'database' => 'framework_db',
    'username' => 'user',
    'password' => 'password',
    'charset'  => 'utf8',
    'prefix'   => '',
),
?>
--
-- 商品テーブル
--
CREATE TABLE IF NOT EXISTS `item` (
  `item_id` int(11) NOT NULL AUTO_INCREMENT,
  `item_image_id` int(11) NOT NULL DEFAULT '1',
  `item_name` varchar(255) NOT NULL,
  `item_price` int(11) NOT NULL DEFAULT '0',
  `del_flg` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

--
-- 商品データ
--
INSERT INTO `item` (`item_id`, `item_image_id`, `item_name`, `item_price`, `del_flg`) VALUES
(1, 1, '銅の剣', 80, 0),
(2, 1, 'ひのきの棒', 50, 0),
(3, 1, 'イバラのムチ', 120, 0),
(4, 1, 'レイピア', 90, 0),
(5, 1, 'ドラゴンキラー', 450, 0),
(6, 1, '竹の槍', 55, 0),
(7, 1, '鉄の鎧', 150, 0),
(8, 1, '麦わら帽子', 35, 0),
(9, 1, '鉄仮面', 0, 0),
(10, 1, '金の鎧', 690, 0),
(11, 1, 'アイスソード', 10000, 0),
(12, 1, '薬草', 5, 0),
(13, 1, 'ポーション', 30, 0),
(14, 1, '赤いリボン', 15, 0),
(15, 1, '釘バット', 40, 0);

--
-- カテゴリーテーブル
--
CREATE TABLE IF NOT EXISTS `category` (
  `category_id` int(11) NOT NULL AUTO_INCREMENT,
  `category_name` varchar(255) NOT NULL,
  `del_flg` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`category_id`)
);
INSERT INTO `category` (`category_id`, `category_name`, `del_flg`) 
VALUES(1, '武器', 0), (2, '防具', 0),(3, '道具', 0);

--
-- 商品とカテゴリー関連付けをするテーブル
--
CREATE TABLE IF NOT EXISTS `category_item` (
  `category_item_id` int(11) NOT NULL AUTO_INCREMENT,
  `category_id` int(11) NOT NULL,
  `item_id` int(11) NOT NULL,
  `del_flg` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`category_item_id`)
);
INSERT INTO `category_item` (`category_item_id`, `category_id`, `item_id`, `del_flg`) VALUES
(1, 1, 1, 0),
(2, 1, 2, 0),
(3, 1, 3, 0),
(4, 1, 4, 0),
(5, 1, 5, 0),
(6, 1, 6, 0),
(7, 2, 7, 0),
(8, 2, 8, 0),
(9, 2, 9, 0),
(10, 2, 10, 0),
(11, 1, 11, 0),
(12, 3, 12, 0),
(13, 3, 13, 0),
(14, 2, 14, 0),
(15, 1, 15, 0);
  • ELOQUENT ORM
    • FuelPHPのモデルはいちいち、フィールドリストを定義しないといけなかったりと、面倒でしたが、Elquentはそんなの不要。
    • モデルの定義
      • Eloquentを継承して、テーブル名と主キーを設定するだけ。
        • public static $table = "テーブル名";
        • public static $key = "主キー";
    • リレーションは1対1、1対多、多対多などできる。
<?php

// models/item.php
class Item extends Eloquent {
  public static $table = "item";
  public static $key = "item_id";
}

// models/category.php
class Category extends Eloquent {

  public static $table = "category";
  public static $key = "category_id";

  public function itemList(){
    $itemList=$this->has_many_and_belongs_to("Item","category_item")->select();
    return $itemList;
  }
}

?>
    • モデルの定義は基本これだけ。
  • コントローラー側での呼び出し
    • application/routes.phpにRoute::controller("home");を記述。
    • 面倒なので、Home_Controllerに書く。
<?php
class Home_Controller extends Base_Controller {

  // 全てのカテゴリ一覧を表示するアクション
  // http://example/home/list/
  public function action_list(){
    var_dump(Category::all());
  }

  // カテゴリー内の商品を表示するアクション
  // http://example/home/category/:categoryId/
  public function action_category($categoryId){
    var_dump(Category::find($categoryId)->itemList);
  }

}
?>
    • と、基本これだけ。ただし、使用する上で、注意しなければいけない点、躓いた点、いろいろあったので、それは後日、書き残しておくことにする。
      • has_many_and_belongs_toを使った上で更にテーブル結合する場合など。