Few weeks ago, while using the
NotBlank constraint from the Symfony Validator to validate a command before executing it, a co-worker of mine noticed that the values are not automatically trimmed, so it would allow strings of empty spaces to be passed as valid values.
We were already using the Symfony Forms component with the command classes set for the
data_class property in the forms, and the trimming was done there by default. As the commands can be initialized from another places in the applications as well, this is not enough and we must be sure that the data is still properly validated.
While looking for multiple approaches how this can be solved, it came to my mind that this is a case where we can finally give Aspect-Oriented Programming a try.
My first contact with the AOP paradigm was few years ago while exploring the Spring framework in Java. My impression was that the AOP way of connecting things is too messy and can easily lead us to having a big mess in our applications, so I didn’t dig much deeper into the approach.
However, after listening to Alexander Lisachenko’s “Solving Cross-Cutting Concerns in PHP” talk at the PHP Serbia Conference this spring, although still suspicous about relying too much on the concept, I was kind of convinced that AOP can be useful for some simpler tasks in our applications.
The Go! Aspect-Oriented Framework is a popular AOP framework in the PHP ecosystem (and probably the only one I had heard of before), so I chose it for the experiment.
Let’s imagine that we want to validate the values passed in a
PostCommentCommand command before executing it.
If we create an instance of the command and pass strings of empty spaces as values for the
$content arguments, we can see that no constraints are violated.
When using the Go! AOP framework, in order to trim the values of the constructor arguments before validating them, we need to create an Aspect class where the actual trimming would take place.
Aspect: A modularization of a concern that cuts across multiple objects. Logging, caching, transaction management are good examples of a crosscutting concern in PHP applications. Go! defines aspects as regular classes implemented empty Aspect interface and annotated with the @Aspect annotation.
In the aspect class, we’ll have an method that we’d like to be called whenever we call a method whose string arguments need to be trimmed. When called, this method will receive an instance of the
MethodInvocation class that will contain details about the originally called method, including a list of the values given as arguments in the call. We can then take the string arguments from the list and trim their values.
The Go! framework needs to know when it should call the method we just created. To tell it, we should define the method as an
Advice and give it a properly defined way to match join points.
Advice: Action taken by an aspect at a particular join point. There are different types of advice: @Around, @Before and @After advice.
In our concrete example, we need to call the aspect before the constructor of the command class is called, so we’ll use the
@Before advice annotation.
Before advice: Advice that executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
The join points matchers are defined using a concept called Pointcuts.
Pointcut: A regular expression that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name).
The first way for defining a pointcut we’ll take a look at is to use a regular expression to define a pattern that will be used to check if the given advice should be called when a method in another place is being executed.
We would like the
trimStringArgumentValues() method of our
TrimStringValuesAspect class to be called before an instance of our command classes (all classes whose name ends with the word Command) is created, so we’ll have the following advice definition:
Another thing we need to do is to register the aspect in the framework. We’ll see how we can do that bellow in this article.
Having the aspect defined, if we validate the created command object again, we get the two expected violations:
Another way for defining when the advice should be called is to use an annotation pointcut. Defining it this way, it will allow the
trimStringArgumentValues() method to be called before execution of any method in any class annotated with a custom annotation we’d create.
We’ll create a new annotation class called, for example,
We’d now change the definition of the advice in our aspect to match only the methods annotated with our annotation:
The constructor method of our command class will not be automatically associated with the aspect anymore, so we’ll annotate it with the
If we validate the command object again, we’ll still get the same expected violations.
Integrating the Go! AOP framework into our applications is well described in the framework’s documentation.
We should create a new kernel class that would extends the
Go\Core\AspectKernel class. In this kernel we’ll register our aspects.
Then, in our front controller (or some other proper place in the application) we’ll initialize and configure the kernel:
If we want to use it withing the Symfony Framework, we can use the official GoAopBundle bundle.
Having this bundle installed, we only need to register our aspects as a services and tag them with a
As I said before, this is a first time I’m giving the AOP approach a try, so I can’t guarantee if this way of dealing with the argument values is actually good on a long term basis.
If you want to share any opinions, ping me on Twitter.