Symfony: Deprecated @Route and @Method Annotations

Doing a composer update on my pet project got me a bunch of deprecation notices:

The “Sensio\Bundle\FrameworkExtraBundle\Configuration\Route” annotation is deprecated since version 5.2. Use “Symfony\Component\Routing\Annotation\Route” instead.

OK, that sounds familiar..
I’ve always had a dilemma when auto-importing @Route in PHPStorm,which of the two should I choose.
Guess I went with the wrong one 🙁
Correcting was easy, yet boring:

The other depreecation looked similar:

The “Sensio\Bundle\FrameworkExtraBundle\Configuration\Method” annotation is deprecated since version 5.2. Use “Symfony\Component\Routing\Annotation\Route” instead.

OK, what? @Method is deprecated, use @Route ? Go home, Symfony, you are drunk 😀
Well.. turns out the message is sane. The new way of specifying the HTTP method is in the @Route annotation, under methods key.
So, I’ve corrected:

To this:

After updating all the annotations, I was left was a cryptic deprecation notice:

Enabling the “sensio_framework_extra.router.annotations” configuration is deprecated since version 5.2. Set it to false and use the “Symfony\Component\Routing\Annotation\Route” annotation from Symfony itself.

I say “cryptic” because I did not enable that configuration myself. It was enabled by default. And there’s a pull request to make it disabled by default.
Until that pull request gets merged, the fix that worked for me was to create a config/packages/framework_extra.yaml and disable those annotations explicitly:

Yay! No more deprecation notices 🙂

Symfony: Inject Doctrine Repositories in Controllers

I’ve come across a cool pattern in Symfony 4 – injecting repositories instead of EntityManager

// Before
public function showAllUsers(EntityManagerInterface $em)
    {
    $users = $em->getRepository(User::class)->findAll();

    return $this->json($users);
    }

// After
public function showAllUsers(UserRepository $userRepository)
    {
    $users = $userRepository->findAll();
    
    return $this->json($users);
    }

In the latter, we keep the scope limited only to the repository we need, not holding on to the whole EntityManager.
It’s possible because all your repositories are automatically registered as services and hence can be autowired.
Previous Symfony versions allowed you to register repositories as services manually and event that was tedious, because it required a custom factory etc.

Since EntityManager is mostly used for persisting and deleting entities, you can add a few helper methods to your repository(ies):

/**
 * @param object $entity
 */
public function save($entity)
    {
    $this->_em->persist($entity);
    $this->_em->flush();
    }

/**
 * @param object $entity
 */
public function delete($entity)
    {
    $this->_em->remove($entity);
    $this->_em->flush();
    }

$this->_em property is already available, if you’re extending the default ServiceEntityRepository

Then, you can shorten your create/delete controllers as well:

// Before
public function createUser(Request $request, EntityManagerInterface $em)
    {
    $user = new User();
    // request handling, form validation..
    
    $em->persist($user);
    $em->flush();

    return $this->json($user);
    }

public function deleteUser(User $user, EntityManagerInterface $em)
    {
    $em->remove($user);
    $em->flush();

    return new Response('', Response::HTTP_NO_CONTENT);
    }

// After
public function createUser(Request $request, UserRepository $userRepository)
    {
    $user = new User();
    // request handling, form validation..
    
    $userRepository->save($user);

    return $this->json($user);
    }

public function deleteUser(User $user, UserRepository $userRepository)
    {
    $userRepository->delete($user);

    return new Response('', Response::HTTP_NO_CONTENT);
    }

EDIT: first version of the post did not utilize the DI for fetching the EntityManager and did an unfair comparison. This is corrected and the actual difference between these two approaches is probably trivial now.

Simplicity vs Power: Symfony Forms

Disclaimer: This article covers using Symfony Framework without the Doctrine ORM

As a follow-up on a conversation on Twitter, I decided to compile our dev team’s experience in using Symfony Forms for almost three years in production.

Coming from a CodeIgniter background, we’ve found Symfony’s Form and Security component to be the most powerful and most complex parts of the framework.

Domain & Type – Duplication

Splitting the form generation/handling into two parts was one of the biggest changes for us. We followed the Symfony guidelines and avoided polluting our controllers with $this->createFormBuilder() but since we don’t use Doctrine entities, every form required us to make a dedicated domain object and a dedicated type object.

This led to us to duplicate a lot of form-related stuff. Symfony has very powerful validation and it’s a pity it has to be split across form domains and their corresponding types.
For example, you can @Assert\Choice for a specific field, but you still have to repeat those choices when adding a ChoiceType; you can @Assert\Type("integer") a field, but you still have to mark it as an IntegerType.
Now, I understand, splitting the form information from form representation is a good thing in theory, but in reality, it was more of a chore than a boost.

Our specific business case required us to  build a lot of forms (133), so duplication hit us hard.
Following the official cookbook article to reduce duplication didn’t work for us, so we ended up not using inherit_data. Eventually, using the plain OO inheritance proved better.

Form Modifiers

If you have to implement multi-step AJAX forms or have some kind of dependent AJAX filters in your form, chances are you’ll get an advice to use them.

Don’t.
We’ve found them so complicated (sometimes even impossible) to setup, test and debug that, if we had another opportunity, would skip using Symfony Forms in AJAX-heavy scenarios altogether.

Debugging Form Validation

Now, this may be because of the first issue – when validation is split between the domain and the type, it’s hard to find out which one caused it.
Or, this could be because of the modular nature of form building (every type inside a form type can have it’s own child type and so on), so it’s not so easy to flatten those errors and find out where and why it occurred.

Either way, every time a form isValid returns false, we know we’re in for a serious head-scratching.

To Conclude..

I didn’t mean for this to be a rant on Symfony Form component’s complexity.
It is a very sophisticated and thoroughly tested library we’d use on a project any day. It has helped us numerous times and we’re always discovering new ways in optimizing using it (it has changed a lot since we’ve started using it in Symfony 2.2).
The point I’m trying to get across is, while powerful, this library has a steep learning curve, a lot of gotchas and one needs really to master it in order to use it effectively, beyond the simplest use cases.

Handling MongoDB Dates in PHP (New Driver)

In case you’re enthusiastic like me and wanted to use the new MongoDB driver for PHP ASAP, you’ve might have stumbled upon differences in date handling.

Untill now, when you’ve wanted to properly insert dates, you’d construct new MongoDate and insert it as such. Well, there is no such class any more in the new driver, and to insert a date properly, you need to use new MongoDB\BSON\UTCDatetime. Notice the difference in constructor arguments, though – the new UTCDateTime accepts Unix timestamp in milliseconds, not seconds like MongoDate did.

Of course, you have the use the new class when querying as well:

$docs = $collection->find([
 'ts' => [
  '$gt' =>new MongoDB\BSON\UTCDatetime($unix_timestamp * 1000)
  ]
 ]);

And when you fetch your objects, you can use toDateTime to get native PHP’s DateTime and format it as you’d like.