1. ホーム
  2. php

[解決済み] MVCでモデルはどのように構成されるべきか?[クローズド]

2022-03-17 02:34:52

質問

私はMVCフレームワークを理解しつつあるところですが、モデルにどれだけのコードを入れるべきか、よく悩みます。私は、このようなメソッドを持つデータアクセスクラスを持つことが多いのです。

public function CheckUsername($connection, $username)
{
    try
    {
        $data = array();
        $data['Username'] = $username;

        //// SQL
        $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

        //// Execute statement
        return $this->ExecuteObject($connection, $sql, $data);
    }
    catch(Exception $e)
    {
        throw $e;
    }
}

私のモデルは、データベースのテーブルにマッピングされたエンティティクラスであることが多いのです。

モデルオブジェクトは、上記のコードだけでなく、データベースにマッピングされたすべてのプロパティを持つべきでしょうか、それとも、実際にデータベース作業を行うコードを分離しても問題ないでしょうか?

結局4層になるのでしょうか?

どのように解決するのですか?

<ブロッククオート

<サブ 免責事項 以下は、私がPHPベースのWebアプリケーションの文脈でMVC的パターンをどのように理解しているかを説明したものです。コンテンツ内で使用されている外部リンクはすべて、用語や概念を説明するためにあるものであり ではなく は、このテーマに関する私自身の信頼性を示唆するものです。

まず、クリアしなければならないのは モデルはレイヤーです .

2つ目: 次のような違いがあります。 古典的なMVC とWeb開発で使っているものです。 ここでは は、私が書いた古い回答の一部で、両者がどのように違うかを簡単に説明しています。

モデルとは何か

モデルは、クラスや単一のオブジェクトではありません。これは非常によくある間違いで (私もそうでしたが、元の回答はそうでないことを学び始めた頃に書かれたものです) というのも、ほとんどのフレームワークがこの誤解を永続させるからです。

ORM(Object-Relational Mapping technique)でもなければ、データベースのテーブルを抽象化したものでもない。そうでないと言う人は、ほとんどの場合、次のことをしようとしています。 '売り' 別の新しいORMやフレームワーク全体があります。

モデルとは何か

適切なMVCの適応では、Mはすべてのドメインのビジネスロジックを含んでおり モデル層 ほとんど の3種類の構造でできています。

  • ドメインオブジェクト

    ドメインオブジェクトは、純粋にドメイン情報の論理的なコンテナであり、通常、問題領域空間における論理的な実体を表している。一般に、以下のように呼ばれる。 ビジネスロジック .

    ここでは、請求書を送る前にデータを検証する方法や、注文の合計金額を計算する方法などを定義することになる。同時に ドメインオブジェクト はストレージを全く意識しません。 どこ (SQLデータベース、REST API、テキストファイルなど)、さらには もし 保存または取得される。

  • データマッパー

    これらのオブジェクトは、ストレージのみを担当します。もしデータベースに情報を保存するのであれば、ここにSQLが格納されることになります。あるいは、XMLファイルを使ってデータを保存しているのであれば データマッパー は、XMLファイルとの間でパースを行っています。

  • サービス内容

    ビジネスロジックの代わりに、より高いレベルのドメインオブジェクトと考えることができます。 サービス との間の相互作用を担当します。 ドメイン・オブジェクト マッパー . これらの構造は、結局のところ、ドメインのビジネスロジックと対話するための "public" インターフェイスを作成することになります。これを避けることもできますが、その場合、ドメインロジックの一部が コントローラ .

    この件に関連する回答が ACLの実装 の質問で、役に立つかもしれません。

モデル層とMVCトライアドの他の部分との間の通信は、モデル層と他の部分の間にある サービス . このように明確に分離することで、さらにいくつかの利点があります。

  • を強制するのに役立ちます。 単一責任原則 (SRP)
  • ロジックが変更された場合に備えて、追加の「ウィグルルーム」を提供します。
  • コントローラを可能な限りシンプルにする
  • 外部APIが必要な場合に、明確な青写真を提供します。

モデルと対話するには?

<ブロッククオート

<サブ 前提条件 講義を見る グローバルステートとシングルトン(Singletons) "物を探すな!" Clean Code Talksより。

サービスインスタンスへのアクセス権取得

の両方について 表示 コントローラ インスタンス(UI層と呼べるもの)にアクセスするために、2つの一般的なアプローチがあります。

  1. ビューやコントローラのコンストラクタに、 必要なサービスを直接インジェクトします。できれば、DI コンテナを使用します。
  2. すべてのビューとコントローラの必須の依存関係として、サービス用のファクトリーを使用すること。

DIコンテナは、よりエレガントなソリューションです(ただし、初心者の方にとっては簡単ではありません)。この機能を実現するために、私がお勧めするのは、Syfmonyのスタンドアロン型の DependencyInjectionコンポーネント または Auryn .

ファクトリーとDIコンテナを使用したソリューションでは、特定のリクエストとレスポンスのサイクルにおいて、選択したコントローラとビューの間で共有するさまざまなサーバーのインスタンスを共有することも可能です。

モデルの状態の変更

コントローラでモデル層にアクセスできるようになったので、実際にモデル層を使い始める必要があります。

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

ユーザーの入力を受け取り、その入力に基づいてビジネスロジックの現在の状態を変更することです。この例では、quot;匿名ユーザーとquot;ログインユーザーの間で変更される状態です。

なぜなら、それはビジネスルールの一部であり、コントローラはSQLクエリを呼び出さないからです。 ここで または こちら (彼らは見当違いをしているのであって、悪ではないのです)。

ユーザーに状態変化を表示する。

OK、ユーザーはログインしました(または失敗しました)。 さて、どうする? 言われたユーザーはまだ気づいていない。そこで、実際にレスポンスを生成する必要がありますが、これはビューの責任です。

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

この場合、ビューはモデルレイヤーの現在の状態に基づいて、2つの可能な応答のうちの1つを生成しました。別のユースケースでは、quot;current selected of article" のようなものに基づいて、ビューがレンダリングするために異なるテンプレートを選択するようになるでしょう。

プレゼンテーション層は、ここで説明するように、実際にはかなり精巧なものにすることができます。 PHPのMVCビューを理解する .

でも、私はREST APIを作っているだけなんです!

もちろん、これが行き過ぎである場合もある。

MVCは、あくまでも コンツェルンの分離 の原則に従います。 MVCでは、ユーザーインターフェースとビジネスロジックを分離し、UIではユーザー入力の処理とプレゼンテーションを分離した。 ここが肝心です。よく「三位一体」と表現されますが、実際には3つの独立したパーツから構成されているわけではありません。どちらかというと、このような構造になっています。

つまり、プレゼンテーション層のロジックがほとんど存在しない場合、実用的なアプローチとしては、それらを1つのレイヤーとして維持することです。また、モデルレイヤーのいくつかの側面を大幅に簡素化することができます。

この方法を用いると、ログインの例(API用)は次のように書くことができる。

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $data = [
        'status' => 'ok',
    ];
    try {
        $identity = $this->identification->findIdentityByEmailAddress($email);
        $token = $this->identification->loginWithPassword(
            $identity,
            $request->get('password')
        );
    } catch (FailedIdentification $exception) {
        $data = [
            'status' => 'error',
            'message' => 'Login failed!',
        ]
    }

    return new JsonResponse($data);
}

これは持続可能ではありませんが、レスポンス・ボディをレンダリングするための複雑なロジックがある場合、この簡略化はより些細なシナリオで非常に有効です。しかし 注意 この方法は、複雑なプレゼンテーションロジックを持つ大規模なコードベースで使用しようとすると、悪夢と化します。

モデルを構築するには?

モデルクラスは1つもありませんので、モデルを構築する必要はありません。その代わり サービス 特定のメソッドを実行することができます。そして ドメインオブジェクト マッパー .

サービスメソッドの一例です。

上記の両方のアプローチにおいて、本人確認サービスのためのこのログインメソッドがありました。実際にはどうでしょうか。から同じ機能を少し改変して使っています。 ライブラリ というのも、私は怠け者なので、私が書いたのです。

public function loginWithPassword(Identity $identity, string $password): string
{
    if ($identity->matchPassword($password) === false) {
        $this->logWrongPasswordNotice($identity, [
            'email' => $identity->getEmailAddress(),
            'key' => $password, // this is the wrong password
        ]);

        throw new PasswordMismatch;
    }

    $identity->setPassword($password);
    $this->updateIdentityOnUse($identity);
    $cookie = $this->createCookieIdentity($identity);

    $this->logger->info('login successful', [
        'input' => [
            'email' => $identity->getEmailAddress(),
        ],
        'user' => [
            'account' => $identity->getAccountId(),
            'identity' => $identity->getId(),
        ],
    ]);

    return $cookie->getToken();
}

見ての通り、この抽象化レベルでは、データがどこから取得されたのかが分からない。データベースかもしれませんし、単なるテスト用のモックオブジェクトかもしれません。実際に使われるデータマッパーも private というメソッドがあります。

private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
}

マッパーの作成方法

永続化の抽象化を実装するために、最も柔軟なアプローチは、カスタムの データマッパー .

<サブ からです。 PoEAA 書籍

実際には、特定のクラスやスーパークラスと相互作用するように実装されています。例えば CustomerAdmin を継承している)。 User スーパークラス)。どちらも異なるフィールドを含むので、おそらく別々のマッチングマッパーを持つことになるでしょう。しかし、共有され、よく使われる操作に行き着くことになります。例えば "last seen online" の時間です。そして、既存のマッパーをより複雑にする代わりに、より現実的なアプローチとして、一般的な "ユーザーマッパー" を持ち、そのタイムスタンプのみを更新するようにすることです。

いくつかの追加コメント

  1. データベースのテーブルとモデル

    データベーステーブルの間に1:1:1の直接的な関係が存在する場合もありますが、そのような場合にも、データベーステーブルを使用することができます。 ドメインオブジェクト マッパー しかし、大規模なプロジェクトでは、期待するほど一般的ではないかもしれません。

    • で使用される情報は、1つの ドメイン・オブジェクト は異なるテーブルからマッピングされるかもしれませんが、オブジェクト自体はデータベース内に永続性を持ちません。

      月次報告書を作成する場合。これは、異なるテーブルから情報を収集しますが、魔法のようなものはありません。 MonthlyReport テーブルを作成します。

    • 単一の マッパー は複数のテーブルに影響を与えることができます。

      のデータを保存している場合 User オブジェクトの場合、この ドメインオブジェクト は、他のドメインオブジェクトのコレクションを含むことができます。 Group インスタンスになります。もし、それらを変更して User を使用します。 データマッパー は、複数のテーブルのエントリを更新または挿入する必要があります。

    • 単一の ドメイン・オブジェクト は複数のテーブルに格納されます。

      大規模なシステム(中規模なソーシャルネットワークなど)では、ユーザー認証データや頻繁にアクセスされるデータを、ほとんど必要とされない大きなコンテンツの塊とは別に保存することが現実的かもしれません。そのような場合、単一の User クラスがありますが、そこに含まれる情報は、完全な詳細が取得されたかどうかに依存します。

    • すべての ドメイン・オブジェクト マッパーは1つ以上存在することができます。

      あるニュースサイトがあり、公開用と管理用の両方のコードベースを共有しているとします。しかし、どちらのインターフェースも同じ Article クラスでは、管理者はより多くの情報を入力する必要があります。この場合、2つの別々のマッパーを持つことになります: "internal" と "external" です。それぞれ異なるクエリを実行したり、(マスターやスレーブのように)異なるデータベースを使用することもできます。

  2. ビューはテンプレートではありません

    ビュー のインスタンスは、(パターンのMVPバリエーションを使用していない場合)プレゼンテーショナルロジックを担当します。つまり、各 ビュー は、通常、少なくともいくつかのテンプレートを扱うことになります。テンプレートからデータを取得します。 モデル層 で、受け取った情報をもとに、テンプレートを選び、値を設定する。

    これによって得られるメリットのひとつは、再利用性です。もしあなたが ListView クラスがあれば、よくできたコードで、記事の下のユーザーリストとコメントの表示を同じクラスで担当させることができます。なぜなら、どちらも同じプレゼンテーションのロジックを持っているからです。テンプレートを入れ替えるだけです。

    のどちらかを使用することができます。 PHPネイティブテンプレート またはサードパーティのテンプレートエンジンを使用します。また、サードパーティのライブラリで 表示 インスタンスを作成します。

  3. 旧バージョンの回答はどうなっていますか?

    大きな変更点はただ一つ、いわゆる モデル は、旧バージョンでは サービス . その他のライブラリのアナロジーはうまくいっていますね。

    なぜなら、本から情報を得ることはできても、本自体には触ることができないからです。もっと適切な例えを考えないといけないかもしれませんね。

  4. の関係はどうなっているのでしょうか? 表示 コントローラ のインスタンスですか?

    MVC構造は、uiとモデルの2つの層で構成されています。の主な構造は UI層 はビューとコントローラです。

    MVCデザインパターンを使用するウェブサイトを扱う場合、ビューとコントローラの関係を1対1にするのがベストです。各ビューは、あなたのウェブサイト内のページ全体を表し、それはその特定のビューのすべての着信要求を処理するために専用のコントローラを持っています。

    たとえば、開いた記事を表すには、次のようにします。 \Application\Controller\Document\Application\View\Document . これはUIレイヤーのすべての主要な機能を含んでおり、記事を扱うことになります。 (もちろん、いくつかの XHR コンポーネントは、記事とは直接関係ない) .