エンティティクラスの使用

CodeIgniterはエンティティクラスをファーストクラスシチズンとしてサポートしており、使用は完全にオプションです。これらは一般的にリポジトリパターンの一部として使用されますが、必要に応じてモデルと直接使用することもできます。

エンティティの使用

エンティティクラスは基本的に、単一のデータベース行を表すクラスです。データベース列を表すクラスプロパティを持ち、その行のビジネスロジックを実装するための追加メソッドを提供します。

注記

理解を容易にするため、ここでの説明はデータベースを使用する場合に基づいています。ただし、エンティティはデータベースから取得されないデータにも使用できます。

しかし、コア機能は、それ自体を永続化する方法について何も知らないことです。これは、モデルまたはリポジトリクラスの責任です。そのため、オブジェクトの保存方法が変更された場合でも、アプリケーション全体でそのオブジェクトの使用方法は変更する必要がありません。

これにより、迅速なプロトタイピング段階でJSONまたはXMLファイルを使用してオブジェクトを保存し、概念が機能することを証明したら簡単にデータベースに切り替えることが可能になります。

非常に単純なUserエンティティと、それをどのように操作するかを順を追って説明します。

次のスキーマを持つusersという名前のデータベーステーブルがあると仮定します。

id          - integer
username    - string
email       - string
password    - string
created_at  - datetime

重要

attributesは内部使用のための予約語です。これを列名として使用すると、エンティティは正しく機能しません。

エンティティクラスの作成

新しいエンティティクラスを作成します。これらのクラスを保存するためのデフォルトの場所がなく、既存のディレクトリ構造には適合しないため、app/Entitiesに新しいディレクトリを作成します。エンティティ自体はapp/Entities/User.phpに作成します。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class User extends Entity
{
    // ...
}

最も単純な例では、これだけです。ただし、すぐにさらに有用なものにします。

モデルの作成

最初にapp/Models/UserModel.phpにモデルを作成して、それらと対話できるようにします。

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $table         = 'users';
    protected $allowedFields = [
        'username', 'email', 'password',
    ];
    protected $returnType    = \App\Entities\User::class;
    protected $useTimestamps = true;
}

このモデルは、すべての操作でデータベースのusersテーブルを使用します。$allowedFieldsプロパティは、外部クラスが変更できるすべてのフィールドを含めるように設定されています。idcreated_atupdated_atフィールドはクラスまたはデータベースによって自動的に処理されるため、変更する必要はありません。最後に、エンティティクラスを$returnTypeとして設定しました。これにより、データベースから行を返すモデルのすべてのメソッドは、通常のオブジェクトや配列ではなく、Userエンティティクラスのインスタンスを返すようになります。

エンティティクラスの操作

すべての部品が整ったので、他のクラスと同様にエンティティクラスを操作します。

<?php

$user = $userModel->find($id);

// Display
echo $user->username;
echo $user->email;

// Updating
unset($user->username);

if (! isset($user->username)) {
    $user->username = 'something new';
}

$userModel->save($user);

// Create
$user           = new \App\Entities\User();
$user->username = 'foo';
$user->email    = '[email protected]';
$userModel->save($user);

Userクラスは列のプロパティを設定していませんが、公開プロパティのようにアクセスできます。基本クラスであるCodeIgniter\Entity\Entityがこれらを処理し、isset()またはunset()を使用してプロパティを確認し、オブジェクトの作成後またはデータベースからの取得後にどの列が変更されたかを追跡する機能も提供します。

注記

エンティティクラスは、内部的に$attributesクラスプロパティにデータを格納します。

Userがモデルのsave()メソッドに渡されると、プロパティを読み取り、モデルの$allowedFieldsプロパティにリストされている列への変更を自動的に保存します。また、新しい行を作成する必要があるか、既存の行を更新する必要があるかも認識します。

注記

insert()を呼び出すときは、エンティティのすべての値がメソッドに渡されますが、update()を呼び出すときは、変更された値のみが渡されます。

プロパティの迅速な設定

エンティティクラスは、fill()というメソッドも提供しており、これにより、キー/値のペアの配列をクラスに挿入してクラスプロパティを設定できます。配列内のプロパティは、エンティティに設定されます。ただし、モデルを介して保存する場合は、$allowedFields内のフィールドのみがデータベースに保存されるため、余分なフィールドが誤って保存される心配をすることなく、エンティティに追加のデータを保存できます。

<?php

$data = $this->request->getPost();

