web-dev-qa-db-ja.com

MVC(Laravel)ロジックを追加する場所

CRUD操作を行うか、特定の方法でリレーションシップを変更するたびに、何か他のこともしたいとします。たとえば、誰かが投稿を公開するたびに、分析のためにテーブルに何かを保存することも必要です。たぶん最良の例ではないかもしれませんが、一般にこの「グループ化された」機能はたくさんあります。

通常、このタイプのロジックはコントローラーに入れられます。この機能を多くの場所で再現したいまでは、これで十分です。パーシャルを取得し始め、APIを作成し、ダミーコンテンツを生成すると、物事をドライに保つことが問題になります。

これを管理するために見た方法は、イベント、リポジトリ、ライブラリ、およびモデルへの追加です。それぞれの私の理解は次のとおりです。

Services:これは、ほとんどの人がおそらくこのコードを置く場所です。サービスに関する私の主な問題は、サービスの特定の機能を見つけることが困難な場合があり、Eloquentの使用に人々が集中していることを忘れられているように感じることです。 $post->is_published = 1を行うことができるのに、ライブラリのpublishPost()メソッドを呼び出す必要があることをどのように知ることができますか?

これがうまく機能する唯一の条件は、サービスのみを使用する場合です(そして、理想的には、Eloquentをコントローラーから一緒にアクセスできないようにすることが理想的です)。

最終的に、リクエストが一般的にモデル構造に従っている場合、これは余分な不要なファイルを作成するだけのようです。

リポジトリ:私の理解では、これは基本的にサービスのようなものですが、インターフェースがあり、ORMを切り替えることができます。これは必要ありません。

イベント:モデルイベントは常にEloquentメソッドで呼び出されることを知っているので、これはある意味で最もエレガントなシステムだと思います。しかし、これらが乱雑になっているのを見ることができます。重要なカップリングにイベントを使用する大規模なプロジェクトの例があれば、それを見てみたいです。

モデル:従来、CRUDを実行し、重要な結合も処理するクラスがありました。 CRUDに関連するすべての機能と、CRUDを使用して行わなければならないことをすべて知っていたので、これは実際に簡単になりました。

シンプルですが、MVCアーキテクチャでは、これは通常私が見ていることではありません。ある意味では、私はサービスよりもこれを好むが、それは少し見つけやすく、追跡するファイルが少ないからだ。ただし、少し混乱する可能性があります。この方法の欠点と、ほとんどの人がそうしない理由を聞きたいです。

各方法の利点/欠点は何ですか?何か不足していますか?

115
Sabrina Leggett

SOLID の原則に従う限り、あなたが提示するすべてのパターン/アーキテクチャは非常に役立つと思います。

ロジックを追加する場所について単一責任原則 を参照することが重要だと思います。また、私の答えは、あなたが中規模/大規模なプロジェクトに取り組んでいると考えています。 throw-something-on-a-pageプロジェクトの場合、この回答を忘れて、すべてをコントローラーまたはモデルに追加してください。

簡単な答えは次のとおりです:それがあなたにとって理にかなっているところ(サービスを利用して).

長い答え:

Controllers:Controllersの責任は何ですか?もちろん、すべてのロジックをコントローラーに入れることができますが、それはコントローラーの責任ですか?そうは思いません。

私にとって、コントローラーはリクエストを受け取ってデータを返す必要があり、これは検証を行ったり、dbメソッドを呼び出したりする場所ではありません。

Models:これは、ユーザーが投稿の投票数を登録または更新したときにウェルカムメールを送信するなどのロジックを追加するのに適した場所ですか?コード内の別の場所から同じメールを送信する必要がある場合はどうなりますか?静的メソッドを作成しますか?そのメールに別のモデルからの情報が必要な場合はどうなりますか?

モデルはエンティティを表すべきだと思います。 Laravelでは、モデルクラスを使用して、fillableguardedtableおよび関係などを追加します(これはリポジトリパターンを使用するためです。 saveupdatefindなどのメソッド)。

Repositories(Repository Pattern):最初はこれにとても戸惑っていました。そして、あなたと同じように、「まあ、MySQLを使って、それだけだ」と思った。

ただし、リポジトリパターンを使用することの長所と短所のバランスを取り、今では使用しています。 now、現時点ではMySQLのみを使用する必要があると思います。しかし、3年後にMongoDBのようなものに変更する必要がある場合、ほとんどの作業は完了しています。すべて1つの追加インターフェイスと$app->bind(«interface», «repository»)を犠牲にします。

