PHPackages                             exteon/chaining-class-resolver - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. exteon/chaining-class-resolver

ActiveLibrary[Framework](/categories/framework)

exteon/chaining-class-resolver
==============================

Weaving class loader framework for PHP modular plugins providing class chaining

2.1.1(4y ago)047Apache-2.0PHP

Since Feb 4Pushed 3y ago1 watchersCompare

[ Source](https://github.com/exteon/chaining-class-resolver)[ Packagist](https://packagist.org/packages/exteon/chaining-class-resolver)[ Docs](https://github.com/exteon/chaining-class-resolver)[ RSS](/packages/exteon-chaining-class-resolver/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (4)Versions (10)Used By (0)

Together with [mapping-class-loader](https://github.com/exteon/mapping-class-loader), this library provides a framework plugin development and loading. By using a "chain-loading" concept, it provides an implementation platform sitting somewhere between mixins and multiple inheritance.

Abstract
--------

[](#abstract)

A common problem concerning a platform that offers plugins functionality is this: we have a number of plugins that are developed against the same code-base (the same contracts), however they are unaware of each other (they must act like black-boxes for other plugins).

By their nature, plugins are tight-coupled to the base code they extend or customize. At the same time, loaded plugins' effect needs to be additive. Multiple plugins coupled to the codebase must be aggregated to work together.

Some of the solutions commonly implemented we found coming short:

- decorators-based approach doesn't provide enough coupling, plugins structure is an "onion layer" where output of an outer layer cannot be fed to the input of an inner layer
- observer pattern plugin systems (hooks, event-driven, generic invoker mixins ect) are very costly to implement, maintain and debug

Most efficient plugin patterns rely on mixins, which are akin' to multiple inheritance. PHP's Traits, while a great feat, are not sufficient to implement the mixin patterns needed for plugins, as they lack a static identity (static properties are duplicated to their implementing classes).

Our solution provides a way of loading plugins in a chaining (layered) dynamic structure (much like decorators), but uses source weaving to modify class hierarchy so that the resulting inheritanve model is fully-coupled.

The below illustrations will provide a more visual explanation of the chaining process. Let's start with how, starting from a codebase, plugin developers would add specialisation by extending the existing classes, with the purpose that their specialisations will replace the initial implementation:

[![](doc/plugin%20development.svg)](doc/plugin%20development.svg)

So in the image above, both `Plugin1` and `Plugin2` extend the base code, while `Plugin3` is based on `Plugin2`.

The question is, how do we rejoin these structure so that we can use all 3 plugins in an app, with only one `A` class and one `B` class?

In comes `chaining-class-resolver`:

[![](doc/app%20development.svg)](doc/app%20development.svg)

With `chaining-class-loader`, code comes in modules, and all base code and plugins are modules that will be linearized. That is done at class load time, by changing the inheritance. In the second diagram, you will have noticed how `\Plugin2\A` now inherits from `\Plugin1\A`, not from `\Code\Base\A` as was in the original source (first diagram).

All such linearized classes are then projected into a *Target namespace* so that an application can use i.e. `\Target\A` with relative obliviousness to the chain that generated it.

This class weaving is done at runtime, so the process is transparent to the application developer, that just needs to configure the modules. Thus, the plugins can be brought in from any source (such as, `composer`) and they won't need to be customized in any way specific to the application, `chaining-class-resolver` will do all the magic.

Usage
-----

[](#usage)

You can find the example in the diagrams above implemented in the [example](example) directory.

The loader setup part is in [setup.inc.php](example/setup.inc.php)

To run the example, run [app.php](example/app.php)

The steps to set up chaining class loading are:

### Creating the modules

[](#creating-the-modules)

We organize the modules in folders with a PSR-4 class structures:

- [base](example/base) with namespace `Code\Base`
- [plugins/plugin1](example/plugins/plugin1) with namespace `Plugin1`
- [plugins/plugin2](example/plugins/plugin1) with namespace `Plugin2`
- [plugins/plugin3](example/plugins/plugin1) with namespace `Plugin3`

Each module defines or extends classes `A` and/or `B` like in the [above diagram](doc/plugin%20development.svg)

You are free to use any directory structure, as long as you have a number of module directories each containing a PSR-4 namespace.

### The chaining resolver

[](#the-chaining-resolver)

We get a resolver instance:

```
use Exteon\Loader\ChainingClassResolver\ChainingClassResolver;
use Exteon\Loader\ChainingClassResolver\Module;
use Exteon\Loader\ChainingClassResolver\ClassFileResolver\PSR4ClassFileResolver;

$chainingClassResolver = new ChainingClassResolver(
    [
        new Module(
            'Code base',
            [new PSR4ClassFileResolver(__DIR__ . '/base', 'Code\\Base')]
        ),
        new Module(
            'Plugin 1',
            [new PSR4ClassFileResolver(__DIR__ . '/plugins/plugin1', 'Plugin1')]
        ),
        new Module(
            'Plugin 2',
            [new PSR4ClassFileResolver(__DIR__ . '/plugins/plugin2', 'Plugin2')]
        ),
        new Module(
            'Plugin 3',
            [new PSR4ClassFileResolver(__DIR__ . '/plugins/plugin3', 'Plugin3')]
        )
    ],
    'Target'
);
```

The `$targetNs` constructor parameter defines the target namespace that the class chain will be weaved into, in our case the classes will be chained under `\Target`.

Modules will be chained in the order they are sent to the constructor; this means, in the above example, `Plugin 1` will extend/override classes in `Code base`, `Plugin 3` will override all, ect.

### Setting up the loader

[](#setting-up-the-loader)

We will use [exteon/mapping-class-loader](https://github.com/exteon/mapping-class-loader)to load the chained files:

```
use Exteon\Loader\MappingClassLoader\MappingClassLoader;
use Exteon\Loader\MappingClassLoader\StreamWrapLoader;

$loader = new MappingClassLoader(
    [],
    [
        $chainingClassResolver
    ],
    [],
    new StreamWrapLoader([
        'enableMapping' => true
    ])
);
$loader->register();
```

For more details on using the `MappingClassLoader`, you can take a look at the [exteon/mapping-class-loader documentation](https://github.com/exteon/mapping-class-loader).

### Using the chained classes

[](#using-the-chained-classes)

We can now use the chained classes. All classes define or override the `whoami()` method, adding the result.

```
    use Target\A;
    use Target\B;

    $a = new A();
    $b = new B();

    var_dump($a->whoami());
    var_dump($b->whoami());
```

The above code will produce the following result:

```
array(3) {
  [0] =>
  string(11) "Code\Base\A"
  [1] =>
  string(9) "Plugin1\A"
  [2] =>
  string(9) "Plugin2\A"
}
array(2) {
  [0] =>
  string(9) "Plugin2\B"
  [1] =>
  string(9) "Plugin3\B"
}

```

So now the classes as chained as represented in [this diagram](doc/app%20development.svg).

### Class hint files

[](#class-hint-files)

If you open [app.php](example/app.php) in your smart IDE, the classes in the `Target` namespace will be unresolved and you will not have any autocompletion for them. That is because `Target\A` and `Target\B` are not yet defined anywhere in the code.

In order to fix this, we need to create class hint files that will contain the stubs for the `Target` classes. We can do this by running [create-hints.php](example/create-hints.php) in the [example](example)directory.

Using the same [setup.inc.php](example/setup.inc.php), this tools runs

```
$loader->dumpHintClasses(__DIR__.'/dev/hints');
```

What this does is generate class hint files in `dev/hints`. These are PHP class files with PSR-4 structure that are generated in the directory you specify. Now just add the hints directory to your source directories in your IDE and now, your `Target\A` class will be defined as such:

```
namespace Target {
    /**
     * @extends \Code\Base\A
     * @extends \Plugin1\A
     */
    class A extends \Plugin2\A {}
}
```

***Note***

The `dumpHintClasses` tool should be ran whenever you add classes or change the classes hierarchy in your project, reconfigure your modules ect. to regenerate the hint files.

***Note***

The hint classes contain only one extend, i.e. `Plugin2\A`, which in your source files extends `Code\Base\A`. Therefore, if you now add a `someMethod()` method on `Plugin1\A`, this method will not be known on static analysis on `Target\A`, there will be no autocomplete for it, unless your IDE can read the multiple `@extends` annotations.

In a future version, we might address this by always using traits for class extending. (See [Traits chaining](#traits-chaining))

However, the primary functionality of a plugin is to modify (i.e. override) existing class methods. Therefore, we advise that when you think about extending an existing class's contract, you consider doing this using separate traits that you `use` or you use object composition to keep added functionality into separate classes.

Other features
--------------

[](#other-features)

### Debugging

[](#debugging)

Even though the `chaining-class-resolver` modifies (weaves) the source code to achieve its functionality, step debugging can be easily performed using `exteon/mapping-class-loader`'s mapping functionality. When debugging code, you will be stepping on the original class files as usual. The only thing you need to do, as in the code examples above, is to set `'enableMapping' => true` to `StreamingWrapLoader`'s config.

To read more about the mapping functionality, please refer to the [exteon/mapping-class-loader documentation](https://github.com/exteon/mapping-class-loader#debug-mapping).

### Caching

[](#caching)

The class weaving process can be a significant overhead; [exteon/mapping-class-loader](https://github.com/exteon/mapping-class-loader)provides caching for the weaved class files. To enable caching, you need to set the `enableCaching` and `cacheDir` config options to the `MappingClassLoader`constructor. For more details about caching, see the [exteon/mapping-class-loader documentation](https://github.com/exteon/mapping-class-loader#caching).

For development, whenever you change the source classes, the cache needs to be cleared and reprimed; there is a work in progress for a change watcher over `inotify`, but until that is published, you should create your own tool, if you use caching in a development setup.

### Traits chaining

[](#traits-chaining)

In PHP, traits that implement the same method cannot be added on the same class ([see here](https://www.php.net/manual/en/language.oop5.traits.php#language.oop5.traits.conflict)). While this is an artefact of the copy-paste trait implementation, it's also a setback in traits reusability and expresiveness. Since `parent::` and `static::`are supported for traits and PHP doesn't support overloading, it makes sense that multiple traits can override the same base method.

`chaining-class-resolver` supports this by linearizing (chaining) the list of traits in a class `uses` clause, creating intermediate classes, each using one trait. Therefore, traits precedence is the order in which they are listed in the `use` clause.

For instance, this is possible with `chaining-class-resolver` (note the different classes need to be implemented in different files as per PSR-4 standard):

```
class A {
    public function whoami(){
        return ['A'];
    }
}
```

```
trait T1 {
    public function whoami(){
        return array_merge(parent::whoami(),['T1']);
    }
}
```

```
trait T2 {
    public function whoami(){
        return array_merge(parent::whoami(),['T2']);
    }
}
```

```
class B extends A {
    use T1,T2;
    public function whoami(){
        return array_merge(parent::whoami(),['B']);
    }

}
```

```
var_dump((new B())->whoami());
```

This will list `['A','T1','T2','B']` as class inheritance.

Please also further see [Multiple Inheritance Considerations](#multiple-inheritance-considerations) for a more dogmatic discussion of the implications.

### Multiple Inheritance Considerations

[](#multiple-inheritance-considerations)

If chained classes, or multiple traits, all define a new method with the same name, the infamous [diamond problem](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem)arises. `chaining-class-resolver` has no special handling for it, but then PHP also doesn't (there is no syntax to explicitly signify an override). Therefore, having multiple traits defining the same new method is allowed, as long as, per PHP rules, the second method's signature is compatible with the first, making it an assumed override.

In the future we might implement a more stringent mechanism to detect and address the diamond problem.

### Chaining reflection

[](#chaining-reflection)

You can find information about the chained classes at runtime using `ChainedClassMeta`:

```
ChainedClassMeta::get('Some\Class')->isChained()
```

Returns whether the named class is attached to a chain.

```
ChainedClassMeta::get('Some\Class')->getChainParent()
```

Returns `Some\Class`'s chain parent class as a `ChainedClassMeta` object or null if there is none. use `->getClassName()` to get this parent's class name.

```
ChainedClassMeta::get('Some\Class')->getChainedClass()
```

Returns the `Some\Class`'s chain target class (i.e. the end of the chain in the target namespace).

```
ChainedClassMeta::get('Some\Class')->getModuleName()
```

Returns the name of the module that defines `Some\Class`.

```
ChainedClassMeta::get('Some\Class')->getChainTraits()
```

Returns the traits that were added in `Some\Class`'s chain by all chained classes. (But not the inherited ones).

```
ChainedClassMeta::get('Some\Class')->getChainInterfaces()
```

Returns the interfaces that were added in `Some\Class`'s chain by all chained classes. (But not the inherited ones).

###  Health Score

26

—

LowBetter than 43% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity8

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~54 days

Recently: every ~20 days

Total

9

Last Release

1483d ago

Major Versions

1.1.0 → 2.0.02022-01-24

### Community

Maintainers

![](https://www.gravatar.com/avatar/fa0651da2cbbda200758a877f6d4073240292b3f8fcc6bc125ff96b2c97b1b79?d=identicon)[dinu](/maintainers/dinu)

---

Top Contributors

[![exteon](https://avatars.githubusercontent.com/u/3275209?v=4)](https://github.com/exteon "exteon (25 commits)")

---

Tags

frameworkmodulesmultiple-inheritancephpplugins

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/exteon-chaining-class-resolver/health.svg)

```
[![Health](https://phpackages.com/badges/exteon-chaining-class-resolver/health.svg)](https://phpackages.com/packages/exteon-chaining-class-resolver)
```

###  Alternatives

[silverstripe/framework

The SilverStripe framework

7213.5M2.5k](/packages/silverstripe-framework)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.0k15.4k](/packages/prestashop-prestashop)[spiral/framework

Spiral, High-Performance PHP/Go Framework

2.0k1.8M57](/packages/spiral-framework)[phalcon/devtools

This tools provide you useful scripts to generate code helping to develop faster and easy applications that use with Phalcon framework.

1.3k2.0M54](/packages/phalcon-devtools)[cakephp/bake

Bake plugin for CakePHP

11211.2M156](/packages/cakephp-bake)[contao/core-bundle

Contao Open Source CMS

1231.6M2.3k](/packages/contao-core-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
