【CakePHP3】Entity の機能について(accessor, virtual fieldsなど)

社内用にまとめた内容の転載。

### ココらへんの話をします

* Entityにキャスト、シリアライズといった「変換」を加えた時に走る処理について
* アクセサー
* 仮想フィールド

関連: [【CakePHP3】 \`Type\` の話 \](http://qiita.com/o0h/items/59772ee4e8eb4c91b26f)

### そもそも: 配列ではなくオブジェクトを返してくれる有り難さについて
CakePHP2時にはモデルから読み取ったデータは全て配列として扱われ、 スカラ値など(というかboolとstringとnull)の集合でしかありませんでした。
例えば、「Userが登録した年を表示する」場合はこんな感じです。

```php
$this->User->id = 1;
$this->User->field('created');
$reggedYear = CakeTime::format('Y', $regged);
```

CakePHP3では、テーブルから読み取った内容は `Entity`というクラスになったので、色々な機能を持てるようになりました。
Entity自体、あるいはEntityの持つプロパティに色々な機能(method)を実装できるということを意味します。
同じく「登録した年」を取るのであれば、これだけでOKです。

```php
$user = $this->Users->get(1);
$reggedYear = $user->created->format('y');
```

なかなか直感的だな、と個人的には思うのでした。
コレは、「datetime型のカラムである `created` は、DateTime系の機能があった方が良いよいよね」という事をUsersテーブルが知っているからであり、実際にテーブルから取り出した時点で [Cake\\I18n\\FrozenDate](http://api.cakephp.org/3.3/class-Cake.I18n.FrozenDate.html) のインスタンスとして利用可能な状態になっています。[^1]
[^1]: 「このfieldをこう！」な話は、別途[【CakePHP3】 \`Type\` の話 \- Qiita](http://qiita.com/o0h/items/59772ee4e8eb4c91b26f)に書き下してみました。

### 仮想フィールド
CakePHP2でも`$Model->virtualFields` の機能はありましたが、CakePHP3ではEntityに仮想フィールドを設定していくことになります。
また、これは必ずしも「テーブルにカラムとして存在しない、SQL関数の結果を取り出す」という限定的な用途を意味しません。すなわち、フェッチした結果に対してPHP(CakePHP)側で生成した結果を1つのpropertyとしてセットすることができます。

[bookで挙げられている非常にシンプルな例](http://book.cakephp.org/3.0/ja/orm/entities.html#entities-virtual-properties) でいえば、 `first_name` `last_name` が実際に存在するUsersTableにおいて、「氏名を扱いやすくする」やり方が詳解されています。

```php
// CakePHP2のvirtualFieldsを使うならこうでしょうか
// モデル側 
class User extends AppModel
{ 
  public $virtualFields = [
    'full_name' => 'CONCAT(last_name, first_name)'
  ];
}

// コントローラー側
$this->User->id = 1;
$fullName = $this->User->field('full_name');

// CakePHP3では、こうです
// Entityにて
protected function _getFullName()
{
  return $this->_properties['last_name'] . $this->_properties['first_name'];
}

// コントローラー側
$fullName = $this->Users->get(1)->full_name;
```
cf: http://book.cakephp.org/3.0/ja/orm/entities.html#entities-virtual-properties

この様に、 

* Entity上にアクセサーを生やす
* アクセサーは、  `_getHogeHoge` という命名でprotectedメソッドを生やす

だけで実現可能です。
また、アクセサーは(仮想フィールドではない)実際に存在するfieldを扱うこともできます。

```php
protected _getLastName()
{
  return "{$this->_properties['last_name']}様";
}

protected _getPoint()
{
  return number_format($this->_properties['point']);
}
```
これは凶悪な例で実用性がないのですが、 *例えばできるよ⚠*という事になります。

### Entityの出力
加えてCakePHP3では「出力するときにどうするか？」というのを定める機構も備えており、「APIの出力として(エンド)ユーザーに見せられる状態にするのも簡単だよ」となっています。

![Kobito.vI9fHx.png](https://qiita-image-store.s3.amazonaws.com/0/941/5590dd3b-9e95-7b4b-114c-6b0eb59dd953.png "Kobito.vI9fHx.png")


弊社プロダクトでも、実際にこれらの機能を用いて「APIでのレスポンスを打つ時に、コントローラー側の仕事を楽にさせる」という状態を実現させています。

1. [配列・JSON への変換](http://book.cakephp.org/3.0/ja/orm/entities.html#json)
2. [プロパティを隠す](http://book.cakephp.org/3.0/ja/orm/entities.html#entities-virtual-properties)

#### 配列・JSONへの変換
[$entity->toArray()](https://github.com/cakephp/cakephp/blob/df1b9b8aa75ece32dbe5e5343a249f3058ba1f3f/src/Datasource/EntityTrait.php#L465)を使うことにより、Entityを連想配列へと変換できます。[^2]
[^2]: 似たメソッドに `toList()` というのがあり、コレは配列のkeyを0から埋めたものに置き換えてくれることで純粋配列を得ることが出来ます

また、 [EntityInterface](http://api.cakephp.org/3.3/class-Cake.Datasource.EntityInterface.html)はPHPの[JsonSerializable インターフェイス](http://php.net/manual/ja/class.jsonserializable.php)を備えているため、「json_encode()をした時に行う処理」を定義可能です。[^3]
[^3]: 実際にjsonとして吐き出す場合、各propertyに対して `toString()`が走った上でレンダリングされるので注意

#### $_hiddenの利用
Entityの持つfieldでは、必ずしも(エンド)ユーザーに見せたくないデータが存在することがあります(ex: passwordなど)。
コレに関して、 `$Entity->_hidden` フィールドに「見せたくないfiedle」の名前をセットしておけば、`toArray()`や`jsonSerialize()`内で「結果から除外する」処理が走ることになります。
(具体的な処理については、そんなに複雑ではないので[実際のコード](https://github.com/cakephp/cakephp/blob/df1b9b8aa75ece32dbe5e5343a249f3058ba1f3f/src/Datasource/EntityInterface.php#L94)を覗いてみて下さい)