未加星标

Achieving Modular Architecture with Forwarding Decorators

字体大小 | |
[开发(php) 所属分类 开发(php) | 发布者 店小二04 | 时间 2017 | 作者 红领巾 ] 0人收藏点击收藏

This article was peer reviewed byYounes Rafie andChristopher Pitt. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

As your web application becomes larger, you certainly start to think more about designing a flexible, modular architecture which is meant to allow for a high amount of extensibility. There are lots of ways to implement such architecture, and all of them circle around the fundamental principles: separation of concerns, self-sufficiency, composability of the parts of an app.

There is one approach which is rarely seen inphp software but can be effectively implemented ― it involves using native inheritance to provide manageable patching of the software code; we call it the Forwarding Decorator.


Achieving Modular Architecture with Forwarding Decorators
Introduction to the Concept

In this article, we are going to observe the implementation of the Forwarding Decorator approach and its pros/cons. You can see the working demo application at this GitHub repository . Also, we’ll compare this approach to other well-known ones such as hooks, and code patching.

The main idea is to treat each class as a service and modify that service by extending it and reversing the inheritance chain through code compilation. If we build the system around that idea, any module will be able to contain special classes (they will be marked somehow to separate from the usual classes), which can inherit from any other class and will be used anywhere instead of the original object.


Achieving Modular Architecture with Forwarding Decorators

That’s why it is called Forwarding decorators: they wrap around the original implementation and forward the modified variant to the forefront to be used instead.

The advantages of such an approach are obvious:

modules can extend almost any part of the system, any class, any method; you don’t have to plan extension points in advance. multiple modules can modify a single subsystem simultaneously. subsystems are loosely coupled and can be upgraded separately. extension system is based on the familiar inheritance approach. you can control extensibility by making private methods and final classes.

With great power comes great responsibility, so the drawbacks are:

you would have to implement some sort of compiler system (more about that later) module developers have to comply with the public interface of the subsystems and not violate the Liskov substitution principle ; otherwise other modules will break the system. you will have to be extremely cautious when modifying the public interface of the subsystems. The existing modules will certainly break and have to be adapted to the changes. extra compiler complicates the debugging process: you can no longer run XDebug on the original code, any code change should be followed by running the compiler (although that can be mitigated, even the XDebug problem) How Can This System Be Used?

The example would be like this:

class Foo {
public function bar() {
echo 'baz';
}
} namespace Module1;
/**
* This is the modifier class and it is marked by DecoratorInterface
*/
class ModifiedFoo extends \Foo implements \DecoratorInterface {
public function bar() {
parent::bar();
echo ' modified';
}
} // ... somewhere in the app code
$object = new Foo();
$object->bar(); // will echo 'baz modified'

How can that be possible?

Achieving this would involve some magic. We have to preprocess this code and compile some intermediate classes with the reversed inheritance graph, so the original class would extend the module decorator like this:

// empty copy of the original class, which will be used to instantiate new objects
class Foo extends \Module1\ModifiedFoo {
// move the implementation from here to FooOriginal
} namespace Module1;
// Here we make a special class to extend the other class with the original code
abstract class ModifiedFoo extends \FooOriginal implements \DecoratorInterface {
public function bar() {
parent::bar();
echo ' modified';
}
} // new parent class with the original code, every inheritance chain would start from such file
class FooOriginal {
public function bar() {
echo 'baz';
}
}

The software has to implement the compiler to build the intermediate classes and the class autoloader, which will load them instead of the original ones.

Essentially, the compiler would take a list of all classes of the system and for each individual non-decorator class find all children that implement DecoratorInterface . It will create a decorator graph, make sure it is acyclic, sort decorators according to the priority algorithm (more on that later), and build intermediate classes, where the inheritance chain would be reversed. The source code would be extracted to the new class which is now parent for the inheritance chain.

Sounds pretty complicated, yeah?

Complicated it is indeed, unfortunately, but such a system allows you to combine the modules in a flexible way, and the modules will be able to modify literally any part of the system.

What If There Are Multiple Modules To Modify a Single Class?

In cases when multiple decorator classes should be in effect, we can place them in the resulting inheritance chain according to their priority. The priority can be configured through annotations. I highly recommend using Doctrine Annotations or some config files. Take a look at this example:

class Foo {
public function bar() {
echo 'baz';
}
} namespace Module1;
class Foo extends \Foo implements \DecoratorInterface {
public function bar() {
parent::bar();
echo ' modified';
}
} namespace Module2;
/**
* @Decorator\After("Module1")
*/
class Foo extends \Foo implements \DecoratorInterface {
public function bar() {
parent::bar();
echo ' twice';
}
} // ... somewhere in the app code
$object = new Foo();
$object->bar(); // will echo 'baz modified twice'

Here, Decorator\After annotation can be used to place another module decorator further up the inheritance chain. The compiler would parse the files, look for annotations, and build the intermediate classes to have this chain of inheritance:


Achieving Modular Architecture with Forwarding Decorators

Also, you could implement Decorator\Before (to place decorator class higher), Decorator\Depend (to enable decorator class only if another module is present or non present). Such a subset of the annotations would be pretty much complete to make any required combination of the modules and classes.

But What About Hooks Or Patching The Code? Is This Better?

Just like Decorators , each of the approaches has its advantages and disadvantages.

For example, hooks (based some sort of Observer pattern) are widely used in WordPress and many other applications. Their benefits are having a clearly defined extension API and a transparent way of registering an observer. At the same time they have a problem of the limited number of extension points and indeterminate time of the execution (difficult to depend on the result of the other hooks).

Patching the code is trivial to get started but it is often considered to be very dangerous, as it can lead to unparseable code and it is often very hard to merge several patches of a file or undo the changes. Patching the system may require its own DSL to control the modifications. Complex modifications would require deep knowledge of the system.

Conclusion

The Forwarding Decorators pattern is at least an interesting approach that can be used to tackle the problem of achieving a modular, extensible architecture of PHP software while using familiar language constructs like inheritance or execution scope to control extensibility.

Some applications have already incorporated the described concept, notably OXID eShop uses something very similar. Reading their dev docs is kinda fun, these folks do have a sense of humor! Another platform, X-Cart 5 eСommerce software, uses the concept exactly in the form described above its code was taken as a basis for the article. X-Cart 5 has a marketplace for the 3rd party extensions that modify the behavior of the system yet do not break the upgradeability of the core.

Such a concept can be difficult to implement and there are some issues with the application’s debugging, but they aren’t impossible to overcome if you spend some time fine-tuning the compiler. In the next article, we will cover how to build an optimal compiler and autoloader and use PHP Stream filters to enable step by step debugging via XDebug on the original code. Stay tuned!

If you have any other tips and tricks you’d like to share please let us know in the comments section below. Also, any questions are welcome.

本文开发(php)相关术语:php代码审计工具 php开发工程师 移动开发者大会 移动互联网开发 web开发工程师 软件开发流程 软件开发工程师

主题: PHPGitGitHubWord
分页:12
转载请注明
本文标题:Achieving Modular Architecture with Forwarding Decorators
本站链接:http://www.codesec.net/view/533560.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(php) | 评论(0) | 阅读(12)