MVC(Laravel)在哪里添加逻辑

Sab*_*ett 115 php design-patterns laravel laravel-4

假设每当我进行CRUD操作或以特定方式修改关系时,我也想做其他事情.例如,每当有人发布帖子时,我也想将某些内容保存到表中进行分析.也许不是最好的例子,但总的来说有很多这种"分组"的功能.

通常我会将这种逻辑放入控制器中.在你想要在很多地方重现这个功能之前,这一切都很好.当你开始进入局部,创建一个API并生成虚拟内容时,就会出现干涸问题.

我看到管理它的方法是事件,存储库,库和添加到模型.以下是我对每个人的理解:

服务:这是大多数人可能会放置此代码的地方.我对服务的主要问题是,有时很难在其中找到特定功能,我觉得他们会忘记人们何时专注于使用Eloquent.我怎么知道我需要publishPost()在库中调用方法$post->is_published = 1

我看到这个工作得很好的唯一条件是,如果你只使用服务(理想情况下,Eloquent不能以某种方式从控制器一起无法访问).

最终,如果您的请求通常遵循您的模型结构,这似乎只会创建一堆额外的不必要的文件.

存储库:从我的理解,这基本上就像一个服务,但有一个接口,所以你可以在ORM之间切换,我不需要.

事件:从某种意义上说,我认为这是最优雅的系统,因为你知道你的模型事件总是会在Eloquent方法中被调用,所以你可以像往常一样编写你的控制器.我可以看到这些变得混乱,如果有人有大型项目的例子使用事件进行关键耦合,我希望看到它.

模型:传统上我会有类执行CRUD并处理关键耦合的类.这实际上让事情变得简单,因为你知道CRUD的所有功能+无论如何都要做到这一点.

很简单,但在MVC架构中,这通常不是我所看到的.从某种意义上说,虽然我更喜欢这种服务,因为它更容易找到,而且需要跟踪的文件较少.但它可能会有点混乱.我想听听这种方法的失败,以及为什么大多数人似乎都没有这样做.

每种方法有哪些优点/缺点?我错过了什么吗?

Luí*_*ruz 147

我认为只要遵循SOLID原则,您提供的所有模式/体系结构都非常有用.

对于添加逻辑位置,我认为参考单一责任原则很重要.另外,我的回答是你正在研究中/大型项目.如果它是一个扔在页面上的东西,请忘记这个答案并将其全部添加到控制器或模型中.

简短的回答是:对你有意义(有服务).

答案很长:

控制器:控制器的责任是什么?当然,您可以将所有逻辑放在控制器中,但控制器是否有责任?我不这么认为.

对我来说,控制器必须接收请求并返回数据,这不是放置验证,调用db方法等的地方.

模型:这是一个添加逻辑的好地方,例如当用户注册或更新帖子的投票数时发送欢迎电子邮件?如果您需要从代码中的其他位置发送相同的电子邮件,该怎么办?你创建一个静态方法吗?如果该电子邮件需要来自其他模型的信息怎么办?

我认为该模型应该代表一个实体.随着Laravel,我只用模型类添加之类的东西fillable,guarded,table和关系(这是因为我使用Repository模式,否则模型本来也有save,update,find,等方法).

存储库(存储库模式):一开始我对此非常困惑.并且,和你一样,我认为"好吧,我使用MySQL就是那样."

但是,我已经平衡了使用存储库模式的优点和缺点,现在我使用它.我想现在,就在这个时刻,我只需要使用MySQL.但是,如果从现在起三年后我需要改变像MongoDB这样的东西,大部分工作都已完成.所有这一切都以一个额外的接口和一个$app->bind(«interface», «repository»).

事件(观察者模式):事件对任何给定时间可以在任何类中抛出的事物都很有用.例如,考虑向用户发送通知.在您需要时,您可以触发事件以在您的应用程序的任何类别发送通知.然后,您可以拥有一个类UserNotificationEvents来处理用户通知的所有已触发事件.

服务:到目前为止,您可以选择向控制器或模型添加逻辑.对我来说,在服务中添加逻辑是有意义的.让我们面对现实吧,服务是课程的一个奇特名称.你可以在你的应用程序中拥有尽可能多的课程.

