Symfony2

Using Doctrine lifecycle callbacks with Symfony2

After my previous post on refactoring controllers, Khepin made a great point about using lifecycle callbacks on your entities to further reduce your code footprint. I’ll reproduce it here for those who don’t care to look it up:

A different take on this would be to use lifecycle callbacks on models. You can tell a model to call a special action when its being created / updated / deleted etc … so whenever a post is deleted it could call a function that would do: $this->author->decreasePostCount() automatically.

This way nothing even appears in the controllers related to this. All you do in the controller is delete one post or all the post in a topic.

Same way, using cascade delete on the topic would mean you’d only have to call deleteTopic and the deletion of related posts would happen behind the scenes.

Lifecycle callbacks, if you’re not aware, are methods on your entities that you register with Doctrine, and are called at specific points in the entity’s existence during a request (its “lifecycle”). A lifecycle callback differs from a lifecycle event in that callbacks do not receive an EventArgs argument: when a lifecycle callback is triggered, it only knows about the object on which it was called.

Let’s clarify with an example. I use annotations in all my entities; check the docs for usage specific to your preferred mapping method.

[code language=”php”]
/**
* @ORMEntity
* @ORMHasLifecycleCallbacks
*/
class Post
{
/**
* @ORMId @ORMColumn(type="integer")
* @ORMGeneratedValue
*/
protected $id;

/**
* @ORMColumn(type="datetime")
*/
protected $createdAt;

/**
* @ORMManyToOne(targetEntity="User", cascade={"persist"})
*/
protected $author;

// … other properties, getters and setters

/**
* @ORMPrePersist
*/
public function onPrePersist()
{
$this->createdAt = new DateTime();
}
}
[/code]

This is a trivial example, of course. Before the entity is persisted for the first time, Doctrine will call any methods marked with the PrePersist callback (note that the method name is arbitrary: it needs only to be public and registered as a callback). In this case, the Post#onPrePersist() method updates the entity with a new creation timestamp just before it is saved to the database. Let’s add a slightly less trivial example:

[code language=”php”]
/**
* @ORMPreRemove
*/
public function onPreRemove()
{
$this->author->modifyPostCount(-1);
}
[/code]

Right before the entity is removed from the database, the author has their post count decremented by one. You will need to configure your entities to cascade persistence, or else manually persist the User entity representing the post author, but this is an excellent way to move code from your controllers onto your objects.

With relation to the example brought up in my previous post, when a topic is deleted, all the posts under it must be deleted as well. If you set up your entities to cascade delete from topic to its child posts, any lifecycle callbacks registered on those entities will be triggered as well. That would reduce the code footprint of TopicManager#deleteTopic() from this…

[code language=”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]

… to this:

[code language=”php”]
public function deleteTopic(Topic $topic, $reason)
{
$this->sendTopicDeletionEmail($topic, $reason);
$this->em->remove($topic);
$this->em->flush();
}
[/code]

The Topic#onPreRemove() method would be triggered, decrementing the topic owner’s count, and since the delete relationship is cascaded to child posts, their onPreRemove() methods would be fired as well. As you can see, this results in much cleaner code.

Be aware that lifecycle callbacks are not the holy grail of entity management, applicable in every situation: as I previously mentioned, the callbacks are not made aware of application state, which limits their scope to dealing with the entity and its relationships directly (you’ll notice that the topic deletion email is still sent from the manager method). Nor should you configure your entities to cascade on relationships without considering the design and performance implications of doing so. But, well-planned use can make your life a lot easier.

Further Reading on Symfony2

Symfony2, Tutorials

Using composer with Symfony2

So it’s been on the radar for a while, but Symfony2 recently switched over to composer for package management, moving away from the homebrewed bin/vendors install method. Playing around with it, a few things were non-obvious to me… and judging by the number of search engine hits on “symfony2 composer tutorial,” it’s apparently non-obvious to a few other people as well. So, I thought I’d just share some observations. Hope they’re helpful.

composer packages are not the same as git repos

The old dependency management system just cloned git repos, and locked them to a specific commit, tag or branch if you asked it to. It was simple, but it lacked functionality like dependencies of dependencies, version requirements, and so on. True, composer is a little more complicated, but it has all these features.

