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.
Split up overall complexity into more manageable parts
Additional work and complexity upfront
Easier extensibility down the road
Quicker to adapt to changing requirements
PHP => Composer
Reusable Library = Package
Plugin = Package
Web Project = Package
// 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;
}
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
Pure code is almost never reusable
Code + Config makes the code 100% reusable
Reusable code is (hopefully) tested code
Inversion of Control (IoC)
Dependency Inversion Principle (D in SOLID)
Dependency Inversion Pattern (DIP)
Dependency Injection (DI)
Dependency Injection Container (DIC)
Business logic receives flow from generic framework
...as opposed to the business logic doing calls into reusable libraries
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.
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.
}
}
Centralize instatiation in one dedicated component
"Auto-wiring" => recursively instantiate dependencies
Aliases can map interfaces to implementations
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 );
}
}
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();
A study path about Clean Code, TDD, Legacy Code, Refactoring, Domain-Driven Design and Microservice Architecture
I'm Alain Schlesser.
Follow me on Twitter:
@schlesseraOr visit my Personal Blog:
www.alainschlesser.com