「共通化」という言葉を使っている時点でなにもわかっていないと思う人もいるかもしれません。ここではデータベースをうまく管理する方法について簡単なことを説明します。
当サイトの姉妹サイトであり、画像アップロードサイトでもあるシラバスのデモ版が完成しました。シラバスは当サイトの画像を管理していますが、アカウントをとれば誰でも手軽に画像をアップロードできます。
シラバスではデータをモデルとクエリという二つのクラスで扱います。例えばあなたは書店の経営者で、本とその売上に関する book というテーブルをもっていたとします。
bookテーブル
カラム | 詳細 |
---|---|
id | PK |
author | 作者 |
title | タイトル |
publisher | 出版社 |
date_published | 出版日 |
最初の id はプライマーキー(通称PK)はほぼ必須のカラムであり、それぞれのデータを識別するために使う。
PHPの作業画面に戻って、次のようなクラスファイルを作ってください。
Book.php
class Book
{
public $id;
public $author;
public $title;
public $publisher;
public $date_published;
}
QueryBook.php
class QueryBook
{
public function __construct()
{
}
public function get_book_by_id( $id )
{
$pdo = new PDO( 'dsn', 'username', 'password' );
$stmt = $pdo->prepare( 'SELECT * FROM book WHERE id=:id' );
$stmt->bindParam( ':id', $id, PDO::PARAM_INT );
$stmt->execute();
$rows = $stmt->fetchall( PDO::FETCH_ASSOC );
return $rows[ 0 ];
}
}
これから Book クラスをモデルクラス、QueryBook をクエリークラスと呼びます。まずモデルクラスはデータベースのテーブルと完全に一対一になるようにメンバ変数(フィールド)を設定します。
作者やタイトルといった各データを取得したりアップデートしたりするときは、クエリークラスでおこないます。get_book_by_id という関数は id から本の情報を取得するものです。アップデートも追加してみます。
class QueryBook
{
public function __construct()
{
}
public function get_book_by_id( $id )
{
$pdo = new PDO( 'dsn', 'username', 'password' );
$stmt = $pdo->prepare( 'SELECT * FROM book WHERE id=:id' );
$stmt->bindParam( ':id', $id, PDO::PARAM_INT );
$stmt->execute();
$rows = $stmt->fetchall( PDO::FETCH_ASSOC );
return $rows[ 0 ];
}
public function update_author( $id, $author )
{
try
{
$pdo = new PDO( 'dsn', 'username', 'password' );
$pdo->beginTransaction();
$stmt = $pdo->prepare( 'UPDATE book SET author=:author WHERE id=:id' );
$stmt->bindParam( ':id', $id, PDO::PARAM_INT );
$stmt->bindParam( ':author', $author, PDO::PARAM_STR );
$stmt->execute();
$pdo->commit();
}
catch ( Exception $e )
{
$pdo->rollback();
}
}
}
二つの関数で pdo の初期化が重複しています。今は book というテーブルのたった二つの関数を扱っているにすぎませんが、プログラムが少し巨大化すると関数は100個、200個と簡単に増えてしまい、そこでいちいち pdo = new PDO とすることはバカげていると気づくのです。
データベースのパスワードを変更したとき、これらの100個の関数のそれぞれにある PDO のパスワードの値をいちいち書きかえる…などということをしないために、私たちはどうするべきでしょうか? データベースの情報を define で定義することが正しいやり方でしょうか?
そこで Connection というデータベース接続用のクラスをつくります。
class Connection
{
protected $pdo;
public function __construct()
{
$this->pdo = new PDO( 'dsn', 'username', 'password' );
}
}
コネクションクラスは実際のデータベース接続を担当します。データベースのユーザー名やパスワードが変更しても、このクラスのコンストラクタ内にある変数を書きかえるだけですみます。
コネクションクラスはあらゆるクエリークラスで呼びます。つまり QueryBook というクラスを次のように変更します。
class QueryBook extends Connection
{
public function __construct()
{
parent::__construct();
}
public function get_book_by_id( $id )
{
$stmt = $this->pdo->prepare( 'SELECT * FROM book WHERE id=:id' );
$stmt->bindParam( ':id', $id, PDO::PARAM_INT );
$stmt->execute();
$rows = $stmt->fetchall( PDO::FETCH_ASSOC );
return $rows[ 0 ];
}
public function update_author( $id, $author )
{
try
{
$this->pdo->beginTransaction();
$stmt = $this->pdo->prepare( 'UPDATE book SET author=:author WHERE id=:id' );
$stmt->bindParam( ':id', $id, PDO::PARAM_INT );
$stmt->bindParam( ':author', $author, PDO::PARAM_STR );
$stmt->execute();
$this->pdo->commit();
}
catch ( Exception $e )
{
$this->pdo->rollback();
}
}
}
変更点は pdo = new PDO() という文が消えたことと、pdo->prepare などがすべて this->pdo->prepare と this がついたことです。
コネクションクラスを導入しただけで少し節約できました。コネクションクラスによって、book 以外のすべてのテーブルでもデータを出し入れできることになります。
ここでモデルクラスはコネクションクラスを継承しません。stackoverflowなどのサイトでよく見かける質問ですが、モデルはテーブルをオブジェクトにしただけのクラスであり、そこではコネクションを継承しません。
モデルはあくまでもデータのかたまりであり、データの出し入れではありません。なぜデータとデータの出し入れをモデルとクエリというクラスに分ける必要があるのでしょうか? それは、データと管理は本来別のものであり、さらにクエリーがモデルを返すような関数を持つことがあるからです。
上の get_book_by_id という関数はなにを返すでしょうか? それは
echo $row[ 'id' ] // '3';
echo $row[ 'author' ] // 'Yamada';
echo $row[ 'title' ] // 'My Diary';
echo $row[ 'publisher' ] // 'ABC Publisher';
echo $row[ 'date_publisher' ] // '2000-10-10';
row という連想配列です。この配列は上のように id や author を指定して値をとりだしますが、これはとても面倒な作業です。よく考えると row は Book というクラスのオブジェクトにすることができます。もし row が連想配列でなくオブジェクトであれば
echo $row->id // '3';
echo $row->author // 'Yamada';
echo $row->title // 'My Diary';
echo $row->publisher // 'ABC Publisher';
echo $row->date_publisher // '2000-10-10';
となります。オブジェクトからメンバ変数へのアクセスは、多くの高機能なエディターでも候補が瞬時に出てくるため、わざわざ date_published とタイプする必要はありません。
また返り値をオブジェクトにすると、それを使う側でもさまざまなメリットがあります。多くのコードと労力が節約され、無駄な変数をいちいち定義するような手間がどんどん減っていきます。実際にシステムを作っているときは、オブジェクトを単位としてやりとりするようにしましょう。