For example, if I tell composer to use the FOSRestBundle as a dependency in my project, but I don’t have the JMSSerializerBundle also listed, it will see that the RestBundle depends on the SerializerBundle, and download that as well. That lets you keep your composer manifest neat and tidy, without worrying about missing sub-dependencies.

You can search through available repos on packagist.org. Most of the well-maintained projects are available there (and I just condemned myself, because I don’t think my ACL manager bundle has a composer file yet. Oops).

The install command may not work like you’d expect

At least, it didn’t for me. I cloned the Symfony2 Standard Edition project, added the FOSUserBundle as a dependency, and ran php composer.phar install. It crunched a minute, said it was downloading some stuff… and I still didn’t end up with the user bundle in my vendor directory. lolwut.

As it turns out, the steps that the install command follows look something like this:

  1. Check for an existing composer.lock file. If one is not found, generate it from the contents of the composer.json manifest
  2. Ensure that the dependencies in the vendor directory match the dependencies required in the lock file, by downloading, updating or deleting vendor bundles as necessary

Coming into a project that has an existing lock file (which the Standard Edition does), running the install command will only ever operate against that lock file, no matter how much the manifest file is changed. The solution turned out to actually be quite simple: modify the manifest file, then run php composer.phar upgrade… the upgrade command will generate a brand spanking new lock file from the latest version of the manifest and update vendor dependencies against it. Simple, but non-obvious to me at first.

autoload.php just got a whole lot easier to manage

Easy as in, you don’t have to touch it at all most of the time. Symfony2 now uses composer’s autoloader, which quite magically knows how to load all of the dependencies that it has downloaded. Once you get all your dependencies in place (php composer.phar upgrade if you’re having trouble, remember), everything should Just Work, and you should only need to modify autoload.php if you have a library that needs special treatment.

script hooks are awesome

True story: you can provide composer with static methods on classes to handle certain events that happen during the composer lifecycle. For example, the Symfony2 Standard Edition calls methods in the SensioDistributionBundle on post-install and post-update to build the bootstrap cache file, clear the cache and install assets. Check ’em out.

This move is a good thing

Yeah, it’s different, and different is scary. But it’s good. Trust me. Remember what I said about focusing your efforts on writing application logic? Yeah, that applies to framework developers, too. Using composer lets the Symfony2 team focus their attention on the framework and its components, and still have the benefit of a feature-rich package manager. Win, all around.

composer isn’t just for Symfony2

Composer was inspired by Ruby’s bundler and node’s npm… it’s not just for Symfony2, you can use it for any PHP project. As long as your naming conventions are PSR-0 compliant, the composer autoloader will hook you tha eff up, homeboy. So have fun.

That about wraps it up, hope it helped. Any questions, post in the comments or shoot me an email.

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

Putting your Symfony2 controllers on a diet, part 2

Okay, so part one technically had a different name, so I can’t really claim that this is part two. Sue me. I was originally going to talk about using param converters to save even more space, but I realized that the process is too trivial for its own post, so here’s the secret: name your routing placeholder after a field on your entity, and typehint the controller to receive an object instanceof your entity. Like so:

[code language=”php”]
// FooController.php
use FooVendorBarBundleEntityFooEntity;

public function viewAction(FooEntity $foo)
{
// $foo is an instance of FooEntity, retrieved from the database by slug
}
[/code]

Voila. The default Doctrine param converter just looked up your FooEntity, found the one with a slug that matched whatever was requested, and injected it into your controller. Magic. You can also get similar free functionality by typehinting the Request object. Very handy; we’ll be doing that with our controller from now on. Keep reading after the break for more “real diet tips” for your Symfony2 controllers (I just tripped a bunch of spam filters by typing that).

Anyway, to bring you up to speed, this is the state of our hypothetical controller action that we’re trying to slim down:

[code language=”php”]
// FooVendor/BarBundle/Controller/ExampleController.php
use FooVendorBarBundleEventCommentEvent;

public function addCommentAction($post_id)
{
$em = $this->container->get(‘doctrine’)->getEntityManager();
$request = $this->container->get(‘request’);

$post = $em->getRepository(‘FooVendorBundle:Post’)->find($post_id);
$comment = new Comment();
$comment->setPost($post);
$form = $this->createForm(new CommentType(), $comment);

if (‘POST’ === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($comment);
$em->flush();

$dispatcher = $this->container->get(‘event_dispatcher’);
$dispatcher->dispatch(‘foo_bundle.post.comment_added’, new CommentEvent($post, $comment));

return $this->redirect(/* some redirect url */);
}
}