$user = new \App\Entities\User();
$user->fill($data);
$userModel->save($user);

コンストラクタにもデータを渡すことができ、インスタンス化中にfill()メソッドを介してデータが渡されます。

<?php

$data = $this->request->getPost();

$user = new \App\Entities\User($data);
$userModel->save($user);

プロパティの一括アクセス

エンティティクラスには、すべての利用可能なプロパティを配列に抽出するための2つのメソッドがあります。toArray()toRawArray()です。rawバージョンを使用すると、マジック「ゲッター」メソッドとキャストをバイパスします。どちらのメソッドも、ブール値の最初の引数を使用して、返された値を変更された値でフィルタリングするかどうかを指定し、ブール値の最終引数を使用して、ネストされたエンティティの場合にメソッドを再帰的に実行できます。

ビジネスロジックの処理

上記の例は便利ですが、ビジネスロジックを強制するのに役立ちません。ベースのEntityクラスは、特別なメソッドをチェックし、属性を直接使用するかわりにそれらのメソッドを使用するスマートな__get()__set()メソッドを実装しており、必要なビジネスロジックやデータ変換を強制することができます。

これがどのように使用できるかの例として、更新されたUserエンティティを以下に示します。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;
use CodeIgniter\I18n\Time;

class User extends Entity
{
    public function setPassword(string $pass)
    {
        $this->attributes['password'] = password_hash($pass, PASSWORD_BCRYPT);

        return $this;
    }

    public function setCreatedAt(string $dateString)
    {
        $this->attributes['created_at'] = new Time($dateString, 'UTC');

        return $this;
    }

    public function getCreatedAt(string $format = 'Y-m-d H:i:s')
    {
        // Convert to CodeIgniter\I18n\Time object
        $this->attributes['created_at'] = $this->mutateDate($this->attributes['created_at']);

        $timezone = $this->timezone ?? app_timezone();

        $this->attributes['created_at']->setTimezone($timezone);

        return $this->attributes['created_at']->format($format);
    }
}

まず注目すべき点は、追加したメソッドの名前です。それぞれについて、クラスはsnake_caseの列名をPascalCaseに変換し、setまたはgetのいずれかをプレフィックスとして付けることを期待します。これらのメソッドは、直接構文(つまり、$user->email)を使用してクラスプロパティを設定または取得するたびに自動的に呼び出されます。他のクラスからアクセスする必要がない限り、これらのメソッドをpublicにする必要はありません。たとえば、created_atクラスプロパティは、setCreatedAt()メソッドとgetCreatedAt()メソッドを介してアクセスされます。

注記

これは、クラスの外部からプロパティにアクセスしようとする場合にのみ機能します。クラス内部のメソッドは、setX()メソッドとgetX()メソッドを直接呼び出す必要があります。

setPassword()メソッドでは、パスワードが常にハッシュされるようにしています。

setCreatedAt()では、モデルから受け取った文字列をDateTimeオブジェクトに変換し、タイムゾーンをUTCにすることで、閲覧者の現在のタイムゾーンに簡単に変換できるようにしています。getCreatedAt()では、アプリケーションの現在のタイムゾーンでフォーマットされた文字列に時間を変換します。

これらの例は非常に単純ですが、Entityクラスを使用すると、ビジネスロジックを柔軟に適用し、使いやすいオブジェクトを作成できることを示しています。

<?php

// Auto-hash the password - both do the same thing
$user->password = 'my great password';
$user->setPassword('my great password');

特別なゲッター/セッター

バージョン4.4.0の新機能です。

たとえば、Entityの親クラスにgetParent()メソッドが既に定義されており、Entityにもparentという名前の列がある場合、EntityクラスでgetParent()メソッドにビジネスロジックを追加しようとすると、メソッドは既に定義されています。

そのような場合は、特別なゲッター/セッターを使用できます。getX()/setX()の代わりに、_getX()/_setX()を設定します。

上記の例では、Entityに_getParent()メソッドがある場合、$entity->parentを取得するときにそのメソッドが使用され、$entity->parentを設定するときに_setParent()メソッドが使用されます。

データマッピング

キャリアの多くの場面で、アプリケーションの使用状況が変化し、データベースの元の列名がもはや意味をなさなくなっている状況に遭遇するでしょう。あるいは、コーディングスタイルではcamelCaseのクラスプロパティを好むのに対し、データベーススキーマではsnake_caseの名前が必要な場合もあります。これらの状況は、Entityクラスのデータマッピング機能で簡単に処理できます。

