Evolution of plugin systems

Agenda


  • What is a plugin system?
  • What do I expect from a plugin system?
  • Real world examples
  • Extensibility by design

About me


  • Daniel Nögel
  • Working for shopware since 2012
  • Experienced with highly extensible standard software

What is a plugin system?

The problem

  • Standard software
  • Behaviour should be changed

Requirements


  • Maintainability
  • Interoperability
  • Plug and Play (-ability)
  • Extensibility

Maintainability


Interoperability


Image by Chester Alvarez

Plug & play


Image by Juan Di Nella

Extensibility


Open box


Glas box


Black box


Open box example


PhpBB2 Mod


PhpBB3 Mod


Glas box example


The naive approach



class PriceCalculator
{
    private function doStuff() {};

    public function calculate($price, $tax)
    {
        $price = $price / $tax *  10;
        eval(
            $this->pdo->fetch('SELECT `code` FROM `ext` WHERE id = 'price_extension')
        );
        return $price;
    }

}

PhpBB 3.1 Extensions


How an event is implemented



$vars = array(
    'user_data',
    'user_lang_name',
    'user_date_format',
    'user_timezone',
    'lang_set',
    'lang_set_ext',
    'style_id',
);
extract($phpbb_dispatcher->trigger_event('core.user_setup', compact($vars)));

How an event is used



class main_listener implements EventSubscriberInterface
{

    static public function getSubscribedEvents()
    {
        return array(
            'core.user_setup' => 'load_language_on_setup',
        );
    }

    public function load_language_on_setup($event)
    {
        $event['user_lang_name'] = 'Peter Meyer';
    }
}

Other event systems

Approaches to extensibility

Event systems

General principal

  • A central event manager will dispatch events to all subscribed listeners
  • The application emits events with some context
  • Registered listeners will be notified and (maybe) change some results

Example: Events

Eventmanager


class EventManager
{
    public function subscribe($name, $callback)
    {
        $this->events[$name][] = $callback;
    }

    public function notify($name, Event $event)
    {
        foreach ($this->events[$name] as $subscriber) {
            $subscriber($event);
        }
    }
}

Subscribing to events


EventManager::subscribe(
    'cart.beforePurchase',
    function(BeforePurchaseEvent $event) {
        // logging $event->getItemId;
    }
);

EventManager::subscribe(
    'cart.afterPurchase',
    function(AfterPurchaseEvent $event) {
        // logging $event->getItemId;
    }
);

Emitting events


class Cart
{
    public function purchase($itemId, $customerId)
    {
        EventManager::notify(
            'cart.beforePurchase',
            new BeforePurchaseEvent($itemId, $customerId)
        );

        // original code, e.g. store order

        EventManager::notify(
            'cart.afterPurchase',
            new AfterPurchaseEvent($itemId, $customerId)
        );
    }
}

Pro & contra

  • Drop in solution
  • No structural changes required
  • Easy to maintain
  • Returning things?

AOP

General principal

  • AOP: Aspect oriented programming
  • Usually implemented using proxies
  • Allows "hooking" to any method on a class

Example: AOP

Proxy pattern

Application will work on this proxies, instead of the original class

class PriceCalculatorProxy extends PriceCalculator implements Proxy
{
    public function calculate($price, $tax)
    {
        list($price, $tax) = EventManager::notify(
            'PriceCalculator::before',
            [ $price, $tax ]
        );

        $result = parent::calculate($price, $tax)

        return EventManager::notify('PriceCalculator::after', $result);
    }
}

Pro & contra

  • Not native language feature (PHP)
  • Makes telling apart public and non-pulic API harder
  • Overal complexity increases
  • Hard to maintain as there are no explicit "extension" points

GoF OOP

General principal

  • OOP patterns address extensibility already
  • Addressing extensibility as an architectural need
  • Will separate the applications in small, interchangable objects

Example: Decorator

Interface + Implementation


interface PriceCalculator()
{
    public function calculate($price, $tax):
}

class CorePriceCalculator implements PriceCalculator
{
    public function calculate($price, $tax)
    {
        return $price / $tax * 10;
    }
}

Decorator


class PriceCalculatorDecorator implements PriceCalculator
{
    protected $decorated;

    public function __construct(PriceCalculator $decorated)
    {
        $this->decorated = $decorated;
    }
    public function calculate($price, $tax)
    {
        return $this->decorated->calculate($price, $tax);
    }
}

Decorating

new PriceCalculatorDecorator(
    new CorePriceCalculator()
);

Nesting decorators

Various calculators can be nested

new AclDecorator(
    new LogginDecorator(
        new PriceCalculatorDecorator(
            new CorePriceCalculator()
        )
    )
);

Pro & contra

  • Clear API
  • Enforces leightweight interfaces
  • Nesting of decorators is possible
  • Even small interface changes (e.g. new optional argument) will break implementations

Roundup

What we've looked at

  • Open box / patches
  • Event system
  • AOP
  • OOP

Extensibility as an architectural need

Image by Has Bonk

Extensibility as a cross cutting concern

Image by Irina Blok

Any questions?

Thank you