Let’s say I’m working on an application that has “users”. First, I’m trying to design and implement business model, leaving MVC and data storage for later.
class UsersManager // or, could be UsersService
{
/**
* All registered users
*
* @var array
*/
private $users = [];
/**
* Registers a user in the system
*
* @param string $firstName
* @param string $lastName
* @param string $email
*/
public function registerNewUser($firstName, $lastName, $email)
{
$this->users[] = new User($firstName, $lastName, $email);
}
}
class User
{
/**
* The user is not activated by default
*
* @param string $firstName
* @param string $lastName
* @param string $email
* @param bool $activated
*/
public function __construct($firstName, $lastName, $email, $activated = false)
{
// ...
}
}
When users are registered in the application they are considered inactive until they have verified their email address.
While the DATA team is still picking the most trending data storage for the application, I implement the logging system. Basically, we need to write to log each time a new user is added:
public function registerNewUser($firstName, $lastName, $email)
{
$this->users[] = new User($firstName, $lastName, $email);
$this->logger->log('New user created: ' . $email); // only dumping the email to the log
}
Now, I want the administrator of the site to be able to create activated users, so that email verification is not required. The same log message should be added to the log file:
/**
* Could be Zend_Log or Monolog or whatever, let's say it was instantiated in __constructor
*
* @var Zend_Log
*/
private $logger;
/**
* Registers a user in the system
*
* @param string $firstName
* @param string $lastName
* @param string $email
*/
public function registerNewUser($firstName, $lastName, $email)
{
$this->users[] = new User($firstName, $lastName, $email);
$this->logger->log('New user created: ' . $email);
}
/**
* Adds an already-activated user
*
* @param string $firstName
* @param string $lastName
* @param string $email
*/
public function addActivatedUser($firstName, $lastName, $email)
{
$this->users[] = new User($firstName, $lastName, $email, true);
$this->logger->log('New user created: ' . $email);
}
It’s not too bad now, but the duplication of code has begun. It gets worse when you’re ready to save the users set in a database:
/**
* Some database abstraction, may be a table gateway, or, Doctrine's Entity Manager
*
* @var Database
*/
private $db;
/**
* Registers a user in the system
*
* @param string $firstName
* @param string $lastName
* @param string $email
*/
public function registerNewUser($firstName, $lastName, $email)
{
$user = new User($firstName, $lastName, $email);
$this->users[] = $user;
$this->db->insert('users', $user->toArray());
$this->logger->log('New user created: ' . $email);
}
/**
* Adds an already-activated user
*
* @param string $firstName
* @param string $lastName
* @param string $email
*/
public function addActivatedUser($firstName, $lastName, $email)
{
$user = new User($firstName, $lastName, $email, true);
$this->users[] = $user;
$this->db->insert('users', $user->toArray());
$this->logger->log('New user created: ' . $email);
}
With all the code duplication and mixing of things, I can say good bye to the nice separation of concerns.
Why are they “cross-cutting”? Because they exist in different layers of your application. They cross the boundaries, if you will. In the example above, “adding a user” may refer to three things: busines-wise adding a user, saving a new record to a RDBMS table or adding a line to a log file.
Business rules. MySql table. Log file. They are all different. Yet they are one: “adding a user”. This is why they are “cross-cutting”. Why is it called a problem of cross-cutting concerns? Because there is no “built-in” way of solving this in classic paradigms and approaches to programming.
Having OOP abstractions helps to some degree. For instance, instead of working with database directly we introduce new concepts into the Business area. $users collection may be a “repository” still acting like an array, it may reference the database under the hood. The so-called UnitOfWork pattern helps dealing with things like DB transactions.
In reality, however, it is pretending. You can have a business requirement about users, but not about “units of work”. These are all made-up entities that you have to introduce into your design again and again to justify/hide the presence of infrastructure-related logic (db/log/…) in your Domain Layer.
The UsersManager
class is responsible not only for the business “aspect” of the application but also deals with database and logging systems. These are the three different aspects of the same activity, that should not be mixed up.
Aspect Oriented Programming is an approach that helps separating these aspects within your application. The idea is that you can store your aspects independently, while they will still be executed “at the same time” (or, you can define a particular order of execution).
As AOP contradicts the OOP in its classic definition, implementing it usually implies relying on “magic” features of a language.
In some languages it is built-in (although still doesn’t feel like it is), but in most cases it’s done by a framework, an extension or a library. In PHP, what’s worth mentioning is the Go framework by lisachenko, AOP extension for PHP and the set of tricks used in Doctrine2.
Implementations may differ depending on which tool you use, but the idea is as follows. I still have the business layer UsersManager as it was in the beginning, independent from Db and Logging:
class UsersManager
{
/**
* All registered users
*
* @var array
*/
private $users = [];
/**
* Registers a user in the system
*
* @param string $firstName
* @param string $lastName
* @param string $email
*/
public function registerNewUser($firstName, $lastName, $email)
{
$this->users[] = new User($firstName, $lastName, $email);
}
/**
* Adds an already-activated user
*
* @param string $firstName
* @param string $lastName
* @param string $email
*/
public function addActivatedUser($firstName, $lastName, $email)
{
$this->users[] = new User($firstName, $lastName, $email, true);
}
}
Then I have the aspect of logging, something like this:
class LoggingAspect
{
private $logger;
public function addUser(User $user)
{
$this->logger->log('New user created: ' . $user->getEmail());
}
}
And the aspect of database interaction:
class DbAspect
{
private $db;
public function addUser(User $user)
{
$this->db->insert('users', $user->toArray());
}
}
Then there would be some initialization logic, wiring all the aspects together:
class AopBootstrap
{
public function bootstrap()
{
// the following is highly fictional, just to illustrate the idea
$addUser= new Concern('UsersManager', '$users[]='); // whenever an element is added to $users array...
$addUser->addAspect(new LoggingAspect());
$addUser->addAspect(new DbAspect());
$this->initConcern($addUser);
}
}
There is a lack of tools at the moment, and the ones we have are limited, not convenient enough to use and hacky due to the magic features they utilize:
I like analogy where OOP/procedural programming is 2-dimensional while adding AOP into the equation makes it 3d. It does require better imagination.