Piece Frameworkによるブログアプリケーションの作成

第8回データベース設計とデータアクセスコードの実装(1)

前回までの作業で、すべてのフローの画面設計とバリデーション定義が一旦完了となりました。今回から2回に渡って、画面設計によって洗い出された項目に基づいてデータベースのテーブルを作成し、アプリケーションにPiece_ORMを使ったデータアクセスを組み込んでいきます。

データベース設計とテーブル定義

データベースのテーブルを作成するためには、当然ながら作成対象の項目(フィールド)がわかっていなければいけません。今回の開発プロセスでは、すでに各画面のフィールドが洗い出されていますので、それを利用することにしましょう。下記は画面と画面のフィールドの対応表になります。

画面フィールド1フィールド2
新規エントリー入力画面 タイトル内容
新規エントリー入力確認画面 タイトル内容
エントリー一覧画面 タイトル
エントリー参照画面 タイトル内容
エントリー編集画面 タイトル内容
エントリー削除確認画面 タイトル内容

ごらんの通り、今回は「タイトル」「内容」の2つのフィールドしかありませんので、これらを「エントリー」テーブルとして表現することにします。また、これらの2つのフィールドでは1つのエントリーを特定できないため、連番型のフィールドも用意し、そのフィールドをプライマリキーに指定します。以上を表現するテーブル作成用のSQLは下記のようになります。

/path/to/pieceblog/data/schemas/entry.ddl
CREATE TABLE entry (
  id serial,
  title varchar(255) NOT NULL,
  content text NOT NULL,
  PRIMARY KEY(id)
);

データベース及びユーザの作成

次に、データベースサービスを起動し、PostgreSQLのデータベースとユーザを作成します。なお、今回はデータベース及びユーザ、パスワードのいずれもpieceblogという文字列を使うことにします。下記はpgbashを使った作成例です。

pgbashによるデータベース及びユーザの作成
> connect to template1@localhost user postgres password;
# PostgreSQL 8.2.1 on i686-pc-mingw32, compiled by GCC gcc.exe (GCC) 3.4.4 (mingw special)
# CONNECT TO  template1@localhost:5432  AS  template1  USER  postgres
> create user pieceblog with password 'pieceblog';
CREATE ROLE
> ?u
[ List of user names ]
 Username         | SupperUser | CreateDB
------------------+------------+----------
 postgres         | yes        | yes
 pieceblog        | no         | no
(2 rows)
> create database pieceblog owner pieceblog encoding 'UTF8'; CREATE DATABASE > ?l [ List of databases ] Name | Owner | Encoding ------------------+------------------+---------- pieceblog | pieceblog | UTF8 postgres | postgres | EUC_JP template0 | postgres | EUC_JP template1 | postgres | EUC_JP (4 rows)

作成が完了したら、接続のテストをしておきましょう。

pgbashによる接続のテスト
> connect to pieceblog@localhost user pieceblog pieceblog;
# PostgreSQL 8.2.1 on i686-pc-mingw32, compiled by GCC gcc.exe (GCC) 3.4.4 (mingw special)
# CONNECT TO  pieceblog@localhost:5432  AS  pieceblog  USER  pieceblog
> ?m
# Connected Databases List (C: current database is '*')
+---+--------------+-----------+---------------------------+-----------------+
| C | connect_name | user_name | target_name(db@host:port) | client_encoding |
+---+--------------+-----------+---------------------------+-----------------+
|   | template1    | postgres  | template1@localhost:5432  |                 |
| * | pieceblog    | pieceblog | pieceblog@localhost:5432  |                 |
+---+--------------+-----------+---------------------------+-----------------+
(2 rows)

テーブルの作成

続いて、先ほどのテーブル作成用のSQLを使ってテーブルを作成します。

pgbashによるテーブルの作成
> CREATE TABLE entry (
>   id serial,
>   title varchar(255) NOT NULL,
>   content text NOT NULL,
>   PRIMARY KEY(id)
> );
NOTICE:  CREATE TABLE will create implicit sequence "entry_id_seq" for serial column "entry.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "entry_pkey" for table "entry"
CREATE TABLE
> ?d entry
[ "entry" data definitions ]
 Attribute | Type                   | NotNull  | Default
