However we want to have a user’s full information before he can participate in more “advanced” parts of the app. For example if you were an online air ticketing company, before allowing to proceed with a ticket checkout, you need the user’s full name, passport number and other things.
Using validation groups it’s very easy to group all the info you need for ticket checkout and then ask the validator if anything is missing. In every action where you need this, you could do something like this:
This so far is all nice and fine however I’m quite a lazy bastard and I would have to repeat these few lines of codes in EVERY action for which I want to verify the user first.
We want a better solution!
I was quite impressed by the power of annotations but never got the opportunity to play with them. But I thought it’d be awesome to be able to change the code we just saw to this one instead:
The validation code would have to be executed before any method that has the “@ValidateUser” annotation. I was first looking at events to save me but didn’t find how or which event to hook into in order to do this.
But I know that JMSSecurityExtraBundle does this kind of stuff because you can secure your controller actions with annotations like that. I’m not gonna explain how to create and read annotations, you can check this excellent blog post for that. Well actually it’s so simple in the end that I just might do it, so here’s my annotation class:
All it does is hold the validation group and the route to redirect the user to if the validation failed.
Now how to hook your code before the method call?
So if AOP was avaliable to us, we could before any method is called on any object execute a bit of our own code!
And it so happens that Symfony2 comes with AOP magic included. This is because all the classes that are generated in cache are generated through a code generation library that allows the use of AOP. All of this is also nicely integrated into this bundle.
So all we need to do now is define a pointcut and an interceptor.
Pointcuts as created here are classes that just have a “matchesClass” and a “matchesMethod” methods. Each of these methods should return true in case they want the interceptor to be started.
A pointcut will be defined as a service with a tag so that it can be found by the AopBundle.
In our case we will match all Controller classes and all methods that have the @ValidateUser annotation. Since the pointcut is defined as a service, it is quite easy to inject the already existing annotation reader if you are using the full symfony2.
We can see that the pointcut doesn’t seem to do much. The real validation work will be done inside of our interceptor.
The interceptor is also a service, that will use:
- The security context so we can get the current user and validate his profile
- The annotation reader
- The validator service to check our user
- The session so we can add flash messages explaining to the user “what the hell just happened?”
- The router to redirect him to the profile page
Then all the work is done in the “intercept” method of that interceptor. That method receives a MethodInvocation and should call the “proceed” method on it after its work is done. In our case, if the user has an incomplete profile, we will completely bypass the execution of the controller and immediately redirect the user to his profile page.
That’s it, everything is ready to work as soon as we configure our services. We also need to let the Pointcut and the Interceptor know they “work together”, which is done in the tag of the Pointcut service:
Is kind of what I thought after making all this. It was really really simple. And I’ve been looking at something like this for a long time. Every time you wish there was an event sent out that you could listen to in order to add some behavior but there is nothing, it might be worth looking at what AOP can do for you.
How well / poorly does it perform? I don’t know yet!
Also be sure to check more about aspect oriented programming, and for example this native extension for PHP that has a different take on how to make AOP but looks really interesting as well.