イベント( 監視者パターン ):イベントは、任意のクラスでいつでもスローできるものに役立ちます。たとえば、ユーザーに通知を送信することを考えてください。必要に応じて、イベントを発生させて、アプリケーションの任意のクラスで通知を送信します。次に、ユーザー通知のために発生したすべてのイベントを処理するUserNotificationEventsなどのクラスを作成できます。

Services:これまで、コントローラーまたはモデルにロジックを追加する選択肢がありました。 私にとっては、Services内にロジックを追加することは理にかなっています。それに直面してみましょう、サービスはクラスの凝った名前です。また、アプリケーション内で意味のあるクラスをいくつでも持つことができます。

この例を見てみましょう:少し前に、Googleフォームのようなものを開発しました。 CustomFormServiceで始まり、CustomFormServiceCustomFormRenderCustomFieldServiceCustomFieldRenderCustomAnswerService、およびCustomAnswerRenderで終わりました。どうして?理にかなっているからです。チームで作業する場合、チームにとって意味のある場所にロジックを配置する必要があります。

サービス対コントローラー/モデルを使用する利点は、単一のコントローラーまたは単一のモデルに制約されないことです。アプリケーションの設計とニーズに基づいて、必要な数のサービスを作成できます。それに、アプリケーションの任意のクラス内でサービスを呼び出す利点を追加します。

これは長くかかりますが、アプリケーションをどのように構成したかを示したいと思います。

app/
    controllers/
    MyCompany/
        Composers/
        Exceptions/
        Models/
        Observers/
        Sanitizers/
        ServiceProviders/
        Services/
        Validators/
    views
    (...)

特定の機能に各フォルダーを使用します。たとえば、Validatorsディレクトリには、特定のバリデータの$rulesおよび$messages(通常はモデルごとに1つ)に基づいて、検証の処理を担当するBaseValidatorクラスが含まれます。このコードをサービス内に簡単に配置できますが、サービス内でのみ使用される場合でも、このための特定のフォルダーを用意することは理にかなっています(現時点では)。

以下の記事を読むことをお勧めします。これらの記事は、あなたに物事を少し良く説明するかもしれません。

Dayle Reesによる型破り (CodeBrightの著者):ニーズに合わせていくつかの変更を加えましたが、ここですべてをまとめています。

Repositories and Servicesを使用してLaravelのコードを切り離す Chris Gooseyによる:この投稿では、ServiceとRepositoryパターンとは何か、またそれらがどのように適合するかについて説明します。

Laracastsには Repositories Simplified および Single Responsibility もあります。これらは実用的な例があります(料金はかかりますが)。

142
Luís Cruz

自分の質問への回答を投稿したかった。私はこれについて何日も話すことができましたが、私はそれをすぐに投稿して、確実に立ち上げるようにします。

Laravelが提供する既存の構造を利用することになりました。つまり、ファイルを主にモデル、ビュー、コントローラーとして保持していました。また、実際にはモデルではない再利用可能なコンポーネント用のライブラリフォルダーもあります。

I DIDサービス/ライブラリでモデルをラップしません。提供された理由のすべてが、サービスを使用することのメリットを私に100%納得させたわけではありません。私は間違っているかもしれませんが、それらを見る限り、余分なほとんど空のファイルを作成するだけで、モデルを操作するときに作成して切り替える必要があり、雄弁を使用する利点を実際に減らす必要があります(特にモデルの検索に関しては) 、たとえば、ページネーション、スコープなどを使用)。

モデルにビジネスロジックを入れ、コントローラーから雄弁に直接アクセスします。ビジネスロジックがバイパスされないようにするために、いくつかのアプローチを使用しています。

  • アクセサーとミューテーター:Laravelには素晴らしいアクセサーとミューテーターがあります。投稿が下書きから公開に移動するたびにアクションを実行したい場合、関数setIsPublishedAttributeを作成し、そこにロジックを含めることでこれを呼び出すことができます
  • 作成/更新などのオーバーライド:モデル内のEloquentメソッドをいつでもオーバーライドして、カスタム機能を含めることができます。これにより、任意のCRUD操作で機能を呼び出すことができます。編集:新しいLaravelバージョンでcreateをオーバーライドするとバグがあると思います(したがって、現在ブートで登録されているイベントを使用します)
  • 検証:同じ方法で検証をフックします。たとえば、必要に応じてCRUD関数とアクセサー/ミューテーターをオーバーライドして検証を実行します。詳細については、Esensiまたはdwightwatson/validatingを参照してください。
  • マジックメソッド:モデルの__getおよび__setメソッドを使用して、必要に応じて機能にフックします
  • Eloquentの拡張:すべての更新/作成に対して実行するアクションがある場合は、Eloquentを拡張して複数のモデルに適用することもできます。
  • イベント:これは単純であり、一般的にこれを行う場所でも合意されています。イベントの最大の欠点は、例外を追跡するのが難しいことです(Laravelの新しいイベントシステムの新しいケースではないかもしれません)。また、イベントが呼び出されたときではなく、何をするかによってイベントをグループ化するのも好きです。たとえば、メールを送信するイベントをリッスンするMailSenderサブスクライバーがいます。
  • Pivot/BelongsToManyイベントの追加:最も長く苦労したことの1つは、belongsToMany関係の変更に動作をアタッチする方法でした。たとえば、ユーザーがグループに参加するたびにアクションを実行します。これについては、カスタムライブラリをほぼ完成させました。まだ公開していませんが、機能的です!リンクをすぐに投稿しようとします。 EDIT最終的にすべてのピボットを通常のモデルに変更し、私の人生はずっと楽になりました...