举个例子:不久之前,我开发了类似Google Forms的东西.我开始与一个CustomFormService并结束了CustomFormService,CustomFormRender,CustomFieldService,CustomFieldRender,CustomAnswerServiceCustomAnswerRender.为什么?因为它对我有意义.如果您与团队合作,您应该将逻辑放在对团队有意义的地方.

使用服务与控制器/模型的优点是您不受单个控制器或单个模型的约束.您可以根据应用程序的设计和需求根据需要创建任意数量的服务.除此之外,还可以在应用程序的任何类中调用服务.

这很长,但我想告诉你我如何构建我的应用程序:

app/
    controllers/
    MyCompany/
        Composers/
        Exceptions/
        Models/
        Observers/
        Sanitizers/
        ServiceProviders/
        Services/
        Validators/
    views
    (...)
Run Code Online (Sandbox Code Playgroud)

我将每个文件夹用于特定功能.例如,该Validators目录包含一个BaseValidator类负责处理所述验证的基础上,$rules$messages特定验证器(通常为每个模型).我可以很容易地将这些代码放在一个服务中,但即使只在服务中使用(现在),我也有一个特定的文件夹.

我建议你阅读以下文章,因为他们可能会向你解释一些事情:

Dayle Rees(CodeBright的作者)打破模具:这就是我把它放在一起的地方,即使我改变了一些东西以满足我的需要.

使用 Chris Goosey使用存储库和服务在Laravel中解耦代码:这篇文章很好地解释了什么是服务和存储库模式以及它们如何组合在一起.

Laracasts还有简单存储库单一责任存储库,它们是实用示例的良好资源(即使您必须付费).

  • 很好的解释.这就是我现在所处的位置 - 在当前的项目中,我将我的业务逻辑放在模型中并且它实际上工作得非常好.我们肯定需要稍微捏一下SOLID,但是,它还没有给我们带来麻烦.它很快,有点脏,但到目前为止,我们的项目非常易于维护,因为它太干了.我现在肯定可以坚持使用它们,因为他们完成了工作,但在未来的任何项目中,我可能会选择任何标准的,这听起来像存储库已经成为. (3认同)
  • 我很高兴你找到了一种对你有意义的方法。小心你_今天_所做的假设。我在一个项目上工作了 3 年以上,最终得到了具有 5000 多行代码的控制器和模型。祝你的项目好运。 (2认同)

Sab*_*ett 20

我想对自己的问题发表回复.我可以谈论这几天,但我会尽快发布,以确保我得到它.

我最终利用了Laravel提供的现有结构,这意味着我将文件保存为模型,视图和控制器.我还有一个Libraries文件夹,用于可重用的组件,而不是真正的模型.

我没有在服务/图书馆中包装我的模型.所提供的所有理由并非100%让我相信使用服务的好处.虽然我可能错了,但据我所知,它们只会产生大量额外的空文件,我需要创建并在使用模型时切换,并且还真正降低了使用雄辩的好处(特别是当涉及到RETRIEVING模型时)例如,使用分页,范围等).

我把业务逻辑放在模型中并直接从我的控制器访问口才.我使用了许多方法来确保不会绕过业务逻辑:

  • 访问者和变异者: Laravel有很好的访问者和变异者.如果我想在帖子从草稿移动到已发布时执行操作,我可以通过创建函数setIsPublishedAttribute并在其中包含逻辑来调用它
  • 覆盖创建/更新等:您始终可以覆盖模型中的Eloquent方法以包含自定义功能.这样您就可以在任何CRUD操作上调用功能.编辑:我认为在新的Laravel版本中覆盖创建有一个错误(因此我使用现在在启动时注册的事件)
  • 验证:我以相同的方式挂钩我的验证,例如,我将通过覆盖CRUD函数以及访问器/ mutator(如果需要)来运行验证.有关详细信息,请参阅Esensi或dwightwatson /验证.
  • 魔术方法:我使用模型的__get和__set方法在适当的地方挂钩功能
  • 扩展口才:如果您想要对所有更新/创建采取行动,您甚至可以扩展口才并将其应用于多个模型.
  • 事件:这是一个直截了当且通常也同意这样做的地方.事件的最大缺点我认为异常很难追踪(可能不是Laravel新事件系统的新案例).我还喜欢按照他们所做的事情而不是在他们被调用时对事件进行分组...例如,让MailSender订阅者收听发送邮件的事件.
  • 添加Pivot/BelongsToMany事件:我最长时间努力的事情之一是如何将行为附加到belongsToMany关系的修改.例如,每当用户加入组时执行动作.我差不多完成了一个自定义库.我还没有发表它但它功能齐全!将尝试尽快发布链接.编辑我最终把我的所有枢轴都变成了正常的模型,我的生活变得如此简单......

解决使用模型时人们的顾虑:

  • 组织:是的如果你在模型中包含更多的逻辑,它们可以更长,但总的来说我发现75%的模型仍然很小.如果我选择组织较大的那些我可以使用特征(例如,根据需要为PostScopes,PostAccessors,PostValidation等更多文件创建模型的文件夹).我知道这不一定是什么特征,但这个系统没有问题.

附加说明:我觉得在服务中包装你的模型就像拥有瑞士军刀,有很多工具,并在它周围建造另一把刀基本上做同样的事情?是的,有时候你可能想要剥离刀片或确保两个刀片一起使用......但通常还有其他方法可以做到......

什么时候使用服务:这篇文章非常清楚地阐述了什么时候使用服务(提示:它不常见).他说,基本上当你的对象在其生命周期的奇怪部分使用多个模型或模型时,这是有道理的.http://www.justinweiss.com/articles/where-do-you-put-your-code/

  • 有趣而有效的想法。但我很好奇 - 如果您的业务逻辑与与 Eloquent 相关的模型相关联,而 Eloquent 与数据库相关联,您如何对其进行单元测试? (2认同)

Rub*_*zzo 17

我用来在控​​制器和模型之间创建逻辑的方法是创建一个服务层.基本上,这是我的应用程序中的任何操作的流程:

  1. Controller获取用户请求的操作并发送参数并将所有内容委托给服务类.
  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是执行与操作相关的逻辑的服务类:

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();
}
Run Code Online (Sandbox Code Playgroud)

这是我的模特:

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',
        ));
    }
}
Run Code Online (Sandbox Code Playgroud)

有关这种方式的更多信息,我用来组织Laravel应用程序的代码:https://github.com/rmariuzzo/Pitimi


Ste*_*man 9

在我看来,Laravel已经有很多选项供您存储业务逻辑.

简短回答:

  • 使用laravel的Request对象自动验证输入,然后将数据保留在请求中(创建模型).由于所有用户输入都请求中直接可用,我相信在此处执行此操作是有意义的.
  • 使用laravel的Job对象来执行需要单个组件的任务,然后只需调度它们.我想Job包括服务类.他们执行任务,例如业务逻辑.

长(呃)回答:

必要时使用 存储库:存储库必须过度浮动,并且大多数情况下,仅用作accessor模型的存储库.我觉得他们肯定有一些用处,但除非你正在开发一个庞大的应用程序,需要你有足够的灵活性才能完全抛弃laravel,远离存储库.你以后会感谢自己,你的代码会更直接.

问问自己是否有可能改变PHP框架 laravel不支持的数据库类型.

如果您的答案是"可能不是",那么请不要实现存储库模式.

除此之外,请不要像Eloquent那样在一个极好的ORM上打一个模式.你只是增加了不需要的复杂性,它根本不会让你受益.

谨慎使用服务: 对我来说,服务类只是存储业务逻辑以执行具有给定依赖关系的特定任务的地方.Laravel开箱即用,称为"Jobs",它们比自定义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;
    }
}
Run Code Online (Sandbox Code Playgroud)

控制器:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,请求输入会自动验证,我们需要做的就是调用persist方法并传入新的Post.我认为可读性和可维护性应该总是胜过复杂和不必要的设计模式.

然后,您可以使用完全相同的持久方法来更新帖子,因为我们可以检查帖子是否已经存在并在需要时执行交替逻辑.

  • 作业不需要排队。您可以通过在 Laravel 提供的作业 `ShouldQueue` 上实现接口来指定。如果您想在命令或事件中编写业务逻辑,只需在这些事件/命令中触发作业即可。Laravel 的工作非常灵活,但最终它们只是普通的服务类。 (3认同)
  • 但是 - 工作不是“应该”排队吗?有时我们可能确实希望它排队,但并非总是如此。为什么不使用命令呢?如果您想编写一些可以作为命令或事件或排队执行的业务逻辑怎么办? (2认同)