web-dev-qa-db-ja.com

PHPクラス構成:DALの場合に「has a」関係を実装する方法

OOPでOODとグッドプラクティスについて学び、いくつかの重要な概念に苦労していることに気づきました。実践として、以前は単一ファイルクラスであったカスタムPDOデータベース抽象化レイヤーを書き換えています> 2000行のコード。

I 学習済み クラスが「is an」関係にある場合は継承を使用し、「has a」関係がある場合は合成を使用する必要があります。 PHPのtraitsを回避することを前提に、構成は次のように実装できます(例 here から):

_<?php

class Head {
}

class Human {
    private $head;
    public function __construct(Head $head) {
       $this->head = $head;
    }
}

$bob = new Human(new Head);
_

良い。ただし、私の場合は、クラスBをAに合成しますが、Bのインスタンスは複数存在する可能性があります。正確には、メインのdatabaseクラス(A)1つまたは複数のtableクラス(B)。上記の例のtableオブジェクトに似たheadオブジェクトを注入することは、私が望むものではないかもしれません。後で、おそらくselectクラスまたはinsertクラスも存在する可能性があります。これは練習のためだけに行い、クラスのファイルサイズを小さく保つ方法を学びます。構築中にすべての依存関係をすべて注入して再利用する必要がありますか?または、メインのdatabaseクラス内でインスタンス化し、サブクラスへの接続を注入する必要があります。メインデータベースクラスは、PDOオブジェクトを '$ _connection'に保持します。

Q1:クラスdatabasetableを構成する最良の方法は何ですか。

私はこれらの戦略を考えることができます。

戦略#1

_<?php

class db extends PDO{

  private $_connection;

  public function __construct($dsn){

    $this->_connection = new parent::__construct($dsn);

  }

  public function createTable($def){

     $table = new Table(this->_connection, $def);    

  }

}
_

短所:

  • new演算子は、一般的に理想的ではないと思われるメソッドに含まれています。すべてのインスタンスを注入する必要があります。
  • 基本クラスでcreateTableメソッドを宣言する必要があります。これは私の基本クラスをスパムします。機能が増えると、基本クラスはどんどん大きくなります。そもそもそれを回避したかったのです。 Table->create()のように、テーブルオブジェクトでcreateを呼び出せるようにしたいのですが。
  • テーブルクラスへの接続の注入についてはわかりません。それは良い習慣ですか?

戦略#2

_<?php

class db extends PDO{

  private $_connection;
  public  $table;

  public function __construct($dsn, $table){

    $this->_connection = new parent::__construct($dsn);
    $this->table = $table;

  }    

}

$db = new db($dsn, new $Table)
$db->table->create($def);
_

短所:

  • 子ではなく、手動で挿入された接続でもないため、Tableクラスでconnectionを使用できません。

私は、dbクラスとTableクラスが「is a」関係にあるとは思わないため、相互に継承すべきではありません。しかし、現在のところ、適切なコンポジション実装が不足しています。

免責事項

私は解決策を模索しましたが、これのためのベストプラクティスとなる可能性があるものについて助けが必要です。例(人間、頭)で投稿された構成は、データベースとテーブルの場合、ここでは正しく感じられません。役立つ回答が得られることを願っています。また、リンクや話題の言葉を歓迎します。今学んでいるだけで、次のレベルに進むのに苦労しているようです。

2
agoldev

Tableオブジェクトがある理由、またはdbクラスがPDOを拡張する理由がわかりませんが、データベースアクセスへの適切なアプローチを説明しますPHPコンテキスト、これは私が多くの時間を費やしていて、非常に興味を持っている分野なので、.


PDOと準備済みステートメントを使用した基本的なDIアプローチ

PHPのPDO/PDOStatementコンテキストでのデータベースアクセスを検討する場合、実際にそれを要約すると、次のようになります。

  1. データベースへの接続を開閉できるようにしたい
  2. トランザクションを開始、コミット、ロールバックできるようにしたい
  3. 声明を作成できるようにしたい
  4. パラメータを提供することにより、SQLを準備済みステートメントとして実行できるようにしたい

ポイント1はデータベースアクセサーの責任であり、PDOの作成を伴います。これは「ある」関係です。

ポイント2と3は、データベースアクセサーのPDOによるデータベースアクセサーの責任でもあると私は主張します。この場合、データベースアクセサーはメディエーターまたはファサードとして機能します。

ポイント4は、データアクセスオブジェクト(DAO)の役割です。

データベースアクセサーは、次のようなインターフェイスを実装します。

_interface DatabaseAccessorInterface
{
    public static function beginTransaction(): void;
    public static function commitTransaction(): void;
    public static function dropConnection(): void;
    public static function getConnection(): PDO;
    public static function isActiveTransaction(): bool;
    public static function prepare($sql): PDOStatement;
    public static function rollbackTransaction(): void;
}
_

...そしてデータアクセスオブジェクトの抽象実装は次のようになります。

_abstract class AbstractDAO
{
    private $db;

    public function __construct(DatabaseAccessorInterface $db)
    {
        $this->db = $db;
    }

    protected function db(): DatabaseAccessorInterface
    {
        return $this->db;
    }
}

class UserDAO extends AbstractDAO
{
    public function getAllUsers(): array
    {
        $sql = "SELECT * FROM user";
        $stmt = $this->db()->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function getUserByUsername($username): array
    {
        $sql = "SELECT * FROM user WHERE username = :username";
        $stmt = $this->db()->prepare($sql);
        $parameters = [
            ':username' => $username
        ];
        $stmt->execute($parameters);
        return $stmt->fetch();
    }
}
_

これは、問題に対する従来の「簡単な」アプローチであり、IMOの最良のアプローチではありませんが、PDOと準備されたステートメントの設計に取り掛かる場合の優れた出発点です。

このアプローチを強化する方法は次のとおりです。

  • データベースアクセサーに「設定がある」ようにコンフィギュレーターを追加する
  • 準備されたステートメントのキャッシュの実装
  • ResultまたはfetchAll()の代わりにカスタムfetchオブジェクトを返すため、rowCounterrorCodeなどの追加情報を取得できます、など.

著者の考え

個人的には、データベースのコンテキストでのデータアクセスについて考えることは、今日の時代では非常に制限されていると思います。今日では、データベースは無数の選択肢の中の永続化の一形態にすぎません。 SQLデータベース、NoSQLデータベース、CSVファイル、リモートAPIなどに接続することができます。データベースへの接続とクエリから、リモートへのアクセスへとデータアクセスの考え方を広げた方がいいと思います。多くの形式を取ることができるデータストア。

このように見ると、さまざまな部分が機能していることがわかります。

  • 特定のタイプのデータソースへの接続を担当するAccessor
  • AccessorConfigurationの構成情報を保持するAccessor
  • データソースのどのアーティファクトがクエリとコマンドのターゲットになるかについての情報を保持するPersistenceStrategy
  • Calls、SQLクエリ文字列に相当
  • Responsesは、Callsの実行に関する情報(たとえば、返される行/列の数、エラー情報など)を保持します。
  • OperationRepositoryの実行を担当し、Callsを返すResponses
  • OperationCacheに対してResponsesを格納するCalls

この道を進むと、インターフェイスの言語が大幅に簡素化され、データベース固有の専門用語でコードを汚染することがなくなります。

_interface PersistenceStrategyInterface
{
    /**
     * Deletes a DataEntity from the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface
     */
    public function delete(DataEntityInterface $dataEntity);

    /**
     * Gets a DataEntity from the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface|null
     */
    public function get(DataEntityInterface $dataEntity);

    /**
     * Saves the DataEntity to the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface
     */
    public function save(DataEntityInterface $dataEntity);
}

interface OperationRepositoryInterface
{
    /**
     * Gets the Response from the OperationRepository.
     *
     * @param CallInterface $call The Call that generates the Response.
     * @param AccessorInterface $accessor The Accessor that the Call is made against.
     * @return ResponseInterface
     */
    public function response(CallInterface $call, AccessorInterface $accessor);
}
_

免責事項:このコードは Circle314フレームワーク からのものです。これは現在開発中です

1
e_i_pi