モデルの使用に関する人々の懸念に対処する:

  • 組織:はい、より多くのロジックをモデルに含めると、より長くなる可能性がありますが、一般に、モデルの75%がまだかなり小さいことがわかりました。大きなものを整理することを選択した場合、特性を使用してそれを行うことができます(たとえば、必要に応じてPostScopes、PostAccessors、PostValidationなどのいくつかのファイルを含むモデルのフォルダーを作成します)。私はこれが必ずしも特性の目的ではないことを知っていますが、このシステムは問題なく動作します。

追加メモ:サービスでモデルを包むことは、多くのツールを備えたスイスアーミーナイフを持ち、基本的にその周りに別のナイフを構築するようなものです同じことですか?ブレードをテープで留めたり、2つのブレードが一緒に使用されていることを確認したい場合もありますが、通常は他の方法もあります...

いつサービスを使用するか:この記事では、いつサービスを使用するかについての素晴らしい例を明確に説明しています(ヒント:それほどではありませんしばしば)。彼は基本的に、オブジェクトが複数のモデルを使用する場合、またはライフサイクルの奇妙な部分でモデルを使用する場合理にかなっています。 http://www.justinweiss.com/articles/where-do-you-put-your-code/

19
Sabrina Leggett

コントローラーとモデルの間のロジックを作成するために使用するのは、サービスレイヤーを作成することです。基本的に、これは私のアプリ内のアクションの私のフローです:

  1. コントローラーは、ユーザーの要求されたアクションを取得し、パラメーターを送信し、すべてをサービスクラスに委任します。
  2. サービスクラスは、入力検証、イベントロギング、データベース操作など、操作に関連するすべてのロジックを実行します。
  3. モデルは、フィールドの情報、データ変換、および属性検証の定義を保持します。

これは私がそれを行う方法です:

これは何かを作成するコントローラーのメソッドです:

public function processCreateCongregation()
{
    // Get input data.
    $congregation                 = new Congregation;
    $congregation->name           = Input::get('name');
    $congregation->address        = Input::get('address');
    $congregation->pm_day_of_week = Input::get('pm_day_of_week');
    $pmHours                      = Input::get('pm_datetime_hours');
    $pmMinutes                    = Input::get('pm_datetime_minutes');
    $congregation->pm_datetime    = Carbon::createFromTime($pmHours, $pmMinutes, 0);

    // Delegates actual operation to service.
    try
    {
        CongregationService::createCongregation($congregation);
        $this->success(trans('messages.congregationCreated'));
        return Redirect::route('congregations.list');
    }
    catch (ValidationException $e)
    {
        // Catch validation errors thrown by service operation.
        return Redirect::route('congregations.create')
            ->withInput(Input::all())
            ->withErrors($e->getValidator());
    }
    catch (Exception $e)
    {
        // Catch any unexpected exception.
        return $this->unexpected($e);
    }
}

これは、操作に関連するロジックを実行するサービスクラスです。

public static function createCongregation(Congregation $congregation)
{
    // Log the operation.
    Log::info('Create congregation.', compact('congregation'));

    // Validate data.
    $validator = $congregation->getValidator();

    if ($validator->fails())
    {
        throw new ValidationException($validator);
    }

    // Save to the database.
    $congregation->created_by = Auth::user()->id;
    $congregation->updated_by = Auth::user()->id;

    $congregation->save();
}

そして、これは私のモデルです:

class Congregation extends Eloquent
{
    protected $table = 'congregations';