return $this->render(‘FooVendorBundle:Post:addComment.html.twig’, array(
‘form’ => $form->createView(),
));
}
[/code]

Tonight I wanted to talk about creating a service to function as a model manager. 85% of the controller actions that I write on a daily basis deal almost exclusively with manipulating a Doctrine entity in some way (or a Propel object, if you swing that way). And sometimes I need to deal with similar actions (updating an existing entity with some information and persisting it to the database, for example) across multiple controllers. If I decide to make a change, I have to propagate it (as essentially a copy-paste job) across n actions, and hope I don’t miss any. The solution to that problem is to create a service class dedicated to handling all that for you: a model manager. Let’s create a very simple one for our example controller (I’ll comment in the code as I go):

[code language=”php”]
// FooVendor/BarBundle/Entity/CommentManager.php
namespace FooVendorBarBundleEntity;

use DoctrineORMEntityManager;
use DoctrineORMEntityRepository;

class CommentManager
{
/**
* Holds the Doctrine entity manager for database interaction
* @var EntityManager
*/
protected $em;

/**
* Entity-specific repo, useful for finding entities, for example
* @var EntityRepository
*/
protected $repo;

/**
* The Fully-Qualified Class Name for our entity
* @var string
*/
protected $class;

public function __construct(EntityManager $em, $class)
{
// Even though we have three properties, we only need two constructor arguments…
$this->em = $em;
$this->class = $class;
$this->repo = $em->getRepository($class);
// … because we can find the repo using those two
}
}
[/code]

That’s the skeleton. It doesn’t do anything yet, but having access to the entity manager and the entity’s repository class give us some powerful options. Let’s make our first addition; we’ll add a createComment() method:

[code language=”php”]
// FooVendor/BarBundle/Entity/CommentManager.php

/**
* @return Comment
*/
public function createComment()
{
$class = $this->class;
$comment = new $class();

return $comment;
}
[/code]

Again, simple. Let’s implement it (and at the same time refactor to use typehinting for the Request object):

[code language=”php”]
// FooVendor/BarBundle/Controller/ExampleController.php
use FooVendorBarBundleEventCommentEvent;
use SymfonyComponentHttpFoundationRequest;

public function addCommentAction(Request $request, $post_id)
{
$em = $this->container->get(‘doctrine’)->getEntityManager();

$post = $em->getRepository(‘FooVendorBundle:Post’)->find($post_id);
$comment = $this->getCommentManager()->createComment();
$comment->setPost($post);
$form = $this->createForm(new CommentType(), $comment);

if (‘POST’ === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($comment);
$em->flush();

$dispatcher = $this->container->get(‘event_dispatcher’);
$dispatcher->dispatch(‘foo_bundle.post.comment_added’, new CommentEvent($post, $comment));

return $this->redirect(/* some redirect url */);
}
}

return $this->render(‘FooVendorBundle:Post:addComment.html.twig’, array(
‘form’ => $form->createView(),
));
}

/**
* @return CommentManager
*/
protected function getCommentManager()
{
return $this->container->get(‘foo_vendor.manager.comment’);
}
[/code]

Okay, wait. We actually just increased the amount of code we had in our controller class, by adding the getCommentManager() method. The reason I did that is to provide typehinting if you use any kind of IDE with code completion. It’s very handy, and is one of the shortcomings that I see in the dependency injection container that Symfony2 uses: retrieving a service from the container does not provide any sort of code completion. So, we add a convenience method. That’s okay. Now, let’s do something useful with our manager, instead of writing methods that just let us replace one line of code with another line of code. The biggest chunk of logic is inside the if($form->isValid()) control statement, so let’s target that. We could just make a saveComment() method that replaces the $em->persist($comment); $em->flush(); calls, but we can do one better than that. First, let’s refactor our CommentManager to take the event dispatcher as a constructor argument:

[code language=”php”]
// FooVendor/BarBundle/Entity/CommentManager.php
namespace FooVendorBarBundleEntity;

