CakePHP3を触ってみました 〜tableとentityはどう使い分けたらええねん〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
テーブルに座るモデルさんの実体(エンティティ)……的な?

この記事を三行にまとめると

遠慮なく突っ込んでいだたきたい
ゆりちゃんって子が好きなら、$yuriとかでも良いよ
消費税は8%ってことにしましょう
CakePHP3になってから、ModelがTableとEntityという二つのクラスに分かれました。今日はそのTableとEntityを見ていきたいと思います。

とは言っても、それぞれの詳細な機能やら中身については触れません。なぜなら、僕自身よく分かってないから。今日は「この二つって、たぶんこんな風に使い分けると思うんだけど、どうなのさ?」ってな感じの、ほんっとーにさわりの部分だけです。

間違ってるところがあれば、遠慮なく突っ込んでいだたきたい。そしてみんなで正しい知識を共有していったら良いじゃない?

追伸:
Twitterなどで「何でこのアイキャッチ画像なん? 釣り?」みたいなご指摘をたびたびいただくので言い訳させていただきますが、決してエロで釣ってやろう的なことではなく、CakePHP2のモデルがCakePHP3ではテーブルになったのでモデルとテーブルが一緒に写っているフリー素材を探したところ、この画像がヒットしたというだけでございやす。みんなの釣り竿(意味深)にヒットさせてやろうなどの意図はござんせん。



Table

僕の中では、テーブルオブジェクトの方は、従来のモデルとほぼ同じ使い方をするイメージです。データベースとアプリケーションをつなぐとでも言いますか、DBにデータを登録したり削除したり、DBからデータを取って来たり……そういうのはテーブルで行うって感じですね。

CakePHP2のモデルと大きく違うなって思うところは、クラス名やファイル名が単数形ではなく「複数形+Table」であることと、コントローラーでモデルを読み込む方法が、$usesというメンバ変数ではなくなったことでしょうか。他にもいくつかあるけど、とりあえずはこの二つ。

ま、言葉で説明するよりコードを見た方が早いっすね。

//CakePHP2のPost.php
class Post extends AppModel {

}

//CakePHP3のPostsTable.php
namespace App\Model\Table;
class PostsTable extends AppTable {

}

データベースにpostsというテーブルを作成したと仮定した場合です。

CakePHP2のときはクラス名が「Post」でしたが、CakePHP3では「PostsTable」となっていますね。ファイル名も同様に「Post.php」だったのが「PostsTable.php」になります。



では続いて、コントローラーでテーブルオブジェクトを使う場合。

//CakePHP2のPostsController.php
class PostsController extends AppController {
  $uses = array('Post');

  public function index() {
    $posts = $this->Post->find('all');
  }
}

//CakePHP3のPostsController.php
namespace App\Controller;
use Cake\ORM\TableRegistry;
class PostsController extends AppController {
  public function index() {
    $this->Post = TableRegistry::get('Posts');
    $posts = $this->Post->find()->all();
  }
}

CakePHP2は「$uses」という変数に読み込みたいモデルを配列で記述していました。でもCakePHP3では、必要なときに自分でインスタンスを作成するようになっています。「TableRegistry::get(‘Posts’)」ってのがそれ。ここでは「PostsTable」ではなくて「Posts」と、複数形のみの書き方になります。

今回はCakePHP2と見比べやすいように、わざわざ「$this->Post」なんて変数に突っ込みましたが、別に変数は何でも良いです。自分の好きな女の子の名前とかでも良いです。ゆりちゃんって子が好きなら、$yuriとかでも良いよ。

$yuri = TableRegistry::get('Posts');
$posts = $yuri->find()->all();

まあコントローラーに対応するモデルに関しては、CakePHPが勝手にインスタンスを作ってくれているから、わざわざ新しく作成する必要はないんですけどね。これはCakePHP2も一緒ですが。

