あなたの質問に上から下まで答えて、あなたの言うことに何を追加できるか見てみましょう.
以下で説明するように、データベース テーブルごとに 1 つのクラスがあります。
User - ユーザー テーブルと対話するためのクラス。このクラスには、createUser、updateUser などの関数が含まれています。
Locations - ロケーション テーブルを操作するためのクラス。このクラスには、searchLocation、createLocation、updateLocation などの関数が含まれています。
基本的に、ここで選択する必要があります。あなたが説明した方法は、アクティブレコードパターンと呼ばれます。オブジェクト自体は、それが格納されている方法と場所を認識しています。データベースとやり取りして作成/読み取り/更新/削除する単純なオブジェクトの場合、このパターンは非常に便利です。
データベース操作がより広範になり、理解するのが単純でなくなる場合は、多くの場合、データ マッパーを使用することをお勧めします (例:この実装)。これは、すべてのデータベース インタラクションを処理する 2 番目のオブジェクトですが、オブジェクト自体 (例: ユーザーまたは場所) はそのオブジェクトに固有の操作 (例: ログインまたは goToLocation) のみを処理します。オブジェクトの保存を可能にしたい場合は、新しいデータ マッパーを作成するだけで済みます。オブジェクトは、実装で何かが変更されたことさえ知りません。これにより、懸念事項のカプセル化と分離が強制されます。
他にもオプションがありますが、これらの 2 つは、データベースの対話を実装する最もよく使用される方法です。
さらに、次のように別のクラスを作成することを考えています: -
DatabaseHelper : データベースへの接続を表す静的メンバーを持つクラス。このクラスには、executeQuery(query,parameters)、executeUpdate(query,parameters) などの SQL クエリを実行するための下位レベルのメソッドが含まれます。
ここで説明しているのはsingletonのように聞こえます。通常、これは本当に良い設計上の選択ではありません。2 番目のデータベースが存在しないことを本当に、本当に確信していますか? おそらくそうではないので、1 つのデータベース接続のみを許可する実装に限定しないでください。静的メンバーで DatabaseHelper を作成する代わりに、接続、切断、クエリの実行などを可能にするいくつかのメソッドを使用して Database オブジェクトを作成することをお勧めします。これにより、2 番目の接続が必要になった場合に再利用できます。
この時点で、他のクラスで DatabaseHelper クラスを使用するための 2 つのオプションがあります。
- User クラスと Locations クラスは DatabaseHelper クラスを拡張して、継承された executeQuery メソッドと executeUpdate メソッドを DatabaseHelper で使用できるようにします。この場合、DatabaseHelper は、データベースへの接続のインスタンスが常に 1 つだけであることを保証します。
- DatabaseHelper クラスは、User および Location インスタンスを作成する Container クラスを介して User および Locations クラスに注入されます。この場合、コンテナーは、アプリケーション内に DatabaseHelper のインスタンスが常に 1 つだけ存在するようにします。
これらは、すぐに頭に浮かぶ2つのアプローチです。どのようなアプローチを取るべきか知りたいです。これらのアプローチは両方とも十分ではない可能性があります。その場合、データベース対話モジュールを実装するために使用できる他のアプローチを知りたいです。
最初のオプションは実際には実行可能ではありません。継承の説明を読むと、継承は通常、既存のオブジェクトのサブタイプを作成するために使用されることがわかります。User は DatabaseHelper のサブタイプではなく、場所でもありません。MysqlDatabase は Database のサブタイプになるか、Admin は User のサブタイプになります。オブジェクト指向プログラミングのベスト プラクティスに従っていないため、このオプションはお勧めしません。
2 番目のオプションの方が適切です。アクティブ レコード メソッドを使用する場合は、データベースを User および Location オブジェクトに挿入する必要があります。もちろん、これは、これらすべての種類の相互作用を処理する何らかの第 3 のオブジェクトによって行われるべきです。おそらく、依存性注入と制御の反転を見たいと思うでしょう。
それ以外の場合、データ マッパー メソッドを選択した場合は、データベースをデータ マッパーに挿入する必要があります。このようにして、すべての懸念事項を分離しながら、複数のデータベースを使用することができます。
アクティブ レコード パターンとデータ マッパー パターンの詳細については、Martin Fowler の著書Patterns of Enterprise Application Architectureを入手することをお勧めします。このようなパターンでいっぱいです。
これがお役に立てば幸いです (私はネイティブ スピーカーではありませんが、本当に下手な英語の文章が含まれている場合は申し訳ありません!)。
==編集==
データマッパーパターンのアクティブレコードパターンを使用すると、コードのテストにも役立ちます(Aurelが言ったように)。1 つのことだけを実行するためにすべてのコード ピースを分離すると、実際にこの 1 つのことを実行していることを確認しやすくなります。PHPUnit (またはその他のテスト フレームワーク) を使用してコードが適切に動作することを確認することにより、各コード ユニットにバグが存在しないことを確信できます。懸念事項を混同すると (選択肢の 1 を選択する場合など)、これは非常に難しくなります。物事はかなり混乱し、すぐに大量のスパゲッティ コードが得られます。
==編集2 ==
アクティブなレコード パターンの例 (かなり怠惰で、実際にはアクティブではありません):
class Controller {
public function main() {
$database = new Database('host', 'username', 'password');
$database->selectDatabase('database');
$user = new User($database);
$user->name = 'Test';
$user->insert();
$otherUser = new User($database, 5);
$otherUser->delete();
}
}
class Database {
protected $connection = null;
public function __construct($host, $username, $password) {
// Connect to database and set $this->connection
}
public function selectDatabase($database) {
// Set the database on the current connection
}
public function execute($query) {
// Execute the given query
}
}
class User {
protected $database = null;
protected $id = 0;
protected $name = '';
// Add database on creation and get the user with the given id
public function __construct($database, $id = 0) {
$this->database = $database;
if ($id != 0) {
$this->load($id);
}
}
// Get the user with the given ID
public function load($id) {
$sql = 'SELECT * FROM users WHERE id = ' . $this->database->escape($id);
$result = $this->database->execute($sql);
$this->id = $result['id'];
$this->name = $result['name'];
}
// Insert this user into the database
public function insert() {
$sql = 'INSERT INTO users (name) VALUES ("' . $this->database->escape($this->name) . '")';
$this->database->execute($sql);
}
// Update this user
public function update() {
$sql = 'UPDATE users SET name = "' . $this->database->escape($this->name) . '" WHERE id = ' . $this->database->escape($this->id);
$this->database->execute($sql);
}
// Delete this user
public function delete() {
$sql = 'DELETE FROM users WHERE id = ' . $this->database->escape($this->id);
$this->database->execute($sql);
}
// Other method of this user
public function login() {}
public function logout() {}
}
データ マッパー パターンの例:
class Controller {
public function main() {
$database = new Database('host', 'username', 'password');
$database->selectDatabase('database');
$userMapper = new UserMapper($database);
$user = $userMapper->get(0);
$user->name = 'Test';
$userMapper->insert($user);
$otherUser = UserMapper(5);
$userMapper->delete($otherUser);
}
}
class Database {
protected $connection = null;
public function __construct($host, $username, $password) {
// Connect to database and set $this->connection
}
public function selectDatabase($database) {
// Set the database on the current connection
}
public function execute($query) {
// Execute the given query
}
}
class UserMapper {
protected $database = null;
// Add database on creation
public function __construct($database) {
$this->database = $database;
}
// Get the user with the given ID
public function get($id) {
$user = new User();
if ($id != 0) {
$sql = 'SELECT * FROM users WHERE id = ' . $this->database->escape($id);
$result = $this->database->execute($sql);
$user->id = $result['id'];
$user->name = $result['name'];
}
return $user;
}
// Insert the given user
public function insert($user) {
$sql = 'INSERT INTO users (name) VALUES ("' . $this->database->escape($user->name) . '")';
$this->database->execute($sql);
}
// Update the given user
public function update($user) {
$sql = 'UPDATE users SET name = "' . $this->database->escape($user->name) . '" WHERE id = ' . $this->database->escape($user->id);
$this->database->execute($sql);
}
// Delete the given user
public function delete($user) {
$sql = 'DELETE FROM users WHERE id = ' . $this->database->escape($user->id);
$this->database->execute($sql);
}
}
class User {
public $id = 0;
public $name = '';
// Other method of this user
public function login() {}
public function logout() {}
}
== 編集 3: ボットによる編集後 ==
Container クラスには、DatabaseHelper 型の静的メンバーが含まれることに注意してください。これには、既存の DatabaseHelper インスタンスを返すか、存在しない場合は新しい DatabaseHelper インスタンスを作成するプライベートな静的 getDatabaseHelper() 関数が含まれます。この場合、DatabaseHelper に接続オブジェクトが設定されます。Container には、DatabaseHelper を User と Locations にそれぞれ注入する makeUser と makeLocation という静的メソッドも含まれます。
いくつかの回答を読んだ後、最初の質問がほとんど回答されていることに気付きました。しかし、次のような最終的な答えを受け入れる前に、明確にする必要がある疑問がまだあります.
接続するデータベースが 1 つではなく複数ある場合の対処方法。DatabaseHelper クラスはこれをどのように組み込み、コンテナは User および Location オブジェクトに適切なデータベースの依存関係をどのように挿入しますか?
静的プロパティは必要ないと思いますし、コンテナには makeLocation メソッドの makeUser も必要ありません。アプリケーションのすべてのフローを制御するクラスを作成するアプリケーションのエントリ ポイントがあるとします。あなたはそれをコンテナと呼んでいるようですが、私はそれをコントローラーと呼んでいます。結局のところ、アプリケーションで何が起こるかを制御します。
$controller = new Controller();
コントローラーは、どのデータベースをロードする必要があるか、また、そのデータベースが 1 つなのか複数なのかを認識している必要があります。たとえば、あるデータベースにはユーザー データが含まれ、別のデータベースには位置データが含まれます。上記のアクティブ レコード User と同様の Location クラスが指定されている場合、コントローラーは次のようになります。
class Controller {
protected $databases = array();
public function __construct() {
$this->database['first_db'] = new Database('first_host', 'first_username', 'first_password');
$this->database['first_db']->selectDatabase('first_database');
$this->database['second_db'] = new Database('second_host', 'second_username', 'second_password');
$this->database['second_db']->selectDatabase('second_database');
}
public function showUserAndLocation() {
$user = new User($this->databases['first_database'], 3);
$location = $user->getLocation($this->databases['second_database']);
echo 'User ' . $user->name . ' is at location ' . $location->name;
}
public function showLocation() {
$location = new Location($this->database['second_database'], 5);
echo 'The location ' . $location->name . ' is ' . $location->description;
}
}
おそらく、すべてのエコーを View クラスなどに移動するとよいでしょう。複数のコントローラー クラスがある場合は、すべてのデータベースを作成してコントローラーにプッシュする別のエントリポイントを用意することで成果が得られる場合があります。たとえば、これをフロントコントローラーまたはエントリーコントローラーと呼ぶことができます。
これは未解決の質問に答えていますか?