use SymfonyComponentEventDispatcherEventDispatcherInterface;
use DoctrineORMEntityManager;
use DoctrineORMEntityRepository;
use FooVendorBarBundleEventCommentEvent;

class CommentManager
{
/**
* Holds the Symfony2 event dispatcher service
*/
protected $dispatcher;

/**
* Holds the Doctrine entity manager for database interaction
* @var EntityManager
*/
protected $em;

/**
* Entity-specific repo, useful for finding entities, for example
* @var EntityRepository
*/
protected $repo;

/**
* The Fully-Qualified Class Name for our entity
* @var string
*/
protected $class;

public function __construct(EventDispatcherInterface $dispatcher, EntityManager $em, $class)
{
$this->dispatcher = $dispatcher;
$this->em = $em;
$this->class = $class;
$this->repo = $em->getRepository($class);
}

/**
* @return Comment
*/
public function createComment()
{
$class = $this->class;
$comment = new $class();

return $comment;
}
}
[/code]

Now we can create our `saveComment()` method…

[code language=”php”]
public function saveComment(Post $post, Comment $comment)
{
$comment->setPost($post);
$this->em->persist($comment);
$this->em->flush();
$this->dispatcher->dispatch(‘foo_bundle.post.comment_added’, new CommentEvent($post, $comment));
}
[/code]

… and refactor our controller:

[code language=”php”]
// FooVendor/BarBundle/Controller/ExampleController.php
use SymfonyComponentHttpFoundationRequest;

public function addCommentAction(Request $request, $post_id)
{
$em = $this->container->get(‘doctrine’)->getEntityManager();

$post = $em->getRepository(‘FooVendorBundle:Post’)->find($post_id);
$comment = $this->getCommentManager()->createComment();
$form = $this->createForm(new CommentType(), $comment);

if (‘POST’ === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$this->getCommentManager()->saveComment($post, $comment);

return $this->redirect(/* some redirect url */);
}
}

return $this->render(‘FooVendorBundle:Post:addComment.html.twig’, array(
‘form’ => $form->createView(),
));
}

/**
* @return CommentManager
*/
protected function getCommentManager()
{
return $this->container->get(‘foo_vendor.manager.comment’);
}
[/code]

Not bad, eh? Now no matter where you’re saving your comments, you can be assured that the exact same things are happening every time. Incidentally, now would be a great time to also create a PostManager service, give it a find() method. Something simple will do:

[code language=”php”]
/**
* @return Post
*/
public function find($id)
{
return $this->repo->find($id);
}
[/code]

With that, you can refine the controller even further:

[code language=”php”]
// FooVendor/BarBundle/Controller/ExampleController.php
use SymfonyComponentHttpFoundationRequest;

public function addCommentAction(Request $request, $post_id)
{
$post = $this->getPostManager()->find($post_id);
$comment = $this->getCommentManager()->createComment();
$form = $this->createForm(new CommentType(), $comment);

if (‘POST’ === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$this->getCommentManager()->saveComment($post, $comment);

return $this->redirect(/* some redirect url */);
}
}

return $this->render(‘FooVendorBundle:Post:addComment.html.twig’, array(
‘form’ => $form->createView(),
));
}
[/code]

You’ve just obviated the need for using the Doctrine EntityManager directly in the controller. That means that if somewhere down the line you switch to Propel, or MongoDB (using the Doctrine ODM), or something else entirely, you don’t have to edit umpteen million controller actions: just a couple manager services. Congrats! We’ve now trimmed our example controller action down by quite a bit, moving the business logic out of the thin controller and into services where it belongs. Doesn’t that make you happy?

Further Reading on Symfony2

Symfony2

Symfony2 bundles I can’t live without

