PHP implements AOP under the three layer structure of of

  • 2020-03-31 20:58:54
  • OfStack

In this paper, the source code download address: http://xiazai.jb51.net/201007/yuanma/TraceLWord.rar
Development environment eclipse(PDT)
Let's focus on the intermediary service layer. The intermediate service layer code is simple, just calling the data access layer code to save the message to the database. As shown in code 1:
 
//Code 1
//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 
} 
}; 

After seeing the presentation on the message board, the company's product and marketing departments may come up with various ideas and requirements. For example, they want to determine the user's permission before adding a message! Only registered users can leave a message! We need to modify the code, as shown in code 2:
 
//Code 2, add login verification
//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
if (!($userLogin)) { 
//Prompt the user to log in
} 
//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 
} 
}; 

The Marketing Department also wants to check the message content before adding the message. If the message contains profanity, it will not be saved. We continue to modify the code, as shown in code 3:
 
//Code 3, add filtering of dirty words
//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
if (!($userLogin)) { 
//Prompt the user to log in
} 
if (stristr($newLWord, "SB")) { 
//Contains dirty words, prompt message sent failed
} 
//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 
} 
}; 

The product department has also come up with new requirements, and they want to add points. Specifically, after each successful user message to the user +5 points. We continue to modify the code, as shown in code 4:
 
//Code 4, add a message credit mechanism
//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
if (!($userLogin)) { 
//Prompt the user to log in
} 
if (stristr($newLWord, "SB")) { 
//Contains dirty words, prompt message sent failed
} 
//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 
//Bonus points for users
$score = getUserScore($userName); 
$score = $score + 5; 
saveUserScore($userName, $score); 
} 
}; 

It wasn't long before the product department refined the requirements again, and they wanted to upgrade users for every 1,000 points they accumulated. We continue to modify the code, as shown in code 5:
 
//Code 5, add user upgrade rule
//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
if (!($userLogin)) { 
//Prompt the user to log in
} 
if (stristr($newLWord, "fuck")) { 
//Contains dirty words, prompt message sent failed
} 

//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 
//Bonus points for users
$score = getUserScore($userName); 
$score = $score + 5; 
saveUserScore($userName, $score); 
//Upgrade users
if (($score % 1000) == 0) { 
$level = getUserLevel($userName); 
$level = $level + 1; 
saveUserLevel($userName, $level); 
} 
} 
}; 

As the demand increases, we need to constantly modify the intermediate service layer code. But it should not be hard to see that the more you need, the more the middle layer of service code gets, the bigger it gets! The end result is that even if we use the three-tier development model, it still does not effectively reduce the engineering difficulty! Another is that after modifying the intermediate service code in response to changing requirements, you need to retest all the code instead of effectively testing the new code...

In fact, let's take a closer look at the message board code, I will first put forward a main business logic and secondary business logic concept. Either way, save the message to the database, which is the backbone of the business logic! This is the main business logic! This part has not been modified as the demand increases. As for before the deposit database to carry out authority check, to carry out content check, after the deposit database to give the user additional points, and then give the user upgrade, these are pre - order work and mop up work, is the business logic! The primary business logic is almost static, while the secondary business logic changes frequently. To improve the readability and maintainability of the code, we can consider putting the sub-business logic somewhere else and try not to let it interfere with the main business logic. The main business logic concentrates on what it should be doing, and does nothing else! Then our code can be written like this, as shown in code 6:
 
//Code 6, separating the primary and secondary business logic
//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
//Before adding a message
beforeAppend($newLWord); 
//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 
//After adding a message
behindAppend($newLWord); 
} 
}; 

We can put all the permission judgment code and message text filtering code into the beforeAppend function, and user credit code into the behindAppend function, thus clearing the secondary business logic from the main business logic code. The main business logic knows that there is an "overture" function beforeAppend and a "epilogue" function behindAppend, but the main business logic does not know and does not need to know exactly what is done in the overture and epilogue functions! Of course, the actual coding is not that simple, and we have to account for more changes in the requirements of the product and marketing departments, so it is best to implement a plug-in way to deal with this change, but just relying on the two functions beforeAppend and behindAppend will not achieve this

If you want to implement the plug-in way, you can set up the interface! The advantage of using an interface is that you can isolate the definition from the implementation, and the other is to implement polymorphism. We create a message extension interface, ILWordExtension, which has two functions beforeAppend and behindAppend. The functions of authority check, content check and bonus points can be regarded as the three implementation classes that implement the ILWordExtension interface. The main business logic traverses these three implementation classes in turn to complete the secondary business logic. As shown in figure 1:
The CheckPowerExtension extension class is used for user privilege checking, the CheckContentExtension extension class for message content checking, and the AddScoreExtension extension class for user bonus points and upgrades. The schematic code is shown in code 7:
< img border = 0 SRC = "http://files.jb51.net/upload/201007/20100704221447601.png" border = 0 >
(figure 1), add the extension interface
 
//Code 7, add the extension interface
//Extension interface
interface ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord); 
//After adding a message
public function behindAppend($newLWord); 
}; 

//Check the permissions
class CheckPowerExtension implements ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord) { 
//This is where user permissions are determined
} 

//After adding a message
public function behindAppend($newLWord) { 
} 
}; 

//Check the message text
class CheckContentExtension implements ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord) { 
if (stristr($newLWord, "SB")) { 
throw new Exception(); 
} 
} 

//After adding a message
public function behindAppend($newLWord) { 
} 
}; 

//User integral
class AddScoreExtension implements ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord) { 
} 

//After adding a message
public function behindAppend($newLWord) { 
//I'm going to give the user credit here
} 
}; 

//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
//Before adding a message
$this->beforeAppend($newLWord); 

//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 

//After adding a message
$this->behindAppend($newLWord); 
} 

//Before adding a message
private function beforeAppend($newLWord) { 
//Get extended array
$extArray = $this->getExtArray(); 

foreach ($extArray as $ext) { 
//Iterate over each extension and call its beforeAppend function
$ext->beforeAppend($newLWord); 
} 
} 

//After adding a message
private function behindAppend($newLWord) { 
//Get extended array
$extArray = $this->getExtArray(); 

foreach ($extArray as $ext) { 
//Walk through each extension and call its behindAppend function
$ext->behindAppend($newLWord); 
} 
} 

//Get the extended array,
//The return value of this function is actually an array of the ILWordExtension interface
private function getExtArray() { 
return array( 
//Check the permissions
new CheckPowerExtension(), 
//Check the content
new CheckContentExtension(), 
//Bonus points
new AddScoreExtension(), 
); 
} 
}; 

If there is a new requirement, we simply add the ILWordExtension implementation class and register it with the getExtArray function. The program is now organized and extensible.



But before you get too excited, there's a problem with this extensibility. After the new requirements are raised, we can add the ILWordExtension implementation class, which is correct. But registering this new class in the getExtArray function means that the main business logic code still needs to be modified. Can't we not change it? It is not always good to inform the main business logic every time there is a new requirement change. Ideally, after the new extension code is added to the system, the main business logic code doesn't have to change because the main business logic doesn't even know about the new extension! Therefore, we need to optimize the design scheme, as shown in figure 2:
< img border = 0 SRC = "http://files.jb51.net/upload/201007/20100704221607600.png" border = 0 >
(figure 2), add the extended family class

For the main program that invokes the extension (that is, the intermediate service class LWordServiceCore), just let it know that there is an ILWordExtension. It doesn't need to know that there are CheckPowerExtension (check permission extension), CheckContentExtension (CheckContentExtension), and AddScoreExtension (add points extension). The calls to these three classes are moved to the LWordExtensionFamily.

LWordExtensionFamily is actually a container class that can hold multiple instances of the ILWordExtension interface. As you can see from figure 2, this container class not only implements the ILWordExtension interface, but also aggregates multiple instances of the ILWordExtension interface, so it is special! For the LWordServiceCore class, this class only knows about the ILWordExtension interface, but does not know that there are three implementation classes for the interface. Just as it happens, the LWordExtensionFamily class implements the ILWordExtension interface, which meets the requirements of the intermediate service class, and the extension family class knows that ILWordExtension has three implementation classes and calls them one by one. The LWordExtensionFamily code looks something like code 8:
 
//Code 8, extended family
//Extended family
class LWordExtensionFamily implements ILWordExtension { 
//Extend the array
private $_extensionArray = array(); 
//Add extensions
public function addExtension(ILWordExtension $extension) { 
$this->_extensionArray []= $extension; 
} 
//Before adding a message
public function beforeAppend($newLWord) { 
foreach ($this->_extensionArray as $extension) { 
$extension->beforeAppend($newLWord); 
} 
} 
//After adding a message
public function behindAppend($newLWord) { 
foreach ($this->_extensionArray as $extension) { 
$extension->behindAppend($newLWord); 
} 
} 
} 

It's not hard to see from code 8 that the LWordExtensionFamily class, which also implements the ILWordExtension interface, doesn't do any real work, passing the calls in a loop. To smooth the way the implementation extends to inserts, it is best to create a factory class called MyExtensionFactory. As shown in code 9:
 
