Symfony2

Symfony2 controllers and application logic, revisited

Got this email today from “a loyal French reader,” (thanks for the compliment!) and I thought I’d go over the answer in today’s post:

… I just read your articles about Symfony2 controllers. Thanks, it’s very interesting, but i still wonder something about the logic structure of my website.

For example, i’d like to develop a forum. A moderator can delete a post. When he deletes it: the user’s number of messages is decreased, like the topic one; the website sends a message to the post author to say him he’s not cool, etc. There is an action in my post controller to do that, right?

Next, the moderator can delete a topic; for each post, i would have to do the same thing than above.

How can i do? For example, where/when do i send the mail to the messages authors? Logically, i would like to call the deletePostAction in my deleteTopicAction, but I think it’s not really possible and good choice. …

So, to answer this, first we need to take a step back and examine some design choices. We have a couple different possible actions going on here:

  1. Delete a post, which:
    a) decrements the affected user’s post count, and
    b) sends him an email telling him why action was taken
  2. Delete a topic, which is essentially looping over every post in the topic and performing the actions of step 1, then removing the post itself.

It’s tempting to think about calling one controller action from another controller action — repeatedly calling deletePostAction from deleteTopicAction for every post in the topic, for example — but let me warn you: this way lies madness. Remember the purpose of a controller: to handle a request and return a response. Calling one controller action from another doesn’t really make sense because by the end of the request, you can only return one single response to the user, so all the responses from the other actions that you called are either lost (along with any important information that they might have included), or have to be hacked into your main response somehow… something that can and will get very ugly, very quickly.

However, calling an embedded controller from a view (which, by the way, creates a separate sub-request entirely) is possible and even encouraged. Since you’re dealing with fully rendered templates at that point, it’s much easier to include relevant information from the sub-request into your main request without trying to defy the laws of physics.

I guess a good way to think about the purpose of a controller action might be that it receives a request, triggers any application logic necessary to handle that request, then returns a response with the results of that application logic. The keyword is “triggers,” meaning that the action doesn’t have to handle the logic itself, it just has to delegate to some code that can. A service, for example.

Thinking in that context about today’s question, then yes: there is a controller action to trigger the deletion of the post, decrementing the user’s post count, and sending an email. There’s also a separate controller action to trigger the deletion of a topic, which will in turn trigger the deletion of each post in that topic (you don’t want your deletion code to be dependent on your controller, but it’s probably okay to have two obviously related pieces of functionality as a post and the topic that contains it depend on each other). But you should push the actual execution of those duties off onto one or more service classes.

Say you have a PostManager service that takes an EntityManager instance and a SwiftMailer instance as constructor arguments. It might have some methods that look something like this (this is just a quick example, there are obviously a few ways you could handle this, including using events as previously mentioned):

[code lang=php]
public function deletePost(Post $post, $reason)
{
$user = $post->getAuthor();
$user->incrementPostCount(-1);
$this->em->persist($user);
$this->sendDeletionEmail($reason);
$this->em->remove($post);
$this->em->flush();
}

protected function sendDeletionEmail($reason)
{
// handle the creation and sending of the email using the mailer instance
}
[/code]

Now your service knows how to handle all the logic behind deleting a post, no matter where the post came from. It is now decoupled from your deletePostAction, meaning that while the deletePostAction (or any other controller action, for that matter) can trigger the deletion of a post, the deletion of the post is not dependent on the controller action. You could just as easily execute the deletion code from a console command that prunes posts with low ratings, or from any other context that you can imagine.

Then to handle the deleteTopicAction, you might create a TopicManager service. I would pass the TopicManager the PostManager service as a constructor argument. It would similarly have a deleteTopic() method, but it would then be able to iterate through each post in the topic and pass each of them to the topic manager’s delete method:

[code lang=php]
public function deleteTopic(Topic $topic, $reason)
{
$user = $topic->getCreator();
$user->incrementTopicCount(-1);
$this->em->persist($user);
foreach ($topic->getPosts() as $post) {
$this->postManager->deletePost($post, $reason);
}
$this->sendTopicDeletionEmail($topic, $reason);
$this->em->remove($topic);
$this->em->flush();
}
[/code]

Obviously, there are a lot of ways to make this prettier. For one, you could make flushing after the deletion of a post optional (default to true), so that you can iterate through and delete each post without making a trip to the database each time, only flushing at the end of the deleteTopic() method. Also, you could consider extracting a collection of users from all the objects to be deleted, removing duplicates, and only sending the deletion email to each user once, instead of once per post they’ve made in the thread. But the point is that you’ve decoupled your application logic from your controller actions, and you’re simply using the controller actions to trigger that logic when a user requests it.

Further Reading on Symfony2

Symfony2

Demystifying Symfony2 Controllers

I came across this Stack Overflow question a couple days ago, asking about the proper place to put business logic in a Symfony2 application: whether it belongs in the controller, a service, or elsewhere. Actually, that’s quite a common question that I see: what, exactly, belongs in the controller? The documentation has this to say:

A controller is a PHP function you create that takes information from the HTTP request and constructs and returns an HTTP response (as a Symfony2 Response object). The response could be an HTML page, an XML document, a serialized JSON array, an image, a redirect, a 404 error or anything else you can dream up. The controller contains whatever arbitrary logic your application needs to render the content of a page.

Now, let me sum that up for you: the controller is meant to construct and return a response. That’s… it, really. It’s not meant for heavy lifting. Way too often, I see controller actions that are completely loaded down with code, heavy with business logic that would be better-suited (not to mention more reusable) in a service, or even a custom entity repository. And frequently, I’ll see controller actions that try to deal with more than one thing (like a view action also handling search results — with the search code written out in the action, of course). As a result, I’ve come up with a short checklist for writing your controllers:

  1. Controllers are meant to be thin. Think of a controller as the glue of your application, a lightweight connection between a model and a view… emphasis on “lightweight.” That means that as much code as possible should be offloaded to services, repositories, etc. I’ll cover those topics in a future post.
  2. Controllers should only handle one thing. Symfony2 has a great feature that lets you render embedded controllers, meaning that any one controller should only perform a specific action: viewing or editing a resource, for example, or retrieving search results.

I did say short, didn’t I? Learn to love and live by the principles of separation of concerns and and DRY: if you’re copy-pasting code to more than one place in a controller, it’s probably time to start thinking about how you can refactor to bring all that duplicated work into one easy-to-maintain location.

Another thing: the base controller available in the Symfony2 FrameworkBundle provides a great collection of helpful methods to get you started, but you can feel free to extend it or ignore it as you please. Because controllers can be any PHP callable, you’re not required to use the FrameworkBundle’s controller at all. In fact, because the FrameworkBundle is technically an optional dependency, it’s considered good practice to provide your own self-contained controller classes instead of using the base controller if you intend to make your code available to other users in the community.

And as always, the community abounds with examples of great code. The FriendsOfSymfony projects are a good place to start looking if you’re wondering about best practices: just dive into any repository and start looking at how they’ve built their controllers. I’m also happy to answer any questions you might have; feel free to leave a comment or shoot me an email to djstobbe@gmail.com. Happy coding!

Edit: the link to wikipedia’s entry on “Don’t repeat yourself” was broken. It’s fixed now.

Further Reading on Symfony2