Implementing Observer, Strategy, and Adapter Design Patterns in PHP for Modular Solutions

Implementing Observer, Strategy, and Adapter Design Patterns in PHP for Modular Solutions

Implementing Observer, Strategy, and Adapter Design Patterns in PHP for Modular Solutions

Observer Pattern

undefined

This diagram illustrates the relationships between the Observer interface, the Subject class, and the concrete observer classes SendMailObserver and LogMailObserver. The Subject class maintains a list of observers and notifies them of state changes, while the concrete observer classes implement the update method to perform specific actions when notified.

// To enforce update on concrete observers (LOD = 2/2)
interface Observer
{
// For concrete observers to perform desired action
public function update(string $address, string $subject, string $message): void;
}

// For concrete observers to extend so it performs update on setState
// (CC = 1 + 1 + 1; LOD = 4/4)
class Subject
{
public string $mailAddress; // Could add getters and setters and make them private
public string $mailSubject;
public string $mailMessage;
public array $observers = [];

// To construct array of observers so they are updated at once
public function attach(Observer $observer): void
{
array_push($this->observers, $observer);
}

// To set needed parameters by update and trigger it
public function setState(string $address, string $subject, string $message): void
{
$this->mailAddress = $address;
$this->mailSubject = $subject;
$this->mailMessage = $message;
$this->notify();
}

// To call update with parameters from setState
public function notify(): void
{
foreach ($this->observers as $observer) {
$observer->update($this->mailAddress, $this->mailSubject, $this->mailMessage);
}
}
}

// To send mail on Subject state change (CC = 1; LOD = 2/2)
class SendMailObserver extends Subject implements Observer
{
// To send mail after attached by Subject and on setState of Subject
public function update(string $address, string $subject, string $message): void
{
mail($address, $subject, $message);
}
}

// To log mail in ElasticSearch on Subject state change (CC = 1; LOD = 2/2)
class LogMailObserver extends Subject implements Observer
{
// To log mail after attached by Subject and on setState of Subject
public function update(string $address, string $subject, string $message): void
{
// Previously got ElasticSearch Client in $client variable
$params = [
'index' => 'my_index',
'id' => 'my_id',
'body' => ['address' => $address, 'subject' => $subject, 'message' => $message]
];
$client->index($params);
}
}

// To perform action with send mail and log mail (CC = 1; LOD = 3/3)
class Module
{
public function action(): void
{
// Some code previously that defines dependency injection container
// Some code previously that defines $address, $subject, $message

$container->Subject->attach($container->SendMailObserver);
$container->Subject->attach($container->LogMailObserver);
$container->Subject->setState($address, $subject, $message);
}
}

Strategy Pattern

undefined

This diagram represents the relationships between the Logger interface, the LogContext class, the concrete DbLogger and FileLogger classes, and the Module class that uses the LogContext to log information using different strategies.

// To enforce log on loggers
interface Logger
{
// To log info
public function log(array $params): void;
}

// To log info based on setLogger
class LogContext
{
private Logger $logger;

// To set logger to be used by log method
public function setLogger(Logger $logger): void
{
$this->logger = $logger;
}

// To log by calling desired logger algorithm
public function log(array $params): void
{
$this->logger->log($params);
}
}

// To log info to database called by LogContext
class DbLogger implements Logger
{
// To log params to database
public function log(array $params): void
{
// Previously got dependency injection container $container

$connection = pg_connect(
"host={$container->getParameter('host')} " .
"dbName={$container->getParameter('db_name')} " .
"user={$container->getParameter('username')} " .
"password={$container->getParameter('password')}"
);
pg_insert($connection, "log_info", $params);
}
}

// To log to file called by LogContext
class FileLogger implements Logger
{
public function log(array $params): void
{
// Previously got dependency injection container $container

$logFilePathAndName = $container->getParameter('log_file');
$current = file_get_contents($logFilePathAndName);
$current .= json_encode($params);
file_put_contents($logFilePathAndName, $current);
}
}

// To make some action and log info to DB and file
class Module
{
// To log info to DB and file using LogContext
public function action(): void
{
// Previously got dependency injection container $container
// Previously performed actions that defined $params

$container->LogContext->setLogger(
$container->DbLogger
)->log($params);

$container->LogContext->setLogger(
$container->FileLogger
)->log($params);
}
}

Object Adapter Pattern

undefined

This diagram represents the relationships between the Database interface, the Client class, the PostgresqlDatabase interface, the PostgresqlClient class, the ClientAdapter class that adapts PostgresqlClient to Database, and the Module class that uses either a Client or a ClientAdapter to perform actions.

// To enforce CRUD operations methods
interface Database
{
// To connect to database
public function connect(): mixed;

// To select data from database
public function select(string $table, string $condition): mixed;
}

// To perform CRUD operations against MySQL
class Client implements Database
{
// To get MySQL connection
public function connect(): mysqli|false
{
// Previously got dependency injection container $container
return mysqli::connect(
$container->getParameter('hostname'),
$container->getParameter('username'),
$container->getParameter('password'),
$container->getParameter('database')
);
}

// To select data from MySQL
public function select(string $table, string $condition): mysqli_result|bool
{
$connection = $this->connect();
$query = "SELECT * FROM {$table} WHERE {$condition}";
return mysqli::execute_query($connection, $query);
}
}

// To enforce CRUD operations methods against PostgreSQL
interface PostgresqlDatabase
{
// To select data from PostgreSQL
public function pSelect(string $table, string $condition): PgSQL\Result|false;
}

// To perform CRUD operations against PostgreSQL
class PostgresqlClient implements PostgresqlDatabase
{
private PgSQL\Connection $connection;

// To create PostgreSQL connection
public function __construct()
{
// Previously got dependency injection container $container
if (!$this->connection) {
$this->connection = pg_connect(
"host={$container->getParameter('p_host')} " .
"dbname={$container->getParameter('p_database')} " .
"user={$container->getParameter('p_user')} " .
"password={$container->getParameter('p_password')}"
);
}
}

// To select data from PostgreSQL
public function pSelect(string $table, string $condition): PgSQL\Result|false
{
return pg_query(
$this->connection,
"SELECT * FROM {$table} WHERE {$condition};"
);
}
}

// To delegate Client methods to PostgresqlClient
class ClientAdapter implements Database
{
public PostgresqlClient $pClient;

// To make available PostgresqlClient
public function __construct(PostgresqlClient $pClient)
{
$this->pClient = $pClient;
}

// To get connection needed for compatibility
public function connect(): PgSQL\Connection|false
{
return $this->pClient;
}

// To delegate select operation to PostgresqlClient select
public function select(string $table, string $condition): PgSQL\Result|false
{
return $this->pClient->pSelect($table, $condition);
}
}

class Module
{
// To perform action and query database
public function action(Client|ClientAdapter $client): void
{
// Same code regardless if Client or PostgresqlClient
$result = $client->select("students", "age > 23");
// Perform the rest of actions with resulting data
}
}