Nowadays Symfony 2 ecosystem on the whole is many developers’ favourite framework. It offers flexibilities to developers in terms of structure which allow to develop complex web applications in not time. It has huge amount of tools to implement your ideas or, on the other hand, this wide range of components can lead you to “dark side” of Complexity and improper use of components.
But among bunches of prons and Symfony has a foundation stone of the framework philosophy – Symfony Best Practice. This great guide tries to protect you from unnecessary completeness and turn to “light” pragmatic way. It’s an excellent guidelines for beginning Symfony developers.
Now we try to find out other “unofficial” best practices that helps you build great applications with best existed things in Symfony.
* P.S. All best practices listed here are debatable since they are based on my experience and humble opinion.
#1 Prevent Autoloading of tests in production
As you may know, when going to production, it’s a best practice to run php composer.phar dump-autoload –optimize –no-dev
as one of your steps, when installing your dependencies. But, if we take a closer look, there are some entries in this array related to your test classes that make the array bigger than needed for production.
But also composer includes different options for removing those unnecessary entries:
1) exclude-files-from-classmaps: If you want to exclude some files or folders from the classmap you can use the exclude-from-classmap property. This might be useful to exclude test classes in your live environment, for example, as those will be skipped from the classmap even when building an optimized autoloader.
"autoload": { "psr-0": { "MyProject": "src/", "SymfonyStandard": "app/" }, "exclude-from-classmap": ["/Tests/", "/test/", "/tests/"] },
2) autoload-dev: This section allows to define autoload rules for development purposes. Classes needed to run the test suite should not be included in the main autoload rules to avoid polluting the autoloader in production and when other people use your package as a dependency.
"autoload": { "psr-4": { "MyProject": "src/" } }, "autoload-dev": { "psr-4": { "MyProject\\Tests\\": "tests/" } },
#2 Try to define your services as Private
When you dump your service container (using app/console container:debug ), and you see many of your services that should not or do not need to be used stand-alone, you should mark them as private. By default, all services you define are publicly available.
Marking services as private (in fact, you should set the “public” attribute of the service to “false”) will clean things up a lot. It will also make it clear to other developers what the main entry points of your bundle are.
Before making all your services private (except one or so…), remember that some lazy-load mechanisms require a service to be public. For instance, all form types registered using the form.type
tag must be public, since they are retrieved from the service container only when needed, by using the get() method of the container.
services: foo: class: MyProject\Services\PrivatesService public: false
#3 Accessing request parameters
Everyone knows the next expressions:
$param1 = $request->query->get('param1'); $param2 = $request->request->get('param2);
But there are other useful methods:
$usersAmount = $request->query->getInt('users'); $isAccepted = $request->request->getBoolean('accepted');
//aefqwe12r3aef%20 -> '123' $numbersCode = $request->query->getDigits('aefq123aef);
Use all power of accessing parameters methods.
#4 Update twig filters without breaking all around yourself.
Imagine situation that your application evolves and some time later you face the issue when you need to update some twig filter. You need to eliminate the use of old_filter within your large development team.
Fortunately, deprecated filter’s property can help you with it.
class MyProjectExtension extends \Twig_Extension { public function getFilters() { return array( new \Twig_SimpleFilter('old_filter', [], array( 'deprecated' => true, 'alternative' => 'new_filter', )), new \Twig_SimpleFilter('new_filter', []), ); } }
#5 Use bcrypt
Using bcrypt is the currently accepted best practice for hashing passwords, but a large number of developers still use older and weaker algorithms like MD5 and SHA1. Some developers don’t even use a salt while hashing. Don’t do that. Use bcrypt:
security: encoders: AppBundle\Entity\User: bcrypt
#6 Be known about abstract relationships
Imagine that we need to create a generic CategoryBundle to be able to work with other parts of application and interact with other entities. The main goal of this bundle to add a hierarchy of categories to different entities and provides come in handy services and methods to work with them.
But how to do that if we have a situation when target entities can change?
As well as we’d like to avoid using special difficult bundle’s configuration or changing code. And, certainly, we can resolve this issue with out of box Symfony tools.
Firstly, we define an abstract “subject” interface:
namespace Acme\CategoryBundle\Entity; interface CategorySubjectInterface { // add here your custom methods }
After that, we make some application’s entities implement this interface
namespace Acme\AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Acme\CategoryBundle\Entity\CategorySubjectInterface; /** * @ORM\Entity */ class Company implements CategorySubjectInterface { // ... }
or it can be another app with another entity
namespace Acme\AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Acme\CategoryBundle\Entity\CategorySubjectInterface; /** * @ORM\Entity */ class Offer implements CategorySubjectInterface { // ... }
The next step to configure the entity association:
namespace Acme\AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Acme\CategoryBundle\Entity\CategorySubjectInterface; /** * @ORM\Entity */ class Category { /** * @ORM\ManyToMany( * targetEntity="Acme\CategoryBundle\Entity\CategorySubjectInterface") */ protected $subject; }
Where subject field is an abstract target entity.
And the last step is to define the target entity (at each application)
# app/config/config.yml doctrine: #... orm: resolve_target_entities: Acme\CategoryBundle\Entity\CategorySubjectInterface Acme\AppBundle\Entity\Company
Specifying this moment we get dynamic entity resolution at runtime. Profit!
#7 Login Success/Failure Handler
Imagine that now we face another task – we want to alter default Symfony authentication process just after authentication was successful. For instance, we want to redirect authenticated user with ROLE_SPECIAL to another site’s location than others.
Symfony Success/Failure handlers will come in handy for that! They are available since Symfony 2.0 but lots of developers don’t use them because they are not documented.
Let’s implement it!
namespace AppBundle\Service; use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface { use ContainerAwareTrait; public function onAuthenticationSuccess(Request $request, TokenInterface $token) { $user = $token->getUser(); $router = $this->container->get('router'); $url = $router->generate('user_dashboard'); if(in_array('ROLE_SPECIAL', $user->getRoles())) { $url = $router->generate('special_user_dashboard'); } return new RedirectResponse( $url ); } }
First of all, our AuthenticationSuccessHandler extends AuthenticationSuccessHandlerInterface and implements it’s single onAuthenticationSuccess() method. Also we use ContainerAwareTrait to get router inside onAuthenticationSuccess() method from container. Of course, you can simply inject @router service. After that if user has ROLE_SPECIAL we render to him another view.
After AuthenticationSuccessHandler was created, we have to enable it:
#app/config/services.yml app.service.authentication_success_handler: class: AppBundle\Service\AuthenticationSuccessHandler calls: - [setContainer, ['@service_container']]
#app/config/security.yml firewalls: main: # ... form_login: login_path: / check_path: / success_handler: app.service.authentication_success_handler
Now AuthenticationSuccessHandler defined in your application.
You can read more here:
http://www.slideshare.net/javier.eguiluz/new-symfony-tips-tricks-symfonycon-paris-2015
#8 Simplify structure – create ParamConverter for mapped entities
If you’re using Doctrine, one of the best practice to use the ParamConverter.
(you can find description here: https://symfony.com/pdf/Symfony_best_practices_2.7.pdf)
ParamConverter allows “automatically query for Doctrine entities when it’s simple and convenient”. And, for instance, you can simply convert request parameters to objects which will be injected as controller method arguments:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; /** * @Route("/blog/{id}") * @ParamConverter("post", class="SensioBlogBundle:Post") */ public function showAction(Post $post) { }
It protects you from calling entities repositories in your action and minifies action code size.
But what do we have to do if we have mapped entities? For instance, we want to receive Article object getting in request Post object name. Association between these entities is One-To-Many.
“We can’t do that with ParamConverter as it does not work with mapped entities.So we need to create custom method in ArticleRepository and use JOIN for getting concrete Acticle” said a lots of us.
Yes, out of box ParamConverter is not able to resolve this relation. But except this way there is another one – we can create custom ParamConverter with this features.
The thing is, Symfony has ParamConverterInterface, extending which you can specify own parameters converter. Your ParamConverter should implement a supports()
method. This method receives a configuration object which it can use to determine if the your ParamConverter can provide the right value for a certain argument. The ParamConverter
should also implement apply()
, which receives the current Request
object and again the configuration object. Using these two (and probably some service you injected using constructor or setter arguments) the apply()
method should try to supply the action with the right argument.
More about that you can get from here:
https://stfalcon.com/ru/blog/post/symfony2-custom-paramconverter
That’s all. Be passionate about what you do and learn the best Symfony2 practices with us!
Comments 2
Hi,
There is an issue on this page; if th browser is not on full screen, the SF image above takes tens of screen height and the text goes out of screen with no scrolling to read it (I guess it spreads along the image width). So it is quite unpleasant to read both on full screen and normal size especially when it comes to copy the code to another window and read explanations at the same time.
Thanks.
Thanks, Florent. Our team will improve article view