    public function getValidator()
    {
        $data = array(
            'name' => $this->name,
            'address' => $this->address,
            'pm_day_of_week' => $this->pm_day_of_week,
            'pm_datetime' => $this->pm_datetime,
        );

        $rules = array(
            'name' => ['required', 'unique:congregations'],
            'address' => ['required'],
            'pm_day_of_week' => ['required', 'integer', 'between:0,6'],
            'pm_datetime' => ['required', 'regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/'],
        );

        return Validator::make($data, $rules);
    }

    public function getDates()
    {
        return array_merge_recursive(parent::getDates(), array(
            'pm_datetime',
            'cbs_datetime',
        ));
    }
}

この方法の詳細については、Laravelアプリのコードを整理するために使用します。 https://github.com/rmariuzzo/Pitimi

17
Rubens Mariuzzo

私の意見では、Laravelには既にビジネスロジックを保存するための多くのオプションがあります。

簡潔な答え:

  • LaravelのRequestオブジェクトを使用して入力を自動的に検証し、リクエスト内のデータを永続化します(モデルを作成します)。すべてのユーザー入力が直接利用可能ですinリクエストなので、ここでこれを実行するのが理にかなっていると思います。
  • LaravelのJobオブジェクトを使用して、個々のコンポーネントを必要とするタスクを実行し、それらを単にディスパッチします。 Jobにはサービスクラスが含まれると思います。ビジネスロジックなどのタスクを実行します。

長い(より)答え:

必要な場合はリポジトリを使用:リポジトリは肥大化する傾向があり、ほとんどの場合、モデルへのaccessorとして使用されます。確かに何らかの用途があるように感じますが、必須 laravelを完全に捨てることができるほどの柔軟性を備えた大規模なアプリケーションを開発していない限り、リポジトリ。後で自分に感謝し、あなたのコードはずっと簡単になります。

PHPフレームワークまたはをlaravelがサポートしていないデータベースタイプに変更する可能性があるかどうかを自問してください。

答えが「おそらくない」の場合、リポジトリパターンを実装しないでください。

上記に加えて、Eloquentのような素晴らしいORMの上にパターンを平手打ちしないでください。必要のない複雑さを追加しているだけであり、まったくメリットはありません。

サービスを控えめに利用する:私にとってサービスクラスは、特定の依存関係で特定のタスクを実行するためのビジネスロジックを保存する場所にすぎません。 Laravelには、「ジョブ」と呼ばれるこれらがすぐに使用でき、カスタムServiceクラスよりもはるかに柔軟性があります。

LaravelにはMVC論理問題に対するバランスのとれたソリューションがあるように感じます。それは単なる問題または組織です。

例:

リクエスト

namespace App\Http\Requests;

use App\Post;
use App\Jobs\PostNotifier;
use App\Events\PostWasCreated;
use App\Http\Requests\Request;

class PostRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title'       => 'required',
            'description' => 'required'
        ];
    }

    /**
     * Save the post.
     *
     * @param Post $post
     *
     * @return bool
     */
    public function persist(Post $post)
    {
        if (!$post->exists) {
            // If the post doesn't exist, we'll assign the
            // post as created by the current user.
            $post->user_id = auth()->id();
        }

        $post->title = $this->title;
        $post->description = $this->description;

        // Perform other tasks, maybe fire an event, dispatch a job.

        if ($post->save()) {
            // Maybe we'll fire an event here that we can catch somewhere else that
            // needs to know when a post was created.
            event(new PostWasCreated($post));

            // Maybe we'll notify some users of the new post as well.
            dispatch(new PostNotifier($post));

            return true;
        }

        return false;
    }
}

コントローラー

namespace App\Http\Controllers;

use App\Post;
use App\Http\Requests\PostRequest;

class PostController extends Controller
{

   /**
    * Creates a new post.
    *
    * @return string
    */
    public function store(PostRequest $request)
    {
        if ($request->persist(new Post())) {
            flash()->success('Successfully created new post!');
        } else {
            flash()->error('There was an issue creating a post. Please try again.');
        }

        return redirect()->back();
    }

   /**
    * Updates a post.
    *
    * @return string
    */
    public function update(PostRequest $request, $id)
    {
        $post = Post::findOrFail($id);

        if ($request->persist($post)) {
            flash()->success('Successfully updated post!');
        } else {
            flash()->error('There was an issue updating this post. Please try again.');
        }

        return redirect()->back();
    }
}

上記の例では、リクエストの入力が自動的に検証されます。必要なのは、persistメソッドを呼び出して新しいPostを渡すことだけです。可読性と保守性は常に複雑で不要なデザインパターンに勝るものだと思います。

投稿が既に存在するかどうかを確認し、必要に応じて代替ロジックを実行できるため、投稿の更新にもまったく同じ永続メソッドを使用できます。

9
Steve Bauman