例として、アプリケーション全体で使用されている簡略化されたUser Entityがあるとします。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class User extends Entity
{
    protected $attributes = [
        'id'         => null,
        'name'       => null, // Represents a username
        'email'      => null,
        'password'   => null,
        'created_at' => null,
        'updated_at' => null,
    ];
}

上司から、誰もユーザー名を使用しなくなったため、ログインにはメールアドレスだけを使用するように変更する必要があると言われました。しかし、アプリケーションを少しパーソナライズしたいので、nameフィールドをユーザー名ではなくユーザーのフルネームを表すように変更したいと考えています。整理を保ち、データベースで意味が通じるようにするために、nameフィールドをfull_nameに名前変更するマイグレーションを作成します。

この例がいかに不自然であるかを無視すると、Userクラスを修正する方法として2つの選択肢があります。$nameから$full_nameにクラスプロパティを変更することもできますが、これにはアプリケーション全体での変更が必要になります。代わりに、データベースのfull_name列を$nameプロパティにマッピングするだけで、Entityの変更を完了することができます。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class User extends Entity
{
    protected $attributes = [
        'id'         => null,
        'full_name'  => null, // In the $attributes, the key is the db column name
        'email'      => null,
        'password'   => null,
        'created_at' => null,
        'updated_at' => null,
    ];

    protected $datamap = [
        // property_name => db_column_name
        'name' => 'full_name',
    ];
}

新しいデータベース名を$datamap配列に追加することで、データベース列をどのクラスプロパティからアクセスできるかをクラスに伝えることができます。配列のキーはマッピング先のクラスプロパティであり、配列の値はデータベース内の列の名前です。

この例では、モデルがUserクラスのfull_nameフィールドを設定すると、実際にはその値がクラスの$nameプロパティに割り当てられるため、$user->nameを介して設定および取得できます。この値は元の$user->full_nameからもアクセスできますが、これはモデルがデータを取り出してデータベースに保存するために必要です。ただし、unset()isset()は、マッピングされたプロパティ$user->nameでのみ機能し、データベース列名$user->full_nameでは機能しません。

注記

データマッピングを使用する場合は、データベース列名に対してset*()メソッドとget*()メソッドを定義する必要があります。この例では、setFullName()メソッドとgetFullName()メソッドを定義する必要があります。

ミューテーター

日付ミューテーター

デフォルトでは、Entityクラスはcreated_atupdated_at、またはdeleted_atという名前のフィールドを、設定または取得するたびにTimeインスタンスに変換します。Timeクラスは、変更不能でローカライズされた方法で、多くの便利なメソッドを提供します。

$datesプロパティに名前を追加することで、自動的に変換されるプロパティを定義できます。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class User extends Entity
{
    protected $dates = ['created_at', 'updated_at', 'deleted_at'];
}

これで、これらのプロパティが設定されると、**app/Config/App.php**で設定されているアプリケーションの現在のタイムゾーンを使用して、Timeインスタンスに変換されます。

<?php

$user = new \App\Entities\User();

// Converted to Time instance
$user->created_at = 'April 15, 2017 10:30:00';

// Can now use any Time methods:
echo $user->created_at->humanize();
echo $user->created_at->setTimezone('Europe/London')->toDateString();

プロパティキャスト

$castsプロパティを使用して、Entityのプロパティを一般的なデータ型に変換するように指定できます。このオプションは、キーがクラスプロパティの名前で、値がキャストされるデータ型である配列である必要があります。

プロパティキャストは読み取り(取得)と書き込み(設定)の両方に影響しますが、一部の型は読み取り(取得)のみに影響します。

スカラー型キャスト

プロパティは、integerfloatdoublestringbooleanobjectarraydatetimetimestampuriint-boolのいずれかのデータ型にキャストできます。プロパティをnull許容としてマークするには、型の先頭に疑問符を追加します(例:?string?integer)。

注記

int-boolはv4.3.0以降使用できます。

たとえば、is_bannedプロパティを持つUserエンティティがある場合、それをブール値としてキャストできます。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class User extends Entity
{
    protected $casts = [
        'is_banned'          => 'boolean',
        'is_banned_nullable' => '?boolean',
    ];
}

配列/JSONキャスト

配列/JSONキャストは、シリアライズされた配列またはJSONを格納するフィールドで特に役立ちます。キャストされた場合

  • arrayの場合、自動的にアンシリアライズされます。

  • jsonの場合、json_decode($value, false)の値として自動的に設定されます。

  • json-arrayの場合、json_decode($value, true)の値として自動的に設定されます。

プロパティの値を設定するときです。キャストできる他のデータ型とは異なり、

  • arrayキャスト型はシリアライズし、

  • jsonjson-arrayキャストは、プロパティが設定されるたびにjson_encode関数を使用します。

値に適用されます。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class User extends Entity
{
    protected $casts = [
        'options'        => 'array',
        'options_object' => 'json',
        'options_array'  => 'json-array',
    ];
}
<?php

$user    = $userModel->find(15);
$options = $user->options;

$options['foo'] = 'bar';

$user->options = $options;
$userModel->save($user);

CSVキャスト

単純な値のフラットな配列があることがわかっている場合、シリアライズされた文字列またはJSON文字列としてエンコードすることは、元の構造よりも複雑になる可能性があります。コンマ区切り値(CSV)としてキャストする方が簡単な代替手段であり、より少ないスペースを使用し、人間がより簡単に読み取ることができる文字列になります。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class Widget extends Entity
{
    protected $casts = [
        'colors' => 'csv',
    ];
}

データベースには「red,yellow,green」として保存されます。

<?php

$widget->colors = ['red', 'yellow', 'green'];

注記

CSVとしてキャストすると、PHPの内部implodeメソッドとexplodeメソッドが使用され、すべての値は文字列として安全であり、コンマを含まないことが前提となります。より複雑なデータキャストを行う場合は、arrayまたはjsonを試してください。

カスタムキャスト

データの取得と設定のための独自の変換型を定義できます。

まず、型のハンドラークラスを作成する必要があります。クラスは**app/Entities/Cast**ディレクトリに配置されるとします。

<?php

namespace App\Entities\Cast;

use CodeIgniter\Entity\Cast\BaseCast;

// The class must inherit the CodeIgniter\Entity\Cast\BaseCast class
class CastBase64 extends BaseCast
{
    public static function get($value, array $params = [])
    {
        return base64_decode($value, true);
    }

    public static function set($value, array $params = [])
    {
        return base64_encode($value);
    }
}

次に、それを登録する必要があります。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class MyEntity extends Entity
{
    // Specifying the type for the field
    protected $casts = [
        'key' => 'base64',
    ];

    // Bind the type to the handler
    protected $castHandlers = [
        'base64' => Cast\CastBase64::class,
    ];
}

// ...

$entity->key = 'test'; // dGVzdA==
echo $entity->key;     // test

値の取得や設定時に値を変更する必要がない場合は、適切なメソッドを実装しないでください。

<?php

namespace App\Entities\Cast;

use CodeIgniter\Entity\Cast\BaseCast;

class CastBase64 extends BaseCast
{
    public static function get($value, array $params = [])
    {
        return base64_decode($value, true);
    }
}

パラメーター

場合によっては、1つの型では不十分です。そのような場合は、追加のパラメータを使用できます。追加のパラメータは角括弧で示され、カンマで区切って列挙されます。type[param1, param2] のように。

<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class MyEntity extends Entity
{
    // Defining a type with parameters
    protected $casts = [
        'some_attribute' => 'class[App\SomeClass, param2, param3]',
    ];

    // Bind the type to the handler
    protected $castHandlers = [
        'class' => 'SomeHandler',
    ];
}
<?php

namespace App\Entities\Cast;

use CodeIgniter\Entity\Cast\BaseCast;

class SomeHandler extends BaseCast
{
    public static function get($value, array $params = [])
    {
        var_dump($params);
        /*
         * Output:
         * array(3) {
         *   [0]=>
         *   string(13) "App\SomeClass"
         *   [1]=>
         *   string(6) "param2"
         *   [2]=>
         *   string(6) "param3"
         * }
         */
    }
}

注記

キャスト型がnull許容型としてマークされている場合(?bool)、渡された値がnullでない場合、nullable という値を持つパラメータがキャスト型ハンドラに渡されます。キャスト型に事前に定義されたパラメータがある場合、nullable はリストの最後に追加されます。

変更された属性の確認

エンティティの属性が作成以降に変更されたかどうかを確認できます。唯一のパラメータは、確認する属性の名前です。

<?php

$user = new \App\Entities\User();
$user->hasChanged('name'); // false

$user->name = 'Fred';
$user->hasChanged('name'); // true

または、変更された値についてエンティティ全体を確認するには、パラメータを省略します。

<?php

$user->hasChanged(); // true