//Code 9
//Custom extension factory
class MyExtensionFactory { 
//Create message extension
public static function createLWordExtension() { 
$lwef = new LWordExtensionFamily(); 
//Add extensions
$lwef->addExtension(new CheckPowerExtension()); 
$lwef->addExtension(new CheckContentExtension()); 
$lwef->addExtension(new AddScoreExtension()); 
return $lwef; 
     //Notice that this returns an extended family class object,
     //The LWordExtensionFamily also happens to implement the interface ILWordExtension,
     //So this is a business logic requirement.
     //From this point on, the business logic can care little about the specific extension objects, as long as it knows the extension family
} 
} 

The advantage of using an extension factory class is that you can add and remove extension instances at will, which is a nice implementation of pluggable programming. For the LWordServiceCore class, you know only one ILWordExtension interface, and for the LWordExtensionFamily you know that you need to call each extension one by one, but exactly how many extensions will be given through the MyExtensionFactory. The accountability structure is also clear. Wouldn't it be more convenient and flexible to assume that the createLWordExtension function of the MyExtensionFactory class gets the list of extensions not by hard-coding them with the new keyword, but by reading the configuration file in a more subtle way? However, this will not be discussed in this article.

The intermediate service layer gets a concrete instance of the ILWordExtension interface through the factory class and then calls its beforeAppend and behindAppend methods. Of course, the intermediary service does not know that the factory class returns a container with multiple ILWordExtension instances (because this container also implements the ILWordExtension interface), so the intermediary service does not know that the extension is called one by one. The complete code is shown in code 10:
 
//Code 10, full code
//Extension interface
interface ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord); 
//After adding a message
public function behindAppend($newLWord); 
}; 
//Check the permissions
class CheckPowerExtension implements ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord) { 
//This is where user permissions are determined
} 
//After adding a message
public function behindAppend($newLWord) { 
} 
}; 
//Check the message text
class CheckContentExtension implements ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord) { 
if (stristr($newLWord, "fuck")) 
throw new Exception(); 
} 
//After adding a message
public function behindAppend($newLWord) { 
} 
}; 
//User integral
class AddScoreExtension implements ILWordExtension { 
//Before adding a message
public function beforeAppend($newLWord) { 
} 
//After adding a message
public function behindAppend($newLWord) { 
//I'm going to give the user credit here
} 
}; 
//Extended family
class LWordExtensionFamily implements ILWordExtension { 
//Extend the array
private $_extensionArray = array(); 
//Add extensions
public function addExtension(ILWordExtension $extension) { 
$this->_extensionArray []= $extension; 
} 
//Before adding a message
public function beforeAppend($newLWord) { 
foreach ($this->_extensionArray as $extension) { 
$extension->beforeAppend($newLWord); 
} 
} 
//After adding a message
public function behindAppend($newLWord) { 
foreach ($this->_extensionArray as $extension) { 
$extension->behindAppend($newLWord); 
} 
} 
} 
//Custom extension factory
class MyExtensionFactory { 
//Create message extension
public static function createLWordExtension() { 
$lwef = new LWordExtensionFamily(); 
//Add extensions
$lwef->addExtension(new CheckPowerExtension()); 
$lwef->addExtension(new CheckLWordExtension()); 
$lwef->addExtension(new AddScoreExtension()); 
return $lwef; 
} 
} 
//Intermediate service layer
class LWordServiceCore implements ILWordService { 
//Add a message
public function append($newLWord) { 
//Access to extension
$ext = MyExtensionFactory::createLWordExtension(); 
$ext->beforeAppend($newLWord); 
//Invoke the data access layer
$dbTask = new LWordDBTask(); 
$dbTask->append($newLWord); 
$ext->behindAppend($newLWord); 
} 
}; 

From code 10, you can see that while CheckPowerExtension, CheckContentExtension, AddScoreExtension, and LWordExtensionFamily all implement the ILWordExtension interface, their beforeAppend and behindAppend functions process completely differently! In particular, the LWordExtensionFamily extension family class does not have the actual business logic processing, but instead passes the calls to each extension in turn. The different implementations of beforeAppend and behindAppend functions in concrete classes are typical of object-oriented programming: polymorphism!
Scattering sub-business logic across extensions is already very similar to the way AOP (Aspect OrientedProgramming) is programmed. Permission checks, content checks, and integrals can be thought of as different facets that intersect with the main business logic without affecting the main business logic... The nice thing about this is that the extension code doesn't interfere with the main business logic, or we can code and unit test an extension, and then insert the extension into the business process through the MyExtensionFactory factory class. The complete execution process is shown in figure 3:
< img border = 0 SRC = "http://files.jb51.net/upload/201007/20100704221810324.jpg" border = 0 >
(figure 3), execute the process


In this paper, the source code download address: (link: http://xiazai.jb51.net/201007/yuanma/TraceLWord.rar)

Related articles: