apeescape2.com
  • メイン
  • トレンド
  • プロジェクト管理
  • アジャイル
  • Uiデザイン
技術

階層構造でスリムなPHPMVCフレームワークを維持する

ファットコントローラーとモデル:YiiやLaravelなどのMVCフレームワークに基づくほとんどの大規模プロジェクトにとって避けられない問題。コントローラとモデルを肥大化させる主なものは アクティブレコード 、そのようなフレームワークの強力で不可欠なコンポーネント。

問題:アクティブレコードとそのSRP違反

Active Recordはアーキテクチャパターンであり、データベース内のデータにアクセスするためのアプローチです。 2003年の本でマーティンファウラーによって名付けられました エンタープライズアプリケーションアーキテクチャのパターン で広く使用されています PHP フレームワーク。

これは非常に必要なアプローチであるという事実にもかかわらず、アクティブレコード(AR)パターンは、ARモデルが次の理由で単一責任原則(SRP)に違反しています。



  • クエリとデータ保存を処理します。
  • システム内の他のモデルについて(関係を通じて)よく知っている。
  • 多くの場合、アプリケーションのビジネスロジックに直接関与します(データストレージの実装は、前述のビジネスロジックと密接に関連しているため)。

このSRPの違反は、アプリケーションプロトタイプをできるだけ早く作成する必要がある場合、迅速な開発とのトレードオフになりますが、アプリケーションが中規模または大規模なプロジェクトに成長する場合は非常に有害です。 「神」モデルとファットコントローラーはテストと保守が難しく、コントローラーのどこでもモデルを自由に使用すると、データベース構造を必然的に変更する必要がある場合に非常に困難になります。

解決策は簡単です。ActiveRecordの責任をいくつかのレイヤーに分割し、レイヤー間の依存関係を注入します。このアプローチでは、現在テストされていないレイヤーをモックできるため、テストも簡素化されます。

解決策:PHPMVCフレームワークの階層構造

「ファット」なPHPMVCアプリケーションには、どこにでも依存関係があり、連動してエラーが発生しやすくなりますが、階層構造では依存性注入を使用して、物事をクリーンで明確に保ちます。

ここで取り上げる主要なレイヤーは5つあります。

  • ザ・ コントローラ層
  • ザ・ サービスレイヤー
  • DTO 、サービスレイヤーのサブセット
  • デコレータを表示する 、サービスレイヤーのサブセット
  • ザ・ リポジトリレイヤー

階層化されたPHP構造

階層構造を実装するには、 依存性注入コンテナ 、オブジェクトをインスタンス化して構成する方法を知っているオブジェクト。フレームワークがすべての魔法を処理するため、クラスを作成する必要はありません。次のことを考慮してください。

class SiteController extends IlluminateRoutingController { protected $userService; public function __construct(UserService $userService) { $this->userService = $userService; } public function showUserProfile(Request $request) { $user = $this->userService->getUser($request->id); return view('user.profile', compact('user')); } } class UserService { protected $userRepository; public function __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } public function getUser($id) { $user = $this->userRepository->getUserById($id); $this->userRepository->logSession($user); return $user; } } class UserRepository { protected $userModel, $logModel; public function __construct(User $user, Log $log) { $this->userModel = $user; $this->logModel = $log; } public function getUserById($id) { return $this->userModel->findOrFail($id); } public function logSession($user) { $this->logModel->user = $user->id; $this->logModel->save(); } }

上記の例では、UserService SiteController、UserRepositoryに注入されますUserServiceに注入されますおよびARモデルUserおよびLogs UserRepositoryに注入されますクラス。このコンテナコードはかなり単純なので、レイヤーについて説明しましょう。

コントローラーレイヤー

LaravelやYiiなどの最新のMVCフレームワークは、従来のコントローラーの課題の多くを引き受けます。入力検証とプレフィルターは、アプリケーションの別の部分に移動されます(Laravelでは、これはいわゆる ミドルウェア 一方、Yiiではそれは 動作 )ルーティングとHTTP動詞ルールはフレームワークによって処理されます。これにより、プログラマーがコントローラーにコーディングする機能が非常に狭くなります。

コントローラの本質は、リクエストを取得して結果を提供することです。コントローラには、アプリケーションのビジネスロジックを含めるべきではありません。そうしないと、コードを再利用したり、アプリケーションの通信方法を変更したりすることが困難になります。たとえば、ビューをレンダリングする代わりにAPIを作成する必要があり、コントローラーにロジックが含まれていない場合は、データを返す方法を変更するだけで、準備は完了です。

この薄いコントローラーレイヤーはプログラマーを混乱させることがよくあります。コントローラーはデフォルトレイヤーであり、最上位のエントリポイントであるため、多くの開発者は、アーキテクチャについて何も考えずに、コントローラーに新しいコードを追加し続けます。その結果、次のような過度の責任が追加されます。

  • ビジネスロジック(ビジネスロジックコードの再利用を不可能にします)。
  • モデルの状態を直接変更します(この場合、データベースを変更すると、コードのあらゆる場所で大きな変更が発生します)。
  • モデルリレーションロジック(複雑なクエリ、複数のモデルの結合など。データベースまたはリレーションロジックで何かが変更された場合は、すべてのコントローラーで変更する必要があります)。

過剰に設計されたコントローラーの例を考えてみましょう。

//A bad example of a controller public function user(Request $request) { $user = User::where('id', '=', $request->id) ->leftjoin('posts', function ($join) { $join->on('posts.user_id', '=', 'user.id') ->where('posts.status', '=', Post::STATUS_APPROVED); }) ->first(); if (!empty($user)) { $user->last_login = date('Y-m-d H:i:s'); } else { $user = new User(); $user->is_new = true; $user->save(); } return view('user.index', compact('user')); }

この例が悪いのはなぜですか?多くの理由で:

  • 含まれているビジネスロジックが多すぎます。
  • Active Recordと直接連携するため、last_loginの名前を変更するなど、データベース内の何かを変更した場合フィールドでは、すべてのコントローラーで変更する必要があります。
  • データベースの関係を知っているので、データベースで何かが変更された場合は、どこでも変更する必要があります。
  • 再利用できないため、コードが繰り返されます。

コントローラは薄くする必要があります。実際、それがすべきことは、リクエストを受け取って結果を返すことだけです。これが良い例です:

//A good example of a controller public function user (Request $request) { $user = $this->userService->getUserById($request->id); return view('user.index', compact('user')); }

しかし、他のすべてのものはどこに行きますか?それはに属します サービスレイヤー 。

オンラインで無料でcプログラミングを学ぶ

サービスレイヤー

サービス層はビジネスロジックの層です。ここで、そしてここでのみ、ビジネスプロセスフローとビジネスモデル間の相互作用に関する情報を配置する必要があります。これは抽象層であり、アプリケーションごとに異なりますが、一般的な原則は、データソース(コントローラーの責任)およびデータストレージ(下位層の責任)からの独立性です。

これは、成長の問題が発生する可能性が最も高い段階です。多くの場合、Active Recordモデルはコントローラーに返されます。その結果、ビュー(またはAPI応答の場合はコントローラー)はモデルと連携し、その属性と依存関係を認識している必要があります。これは物事を厄介にします。 Active Recordモデルのリレーションまたは属性を変更する場合は、すべてのビューとコントローラーのどこでも変更する必要があります。

ビューで使用されているActiveRecordモデルに出くわす可能性のある一般的な例を次に示します。

    @foreach($user->posts as $post)
  • {{$post->title}}
  • @endforeach

簡単そうに見えますが、名前を変更するとfirst_nameフィールド、突然、このモデルのフィールドを使用するすべてのビューを変更する必要があります。これはエラーが発生しやすいプロセスです。この難問を回避する最も簡単な方法は、データ転送オブジェクト(DTO)を使用することです。

データ転送オブジェクト

サービスレイヤーからのデータは、単純な不変オブジェクトにラップする必要があります。つまり、作成後に変更することはできません。そのため、DTOのセッターは必要ありません。さらに、DTOクラスは独立している必要があり、ActiveRecordモデルを拡張しないでください。ただし、注意が必要です。ビジネスモデルは必ずしもARモデルと同じではありません。

食料品の配達アプリケーションを考えてみましょう。論理的には、食料品店の注文には配達情報を含める必要がありますが、データベースには注文を保存してユーザーにリンクし、ユーザーは配達先住所にリンクします。この場合、複数のARモデルがありますが、上位層はそれらについて認識してはなりません。 DTOクラスには、注文だけでなく、配送情報やビジネスモデルに沿ったその他のパーツも含まれます。このビジネスモデルに関連するARモデルを変更する場合(たとえば、配信情報を注文テーブルに移動する場合)、コード内のあらゆる場所でARモデルフィールドの使用法を変更するのではなく、DTOオブジェクトのフィールドマッピングのみを変更します。

DTOアプローチを採用することで、コントローラーまたはビューでActiveRecordモデルを変更したいという誘惑を取り除きます。次に、DTOアプローチは、物理データストレージと抽象的なビジネスモデルの論理表現との間の接続の問題を解決します。データベースレベルで何かを変更する必要がある場合、その変更はコントローラーやビューではなくDTOオブジェクトに影響します。パターンを見ていますか?

簡単なDTOを見てみましょう。

//Example of simple DTO class. You can add any logic of conversion from an Active Record object to business model here class DTO { private $entity; public static function make($model) { return new self($model); } public function __construct($model) { $this->entity = (object) $model->toArray(); } public function __get($name) { return $this->entity->{$name}; } }

新しいDTOの使用も同様に簡単です。

//usage example public function user (Request $request) { $user = $this->userService->getUserById($request->id); $user = DTO::make($user); return view('user.index', compact('user')); }

デコレータを表示する

ビューロジックを分離するために(ステータスに基づいてボタンの色を選択するなど)、デコレータの追加レイヤーを使用することは理にかなっています。 A デコレータ は、カスタムメソッドでラップすることにより、コアオブジェクトの装飾を可能にするデザインパターンです。これは通常、ビュー内でやや特殊なロジックで発生します。

あなた自身のグーグルグラスを作る方法

DTOオブジェクトはデコレータのジョブを実行できますが、実際には、日付の書式設定などの一般的なアクションに対してのみ機能します。 DTOはビジネスモデルを表す必要がありますが、デコレータは特定のページのデータをHTMLで装飾します。

デコレータを使用しないユーザープロファイルステータスアイコンのスニペットを見てみましょう。

@if($user->status == AppModelsUser::STATUS_ONLINE) Online @else Offline @endif {{date('F j, Y', strtotime($user->lastOnline))}}

この例は単純ですが、開発者がより複雑なロジックに迷うのは簡単です。これは、HTMLの読みやすさをクリーンアップするためのデコレータの出番です。ステータスアイコンスニペットを完全なデコレータクラスに拡張しましょう。

class UserProfileDecorator { private $entity; public static function decorate($model) { return new self($model); } public function __construct($model) { $this->entity = $model; } public function __get($name) { $methodName = 'get' . $name; if (method_exists(self::class, $methodName)) { return $this->$methodName(); } else { return $this->entity->{$name}; } } public function __call($name, $arguments) { return $this->entity->$name($arguments); } public function getStatus() { if($this->entity->status == AppModelsUser::STATUS_ONLINE) { return 'Online'; } else { return 'Offline'; } } public function getLastOnline() { return date('F j, Y', strtotime($this->entity->lastOnline)); } }

デコレータの使用は簡単です。

public function user (Request $request) { $user = $this->userService->getUserById($request->id); $user = DTO::make($user); $user = UserProfileDecorator::decorate($user); return view('user.index', compact('user')); }

これで、条件やロジックなしでビューでモデル属性を使用できるようになり、はるかに読みやすくなりました。

{{$user->status}} {{$user->lastOnline}}

デコレータを組み合わせることもできます。

public function user (Request $request) { $user = $this->userService->getUserById($request->id); $user = DTO::make($user); $user = UserDecorator::decorate($user); $user = UserProfileDecorator::decorate($user); return view('user.index', compact('user')); }

各デコレータはそれぞれの役割を果たし、独自の部分のみを装飾します。複数のデコレータのこの再帰的な埋め込みにより、追加のクラスを導入することなく、それらの機能を動的に組み合わせることができます。

リポジトリレイヤー

リポジトリ層は、データストレージの具体的な実装で機能します。柔軟性と簡単な交換のために、インターフェースを介してリポジトリを注入するのが最善です。データストレージを変更する場合は、リポジトリインターフェースを実装する新しいリポジトリを作成する必要がありますが、少なくとも他のレイヤーを変更する必要はありません。

リポジトリはクエリオブジェクトの役割を果たします。リポジトリはデータベースからデータを取得し、いくつかのActiveRecordモデルの作業を実行します。このコンテキストでは、アクティブレコードモデルは、単一のデータモデルエンティティ(情報をモデル化して保存する必要のあるシステム内のオブジェクト)の役割を果たします。各エンティティには情報が含まれていますが、それがどのように表示されたか(データベースから作成または取得されたかどうか)、または独自の状態を保存および変更する方法はわかりません。リポジトリの責任は、エンティティを保存および/または更新することです。これにより、リポジトリ内のエンティティの管理が維持され、エンティティが単純になるため、関心の分離が向上します。

データベースとActiveRecordの関係に関する知識を使用してクエリを作成するリポジトリメソッドの簡単な例を次に示します。

public function getUsers() { return User::leftjoin('posts', function ($join) { $join->on('posts.user_id', '=', 'user.id') ->where('posts.status', '=', Post::STATUS_APPROVED); }) ->leftjoin('orders', 'orders.user_id', '=', 'user.id') ->where('user.status', '=', User::STATUS_ACTIVE) ->where('orders.price', '>', 100) ->orderBy('orders.date') ->with('info') ->get(); }

単一責任レイヤーでスリムに保つ

新しく作成されたアプリケーションでは、コントローラー、モデル、およびビューのフォルダーのみが見つかります。 YiiもLaravelも、サンプルアプリケーションの構造にレイヤーを追加していません。初心者でも簡単で直感的なMVC構造により、フレームワークでの作業が簡素化されますが、サンプルアプリケーションが例であることを理解することが重要です。これは標準でもスタイルでもありません。また、アプリケーションアーキテクチャに関する規則を課すこともありません。タスクを個別の単一責任レイヤーに分割することで、保守が容易な柔軟で拡張可能なアーキテクチャーを実現します。覚えておいてください:

  • エンティティ 単一のデータモデルです。
  • リポジトリ データをフェッチして準備します。
  • ザ・ サービスレイヤー ビジネスロジックしかありません。
  • コントローラー ユーザー入力やサードパーティサービスなど、すべての外部ソースと通信します。

したがって、複雑なプロジェクトや将来成長する可能性のあるプロジェクトを開始する場合は、責任をコントローラー、サービス、およびリポジトリの各レイヤーに明確に分割することを検討してください。

パフォーマンスと効率:HTTP / 3での作業

バックエンド

パフォーマンスと効率:HTTP / 3での作業
4Go言語批評

4Go言語批評

バックエンド

人気の投稿
ヘッジファンドのディープラーニングトレーディング入門
ヘッジファンドのディープラーニングトレーディング入門
エンタープライズクライアントサービスディレクター、コミュニケーション、メディア、エンターテインメント、テクノロジー
エンタープライズクライアントサービスディレクター、コミュニケーション、メディア、エンターテインメント、テクノロジー
Fintechスタートアップを評価する方法
Fintechスタートアップを評価する方法
Sassスタイルガイド:より良いCSSコードを書く方法に関するSassチュートリアル
Sassスタイルガイド:より良いCSSコードを書く方法に関するSassチュートリアル
レスポンシブデザインでは不十分です。レスポンシブパフォーマンスが必要です
レスポンシブデザインでは不十分です。レスポンシブパフォーマンスが必要です
 
ShopifyのデザインのヒントとUXのベストプラクティス
ShopifyのデザインのヒントとUXのベストプラクティス
ゲーミフィケーションデザインへの実用的なアプローチ
ゲーミフィケーションデザインへの実用的なアプローチ
非同期JavaScript:コールバック地獄から非同期と待機まで
非同期JavaScript:コールバック地獄から非同期と待機まで
並行プログラミング入門:ビギナーズガイド
並行プログラミング入門:ビギナーズガイド
ブランドは依然として重要–ブランドレスブームからバストへ
ブランドは依然として重要–ブランドレスブームからバストへ
人気の投稿
  • cはどのような言語ですか
  • s法人とc法人の違い
  • anglejsおよびnodejsプロジェクト
  • データウェアハウスのデータ品質
  • 機械学習の概要
  • ボタンにクリックイベントリスナーを追加します。空の関数を2番目のパラメーターとして渡します。
カテゴリー
革新 ツールとチュートリアル アジャイル 計画と予測 プロジェクト管理 Uxデザイン 設計プロセス データサイエンスとデータベース その他 製品ライフサイクル

© 2021 | 全著作権所有

apeescape2.com