关于我的模型层的反馈方法Symfony 2模型层+原则

时间:2021-05-24 06:49:31

Please provide feedback on the following approach of creating a Model Layer which consists of business rules which utliises Doctrine for data access.

请提供以下创建模型层的方法的反馈,该模型层包含用于数据访问的业务规则。

My current approach is based around the notion that the Model is a ContainerAware class / object where all the non-library, business specific domain logic is in.

我目前的方法基于这样的概念:模型是一个容器感知类/对象,其中包含所有非库、特定于业务的域逻辑。

I find that I have to hammer Frameworks to do things this way, which is why part of my brain questions my approach.

我发现我必须敲打框架才能做到这一点,这就是为什么我的大脑会质疑我的方法。

I'm currently using Symfony 2 which, like all modern PHP MVC frameworks, utilise an ORM layer like Doctrine 2 and inevitably regards it as the Model Layer. I'm guessing the situation will be similar with ZF2, so although my example is written in SF2, regard this as a framework agnostic question.

我目前正在使用Symfony 2,它和所有现代的PHP MVC框架一样,使用ORM层,如Doctrine 2,不可避免地将它视为模型层。我猜ZF2的情况与ZF2类似,因此尽管我的示例是在SF2中编写的,但这是一个框架不可知论的问题。

Concrete Example

具体的例子

As a concrete example, consider the following scenario:

作为一个具体的例子,请考虑以下场景:

A Message Requirement

一个信息的要求

  • As a user, I can create a Message that belongs to me.
  • 作为用户,我可以创建属于我的消息。
  • As a user, I can update a Message that belongs to me.
  • 作为用户,我可以更新属于我的消息。
  • As a user, I can archive a Message that belongs to me.
  • 作为用户,我可以存档属于我的消息。

The Controller

控制器

In Symfony2, these requirements are coded up as Actions in the controller layer. I'll skip the extraneous code which checks if a message actually belongs to a user, but obviously, this should be part of the domain logic also. In a method "belongsToUser" or similar.

在Symfony2中,这些需求被编码为控制器层中的操作。我将跳过检查消息是否真正属于用户的无关代码,但显然,这也应该是域逻辑的一部分。在方法中“归属者”或类似的。

 // Vendor\MessageBundle\DefaultController 
 public function archiveAction(Request $request) {
    // ... 
    $em = $this->getDoctrine()
               ->getManager();
    $message = $em->getRepository('MessageBundle:Message');
                    ->getManager()
                    ->getRepository('MessageBundle:Message')
                    ->find($request->get('id'));
    $message->setIsArchived(true);
    $em->persist($entity);
    $em->flush();
    $this->flashMessage('Message has been archived.');
    // ...
}

The Model

该模型

If I was to put this into a model, it would look like the following:

如果我把它放到一个模型中,会是这样的:

class MessageModel 
{
     public function archive($messageId) {
     // ... 
        $em = $this->getDoctrine()
                   ->getManager();
        $message = $em->getRepository('MessageBundle:Message')
                    ->find($messageId);
        $message->setIsArchived(true);
        $em->persist($entity);
        $em->flush();
     // ... return true on success, false on fail.
     }
}

Revised Controller

修改后的控制器

My Revised Controller will now look like this:

我修改后的控制器将如下:

 // Vendor\MessageBundle\DefaultController 
 public function archiveAction(Request $request) {
    // ... 
    $model = new MessageModel(); // or a factory.
    $result = $model->archive($request->get('id'));
    if($result) {
        $this->flashMessage('Message has been archived.');
    } else { 
        $this->flashMessage('Message could not be archived due to a system error.');
    }
    return array('result'=>$result);
    // ...
}

The other two requirements will also be implemented on the model.

另外两个需求也将在模型上实现。

My Approach in a nutshell

我的方法很简单。

In a nutshell, this is my current approach:

简而言之,这是我目前的做法:

  • Controller - less logic
  • 控制器——更少的逻辑
  • View - stays the same
  • 视图-保持不变
  • Model - container aware and houses all business logic, accesses Doctrine as data access.
  • 模型-容器感知和存储所有业务逻辑,访问原则作为数据访问。
  • ORM - Regarded as part of the Model Layer, but not regarded as THE model layer.
  • ORM -被视为模型层的一部分,而不是模型层。
  • Service Layer - when required, I can use the Service Layer to work with multiple layers, but I've found that I've only had to use it in a few occasions due to the simple nature of the applications I've had to build.
  • 服务层——当需要时,我可以使用服务层来处理多个层,但是我发现由于我必须构建的应用程序的简单特性,我只需要在少数情况下使用它。