-----------+------------------------+----------+-----------------------------------
 id        | integer                | not null | nextval('entry_id_seq'::regclass)
 title     | character varying(255) | not null |
 content   | text                   | not null |
(3 rows)

PrimaryKey: entry_pkey

以上でデータベース側の準備は完了です。

PieceORMコンポーネントとMDB2_Driver_pgsqlのインストール

PHPからデータベースにアクセスするには下記の方法が考えられます。

  • PHPにバンドルされている各RDBMS用のエクステンションを直接使う(抽象度: 低)
  • 古くはPEAR::DBに代表されるようなデータベース抽象化ライブラリを使う(抽象度: 中)
  • オブジェクトリレーショナルマッピングフレームワークを使う(抽象度: 高)

抽象度が高くなるにつれて、コードの重複や制御フローの不具合の削減が期待でき、ユーザはより本質的なコードのみを記述できるようになります。今回は、上記の中で最も抽象度の高いオブジェクトリレーショナルマッピングフレームワークのひとつ、Piece_ORMを使ってデータアクセスを実装していきます。

Piece_Unityを使ったアプリケーションでPiece_ORMを使う場合、Piece_ORMの設定をPiece_Unityから行うためのPieceORMコンポーネントを使うとより便利です。Piece_Unityでは、コア機能以外のコードが個別のパッケージとして提供されており、それらはPiece_Unityコンポーネントと呼ばれています。

では、下記のようにPieceORMコンポーネントをインストールしましょう。この時、環境にPiece_ORM, MDB2がインストールされていなければ自動的にインストールされます。

PieceORMコンポーネントのインストール
> pear.bat install piece/piece_unity_component_pieceorm
...
> pear.bat list -a
INSTALLED PACKAGES, CHANNEL __URI:
==================================
(no packages installed)
INSTALLED PACKAGES, CHANNEL PEAR.PHP.NET: ========================================= PACKAGE VERSION STATE Archive_Tar 1.3.2 stable Cache_Lite 1.7.2 stable Console_Getopt 1.2.3 stable HTML_Template_Flexy 1.2.5 stable MDB2 2.4.1 stable Net_URL 1.0.15 stable PEAR 1.6.2 stable Structures_Graph 1.0.2 stable INSTALLED PACKAGES, CHANNEL PEAR.PIECE-FRAMEWORK.COM: ===================================================== PACKAGE VERSION STATE Piece_Examples_Basics 1.0.0 stable Piece_Flow 1.13.0 stable Piece_ORM 0.8.0 beta Piece_Right 1.7.0 stable Piece_Unity 1.1.0 stable Piece_Unity_Component_Authentication 0.13.0 beta Piece_Unity_Component_Flexy 1.1.0 stable Piece_Unity_Component_NullByteAttackPreventation 1.0.0 stable Piece_Unity_Component_PieceORM 1.1.0 stable Stagehand_FSM 1.9.0 stable INSTALLED PACKAGES, CHANNEL PECL.PHP.NET: ========================================= (no packages installed)

MDB2はそれ単体では抽象化されたAPIを定義しているにすぎませんので、実際に各RDBMSにアクセスするためには各RDBMS用のドライバが必要になります。今回はPostgreSQLを使っていますので、PostgreSQL用のドライバであるMDB2_Driver_pgsqlをインストールします。

