ECサイト運営開発記

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

Laravel4で「HelloWorld」を表示させるまで

最近、ようやく、注目が集まってきたPHPのWebアプリケーションフレームワークLaravelのLaravel4 Beta4が
リリースされたので、HelloWorldを出力するまでの流れを簡単に書いていこうと思います。

【Laravel4のインストール】

  • Laravel4はLaravel3とは違ったインスール方法が採用されている。
{
	"require": {
		"laravel/framework": "4.0.*"
	},
	"autoload": {
		"classmap": [
			"app/commands",
			"app/controllers",
			"app/models",
			"app/database/migrations",
			"app/database/seeds",
			"app/tests/TestCase.php"
		]
	},
	"minimum-stability": "dev"
}
  • requireの項目に、"laravel/framework": "4.0.*" という記述が。
    • どういうことか?
      • https://packagist.org/packages/からrequireで指定されたパッケージのバージョンがダウロード&インストールされる。
      • https://packagist.org/packages/laravel/framework/へブラウザでアクセスすると、Laravelのパッケージ一覧が閲覧できる。
      • requireの項目に "laravel/framework": "4.0.*@dev"を指定すると最新版が、"laravel/framework": "v4.0.0-BETA3"を指定すると、BETA3がインストールされる。
        • requireでバージョンが指定されていない場合minimum-stabilityの値が"dev"となっているので、最新版が自動的にインストールされる形になる。
  • どうやってインストールする?
    • コマンドライン操作
      • composer.jsonがあるディレクトリで以下のコマンドを実行。
        • php composer.phar install
          • WindowsでPATHが通っている場合は composer install だけでOK。
          • vendorディレクトリが作成される
          • 依存関係をチェックした上で、プロジェクトにLaravelが使用しているライブラリ(Symfonyのライブラリ)なども含めてインストールしてくれる。


    • vendorディレクトリの大事なファイルたち
      • vendor/composerディレクトリのファイルたち
        • autoload_classmap.phpとautoload_namespaces.php
        • ここにはアプリケーションで使用するクラスマップと名前空間の定義が記述されている
          • 例えば、autoload_classmap.phpを開いてみる
<?php

// autoload_classmap.php generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'BaseController' => $baseDir . '/app/controllers/BaseController.php',
    'DatabaseSeeder' => $baseDir . '/app/database/seeds/DatabaseSeeder.php',
    'HomeController' => $baseDir . '/app/controllers/HomeController.php',
    /* 中略 */ 
    'User' => $baseDir . '/app/models/User.php',
);
    • composer.jsonで指定した、autoload classmapで指定したディレクトリ内のファイルとクラス名がマッピングされている事がわかる。
      • 簡単に言うと、ここに記述されているクラスファイルたちはちゃんと自動的にロードされますよ!っていうもの。
    • 同様にautoload_namespaces.phpを開くと、Laravel4の名前空間 Illuminate が定義されている事がわかる。
<?php

// autoload_namespaces.php generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Symfony\\Component\\Translation\\' => $vendorDir . '/symfony/translation/',
    'Symfony\\Component\\Routing\\' => $vendorDir . '/symfony/routing/',
    'Symfony\\Component\\Process\\' => $vendorDir . '/symfony/process/',
    /* 中略 */
    'Illuminate' => $vendorDir . '/laravel/framework/src/',
);
  • で、ようやく、Laravel4の初期設定。
    • php artisan key:generate を実行しておく。すると、app/config/app.php のkeyに値がセットされる。
    • .htaccessを編集。
      • RewriteBase /project_root/public/ 任意のディレクトリを指定
        • 127.0.0.1/project_root/public/へアクセスする
          • Hello World!」が出力されていればとりあえずは成功。
          • app/routes.php にルーティングが定義されていおり、デフォルトではトップページのルーティングが定義されているだけ。詳しくは、別のエントリーで。
          • ちなみに、このViewファイルはapp/views/hello.php

とりあえずは、今回は、こんな具合で終わり。
次は、新しいコントローラーの作成とルーティングの定義やモデルファイルの作成を書いていこうと思います。

LaravelのORM Eloquentで括弧を使ったクエリーのグループ化する方法

  • 商品テーブルから削除フラグが0で公開ステータスが1の商品で、更に入力されたキーワードに該当する商品を取得したい
<?php

// キーワード
$keyword=Input::get("keyword");

// 検索
$itemList=Item::where("del_flg", "=", 0)
->where("item_public_status", "=", 1)
->where(function($query) use($keyword)
{
  $query->where("item_name", "LIKE", "%$keyword%");
  $query->or_where("item_meta_keywords", "LIKE", "%$keyword%");
  $query->or_where("item_meta_description", "LIKE", "%$keyword%");
})
->get();
  • ポイントは無名関数を使うときに、useを使うこと。
    • 無名関数内では、$keywordにアクセスできないためuse構文を使用する。
    • useには複数指定できるので、引き継ぎたい変数をどんどんぶっこめばいいだけ。
  • こうして実行されるSQL
SELECT 
 * 
FROM 
 `item` 
WHERE 
`del_flg` = '0' AND `item_public_status` = '1' AND 
 (`item_name` LIKE '%チョコ%' OR `item_long_description` LIKE '%チョコ%' OR `item_meta_keywords` LIKE '%チョコ%' OR `item_meta_description` LIKE '%チョコ%')

LaravelのキャッシュでRedisを使う

PHPフレームワークのLaravelでキャッシュエンジンをRedisにしてみた。以外と、すんなり言ったので、記事を残しておく。

  • Redisの概要
    • オープンソースのkey/valueなデータストア
    • インメモリデータベースで非常に高速に動作する
      • ファイルキャッシュするよりいいらしい。
    • レプリケーションも可能で、非常に簡単らしい。
      • 設定ファイルにちょこっと記述するだけ


  • Laravelでの設定。
    • application/config/database.php にRedisに関する項目を探す
<?php
// application/config/database.php
/* 中略 */
'redis' => array(
  'default' => array(
    'host'     => '127.0.0.1',
    'port'     => 6379,
    'database' => 0
  ),
),
    • キャッシュの設定
      • ドライバーにRedisを指定する
    • application/config/cache.php にRedisに関する項目を探す
<?php
// application/config/cache.php
'driver' => 'redis'

基本これだけでOK。あとは通常通り、キャッシュを使えばいいだけ

<?php

// 保存
// 3番目の引数は分数
Cache::put('key', 'value', 10);

// 取得
$value= Cache::get('key');

// 削除
Cache::forget('key');

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"=>"住所",
)

?>
    • 簡単でしょ?

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