My Question(s)

我的问题(s)

  • Is my approach in line with what others are doing?
  • 我的做法是否符合其他人的做法?
  • Am I missing something glaringly obvious?
  • 我是否遗漏了一些显而易见的东西?
  • Have you tried anything similar to this and found it good / bad?
  • 你试过类似的东西吗?

Thank you in advance.

提前谢谢你。

1 个解决方案

#1


3  

At first you can use a ParamConverter to simplify your controller. An exception is automatically thrown if the entity is not found.

首先,您可以使用参数转换器来简化您的控制器。如果没有找到实体,将自动抛出异常。

I would call the message methods archive and restore but that's a matter of preference.

我将调用消息方法存档和恢复,但这是一个偏好问题。

A nice way to integrate security checks is provided by JMSSecurityExtraBundle. Use ACLs to check wether your current user is allowed to archive a message.

JMSSecurityExtraBundle提供了一种集成安全检查的好方法。使用acl检查当前用户是否允许存档消息。

Your MessageModel is similar to the Manager (interface/abstract and i.e. doctrine ODM/ORM implementation) pattern you can find in the FOSUserBundle.

您的MessageModel类似于可以在FOSUserBundle中找到的Manager(接口/抽象,即doctrine ODM/ORM实现)模式。

These managers are building the bridge between storage layer and your controller and all share the same Interface (i.e. UserManagerInterface). This way you can exchange i.e. doctrine with propel easily.

这些管理器正在构建存储层和控制器之间的桥梁,它们共享相同的接口(例如UserManagerInterface)。这样你就可以很容易地交换教条和推动力。

Improve by turning your controller into a service and injecting your Manager service instead of injecting the whole container (i.e. by extending Symfony\Bundle\FrameworkBundle\Controller\Controller) and getting it from there.

通过将您的控制器转换为服务并注入您的Manager服务而不是注入整个容器(即通过扩展Symfony\ FrameworkBundle\ controller控制器)并从那里获得它来改进。

You should inject only the dependencies you need into your services to ease testing. In the example of doctrine orm/odm a manager service would retrieve the classname parameter, the entity-/documentmanager service, and the repository service. (service definition)

您应该只将所需的依赖项注入到服务中,以简化测试。在doctrine orm/odm的示例中,管理器服务将检索classname参数、实体-/documentmanager服务和存储库服务。(服务定义)

You can find additional inspiration for creating a controller-utilities service in Benjamin Eberlei's blog post "Extending Symfony2: Controller Utilities".

在Benjamin Eberlei的博客“扩展Symfony2:控制器实用程序”中,您可以找到创建控制器实用程序服务的额外灵感。

Another often used technique is to create an abstract parent service for your most commonly used controller dependencies. (example)

另一个常用的技术是为您最常用的控制器依赖项创建一个抽象的父服务。(例子)

The last quick tip i have is moving the creation of flashmessages to event listeners/subscribers. Just check wether the method returns an object of type Message or better MessageInterface and add the success-flashmessage. If the entity is not found you can catch the exception and add the flashmessage with the error message.

我的最后一个快速提示是将flashmessages的创建移动到事件监听器/订阅者。检查方法是否返回类型消息的对象或更好的MessageInterface,并添加成功的flashmessage。如果没有找到实体,可以捕获异常并添加带有错误消息的flashmessage。

You could even ommit the return at the end of the method when using a view response listener like FOSRestBundle's that automatically assumes you're returning the original argument if the method returns nothing - read about it here.

当使用像FOSRestBundle's这样的视图响应监听器时,您甚至可以省略方法末尾的返回值,该侦听器会自动假定,如果该方法不返回任何内容,您将返回原始参数——请阅读本文。

Finally you can end up with a method in a nicely testable controller service that looks like this:

最后,您可以在一个漂亮的、可测试的控制器服务中找到这样的方法:

/** 
 * @PreAuthorize("hasPermission(#message, 'ARCHIVE')")
 */
