Chrome for Android fix.

Structuring Larger OOP Plugins

Alain Schlesser
Software Engineer & WordPress Consultant

The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.

Modularity

Monolithic Codebase

Monolithic Codebase

Modular Codebase

Modular Codebase

Modular Codebase

Modular Codebase

Modular Codebase

Embrace Modularity

  • Split up overall complexity into more manageable parts

  • Additional work and complexity upfront

  • Easier extensibility down the road

  • Quicker to adapt to changing requirements

Package Management

Package Management

Composer at the Plugin level

Composer at the Site level


                // Make sure the autoloader file exists before loading it.
                // It might have been created at a higher level instead.
                $autoloader = __DIR__ . '/vendor/autoloader.php';
                if ( is_readable( $autoloader ) ) {
                    require_once $autoloader;
                }
            

Architectural Layers

Architectural Layers

  • Think of your web site/application as a series of interacting layers

  • Enforce the separation between layers

  • Higher-level layers depend on lower-level layers

  • Be conscious about crossing layer boundaries

Example of a Layered Architecture

Example of a Layered Architecture

Configuration

Configuration

  • Pure code is almost never reusable

  • Code + Config makes the code 100% reusable

  • Reusable code is (hopefully) tested code

Example of a Configurable Object

Example of a Configurable Object

Example of a Configurable Object

Example of a Configurable Object

Control & Coupling

Control & Coupling

  • Inversion of Control (IoC)

  • Dependency Inversion Principle (D in SOLID)

  • Dependency Inversion Pattern (DIP)

  • Dependency Injection (DI)

  • Dependency Injection Container (DIC)

Inversion of Control (IoC)

  • Business logic receives flow from generic framework

    ...as opposed to the business logic doing calls into reusable libraries

Dependency Inversion Principle (D in SOLID)

  • High-level modules should not depend on low-level modules.

    Both should depend on abstractions.

  • Abstractions should not depend on details.

    Details should depend on abstractions.

Dependency Inversion Pattern (DIP)

Dependency Inversion Pattern (DIP)

Dependency Inversion Pattern (DIP)

Dependency Inversion Pattern (DIP)

Dependency Injection (DI)

  • The concept of passing in dependencies from the outside code, instead of fetching them from within the code that needs them.


                class MySQLDatabase {
                    public function get( string $key ): string { /* [...] */ };
                }
            

                class SomeService {
                    public function do_something( string $key ) {
                        $database = new MySQLDatabase();
                        $data = $database->get( 'lyrics.hello_dolly' );
                        // Do something with the data.
                    }
                }
            

                class MySQLDatabase {
                    public function get( string $key ): string { /* [...] */ };
                }
            

                class SomeService {
                    private $database;
                    public function __construct( MySQLDatabase $database ) {
                        $this->database = $database;
                    }
                    public function do_something( string $key ) {
                        $data = $this->database->get( 'lyrics.hello_dolly' );
                        // Do something with the data.
                    }
                }
            

Dependency Injection Container (DIC)

  • Centralize instatiation in one dedicated component

  • "Auto-wiring" => recursively instantiate dependencies

  • Aliases can map interfaces to implementations

Code Against Interfaces

Code Against Interfaces

  • Code against interfaces, not implementations

  • Decouples your code

  • Let's you flexibly extend or replace implementations


                // Implementation to fetch the number of stars remotely.
                class GithubRemoteClient {
                    public function get_stars( string $repo ): int { /* [...] */ };
                }

                // Renderer that renders a display of the number of stars in HTML.
                class StarRenderer {
                    private $github;
                    public function __construct( GithubRemoteClient $github ) {
                        $this->github = $github;
                    }
                    public function render() {
                        $stars = $this->github->get_stars( $this->repo );
                    }
                }
            

                // Interface to fetch the number of stars.
                interface StarsClient {
                    public function get_stars( string $repo ): int;
                }
                // Implementation to fetch the number of stars remotely.
                class GithubRemoteClient implements StarsClient {
                    public function get_stars( string $repo ): int { /* [...] */ };
                }
                // Renderer that renders a display of the number of stars in HTML.
                class StarRenderer {
                    private $stars_client;
                    public function __construct( StarsClient $stars_client ) {
                        $this->stars_client = $stars_client;
                    }
                    public function render() {
                        $stars = $this->stars_client->get_stars( $this->repo );
                    }
                }
            

What's the actual benefit?

  • Requirements change:

    "Remote fetching of stars needs to be cached for 1 hour"

  • Where to put this change?

    Both the GitHub remote client and the renderer might be external packages

  • Power of coding against interfaces!

    => Create a new CachedStarsClient Decorator class


                // Caching decorator as a StarsClient implementation.
                class CachedStarsClient implements StarsClient {
                    private $client;
                    private $cache;
                    public function __construct( StarsClient $client, Cache $cache ) {
                        $this->client = $client;
                        $this->cache  = $cache;
                    }
                    public function get_stars( string $repo ): int {
                        $stars = $this->cache->get( $repo );
                        if ( ! $stars ) {
                            $stars = $this->client->get_stars( $repo );
                            $this->cache->put( $repo, $stars );
                        }
                        return $stars;
                    };
                }
            

                $injector->alias( StarsClient::class, GithubRemoteClient::class );
            

                $renderer = $injector->make( StarRenderer::class );
                echo $renderer->render();
            

                $injector->alias( Cache::class, RedisCache::class );

                $injector->delegate( StarsClient::class, function( $injector ) ) {
                    $github = $injector->make( GithubRemoteClient::class );
                    return $injector->make( CachedStarsClient::class, [ $github ] );
                }
            

                $renderer = $injector->make( StarRenderer::class );
                echo $renderer->render();
            

What's Next ?

Sample Plugin

Study Path

Questions ?

I'm Alain Schlesser.


Follow me on Twitter:

  @schlessera

Or visit my Personal Blog:

   www.alainschlesser.com