How do I use OOP principles with symfony ?


The separation of concern principle

As you know symfony2 is a MVC framework and MVC mean Model View Controller. Therefore the view has nothing to do with models, the model has nothing to do with view, etc..... Each layer has its own concern.

When you send doctrine objects to the view, as many developers are doing, you are breaking this principle, the view layer doesn't have to know about any doctrine object. Instead you should have models that are specific to your views (ie view model or data tranfert object).  That's the same for forms, don't couple them to doctrine object. When you have complex forms that involve multiple doctrines objects you always end up having multiple embedded forms and/or some repository function inside (to fill choices field for example). Let 's see examples of bad practise below

<?php
$builder->add('users', 'entity', array(
    'class' => 'AcmeHelloBundle:User',
    'property' => 'username',
));

?>
// or

<?php
$builder->add('bestfriend', 'entity', array(
    'class' => 'AcmeHelloBundle:User',
    'query_builder' => function(EntityRepository $er) {
        return $er->createQueryBuilder('u')
            ->orderBy('u.username', 'ASC');
    },
));
?>

Model layer should not be coupled to form type. If you do, your code become complex and less maintainable. Forms types in symfony2 are parts of the view layer so do not couple them with doctrine objects, create view models for each form type, it's cleaner.

 

The single responsability principle

The difficult thing, in order to respect this principle is to know what a responsability is. Generally people define this principle by saying that "Every class should have one single raison to change", but it's is not very clear in my opinion. Taking the example of CRUD operation; is it one responsability or do we need 4 differents classes : one for "Create", one for "Read", one for "Update" and one for "Delete", I don't know.

The class name is a very good starting point in order to know if you will have multiple responsabilities or only one responsability. The naming of a class shouldn't be generic.
Bad naming examples : Product(Manager).php, Export(Manager).php, UserCRUD(Manager).php,... 
A class name must be precise, not generic. When reading the name of a class whe should have an idea about what the class is doing.
Better naming examples : ProductExport.php, UserSubscription.php, ...

 

Other points

  • Avoid embedded functions calls

Sometimes, I see code where you have a function f1 who calls another function f2 and f2 call f3 and f3 call f4, ..... These kind of code are just unreadable, it's like entering an endless tunnel. Imagine if you want to unit test f1 .... The good way to go is to have only one high level function f who calls f1 and get the result of f1, let us say r1 and call f2 with r1 as parameter if needed, then f call f3 with r2, etc, ....

  • Think about how to unit test a function while writing it. (or better, use TDD)

When you follow this rule : thinking about how to unit test function while writing it, you generally, always write good functions. Try it, you will be amazed.

 

Conclusion

To sump up these are my advices :

  1. Decouple forms and doctrine entities
  2. Don't send orm's object to the view, use data transfer object instead 
  3. Keep thin everything that doesn't belong to the domain layer : thin controller, thin voter, thin commands, thin listener / subscriber, ... 
  4. Think about testability when writing codes
  5. Take time to think about how to name things, you will get tremendous results.
  6. It's important to follow good practises but stay pragmatic depending on your project's context

 

 



Amady, 20/08/2015