MDB2_Driver_pgsqlのインストール
> pear.bat install mdb2_driver_pgsql
...
> pear.bat list -a
INSTALLED PACKAGES, CHANNEL __URI:
==================================
(no packages installed)
INSTALLED PACKAGES, CHANNEL PEAR.PHP.NET:
=========================================
PACKAGE             VERSION STATE
Archive_Tar         1.3.2   stable
Cache_Lite          1.7.2   stable
Console_Getopt      1.2.3   stable
HTML_Template_Flexy 1.2.5   stable
MDB2                2.4.1   stable
MDB2_Driver_pgsql   1.4.1   stable
Net_URL             1.0.15  stable
PEAR                1.6.2   stable
Structures_Graph    1.0.2   stable
INSTALLED PACKAGES, CHANNEL PEAR.PIECE-FRAMEWORK.COM:
=====================================================
PACKAGE                                          VERSION STATE
Piece_Examples_Basics                            1.0.0   stable
Piece_Flow                                       1.13.0  stable
Piece_ORM                                        0.8.0   beta
Piece_Right                                      1.7.0   stable
Piece_Unity                                      1.1.0   stable
Piece_Unity_Component_Authentication             0.13.0  beta
Piece_Unity_Component_Flexy                      1.1.0   stable
Piece_Unity_Component_NullByteAttackPreventation 1.0.0   stable
Piece_Unity_Component_PieceORM                   1.1.0   stable
Stagehand_FSM                                    1.9.0   stable
INSTALLED PACKAGES, CHANNEL PECL.PHP.NET:
=========================================
(no packages installed)

以上で必要なパッケージのインストールは完了です。

PieceORMコンポーネントの設定

PieceORMコンポーネントを利用するためには、ふたつのプラグイン、ConfiguratorChain及びConfigurator_PieceORMの設定が必要となります。ConfiguratorChainはPiece_Unity自体が提供するプラグインで、Piece_Unityコンポーネントやアプリケーションを設定するプラグイン(コンフィギュレータ)を実行するために使用されます。Configurator_PieceORMは、PieceORMコンポーネントが提供するプラグインで、Piece_ORMを設定するために使用されます。これらのプラグインの設定は下記のようになります。

/path/to/pieceblog/web/webapp/config/piece-unity-config.yaml
...
- name: ConfiguratorChain
  point:
    - name: configurators
      type: extension
      value:
        - Configurator_PieceORM

- name: Configurator_PieceORM
  point:
    - name: configDirectory
      type: configuration
      value: ../webapp/config/orm
    - name: cacheDirectory
      type: configuration
      value: ../webapp/cache/orm
    - name: mapperConfigDirectory
      type: configuration
      value: ../webapp/config/orm/mappers

ConfiguratorChainプラグインの拡張ポイントconfiguratorsは、実際に設定を行うためのプラグインを登録するために使用されます。Piece_Unityはこの拡張ポイントに登録されたプラグインを、自身の実行環境構築後に実行します。上記の場合は、Configurator_PieceORMプラグインが実行されることになります。

Configurator_PieceORMプラグインの設定ポイントについては下記の表をご参照ください。また、設定した各ディレクトリを忘れずに作成しておきましょう。

設定ポイント概要
configDirectoryPiece_ORM設定ファイル(piece-orm-config.yaml)の配置先ディレクトリ
cacheDirectory各種キャッシュの配置先ディレクトリ
mapperConfigDirectoryマッパー定義ファイルの配置先ディレクトリ

データアクセスコード配置先の検討とフローの変更-新規エントリー入力フロー

Piece_ORMを使ったデータアクセスの準備が整いましたので、いよいよアプリケーションにデータアクセスコードを組み込んでいきます。新規エントリー入力フロー以外の2つのフローでは、レコードの存在が前提になってきますので、最初に新規エントリー入力フローに対してデータアクセスコードを実装することにします。

さて、いきなりコードを書き始めたいところですが、まずはコードの配置先について検討してみましょう。新規エントリー入力フローにおいてレコード作成が期待されるタイミングは、新規エントリー入力確認画面のCreateボタンがクリックされたときになります。

新規エントリー入力確認画面
新規エントリー入力確認画面