何を言ってるかってーと、PostsController.phpの中でPostモデル、あるいはPostsテーブルを使う場合は、自分で明示的に「$uses = array(‘Post’)」とか「$yuri = TableRegistry::get(‘Posts’)」とかやらなくても、インスタンスはすでに作成されているってことです。でもPostsController.phpの中でTagモデルとかTagsテーブルを使うなら、インスタンスを作成しないといけない。

//CakePHP2
class PostsController extends AppController {
  public function index() {
    $posts = $this->Post->find('all');//機能する
    $tags = $this->Tag->find('all');//エラーになる
  }
}

//CakePHP3
class PostsController extends AppController {
  public function index() {
    $posts = $this->Posts->find()->all();//機能する
    $tags = $this->Tags->find()->all();//エラーになる
  }
}

$usesとかTableRegistryを省略した場合の動きです。反対にTagsControllerの中だったら、TagモデルやTagsテーブルのインスタンスは自動的に作られていて、Postsテーブルはインスタンスを自分で作成しないといけないってことですね。

ちなみにCakePHP3の場合、自動的に作られたテーブルのインスタンスは、「$this->Posts」みたいに複数形の変数に入っているので、注意っす。



そういえば、find()の書き方もちょっと変わったんですよね。ここでそれについて言っちゃっても良いんですが、自分なりにいくつか気になることがあるんで、今度その辺をまとめて書きます。長くなっちゃいそうだし。

とりあえず今日のところは、「find()->all()」と書けば、CakePHP2のときの「find(‘all’)」の動きをするってことだけ、把握しておきやしょう。firstやcountについても同様です。

//CakePHP2
$posts = $this->Post->find('all');
$post = $this->Post->find('first');
$count = $this->Post->find('count'):

//CakePHP3
$posts = $this->Posts->find()->all();
$post = $this->Posts->find()->first();
$count = $this->Posts->find()->count();



Entity

続いてエンティティです。こっちの正しい使い方が、いまいち分かってないんですよね。僕の中では、データベースと直接やり取りしないような処理はエンティティを使えば良いとか、そんなイメージなんですけど……合ってる?

エンティティは、単数形のクラス名を使います。

namespace App\Model\Entity;
class Post extends AppEntity {

}

こうですね。この場合は、ファイル名もPost.phpとなります。



じゃあこれをどうやって使っていくかっていう話なんですが、そうね……例えば、商品データが入ってるテーブルを想像してみましょう。productsというテーブルにデータが入っていると思ってください。

テーブルの中には、IDと商品名、商品の税抜き価格の三つのデータが入ってるとします。

//productsテーブル
+----+----------+-------+
| id |   name   | price |
+----+----------+-------+
|  1 | product1 |  1000 |
|  2 | product2 |  2000 |
|  3 | product3 |  3000 |
|  4 | product4 |  4000 |
+----+----------+-------+

id・・・ユニークのID
name・・・商品名
price・・・商品の税抜き価格

分かり辛いかしら……大丈夫かな?

現在は4つの商品データがあって、それぞれの商品名と税抜き価格が登録されています。

じゃあこのデータを取得して、ビューで商品名と税込み価格を表示してみたいと思います。税抜きじゃなくて、税込みね。ついでに価格の表示は、千円ごとにコンマ区切りで表示しましょうか。消費税は8%にしときましょう。将来、消費税が15%くらいになったときにこの記事を見て「ああ、そういや昔は8%だったんだよなぁ……」って、しみじみ思い返せるように。



ではまず、CakePHP2でこれをやった場合。

//コントローラー
class ProductsController extends AppController {
  public function index() {
    $products = $this->Product->find('all');
    $this->set('products', $products);
  }
}

//ビュー
foreach($products as $product) {
  $product['Product']['price'] *= 1.08;
  echo $product['Product']['name'].':'.number_format($product['Product']['price']).'円';
}

//画面の出力結果
product1:1,080円
product2:2,160円
product3:3,000円
product4:4,000円

ビューの方がちょっと粗っぽいですが、まあこんな感じです。phpタグとかも省略しちゃってますが、その辺は脳内補完で頼んます。

消費税の計算とかは直接ビューに書いても良いし、ヘルパーを作成してそこで処理しても良いと思いますが、とにかくこれで、商品と税込み価格の一覧が画面に出力されますね。