public function archiveAction(Message $message) 
{
    $message->archive();
    $this->messageManager->update($message, true);
}

The manager->update() method behaves like the one found in FOS\UserBundle\Doctrine\UserManager in my example.

在我的例子中,manager->update()方法的行为就像在FOS\UserBundle\Doctrine\UserManager中发现的一样。

#1


3  

At first you can use a ParamConverter to simplify your controller. An exception is automatically thrown if the entity is not found.

首先,您可以使用参数转换器来简化您的控制器。如果没有找到实体,将自动抛出异常。

I would call the message methods archive and restore but that's a matter of preference.

我将调用消息方法存档和恢复,但这是一个偏好问题。

A nice way to integrate security checks is provided by JMSSecurityExtraBundle. Use ACLs to check wether your current user is allowed to archive a message.

JMSSecurityExtraBundle提供了一种集成安全检查的好方法。使用acl检查当前用户是否允许存档消息。

Your MessageModel is similar to the Manager (interface/abstract and i.e. doctrine ODM/ORM implementation) pattern you can find in the FOSUserBundle.

您的MessageModel类似于可以在FOSUserBundle中找到的Manager(接口/抽象,即doctrine ODM/ORM实现)模式。

These managers are building the bridge between storage layer and your controller and all share the same Interface (i.e. UserManagerInterface). This way you can exchange i.e. doctrine with propel easily.

这些管理器正在构建存储层和控制器之间的桥梁,它们共享相同的接口(例如UserManagerInterface)。这样你就可以很容易地交换教条和推动力。

Improve by turning your controller into a service and injecting your Manager service instead of injecting the whole container (i.e. by extending Symfony\Bundle\FrameworkBundle\Controller\Controller) and getting it from there.

通过将您的控制器转换为服务并注入您的Manager服务而不是注入整个容器(即通过扩展Symfony\ FrameworkBundle\ controller控制器)并从那里获得它来改进。

You should inject only the dependencies you need into your services to ease testing. In the example of doctrine orm/odm a manager service would retrieve the classname parameter, the entity-/documentmanager service, and the repository service. (service definition)

您应该只将所需的依赖项注入到服务中,以简化测试。在doctrine orm/odm的示例中,管理器服务将检索classname参数、实体-/documentmanager服务和存储库服务。(服务定义)

You can find additional inspiration for creating a controller-utilities service in Benjamin Eberlei's blog post "Extending Symfony2: Controller Utilities".

在Benjamin Eberlei的博客“扩展Symfony2:控制器实用程序”中,您可以找到创建控制器实用程序服务的额外灵感。

Another often used technique is to create an abstract parent service for your most commonly used controller dependencies. (example)

另一个常用的技术是为您最常用的控制器依赖项创建一个抽象的父服务。(例子)

The last quick tip i have is moving the creation of flashmessages to event listeners/subscribers. Just check wether the method returns an object of type Message or better MessageInterface and add the success-flashmessage. If the entity is not found you can catch the exception and add the flashmessage with the error message.

我的最后一个快速提示是将flashmessages的创建移动到事件监听器/订阅者。检查方法是否返回类型消息的对象或更好的MessageInterface,并添加成功的flashmessage。如果没有找到实体,可以捕获异常并添加带有错误消息的flashmessage。

You could even ommit the return at the end of the method when using a view response listener like FOSRestBundle's that automatically assumes you're returning the original argument if the method returns nothing - read about it here.

当使用像FOSRestBundle's这样的视图响应监听器时,您甚至可以省略方法末尾的返回值,该侦听器会自动假定,如果该方法不返回任何内容,您将返回原始参数——请阅读本文。

Finally you can end up with a method in a nicely testable controller service that looks like this:

最后,您可以在一个漂亮的、可测试的控制器服务中找到这样的方法:

/** 
 * @PreAuthorize("hasPermission(#message, 'ARCHIVE')")
 */
public function archiveAction(Message $message) 
{
    $message->archive();
    $this->messageManager->update($message, true);
}

The manager->update() method behaves like the one found in FOS\UserBundle\Doctrine\UserManager in my example.

在我的例子中,manager->update()方法的行为就像在FOS\UserBundle\Doctrine\UserManager中发现的一样。