by Yitzchak Schaffer (@yitznewton)
I’ve been doing software for about six years professionally. It was a few years into my career that I first heard about Aspect Oriented design (AOP == Aspect Oriented Programming is your buzzword).
Here’s the premise. A given piece of code exists for a certain purpose - let’s say, to retrieve a record from a database. But there may be any number of other things that need to happen in addition to the actual retrieval: logging, access control, caching… those are known as cross-cutting concerns — issues that are relevant across the codebase, but are not specifically relevant to any one piece of code where they might be needed. And being that these bits of functionality are not intrinsically connected with data retrieval, in our example, it would make sense for them to be disconnected from the retrieval implementation.
Pedantic disclaimer: the following code is meant for illustrative purposes only; any design issues are beside the point
So if I wanted to log the retrieval, I could do:
class UserController
{
public function show($userId)
{
Logger::debug('retrieving user record ' . $userId);
$user = $this->repository(User::class)->fetch($userId);
Logger::debug('retrieved user record ' . $userId);
return $this->renderView($user);
}
}
But what if you could remove that clutter?
public function show($userId)
{
$user = $this->repository(User::class)->fetch($userId);
return $this->renderView($user);
}
…and put the logging somewhere else:
/**
* @Aspect.before(Logger, debug, 'retrieving user record {$userId}')
* @Aspect.after(Logger, debug, 'retrieved user record {$userId}')
*/
public function show($userId)
{
$user = $this->repository(User::class)->fetch($userId);
return $this->renderView($user);
}
…or even better,
class UserController
{
public function show($userId)
{
$user = $this->repository(User::class)->fetch($userId);
return $this->renderView($user);
}
}
aspect Logging
{
UserController::show::before($userId)
{
Logger::debug('retrieving user record ' . $userId);
}
UserController::show::after($userId)
{
Logger::debug('retrieved user record ' . $userId);
}
UserController::show::exception($userId, Exception $e)
{
$message = sprintf(
'failed to retrieve user record %s: %s',
$userId,
$e->getMessage()
);
Logger::error($message);
}
}
Obviously that cool little aspect Logging thing is total pseudocode, but it’s easy to see the power of pulling the logging concern out.
Caching methods
My real-world use case and motivation to explore the possibilities of AOP relates to caching an expensive method call. Whether it’s a call to a remote server, or a complex calculation, sometimes there is an operation whose result you want to store within the code that requests it, rather than have to run the operation again if the result is needed a second time. A typical implementation might be:
class ComplexCalculation
{
private $calculatedValue;
public function calculate()
{
if ($this->calculatedValue) {
return $this->calculatedValue;
}
// do all sorts of complex stuff
return $this->calculatedValue = $calculatedValue;
}
}
But storing the result as an instance variable introduces the need to create, check and set that variable — another 4.5 lines of code, which hinders the ability to quickly understand the code, and adds to the cost of maintaining it. And you need to repeat it for every method you want to cache, across your codebase.
Why can’t we just do this:
class ComplexCalculation
{
/**
* @CacheResult
*/
public function calculate()
{
// do all sorts of complex stuff
return $calculatedValue;
}
}
Now we have a single, central place for the caching logic to live: the implementation of the CacheResult aspect. The main implementation is also cleared of extraneous concerns. So how to implement this?
One approach in PHP
The Lithium framework makes extensive use of closures to add functionality dynamically. Lith
Truncated by Planet PHP, read more at the original (another 11646 bytes)
more
{ 0 comments... » Pushing the limits of metaprogramming in PHP: aspect oriented design read them below or add one }
Post a Comment