現在のフロー定義では、CreateボタンがクリックされるとDisplayNewFinishFromDisplayNewConfirmイベントが発生し、DisplayNewFinishステートに遷移します。コードの配置先として考えられるのは、遷移イベント発生時、ステートからの退場時、ステートへの入場時、そしてステート更新時のいずれかのイベントハンドラです。現在のところ、最もシンプルかつ意図が明確なことから、筆者はビュー関連以外のコードをアクションステートのアクティビティ(ステート更新時のイベントハンドラに対応)に配置することを推奨しています。しかしながら、現在のフロー定義にはまだ対応するアクションステートが存在しないため、新たに作成する必要があります。以上を踏まえて、フロー定義にステートProcessCreateNewを追加し、そのアクティビティを準備します。変更前後のフロー定義ファイル及びステートチャート図を下記に示します。

(変更前)/path/to/pieceblog/web/webapp/config/flows/Entry/New.yaml
...
  - name: DisplayNewConfirm
    view: NewConfirm
    activity:
      class: Entry_NewAction
      method: doActivityOnDisplayNewConfirm
    transition:
      - event: DisplayNewFinishFromDisplayNewConfirm 
        nextState: DisplayNewFinish
      - event: DisplayNewFromDisplayNewConfirm
        nextState: DisplayNew
...
(変更前)新規エントリー入力フローのステートチャート図
(変更前)新規エントリー入力フローのステートチャート図
(変更後)/path/to/pieceblog/web/webapp/config/flows/Entry/New.yaml
...
  - name: DisplayNewConfirm
    view: NewConfirm
    activity:
      class: Entry_NewAction
      method: doActivityOnDisplayNewConfirm
    transition:
      - event: ProcessCreateNewFromDisplayNewConfirm
        nextState: ProcessCreateNew
      - event: DisplayNewFromDisplayNewConfirm
        nextState: DisplayNew

actionState:

  - name: ProcessValidateNew
    activity:
      class: Entry_NewAction
      method: doActivityOnProcessValidateNew
    transition:
      - event: DisplayNewConfirmFromProcessValidateNew
        nextState: DisplayNewConfirm
      - event: DisplayNewFromProcessValidateNew
        nextState: DisplayNew

  - name: ProcessCreateNew
    activity:
      class: Entry_NewAction
      method: doActivityOnProcessCreateNew
    transition:
      - event: DisplayNewFinishFromProcessCreateNew
        nextState: DisplayNewFinish
(変更後)新規エントリー入力フローのステートチャート図
(変更後)新規エントリー入力フローのステートチャート図

その他にも、HTMLテンプレートに埋め込まれたイベント名の変更、アクションクラスへのイベントハンドラの追加を行う必要があります。

/path/to/pieceblog/web/webapp/templates/Entry/New.html
<h4 class="date-header">NewConfirm</h4>
<p>Title: {entry.title}</p>
<p>Content: {GLOBALS.displayTextArea(entry.content):h}</p>
<form name="NewConfirm" id="NewConfirm">
  <input type="hidden" name="{__flowExecutionTicketKey}" value="{__flowExecutionTicket}" />
  <p>
    <input type="submit" name="{__eventNameKey}_DisplayNewFromDisplayNewConfirm" value="Back" />
    <input type="submit" name="{__eventNameKey}_ProcessCreateNewFromDisplayNewConfirm" value="Create" />
  </p>
</form>
/path/to/pieceblog/web/webapp/actions/Entry/NewAction.php
...
    function doActivityOnDisplayNewConfirm()
    {
        $flexyElement = &new Piece_Unity_Service_FlexyElement();
        $flexyElement->addForm($this->_flow->getView(), $this->_context->getScriptName());

        $viewElement = &$this->_context->getViewElement();
        $viewElement->setElementByRef('entry', $this->_entry);
    }

    function doActivityOnProcessCreateNew()
    {
        return 'DisplayNewFinishFromDisplayNewConfirm';
    }
}
?>

変更が完了したら、ブラウザから動作を確認しておきましょう。

データアクセスコードの実装─新規エントリー入力フロー

では、データアクセスコードの実装を行います。具体的な作業は、アクションクラスへのデータアクセスコードの実装、マッパー定義ファイルの作成、Piece_ORM設定ファイルの作成の3つになります。下記はデータアクセスコードの実装です。

/path/to/pieceblog/web/webapp/actions/Entry/NewAction.php
<?
require_once 'Piece/Unity/Service/FlowAction.php';
require_once 'Piece/Unity/Service/FlexyElement.php';
require_once 'Piece/ORM.php';

class Entry_NewAction extends Piece_Unity_Service_FlowAction
{
    var $_entry;

    function Entry_NewAction()
    {
        $this->_entry = &Piece_ORM::createObject('Entry');
    }
...
    function doActivityOnProcessCreateNew()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        $mapper->insert($this->_entry);

        return 'DisplayNewFinishFromDisplayNewConfirm';
    }
}
?>

最初にコンストラクタのstdClassのインスタンスを生成していた部分がPiece_ORM::createObject('Entry')で置換されていますが、返されるオブジェクトは依然としてstdClassのインスタンスであることに注意してください。元のコードとの違いは、Piece_ORM::createObject()によってあらかじめテーブルのフィールドに対応したプロパティが用意されていることだけです。

イベントハンドラには実際のデータアクセスコードが記述されています。Piece_ORM::getMapper()は引数に与えられたマッパー名に対応するマッパーオブジェクトを返します。$mapper->insert()は、引数に与えられたオブジェクトを新規レコードとしてマッパーに対応するテーブルに追加します。

マッパー名はテーブル名に対応しており、マッパーのコンテキストではテーブル名はキャメルケースで取り扱われます。名称の先頭は大文字に変換されます。また、⁠_⁠(アンダースコア)は削除され、⁠_⁠(アンダースコア)の直後の文字が大文字に変換されます。今回の場合、対象テーブル名はentryなのでマッパー名はEntryになります。

現在のところ、Piece_ORMはメタデータに基づいたマッパーオブジェクト生成を行いません。言い換えると、対象テーブルが存在することによってマッパーが有効になるわけではありません。そのため、Entryマッパーを有効にするには、マッパー定義ファイルEntry.yamlを準備する必要があります。マッパー定義ファイルの最も重要な用途は対象テーブルのマッパーを有効にすることです。その他の用途としては、任意のメソッド名とSQLのマッピング、SQLのオーバーライト、メソッドに対するリレーションシップの設定などがあります。対象テーブルのマッパーを有効にする用途のみに使用する場合は、単に空のファイルを用意します。よって今回の場合、空のファイル/path/to/pieceblog/web/webapp/config/orm/mappers/Entry.yamlを作成することになります。

最後の作業は、データベースへの接続情報をPiece_ORM設定ファイルに定義することです。

/path/to/pieceblog/web/webapp/config/orm/piece-orm-config.yaml
- name: pieceblog
  dsn: pgsql://pieceblog:pieceblog@localhost/pieceblog

作業が完了したら、ブラウザから動作を確認してください。エラーが発生せずにエントリー一覧画面が表示されたら成功です。現段階ではエントリー一覧画面には本物のレコードは反映されないため、直接データベースにアクセスし、レコードが追加されているかどうかを確認しましょう。

新規エントリー入力確認画面
新規エントリー入力確認画面
pgbashによるレコード追加の確認
$ select * from entry;
 id | title     | content 
----+-----------+---------
  1 | タイトル1 | 内容1\r

(1 row)

おわりに

今回はデータベース設計とテーブル定義、各種環境設定を行ったあと新規エントリー入力フローにデータアクセスコードを組み込みました。

上記のようにPiece_ORMによるデータアクセスコードは非常に簡潔なものになります。一般的にアクションクラスへのデータアクセスコードの配置は推奨されていませんが、上記のような簡潔なコードを別のクラスに配置することに意味があるとは思えません。筆者はPiece_ORMを使ったデータアクセスコードはむしろイベントハンドラに記述することを推奨しています。とはいえ、イベントハンドラやアクションクラスが複雑になったときは、他のクラス同様メソッド抽出やクラス抽出によるリファクタリングを行ってください。

次回は残りの2つのフローにデータアクセスコードを組み込み、アプリケーション全体がデータベースを使った形で動作するようにします。

おすすめ記事

記事・ニュース一覧