Feedback on my Symfony 2 Model Layer + Doctrine Modeling Approach

Please provide feedback on the following approach to creating a model layer, which consists of business rules that use Doctrine to access data.

My current approach is based on the notion that a model is a ContainerAware class / object that contains all the non-library, domain business logic.

I find that I have to hammer in Frameworks to do something this way, so part of my brain asks me a question.

I am currently using Symfony 2, which, like all modern PHP MVC frameworks, uses an ORM layer, such as Doctrine 2, and inevitably treats it as a model layer. I assume that the situation will be similar to ZF2, so although my example is written in SF2, consider this as an agnostic framework question.

Specific example

As a specific example, consider the following scenario:

Message Requirement

  • As a user, I can create a message that belongs to me.
  • As a user, I can update the message that belongs to me.
  • As a user, I can archive a message that belongs to me.

Controller

In Symfony2, these requirements are encoded as Controller Level Actions. I will skip the extraneous code that checks if the message really belongs to the user, but obviously this should also be part of the domain logic. In the method "belongs to TOUser" or the like.

// 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.'); // ... } 

Model

If I were placed in a model, it would look like this:

 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 modified 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 remaining two requirements will also be implemented on the model.

My approach in a nutshell

In short, this is my current approach:

  • Controller - less logic
  • View - remains the same
  • Model - is aware of the container and contains all the business logic, refers to Doctrine as access to data.
  • ORM . It is considered as part of the model layer, but is not considered as a model layer.
  • Service level - if necessary, I can use the service level to work with several layers, but I found that I had to use it in several cases due to the simple nature of the applications that I had to create.

My question (s)

  • Is my approach what others do?
  • Am I missing something obvious?
  • Have you tried something like this and considered it good / bad?

Thanks in advance.

+6
source share
1 answer

First you can use ParamConverter to simplify your controller. An exception is automatically thrown if an object is not found.

I would call the message methods archive and restore , but this is a matter of preference.

A good way to integrate security checks is provided by the JMSSecurityExtraBundle . Use the ACL to see if your current user can archive the message.

Your MessageModel is similar to the Manager interface ( interface / abstract , which means the doctrine of ODM / ORM implementation ), which you can find in FOSUserBundle.

These managers build a bridge between the storage layer and your controller, and they all have the same interface (i.e., UserManagerInterface ). Thus, you can easily exchange, for example, doctrine.

Improve by including your controller in the service and introduce your Manager service instead of inserting the entire container (i.e. expanding Symfony\Bundle\FrameworkBundle\Controller\Controller ) and getting it from there.

You should only introduce the dependencies you need into your services in order to facilitate testing. In the orm / odm doctrine example, the manager service will receive a class parameter, an entity- / documentmanager service, and a repository service. ( service definition )

You can find additional inspiration for creating a controller service on the Benjamin Eberlei blog, " Symfony2 Extension: Controller Utilities ."

Another commonly used method is to create an abstract parent service for the most commonly used controller dependencies. ( example )

The last quick tip I have is to move the creation of flashmessages to event listeners / subscribers . Just check if the method returns an object of type Message or better MessageInterface and adds a success error message. If the object is not found, you can catch an exception and add a flashmessage message with an error message.

You can even omit return at the end of the method when using a response listener of the form, for example, FOSRestBundle , which automatically assumes that you are returning the original argument if the method returns nothing - read about it here .

Finally, you can end the method in a well-verifiable controller service, which 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.

+3
source

Source: https://habr.com/ru/post/957627/


All Articles