じゃあこれと同じことを、今度はCakePHP3で、エンティティを使ってやってみましょう。

//コントローラー
class ProductsController extends AppController {
  public function index() {
    $products = $this->Product->find()->all();
    $this->set('products', $products);
  }
}

//エンティティ
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Product extends AppEntity {
  //税込み価格の算出
  public function calc() {
    $this->price *= 1.08;
  }

  //価格表示の文字列を返す
  public function show() {
    $this->calc();
    return $this->name.':'.number_format($this->price).'円';
  }
}

//テンプレート
foreach($products as $product) {
  echo $product->show();
}

//画面の出力結果
product1:1,080円
product2:2,160円
product3:3,000円
product4:4,000円

この程度だったらわざわざエンティティのメソッドを二つに分けるまでもないですが、今はあえて分けてみます。

言葉での説明が上手くできないんですが、CakePHP2のときは、モデルを使ってDBからデータを取得すると、データのみが配列でセットされていました。でもCakePHP3では、DBのデータと一緒に、エンティティやら何やらもセットされます。上記の例で言えば、$productの中ですね。それにより、$productからエンティティの中で作成したメソッドを呼び出すことができます。「$product->show()」みたいに。

ちなみにCakePHP3では、データは配列ではなくてオブジェクトでセットされるようになりました。



僕のイメージとしては、ヘルパーでやってたようなことを、ある程度エンティティでやっちゃえば良いんじゃないかなと思うんですが……どうっすかね?

ただしヘルパーと違って、エンティティは$productの変数の中に入っているので、変数さえ渡せば、ビューに限らずどこでも簡単に使えます。つまりコントローラーでデータを登録するときなどにも、エンティティのメソッドが使えるわけですね。

たぶん、これがエンティティの利点の一つなんですかね。



じゃあ今度は、税込み価格をpriceカラムに登録してみたいと思います。POSTされたデータには税抜き価格が入力されていると仮定して、システム側で税込み価格を計算して登録する感じ。

//ProductsController.php
class ProductsController extends AppController {
  public function index() {
    //商品データがPOSTされたと仮定
    $product = $this->Products->newEntity($this->request->data);

    //税込み価格をpriceに入力
    $product->calc();

    //データの登録
    $this->Products->save($product);
  }
}

こんな風に、さっきエンティティに作成したcalc()メソッドが、ここでも使えるわけですね。

「newEntity()」ってのは新しくエンティティを作成するメソッドなんですが、これもまあ、今度触れますね。今日は、データを登録する際にこのメソッドが使われるわよってことだけ知っといてもらえれば。



エンティティの基本的な使い方はこんな感じだと思うんですけど、どうなんでしょうかね……? いまいち自信が持てないです。

個人的には、あくまでも一例ですが、パスワードをハッシュ化したり、時間を秒に直して表示したり(例えば「01:00:00」を「3600」に直すとか)、その逆の操作をするような処理が必要なときは、エンティティを活用すると良いんじゃないかな〜と思います。そうすればコントローラーもビューもすっきりするし。






エンティティは、ヘルパーっぽい感じにメソッドを作るだけじゃなくて、もっといろんなことができるみたいです。アクセスできるフィールドを限定したり、データ編集時に値が変更されたかをチェックしたり、変更されたときに変更前のデータを取得できたり。上手く使えばいろいろと便利だと思われるんですが、僕がその辺をまだ全然使えてないってこともあるので、今回はここまでにしときます。今後記事を書いていく中で、エンティティが絡むところはいくつも出てくると思うんで、そのときにまた、改めて書いていくことにしましょう。

テーブルの方も、find()だけじゃなくて、アソシエーションの設定やらバリデーションの書き方も最初よく分からなくて苦戦したんですが、それも書くとさらーに長くなってしまうんで、またの機会にしましょう。

ほんじゃあ、今日はこの辺で。



その他のCakePHP3を触ってみましたの記事はこちら
まとめという名の箸休め
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください