Skip to content

Instantly share code, notes, and snippets.

@SoftCreatR
Created November 19, 2024 01:48
Show Gist options
  • Save SoftCreatR/5c599353cddd24b3f9c95b7e5a457426 to your computer and use it in GitHub Desktop.
Save SoftCreatR/5c599353cddd24b3f9c95b7e5a457426 to your computer and use it in GitHub Desktop.
This script serves as an exaggerated example of over-engineering a simple "Hello World" application in PHP. It incorporates numerous design patterns, interfaces, traits, and a rudimentary dependency injection container to demonstrate how complexity can be artificially introduced into a basic task.
<?php
/*
* Copyright by SoftCreatR.dev.
*
* License: https://softcreatr.dev/license-terms
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* The above copyright notice and this disclaimer notice shall be included in all
* copies or substantial portions of the Software.
*/
declare(strict_types=1);
/**
* Autoloader Registration
*
* Registers an autoload function to dynamically load classes based on their
* namespaces and directory structures. This autoloader searches within the
* 'src' and 'lib' directories for class files corresponding to the fully
* qualified class names.
*
* @return void
*/
\spl_autoload_register(static function (string $class_name): void {
$paths = [
__DIR__ . '/src/' . \str_replace('\\', '/', $class_name) . '.php',
__DIR__ . '/lib/' . \str_replace('\\', '/', $class_name) . '.php',
];
/**
* Iterate through each path and include the class file if it exists.
*/
foreach ($paths as $path) {
if (\file_exists($path)) {
include $path;
return;
}
}
});
/**
* Interface MessageProviderInterface
*
* Defines the contract for any message provider within the application.
* Ensures that implementing classes can supply a message string.
*/
interface MessageProviderInterface
{
/**
* Retrieve the message to be processed and displayed.
*
* @return string The message content.
*/
public function getMessage(): string;
}
/**
* Interface FormatterInterface
*
* Establishes a standard for message formatting within the application.
* Any formatter must implement this interface to ensure consistent
* message transformation.
*/
interface FormatterInterface
{
/**
* Format the provided message string according to specific rules.
*
* @param string $message The original message to format.
* @return string The formatted message.
*/
public function format(string $message): string;
}
/**
* Interface LoggerInterface
*
* Outlines the essential logging functionalities required by the
* application. Implementing classes must provide mechanisms to log
* messages effectively.
*/
interface LoggerInterface
{
/**
* Log a message to a designated logging medium.
*
* @param string $message The message content to log.
* @return void
*/
public function log(string $message): void;
}
/**
* Trait LoggerTrait
*
* Provides logging capabilities to classes that utilize this trait.
* Facilitates the injection and usage of a LoggerInterface implementation.
*/
trait LoggerTrait
{
/**
* @var LoggerInterface The logger instance used for logging messages.
*/
protected LoggerInterface $logger;
/**
* Inject a logger instance into the using class.
*
* @param LoggerInterface $logger The logger implementation.
* @return void
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* Log a message using the injected logger instance.
*
* @param string $message The message to log.
* @return void
*/
public function logMessage(string $message): void
{
$this->logger->log($message);
}
}
/**
* Class SimpleLogger
*
* A straightforward implementation of the LoggerInterface that writes
* log messages to a file named 'app.log'. This logger appends each
* message with a newline character to ensure readability.
*/
class SimpleLogger implements LoggerInterface
{
/**
* Path to the log file where messages will be appended.
*
* @var string
*/
private string $logFilePath;
/**
* Constructor
*
* Initializes the log file path upon instantiation.
*/
public function __construct()
{
$this->logFilePath = __DIR__ . '/app.log';
}
/**
* Log a message by appending it to the log file.
*
* @param string $message The message content to log.
* @return void
*/
public function log(string $message): void
{
// Append the message to the log file with a newline.
\file_put_contents($this->logFilePath, $message . \PHP_EOL, \FILE_APPEND);
}
}
/**
* Abstract Class AbstractMessageProvider
*
* Serves as a foundational blueprint for message providers within the
* application. Enforces the implementation of message retrieval logic
* through the abstract method 'retrieveMessageParts'.
*/
abstract class AbstractMessageProvider implements MessageProviderInterface
{
/**
* Retrieve the constituent parts of the message.
*
* Implementing classes must define how the message parts are obtained.
*
* @return array An array of message segments.
*/
abstract protected function retrieveMessageParts(): array;
/**
* Assemble the complete message from its parts.
*
* @return string The fully constructed message.
*/
public function getMessage(): string
{
$parts = $this->retrieveMessageParts();
return \implode(' ', $parts);
}
}
/**
* Class HelloWorldProvider
*
* A concrete implementation of AbstractMessageProvider and FormatterInterface.
* Constructs the "Hello World" message by piecing together predefined segments
* and formatting the final output. Utilizes logging to record the formatting
* process.
*/
class HelloWorldProvider extends AbstractMessageProvider implements FormatterInterface
{
use LoggerTrait;
/**
* Retrieve the individual parts that constitute the "Hello World" message.
*
* @return array An array containing the greeting, subject, and punctuation.
*/
protected function retrieveMessageParts(): array
{
return [
$this->getGreeting(),
$this->getSubject(),
$this->getPunctuation(),
];
}
/**
* Get the greeting component of the message.
*
* @return string The greeting phrase.
*/
private function getGreeting(): string
{
return "Hello";
}
/**
* Get the subject component of the message.
*
* @return string The subject noun.
*/
private function getSubject(): string
{
return "World";
}
/**
* Get the punctuation component of the message.
*
* @return string The punctuation mark.
*/
private function getPunctuation(): string
{
return "!";
}
/**
* Format the complete message by capitalizing words and appending exclamation marks.
*
* Logs the formatted message for auditing purposes.
*
* @param string $message The original message to format.
* @return string The formatted message.
*/
public function format(string $message): string
{
$formatted = \ucwords(\strtolower($message));
$this->logMessage("Formatted message: {$formatted}");
return $formatted;
}
}
/**
* Class Application
*
* Orchestrates the overall flow of the application by managing message
* providers, handling dependency injection, and executing the primary
* runtime operations. Implements the Singleton design pattern to ensure
* a single instance throughout the application's lifecycle.
*/
class Application
{
/**
* @var Application|null The singleton instance of the Application.
*/
private static ?Application $instance = null;
/**
* @var MessageProviderInterface[] An array holding registered message providers.
*/
private array $providers = [];
/**
* @var Container The dependency injection container managing dependencies.
*/
private Container $container;
/**
* @var LoggerInterface The logger instance for logging application activities.
*/
private LoggerInterface $logger;
/**
* Private Constructor
*
* Initializes the Application with a dependency injection container and a logger.
*
* @param Container $container The dependency injection container.
* @param LoggerInterface $logger The logger implementation.
*/
private function __construct(Container $container, LoggerInterface $logger)
{
$this->container = $container;
$this->logger = $logger;
}
/**
* Retrieve the singleton instance of the Application.
*
* If an instance does not exist, it is created using the provided container and logger.
*
* @param Container $container The dependency injection container.
* @param LoggerInterface $logger The logger implementation.
* @return self The singleton Application instance.
*/
public static function getInstance(Container $container, LoggerInterface $logger): self
{
if (self::$instance === null) {
self::$instance = new self($container, $logger);
}
return self::$instance;
}
/**
* Register a message provider with the application.
*
* Resolves the provider via the container, injects the logger if necessary,
* and appends it to the providers array. Logs the registration event.
*
* @param string $providerInterface The interface name of the provider to register.
* @return void
*
* @throws InvalidArgumentException If no binding is found for the provider.
*/
public function registerProvider(string $providerInterface): void
{
$provider = $this->container->make($providerInterface);
if (\in_array(LoggerTrait::class, \class_uses($provider), true)) {
$provider->setLogger($this->logger);
}
$this->providers[] = $provider;
$this->logger->log("Registered provider: " . \get_class($provider));
}
/**
* Execute the main application flow.
*
* Iterates through each registered provider, formats messages if applicable,
* displays them, logs actions, and inspects providers via reflection.
*
* @return void
*/
public function run(): void
{
foreach ($this->providers as $provider) {
if ($provider instanceof FormatterInterface) {
$message = $provider->getMessage();
$formattedMessage = $provider->format($message);
$this->display($formattedMessage);
$this->logger->log("Displayed formatted message: {$formattedMessage}");
} else {
$message = $provider->getMessage();
$this->display($message);
$this->logger->log("Displayed message: {$message}");
}
$this->inspectProvider($provider);
}
}
/**
* Output the message to the standard output.
*
* @param string $message The message content to display.
* @return void
*/
private function display(string $message): void
{
echo $message;
}
/**
* Inspect the provider's class methods using reflection and log them.
*
* Provides introspection capabilities by examining the provider's
* methods and logging their names for auditing or debugging purposes.
*
* @param MessageProviderInterface $provider The provider to inspect.
* @return void
*/
private function inspectProvider(MessageProviderInterface $provider): void
{
$reflection = new ReflectionClass($provider);
$methods = $reflection->getMethods(
ReflectionMethod::IS_PRIVATE | ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PUBLIC
);
$methodNames = \array_map(static fn($method) => $method->getName(), $methods);
$this->logger->log("Provider " . \get_class($provider) . " has methods: " . \implode(', ', $methodNames));
}
}
/**
* Class Container
*
* A rudimentary dependency injection container that manages the binding and
* resolution of class dependencies. Facilitates loose coupling and
* adherence to the Dependency Inversion Principle by abstracting
* instantiation logic.
*/
class Container
{
/**
* @var array<string, callable> An associative array mapping abstract types to factory callables.
*/
private array $bindings = [];
/**
* Bind an abstract type to a concrete factory implementation.
*
* Registers a factory callable responsible for creating instances of the abstract type.
*
* @param string $abstract The abstract type or interface name.
* @param callable $factory The factory callable that returns an instance of the abstract type.
* @return void
*/
public function bind(string $abstract, callable $factory): void
{
$this->bindings[$abstract] = $factory;
}
/**
* Resolve an instance of the given abstract type.
*
* Invokes the bound factory callable to create and return an instance of the abstract type.
*
* @param string $abstract The abstract type or interface name to resolve.
* @return mixed An instance of the resolved type.
*
* @throws InvalidArgumentException If no binding is found for the abstract type.
*/
public function make(string $abstract)
{
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]($this);
}
throw new InvalidArgumentException("No binding found for {$abstract}");
}
}
/**
* Dependency Injection Container Setup
*
* Configures the container by binding interfaces to their respective concrete
* implementations. This setup ensures that dependencies are resolved correctly
* during application runtime.
*/
// Instantiate the container.
$container = new Container();
// Bind LoggerInterface to SimpleLogger.
$container->bind(LoggerInterface::class, static function (Container $c): SimpleLogger {
return new SimpleLogger();
});
// Bind MessageProviderInterface to HelloWorldProvider.
$container->bind(MessageProviderInterface::class, static function (Container $c): HelloWorldProvider {
return new HelloWorldProvider();
});
// Bind FormatterInterface to the implementation resolved via MessageProviderInterface.
$container->bind(FormatterInterface::class, static function (Container $c): FormatterInterface {
return $c->make(MessageProviderInterface::class);
});
/**
* Application Initialization and Execution
*
* Retrieves the necessary dependencies from the container, initializes the
* Application instance, registers the message provider, and executes the
* application flow.
*/
// Retrieve the logger instance from the container.
$logger = $container->make(LoggerInterface::class);
// Obtain the singleton Application instance, injecting the container and logger.
$app = Application::getInstance($container, $logger);
// Register the message provider using its interface to leverage the container's binding.
$app->registerProvider(MessageProviderInterface::class);
// Execute the application, resulting in the display and logging of the "Hello World" message.
$app->run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment