Omelet Reference(Draft) 4

エンティティ

エンティティは、詰め込んだ値をDAOのパラメータとして渡したり、結果セットを詰め替えて返すために用いるオブジェクトです。

エンティティの定義

エンティティはドメイン同様クラスですが、ドメインとは異なり何か別のクラスを継承する必要はありません。

ただし、エンティティとして定義するためには、以下の条件を満たす必要があります。

  • エンティティのフィールドは、プリミティブな型か、もしくはドメインのみ。
  • エンティティのフィールドは public のアクセス制限が必要。
  • public として公開された、引数なしのコンストラクタが存在すること。

以下はプリミティブ値として使用可能な型です。

  • 文字列(string)
  • 整数(integer / int)
  • 論理型(boolean / bool)
  • 不動小数点数(float / double)
  • タイムゾーンなし日時(DateTime)

フィールドの型は、ドキュメントコメントで指定します。指定がない場合、 string であると仮定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Todo {
  /**
  * @var integer
  */
  public $id;

  /**
  * @var string
  */
  public $todo;

  /**
  * @var DateTime
  */
  public $createdAt;

  /**
  * @var Hidden
  *
  * Hiddenというドメインが定義されているとする
  */
  public $hidden;
}

上の例で、コンストラクタを定義していないですが、その場合デフォルトコンストラクタが自動的に用意されるため、問題ないです。

エンティティフィールドの別名

エンティティをDAOのパラメータや、結果セットの詰め替え先として用いることができます。

しかし、必ずしも、パラメータ名や結果セットの列名と一致させることができるとは限りません。

そのために、エンティティのフィールドに対して別名を設定することができます。

別名の設定は、ドメイン同様 @Omelet\Annotation\Column アノテーションを使用します。

1
2
3
4
5
6
7
8
9
10
11
use Omelet\Annotation\Column;

class Todo {
  /**
  * @Column(alias="todo_id") 
  *
  * @var integer
  */
  public $id;
...
}

エンティティフィールドの規定値

@Column アノテーションは、結果セットに該当の列が存在しない時に適用される規定値を指定することができます。

1
2
3
4
5
6
/**
 * @Column(default="nothing")
 *
 * @var string
 */
public $todo;
  • 現在クラスのフィールドに定義された規定値は無視しています
1
2
3
4
5
6
/**
 * @Column(alias="some_val") 
 *
 * @var integer
 */
public $someValue = 0; // 結果セットに列がない場合でも、この値は使用されない

ドメインの関数従属値の指定

結果セットの値をエンティティに詰め込む際、フィールドにドメインが存在し、そのドメインが関数従属の値を受け入れる場合、 @Column アノテーションで、その列名を指定することができます。

1
2
3
4
5
6
7
8
/**
 * @Column(alias="edit_user_id", optFields={"edit_user_name", "edit_user_initial_name"})
 *
 * @var Editor
 *
 * Editorには関数従属としてname, short_nameがあるとする
 */
 public $editor;

@Column の optFields に宣言した順に、ドメインのコンストラクタに渡されることに注意してください。

Omelet Reference(Draft) 3

DAO (Data Access Object)

データベースにアクセスし、結果を返すための窓口となるインターフェースです。

メソッドアノテーション

インターフェースの各メソッドは、問い合わせ/コマンドに応じて、以下のアノテーションのうちの一つを指定します

  • @Select
    • 問い合わせ
  • @Insert
    • レコード追加
  • @Update
    • レコード更新
  • @Delete
    • レコード削除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use Omelet\Annotation\Select;
use Omelet\Annotation\Insert;
use Omelet\Annotation\Update;
use Omelet\Annotation\Delete;

interface TodoDao {
  /**
  * @Select
  */
  function selectAll();

  /**
  * @Insert
  */
  function insert(array $values);

  /**
  * @Update
  */
  function update(array $values);

  /**
  * @Delete
  */
  function delete($id);
}

パラメータ

引数をもつメソッドを定義することで、SQLのパラメータとして提供することができます。

また、タイプヒンティングまたは @param ドキュメントコメントを示すことで、パラメータを適切に型変換します。

1
2
3
4
5
6
/**
 * @Select
 *
 * @param boolean unfinished
 */
function find($unfinished);

上の例は、bool型として引数を処理します

タイプヒンティングまたは @param ドキュメントコメントがない場合、引数を文字列として処理することに注意してください。

例えば、以下のメソッド定義において

1
2
3
4
5
/**
 * @Select
 *
 */
function findByDateBetween($from, $to);

$from$to は、 \Doctrine\DBAL\Types\Type::STRING として処理されます。

このタイプは、\PDO::PARAM_STR として、引数の値を変換せずそのままパラメータとしてバインドします。

もし、$from$to\DateTime 型の場合、SQL実行時にエラーとなるでしょう。

以下のように、タイプヒンティングかまたはドキュメントコメント(もしくは両方)を明示してください。

1
2
3
4
5
6
7
/**
 * @Select
 *
 * @param DateTime from
 * @param DateTime to
 */
function findByDateBetween(\DateTime $from, \DateTime $to);

プリミティブ値を渡す

メソッドの引数名がそのままSQLのパラメータ名となります。

1
2
select * from todo
where id = :id

というSQLの場合、

1
2
3
4
5
6
/**
 * @Select
 *
 * @param integer id
 */
function findById($id);

というメソッド定義となります。

プリミティブとして扱える型は、

  • 文字列(string)
  • 整数(integer / int)
  • 論理型(boolean / bool)
  • 不動小数点数(float / double)
  • タイムゾーンなし日時(DateTime)

となります。

連想配列を渡す

{引数名}_{各要素のキー}をSQLのパラメータ名として使用します。

例えば、

1
2
3
4
5
6
/**
 * @Insert
 *
 * @param string[]
 */
function insert($params);

というメソッド定義があり、引数として渡される値が

1
[ 'id' => '10', 'todo' => 'abc-xyz-12345', 'updated_at' => '2015-05-30' ]

の場合、

1
2
insert into todo (id, todo, updateAt)
values (:params_id, :params_todo, :params_updated_at)

という、SQLを用意します。

引数として、配列を渡す場合、すべての要素はドキュメントコメントに従った同じ型をもつこととなります。

上の例のように、パラメータに文字列として日付を渡す場合、 例えばSQLiteでは日付のフォーマットが異なれば正しくフィルタできなくなる可能性があります。

従って、連想配列を使う場合は、すべての配列要素が、 string[] , integer[] , DateTime[] の場合に限定する方が良いでしょう。

複数の型が混在する場合、後述のエンティティに詰め込むことを推奨します。

ドメインを渡す

メソッドの引数名がそのままSQLのパラメータ名となります。

ドメインは、 \Omelet\Domain\CustomDomain の派生クラスを指します。

ドメイン定義時に指定された \Doctrine\DBAL\Types\Type の型定数を元に、保持するドメイン値を適切に整形してSQLのパラメータとして渡します。

1
2
3
4
5
6
/**
 * @Select
 *
 * @param PrimaryKey id
 */
function findById(PrimaryKey $id);

と、メソッドを定義する場合、SQLは

1
2
select * from todo
where id = :id

と、なります。

エンティティを渡す

エンティティの公開プロパティ(public)をパラメータとして適用します。パラメータ名は、<メソッドの引数名>_<プロパティ名>となります。

例えば、

1
2
3
4
class Todo {
  public $id;
  public $content;
}

というエンティティ定義がり、

1
2
3
4
5
6
class TodoDao {
  /**
  * @Insert
  */
  function insert(Todo $todo);
}

DAOが上のように定義されていた場合、

1
insrt into todo (id, todo) values (:todo_id, :todo_content)

のようなパラメータ付きのSQLを定義します。

結果の取得(Select)

メソッドに @return ドキュメントコメントが明示されている場合、その型での結果セットの返却を行います。

1
2
3
4
5
6
7
/**
 * @Select
 *
 * @param integer id
 * @return DateTime
 */
function getPublishedDateById($id);

上の例では、結果から DateTime 型のオブジェクトを生成して返します。

メソッドに @return ドキュメントコメントが明示されていない場合、行の配列として返します。 行は列名をキーとする連想配列で、その値の型はすべて文字列です。

プリミティブ値として取得

結果をプリミティブ値として返却します。

プリミティブとして扱える型は、引数と同様

  • 文字列(string)
  • 整数(integer / int)
  • 論理型(boolean / bool)
  • 不動小数点数(float / double)
  • タイムゾーンなし日時(DateTime)

です。

SQLの実行結果が複数行となる場合、その先頭行、先頭列の値を返します。 これは適切にソートされていないと、受け取る結果が安定しないということを意味しています。

プリミティブ値の配列として取得

メソッドのドキュメントコメントがプリミティブな型の配列の場合、結果をプリミティブ値として返却します。

PhpDocumenterの仕様に従い、配列は型名の後ろに [ ] を付与します。

1
2
3
4
5
6
/**
 * @Select
 *
 * @return DateTime[]
 */
function listPublishedDateAll();

ドメインとして取得

結果をドメインとして返却します。

1
2
3
4
5
6
7
/**
 * @Select
 *
 * @param PrimaryKey id
 * @return Hidden
 */
function hidden(PrimaryKey $id);

結果セットの列名に、ドメインのコンストラクタの引数名、もしくは別名が存在する場合、ドメインとしてインスタンス化して返します。

一致する列名が存在しない場合でも、ドメインをインスタンス化ことに注意してください。

また、ドメイン定義として、関数従属の要素を持ち、その名前、または別名が結果セットに存在する場合、関数従属の値付きでドメインをインスタンス化します。

プリミティブ値として取得の時と同様、SQLの実行結果が複数行となる場合、先頭行の内容からドメインの構築を試みます。

ドメインの配列として取得

結果をドメインの配列として返却します。

各行のドメインの構築は、ドメインとして取得と同様です。

1
2
3
4
5
6
/**
 * @Select
 *
 * @return PrimaryKey[]
 */
function listAllId();

エンティティとして取得

結果をエンティティとして返却します。

エンティティとして返すためには、DAOメソッドのドキュメントコメントの戻り値として明示します。

1
2
3
4
5
6
/**
 * @Select
 * 
 * @return Todo
 */
function findById($id);

エンティティの公開プロパティ(public)に対して、結果セットの列の値がから割り当てられます。

このとき結果セットの列名は、エンティティのプロパティ名、 またはプロパティに付与した @Column アノテーションの alias のと同じ名前値にします。

例えば、

1
2
3
4
5
6
7
8
class Todo {
  /**
  * @Column(alias="todo_id")
  */
  public $id;

  public $content;
}

というエンティティ定義であれば、

1
2
3
select todo_id as id, content
from todo
where todo_id = :id

または、

1
2
3
select todo_id, content
from todo
where todo_id = :id

というSQLとなります。

結果セットの列名が、エンティティのプロパティ名(またはエイリアス)に存在しない場合、エラーとはせず単に無視します。

エンティティのプロパティにドメインが存在する場合

エンティティは、プロパティとしてドメインを持つことができます。

結果セットにそのプロパティ名(またはエイリアス)が存在する場合、ドメインのコンストラクタに適用して作成します。

例えば、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Todo {
  /**
  * @Column(alias="todo_id")
  */
  public $id;

  public $content;

  /**
  * @Column(alias="editor_id") 
  *
  * @var Editor
  */
  public $editor;
}

の場合、

1
2
3
select todo_id, content, editor_id as editor
from todo
where todo_id = :id

のようにドメインと同じ名前があれば、ドメインとして作成されます。

エンティティのドメインが関数従属値を有する場合

加えてドメインは、関数従属な値を持たせることができます。

これはエンィティのフィールドにおいても有効です。

結果セットから、関数従属な値を割り当てるために、 @Column アノテーションの optFields にその列名を配列として指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Todo {
  /**
  * @Column(alias="todo_id")
  */
  public $id;

  public $content;

  /**
  * @Column(alias="editor_id", optFields={"editor_name"}) 
  *
  * @var Editor
  */
  public $editor;
}

のようなエンティティ定義の場合、

1
2
3
4
select t.todo_id, t.content, t.editor_id, m.editor_name
from todo t
join editor m on m.editor_id = t.editor_id
where todo_id = :id

のように、関数従属として指定した名前と同じ列名があれば、ドメインのコンストラクタに合わせて適用します。

エンティティの配列として取得

結果をエンティティの配列として返却します。

エンティティの配列として返すためにすることは、エンティティを返す時と同様、DAOメソッドのドキュメントコメントの戻り値として明示します。

1
2
3
4
5
6
/**
 * @Select
 * 
 * @return Todo[]
 */
function listAll();

エンティティの各プロパティと、結果セットの列の関係は、エンティティを返す時と同様です。

結果の取得(Insert, Update, Delete)

現在の仕様では、作用されたレコード数を返します。

クラスアノテーション

DAOインターフェースには、専用のアノテーションとして、 @Dao が用意されています。

このアノテーションは以下のパラメータを持っています。

  • route

通常、SQLはインターフェースの完全クラス名(名前空間\インターフェース名)をパスと見立てたディレクトリの中に、メソッド名と同名のSQLファイルを用意します。

名前空間が長くなると、SQLファイルを探し出すのが大変になります。

route パラメータを指定することで、名前空間の分をショートカット、もしくは別ディレクトリにSQLを置くことができます。

例えば、

1
2
3
4
5
6
7
8
namespace Foo\Bar\Baz;

/**
 * @Dao(route="FBB")
 */
interface HogeDao {
  
}

という定義の場合、

Foo/Bar/Baz/HogeDao の代わりに * FBB* を探しに行くようになります。

Omelet Reference(Draft) 2

ドメイン

ドメインは、リレーショナルモデルがらくる概念で、対象の文脈においてこれ以上分解できない値の集合に対して名前をつけたものです。

ドメインは、一例として、郵便番号、氏名、日時などが挙げられます。

今挙げた例を思い浮かべてみればわかるように、別の文脈ではさらに分解する場合もあるでしょう(日時 -> 日付 + 時刻)。

ドメイン型の定義

Omeletにおいては、その値が属する型であり、 \Omelet\Domain\CustomDomain の派生クラスを指します。

1
2
3
4
5
// 郵便番号の場合

class Zip extends \Omelet\Domain\CustomDomain {
  
}

\Omelet\Domain\CustomDomain クラスのコンストラクタは、protected として定義されています。 そのため、派生クラスのコンストラクタでは、明示的に親クラスのコンストラクタを読んであげる必要があります。

1
2
3
public function __construct($zip) {
  parent::__construct(\Doctrine\DBAL\Types\Type::STRING, $zip);
}

親クラスのコンストラクタの引数は、以下の通りです

  • $type
    • \Doctrine\DBAL\Types\Type で定義されている、利用可能な型定数のいずれかを指定。
  • $value
    • 派生先のコンストラクタに渡された値。

保持する値の取得は、 getValue() を使用します。

関数従属の付与

ドメインは本来、一つの値をパッケージングしたものとなりますが、 Omeletでは、例えば郵便番号に対して住所のような関数従属となる値を保持できるようにしています。

1
2
3
public function __construct($zip, $address = null) {
  parent::__construct(\Doctrine\DBAL\Types\Type::STRING, $zip, ['address' => $address]);
}

コンストラクタの第二引数以降に、保持させたい値を割り当てるようにします。

親クラスのコンストラクタの第3引数に、これらの値を連想配列として渡します。

付与した値の取り出しは、 getOptValue(連想配列のキー名) を使用します。しかしこの関数はあえて protected として定義しています。 派生クラスで public として再定義するよりはむしろ、値ごとの取得関数を定義することを期待しているためです。

関数名は、後述の結果セットの変換の都合上、引数名() または、 get引数名() としてください。

1
2
3
function getAddress() {
  return $this->getOptValue('address');
}

結果セットからの変換

Omeletは、SQLの問い合わせにより得られた結果セットから、ドメインを作成させることができます。

通常、結果セットは連想配列として得られますが、ドメインのコンストラクラの引数名を連想配列のキーとして、値を取り出し、ドメインインスタンスの生成を自動的に行います。

別名の指定

コンストラクタの引数名と、結果セットのキー(カラム名)は必ずしも一致するとは限りません。

SQLの記述で合わせてしまうのもいいですが、Omeletでは、ドメインのクラス定義として別名を与えることができます。

ドメイン自身が保持する値については、コンストラクタにアノテーションコメント、 @Column (\Omelet\Annotation\Column) を付与します。

1
2
3
4
5
6
7
8
use Omelet\Annotation\Column;

class ....

/**
 * @Column(alias="zip_number")
 */
public function __construct($zip, $address = null) {

Column の alias プロパティに別名を指定します。

関数従属の値については、前の節で説明した取得関数に、 @Column アノテーションを付与します。

1
2
3
4
/**
 * @Column(alias="adrs")
 */
function getAddress() {

また、コンストラクタの引数名や別名は、設定の returnCaseSensor の影響を受けます。

引数や別名はキャメルケースであるが、設定を大文字のスネークケースにした場合、名称の変換が行われます。

結果セット値の型変換

結果セットはすべて文字列として返されます。別の型で保持したい場合型変換が必要となります。例えば日時を DateTime で保持するなど。

この型は、コンストラクタのタイプヒントの他に、コンストラクタのドキュメントコメントからの取得を試みます。

/**
 * @Column(alias="zip_number")
 *
 * @param string zip
 * @param string address
 * @param integer streetNumber 
 */
public function __construct($zip, $address = null, $streetNumber = null) {

Omelet Reference(Draft) 1

設定

  • \Omelet\Build\Configuration クラス
    • データベース接続先などの、共通設定情報を格納する。

プロパティ

  • daoClassPath

    • DAO (Data Access Object)の具象実装クラスが保存されるディレクトリまでのパスを指定する。
    • 文字列
    • 規定値:auto_generated
  • sqlRootDir

    • SQLが置かれているルートのディレクトリまでのパスを指定する。
    • 文字列
    • 規定値:sql
  • daoClassSuffix

    • DAOの具象実装クラス名のサフィックスとして付与する文字列。
    • 文字列
    • 規定値: Impl
    • 例: TodoDao -> TodoDaoImpl
  • connectionString

    • データベースの接続情報
    • 文字列
  • watchMode = 'Whenever';

    • 作成タイミングの指定
      • Always , Whenever , Once のいずれかを指定する。
        • Always:DAOインターフェースの変更の有無に関わらず、毎回実装クラスを作成する。
        • Whenever:初回または、DAOインターフェースの内容が変更した場合のみ、実装クラスを再作成する。
        • Once:初回に一度だけ、実装クラスを作成する
    • 文字列
    • 規定値:Whenever
  • paramCaseSensor

    • SQLで使用するパラメータ名の命名規則
    • 例えば、DAOメソッドの引数をキャメルケースで定義し、設定を UpperSnake にした場合、アルファベット大文字のスネークケースに変換する。
      • LowerSnake , UpperSnake , LowerCamel , UpperCamel のいずれかを指定する。
        • LowerSnake
          • アルファベット小文字で単語間を アンダースコア文字(_) でつなぐ。
        • UpperSnake
          • アルファベット大文字で単語間を アンダースコア文字(_) でつなぐ。
        • LowerCamel
          • 先頭の単語を除いて、2番目以降の単語の1文字目を大文字にしてそのままつなげる。
          • 元の規則がスネークケースの場合、アンダースコア文字は取り除かれることに注意。
        • UpperCamel
          • 単語の1文字目を大文字にしてそのままつなげる。
          • 元の規則がスネークケースの場合、アンダースコア文字は取り除かれることに注意。
    • 文字列
    • 規定値: LowerSnake
  • returnCaseSensor = 'LowerSnake';

    • クエリ実行により得られる結果セットの列名の命名規則
    • 結果をオブジェクト(エンティティクラス)やドメインに詰め替える際、命名規則の変換が適用される。
      • LowerSnake , UpperSnake , LowerCamel , UpperCamel のいずれかを指定する。
      • それぞれの値の意味は、 paramCaseSensor と同じ
    • 文字列
    • 規定値: LowerSnake

RESTful Web APIs 読書メモ Index

Restful Web APIs - Index

RESTful Web APIs 読書メモ(14)

Appendix C. An API Designer's Guide to the Fielding Dissertation

アーキテクチャ特性

  • Low Entry-barrier
    • FTPやTelnetのようにたくさんのコマンドを駆使する必要がない
    • 使い方を学ぶのが容易で、サイト構築も容易
  • Extensibility
  • Distributed Hypermedia
  • Internet-scale

アーキテクチャ制約

  • Client-Servet
    • webにおける通信は1:1
  • Statelessness
    • クライアントがリクエストを作らなければ、サーバーはクライアントの存在を知らない
  • Caching
    • キャッシュから前回のレスポンスを再利用することで、通信を抑える
  • Uniform Interface
    • リソースの一意化
      • リソースは安定したURIで一意化される
    • 表現を通したリソースの操作
      • サーバーはクライアントに表現を送ることで、リソース状態を記述
      • クライアントはサーバーにアプリケーション状態を送ることでリソース状態を操作する
    • 自己記述メッセージ
      • すべての情報は、リクエストやレスポンスがリンクを介してメッセージ自身に含まれることを理解する必要がある
    • ハイパーメディア制約
      • サーバーはハイパーメディアメニューを送ることでアプリケーション状態を操作する
        • メニューとは、クライアントが自由に操作できるオプション
  • Layered System
    • クライアントとサーバーの間に入れられたプロキシは不可視
  • Code on Demand
    • サーバーはデータと一緒に実行コードを送ることができる
    • クライアントのリクエストで、自動的に配置される
    • コードの変更でも自動的に再配置される

RESTful Web APIs 読書メモ(13)

Chapter 13. CpAP: REST for Embedded Systems

  • CoAP
    • IOT(Internet of Things)のような低出力組み込み環境用
    • HTTPの考えを踏襲
    • ハイパーメディア駆動のRESTful APIが使用可能
続きを読む・・・

Restful-web-api-memo-15

Chapter 11. HTTP for APIs

  • Web APIの技術的スタック
    • ハイパーメディア
      • 次は何?に答える
    • HTTP
      • リソースとはどのようにしてやりとりするのか?に答える
    • URL
      • リソースはどこにあるのか?に答える
続きを読む・・・

RESTful Web APIs 読書メモ(12)

Chapter 12. Resource Description and Linked Data

  • ハイパーメディアフォーマット
    • 表現戦略と記述戦略の両方を有する
続きを読む・・・

RESTful Web APIs 読書メモ(11)

Chapter 10. The Hypermedia Zoo (Part. 2)

  • アプリケーションセマンティクスカタログ
続きを読む・・・