I really apologize for the micropost today; I’m feeling under the weather and trying to get to bed at a decent hour. But, less talk, more lists! Here are a few Symfony2 bundles that make it into nearly ever app I create. In no particular order:

  • [ProblematicAclManagerBundle](https://github.com/Problematic/ProblematicAclManagerBundle) – Okay, I lied, there is an order: I put mine first. But after this, no favoritism. Scout’s honor. The Symfony2 ACL system is kind of bulky and awkward; I wrote this bundle (and a few great people helped out with it) to make it easier to manage user permissions on objects.
  • [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle) – Remember what I said about [Not Invented Here](http://iamproblematic.com/getting-over-the-not-invented-here-mentality/)? This is a classic example of “somebody else already did it better.” I still haven’t found a use case in which I didn’t want to use this bundle for user management.
  • [FOSRestBundle](https://github.com/FriendsOfSymfony/FOSRestBundle) – Two words: free REST API.
  • [JMSSerializerBundle](https://github.com/schmittjoh/JMSSerializerBundle) – A dependency of the FOSRestBundle; I love this serializer because I can feed it an enormous collection of entities and get back usable JSON (oh, and if anyone uses the Symfony2 serializer for anything other than JSON, I’d love to hear about it). Also: you can configure your entities to only serialize certain sets of information based on requested version. Yes, you can _version your APIs automatically._ Sweet.
  • [StofDoctrineExtensionsBundle](https://github.com/stof/StofDoctrineExtensionsBundle) – Original credit goes to the [GedmoDoctrineExtensions](https://github.com/l3pp4rd/DoctrineExtensions), Stof just made them easily available in Symfony. Automatic creation of slug fields, entity translation, change history, etc. Use it!
  • [DoctrineFixturesBundle](https://github.com/doctrine/DoctrineFixturesBundle) – If you’re not using fixtures for test data, shame on you. Or maybe not, but it makes life lots easier.
  • [SncRedisBundle](https://github.com/snc/sncRedisBundle) – This one is maybe not as universally useful as the others, but I do love me some [Redis](http://redis.io/), and this bundle leverages the [predis](https://github.com/nrk/predis) library, which I’ve never had any problems with.

So, there you have it. Some bundles that I find extremely useful. I’ll keep this list as up to date as possible, so if you have some bundles in your app stack that you just can’t live without, let me know in the comments!

Symfony2, Tutorials

Leveraging the Symfony2 event dispatcher

Okay, so maybe you were paying attention when I told you to keep your Symfony2 controllers thin and you want to do a little housekeeping, but you’re not quite sure where to put all this extra code that you have lying around. For the sake of an example, let’s say that you had a messy controller that looked something like this (I apologize, it’s a little long, but I want to walk you through how you might clean it up over the course of a few posts):

[code language=”php”]
// FooVendor/BarBundle/Controller/ExampleController.php
public function addCommentAction($post_id)
{
$em = $this->container->get(‘doctrine’)->getEntityManager();
$request = $this->container->get(‘request’);

$post = $em->getRepository(‘FooVendorBundle:Post’)->find($post_id);
$comment = new Comment();
$comment->setPost($post);
$form = $this->createForm(new CommentType(), $comment);

if (‘POST’ === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($comment);
$em->flush();

foreach ($post->getSubscribers() as $subscriber) {
$message = Swift_Message::newInstance()
->setSubject(‘New comment posted on ‘ . $post->getTitle())
->setFrom(‘send@example.com’)
->setTo($subscriber->getEmail())
->setBody("Hey, somebody left a new comment on a post you’re subscribed to! It says: " . $comment->getBody())
;
$this->get(‘mailer’)->send($message);
}

return $this->redirect(/* some redirect url */);
}
}

return $this->render(‘FooVendorBundle:Post:addComment.html.twig’, array(
‘form’ => $form->createView(),
));
}
[/code]

Basically all it’s doing is creating a new Comment entity on a blog post from user (form) input, saving it to the database, and then emailing an arbitrary list of subscribers about the new comment. It’s little bit of a contrived example, but it’ll do. There are a few things that can be done to clean up this controller, but I want to deal with the most glaring issue first: that nasty foreach loop that’s firing off emails. We’ll fix it after the cut.

Continue reading

Screencasts, Symfony2, Tutorials

Developing with Symfony2, part 1: Setting up shop

I’m pleased to announce that, after some technical difficulties, I have uploaded a Symfony2 screencast to YouTube. It’s part one of a series in which I’ll be walking through the development, from the ground up, of a working Symfony2 application… look for more installments in the coming weeks.

This screencast walks through a few of the basics:

  • downloading Symfony2 Standard Edition using git
  • using composer to install the framework dependencies
  • best practices for preserving privacy of sensitive information
  • making the logs/ and cache/ directories writable (note: if you can’t get this working, a good ol’ chmod -R 777 app/cache/ app/logs/ ought to get you started)
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