Purple Container is a lightweight dependency injection container for managing class dependencies and performing dependency injection in PHP applications.
Purple is a comprehensive PHP framework designed to provide a robust foundation for building scalable and maintainable applications. It features a powerful dependency injection container, modular architecture, and various tools to enhance development productivity. Inspired by popular frameworks like Symfony but with its own unique features. It provides a flexible and extensible way to manage dependencies and services in PHP applications. NOTE STILL UNDER DEVELOPMENT BUT CAN BE USED IN APPLLICATIONS.
You can install PHP Container via Composer. Run the following command in your terminal:
composer require purpleharmonie/dependency-injection
The heart of the Purple framework, managing service instantiation and dependencies.
Features:
Manages the application's lifecycle and bootstraps the framework.
Features:
<?php
// config/bundles.php
return [
Purple\Core\Services\Bundles\ExampleBundle::class,
];
set($id, $class)
: Register a servicealias($alias, $service)
: Create an alias for a serviceaddTag($id, $attributes)
: Tag a serviceConfiguring service visibility:
(asGlobal, asShared)asGlobal(true)
/ asGlobal(true)
: Set service visibility. false by defaultasShared(true)
: Mark a service as shared (singleton)setLazy(true)
: Enable/disable lazy loading for a serviceaddArgument($id, $argument)
: Add an argument to a service.arguments($id, array $arguments)
: Set all arguments for a serviceaddMethodCall($id, $method, $arguments)
: Add a method call to a servicefactory($id, $factory)
: Set a factory for a service. inline factorie are not cached and also only php not yamlimplements($name, $interface)
: Specify an interface implementationextends($id, $abstract)
: Set a service to extend anotherautowire($id)
: Enable autowiring for a service using type hintsannotwire($id)
: Enable autowiring for a service using annotation
<?php
//services.php
return function ($containerConfigurator) {
$services = $containerConfigurator->services();
//define harmony service with argument
$services->set('harmony', Harmony::class)
->addArgument(new Reference('adapter'))
->addArgument(new Reference('sql_builder'))
->addArgument(new Reference('schema'))
->addArgument(new Reference('executor'))
//->addArgument('%harmonyConfig%')
->addMethodCall('initialize', [/*pass array of arguments*/]) //[@service,'%DB_ENV%', %PARAMETER%]
->asGlobal(true)
->asShared(true);
// Define a service using an inline factory
$services->set('database_connection', function ($container) {
$host = $container->getParameter('db.host');
$port = $container->getParameter('db.port');
$username = $container->getParameter('db.username');
$password = $container->getParameter('db.password');
$database = $container->getParameter('db.database');
return new DatabaseConnection($host, $port, $username, $password, $database);
})
->asGlobal(true)
->asShared(true);
// Define a service using an inline factory with dependencies
$services->set('user_repository', function ($container) {
$dbConnection = $container->get('database_connection');
return new UserRepository($dbConnection);
})
->asGlobal(false)
->asShared(true);
// You can also use arrow functions (PHP 7.4+) for more concise definitions
$services->set('logger', fn($container) => new Logger($container->getParameter('log_file')))
->asGlobal(true) //available within and outiside the container
->asShared(true); //either a shared instance or new instance per request
//Defining factory with classes .
$services->set('platform', PlatformFactory::class)
->factory([PlatformFactory::class, 'create']) // Factory class name and method that returns the service
//->addArgument('$DB_CONNECTION$')
->implements(DatabasePlatform::class) // handles the interface
->asGlobal(false)
->asShared(true)
->autowire();
$services->set('some_service', SomeService::class)
->asGlobal(false)->lazy()
->asShared(true)
->addMethodCall('doSomething',[]) // since the method is defined with arguments passed. it will be autorired. cos of autowire set on this service
->addTag(['example'])
->autowire(); //will use parameter type hinting to resolve the class
//with automatically autowire class and specified method using annotations.
$services->set('userManager', UserManager::class)
->asGlobal(true)
->asShared(true)
->addMethodCall('createUser',[])
->annotwire();
//arguments setting in bulk @service , env param %HOST% or passed param %parameter%
$services->set('xpressive', Xpressive::class)
->asGlobal(false)
->asShared(true)
->autowire();
->arguments(['@platform','@adapter','@executor']);
};
bindIf($abstract, $concrete)
: Conditionally bind a servicecallable($abstract, callable $factory)
: Register a service with a callable factoryAllows hooking into various points of the application lifecycle.
Features:
//event dispatcher boot
$eventDispatcher->addListener('kernel.pre_boot', function() {
echo "Kernel is about to boot!\n";
});
$eventDispatcher->addListener('kernel.post_boot', function() {
echo "Kernel has finished booting!\n";
});
Modular way to extend and configure the kernel and container.
Features:
$kernel->addExtension(new DatabaseExtension());
<?php
namespace Purple\Core\EventsExamples;
use Purple\Core\Services\Container;
use Purple\Core\Services\Interface\KernelExtensionInterface;
// Example implementation
class DatabaseExtension implements KernelExtensionInterface
{
public function load(Container $container): void
{
$container->set('database', function(Container $c) {
return new DatabaseConnection(
$c->getParameter('db_host'),
$c->getParameter('db_name'),
$c->getParameter('db_user'),
$c->getParameter('db_pass')
);
});
}
}
//the extension interface that extensions must implement
<?php
namespace Purple\Core\Services\Interface;
use Purple\Core\Services\Container;
interface KernelExtensionInterface
{
public function load(Container $container): void;
}
// Load environment variables
$kernel->loadEnv(__DIR__ . '/../.env');
//either one or both
// Load service configurations from a file (e.g., YAML)
$kernel->loadConfigurationFromFile(__DIR__ . '/../config/services.yaml');
// Define services in PHP
$kernel->loadConfigurationFromFile(__DIR__ . '/../public/service.php');
Automatic discovery and registration of services based on directory structure and namespaces.
//using type hinting
$kernel->autoDiscoverServices('../core/Db/Example', 'Purple\Core\Db\Example');
using annotations
$container->annotationDiscovery([
'namespace' => [
'Purple\\Core\\AnnotEx' => [
'resource' => __DIR__.'/../core/AnnotEx',
'exclude' => []
]
]
]);
//services file
//internal autodirectory scanning and add to services definitions
$containerConfigurator->discovery([
'Purple\Core\Db\Example\\' => [
'resource' => '../core/Db/Example/*',
'exclude' => ['../core/Db/Example/{DependencyInjection,Entity,Migrations,Tests}']
]
]);
//yaml services
discovery:
App\Directory\:
resource: '../core/Db/Example/*'
exclude: '../core/Db/Example/{DependencyInjection,Entity,Migrations,Tests}'
Monitors service usage and implements basic garbage collection:
Custom logic for modifying container configuration before it's compiled. view usage examples below and the examples folder
Features:
Customizable error handling and logging system.
This documentation provides an overview of the main features and components of the Purple framework. For detailed usage instructions and advanced configurations, please refer to the specific component documentation or code examples.
Defines methods for dispatching events and adding listeners. The example implementation allows adding listeners with priorities and dispatching events to all registered listeners.
Defines a load method that extensions use to configure the container. The example DatabaseExtension shows how you might set up a database connection service.
Allows setting a default implementation that won't be overwritten if already defined. In the example, we set a FileLogger as the default, and a subsequent attempt to bind a ConsoleLogger doesn't overwrite it.
Allows defining a service using a callable factory. In the example, we define a Mailer service that depends on other services from the container.
// Assuming we have a Container instance
$container = new Container(/* ... */);
// Using bindIf()
$container->bindIf('logger', function(Container $c) {
return new FileLogger($c->getParameter('log_file'));
});
// This won't overwrite the existing 'logger' binding
$container->bindIf('logger', function(Container $c) {
return new ConsoleLogger();
});
// Using callable()
$container->callable('mailer', function(Container $c) {
$transport = $c->get('mailer.transport');
$logger = $c->get('logger');
return new Mailer($transport, $logger);
});
// Later in the code, you can get these services
$logger = $container->get('logger'); // This will be a FileLogger
//original use
$services->set('mailer', Mailer::class);
// Define the decorator
$services->set('decorMailer', LoggingDecorator::class)
->addArgument(new Reference('mailer'))
->decorate('mailer');
//or alternatively
$services->set('decorMailer', LoggingDecorator::class)
->addArgument(new Reference('mailer'));
The setAlias() method in Container now automatically sets the alias as public.
// Using setAlias
$container->setAlias('app.database', DatabaseConnection::class);
// Binding interfaces to concrete implementations
$container->set('interfaceservicename', ConcreteUserRepositoryInterface::class);
$container->set('concreteclassforinterfaceservicename', ConcreteUserRepository::class);
$container->setAlias('interfaceservicename', 'concreteclassforinterfaceservicename');
// Quick alias chaining
$services->set('mailer', Mailer::class)->alias('public_mailer_service');
// Using aliases in service definitions
$container->set('another_service', AnotherService::class)
->addArgument(new Reference('app.user_repository'));
// Retrieving services using aliases
$dbConnection = $container->get('app.database');
$userRepo = $container->get(UserRepositoryInterface::class);
$mailer = $container->get('public_mailer_service');
// In your configuration
$container = new Container();
$configurator = new ContainerConfigurator($container);
// Add global middleware
$configurator->addGlobalMiddleware(new LoggingMiddleware());
$configurator->addGlobalMiddleware(new ProfilingMiddleware());
// Configure a service with specific middleware
$configurator->set('user_service', UserService::class)
->addServiceMiddleware(new ValidationMiddleware());
// Usage
$userService = $container->get('user_service');
// This will log creation, validate the service, and profile creation time
middleware interface
<?php
namespace Purple\Core\Services\Interface;
use Closure;
interface MiddlewareInterface
{
public function process($service, string $id, Closure $next);
}
// Usage example outside service files or index
$container = new Container();
$container->annotationDiscovery([
'namespace' => [
'App\\Core\\Db\\Example' => [
'resource' => '../core/Db/Example/*',
'exclude' => ['../core/Db/Example/{DependencyInjection,Entity,Migrations,Tests}']
]
]
]);
// example of annotation classes with contructor and method inject
<?php
namespace Purple\Core;
use Purple\Core\FileLogger;
use Purple\Core\Mailer;
use Purple\Core\SomeService;
#[Service(name: "userManager")]
class UserManager {
private FileLogger $logger;
public function __construct(#[Inject("@logger")] FileLogger $logger, $basic ="ghost") {
$this->logger = $logger;
}
public function createUser(#[Inject("@mailer")] Mailer $mailer,#[Inject("@some_service")] SomeService $some_service): bool {
echo "anot method is called ";
return false;
}
}
//example with property inject
class UserManager {
#[Inject("@logger")]
private FileLogger $logger;
#[Inject("@mailer")]
private Mailer $mailer;
public function __construct(#[Inject("@database")] Database $db) {
$this->db = $db;
}
// ... other methods ...
}
//services.php
return function ($containerConfigurator) {
$configPath = __DIR__ . '/../config/database.php';
$services = $containerConfigurator->services();
$middleware = $containerConfigurator->middlewares();
$defaults = $containerConfigurator->defaults();
//takes a string either hints to type hints autowire for all services by default or use annots for annotations
//$defaults->wireType("annots");
//when its set to true, the container will use annotations along with wiretype
//$defaults->setAnnotwire("annots");
//when its set to true, the continue will use parameter hints for autowring along with wiretype
//$defaults->setAutowire("annots");
//by defaults all service have asGlobal false hence private, only alias makes them public or asGlobal method
//by configuring this to true all methods become public by default
//$defaults->setAsGlobal("annots");
$services->parameters('db.host', 'localhost');
$services->parameters('db.port', 3306);
$services->parameters('db.username', 'root');
$services->parameters('db.password', 'secret');
$services->parameters('db.database', 'my_database');
$services->parameters('db.charset', 'utf8');
$services->parameters('db.options', []);
$services->parameters('migrations_dir', '/path/to/migrations');
$services->parameters('migrations_table', 'migrations');
$services->parameters('column', 'default_column');
$services->parameters('config.database_path', $configPath);
$services->parameters('harmonyConfig', [
'setting1' => 'value1',
'setting2' => 'value2',
// Other configuration parameters...
]);
//internal autodirectory scanning and add to services definitions
$containerConfigurator->discovery([
'Purple\Core\Db\Example\\' => [
'resource' => '../core/Db/Example/*',
'exclude' => ['../core/Db/Example/{DependencyInjection,Entity,Migrations,Tests}']
]
]);
// Add middleware
// Add global middleware
$middleware->addGlobalMiddleware(new LoggingMiddlewares());
//for example use to be deleted
$services->parameters('session_data', "dfgfhgfgghjkhjkh");
//defining event dispatcher
$services->set('event_dispatcher', EventDispatcher::class) ->asGlobal(false)
->asShared(true);
//define harmony service
$services->set('harmony', Harmony::class)
->addArgument(new Reference('adapter'))
->addArgument(new Reference('sql_builder'))
->addArgument(new Reference('schema'))
->addArgument(new Reference('executor'))
//->addArgument('%harmonyConfig%')
->addMethodCall('initialize', [])
->asGlobal(true)
->asShared(true);
//defining adapter through factory
// Define a service that uses a factory method to create an instance
$services->set('adapter', AdapterFactory::class)
->factory([AdapterFactory::class, 'create'])
->asGlobal(false)
->asShared(true)
//->addArgument('$DB_CONNECTION$')
//->addArgument('$DB_DATABASE$')
//->addArgument('$DB_HOST$')
//->addArgument('$DB_USERNAME$')
//->addArgument('$DB_PASSWORD$')
//->addArgument('$DB_CHARSET$')
->implements(DatabaseAdapterInterface::class)
->autowire()
->asGlobal(false)
->asShared(true);
//defining service sqlbuilder
$services->set('sql_builder', QueryBuilderFactory::class)
->factory([QueryBuilderFactory::class, 'create'])
//->addArgument(new Reference('adapter'))
//->addArgument(new Reference('executor'))
->autowire()
->asGlobal(false)
->asShared(true);
//defining platform
$services->set('platform', PlatformFactory::class)
->factory([PlatformFactory::class, 'create'])
//->addArgument('$DB_CONNECTION$')
->implements(DatabasePlatform::class)
->asGlobal(false)
->asShared(true)
->autowire();
// defining schema service
$services->set('schema', Schema::class)
->addArgument(new Reference('adapter'))
->addArgument(new Reference('sql_builder'))
->addArgument(new Reference('event_dispatcher'))
->asGlobal(false)
->asShared(true);
//defining executor
$services->set('executor', QueryExecutor::class)
->addArgument(new Reference('adapter'))
->addArgument(new Reference('event_dispatcher'))
->asGlobal(true)
->asShared(true);
//defining migration manager
$services->set('migration_manager', MigrationManager::class)
->addArgument(new Reference('harmony'))
->addArgument('%migrations_dir%')
->addArgument('%migrations_table%')
->asGlobal(false)
->asShared(true);
//defining xpressive fluent query builder
$services->set('xpressive', Xpressive::class)
->asGlobal(false)
->asShared(true)
->autowire();
//->arguments(['@platform','@adapter','@executor']);
//example use
$services->set('mailer', Mailer::class)
->asGlobal(false)
->asShared(true);
// Define the decorator
$services->set('decorMailer', LoggingDecorator::class)
->addArgument(new Reference('mailer'))
->decorate('mailer');
$services->set('database', DbaseConnection::class) ->asGlobal(false)
->asShared(true)->setAlias('database')->addTag(['example']);
$services->set('logger', FileLogger::class)
->addArgument(new Reference('mailer'))
->asGlobal(true)
->asShared(true)
->lazy()
->addServiceMiddleware(new LoggingMiddlewares());
$services->set('some_service', SomeService::class)->asGlobal(false)->lazy()
->asShared(true)->addMethodCall('doSomething',[]) ->addTag(['example']) ->autowire();
$services->set('userManager', UserManager::class)
->asGlobal(true)
->asShared(true)
->addMethodCall('createUser',[])
->annotwire();
// Finalize and detect circular dependencies
$containerConfigurator->finalize();
};
// services.yaml
_defaults:
setAsGlobal: true
setAutowire: false
setAnnotwire: false
wireType: hints
addGlobalMiddleware: Purple\Core\MiddlewareExamples\LoggingMiddlewares
parameters:
db.host: '%DB_HOST%'
db.port: '%DB_PORT%'
db.username: '%DB_USERNAME%'
db.password: '%DB_PASSWORD%'
db.database: '%DB_DATABASE%'
db.charset: '%DB_CHARSET%'
db.options: []
migrations_dir: '/path/to/migrations'
migrations_table: 'migrations'
column: 'default_column'
config.database_path: '%config.database_path%'
harmonyConfig:
setting1: 'value1'
setting2: 'value2'
services:
event_dispatcher:
class: Purple\Libs\Harmony\Events\EventDispatcher
harmony:
class: Purple\Libs\Harmony\Harmony
arguments:
- '@adapter'
- '@sql_builder'
- '@schema'
- '@executor'
method_calls:
- [initialize, []]
asGlobal: true
asShared: true
adapter:
factory: [Purple\Libs\Harmony\Factories\AdapterFactory, create]
arguments:
- '%DB_CONNECTION%'
- '%DB_DATABASE%'
- '%DB_HOST%'
- '%DB_USERNAME%'
- '%DB_PASSWORD%'
- '%DB_CHARSET%'
implements: Purple\Libs\Harmony\Interface\DatabaseAdapterInterface
sql_builder:
factory: [Purple\Libs\Harmony\Factories\QueryBuilderFactory, create]
arguments:
- '@adapter'
- '@executor'
platform:
factory: [Purple\Libs\Harmony\Factories\PlatformFactory, create]
arguments:
- '%DB_CONNECTION%'
implements: Purple\Libs\Harmony\Interface\DatabasePlatform
schema:
class: Purple\Libs\Harmony\Schema
arguments:
- '@adapter'
- '@sql_builder'
- '@event_dispatcher'
executor:
class: Purple\Libs\Harmony\QueryExecutor
arguments:
- '@adapter'
- '@event_dispatcher'
migration_manager:
class: Purple\Libs\Harmony\MigrationManager
arguments:
- '@harmony'
- '%migrations_dir%'
- '%migrations_table%'
xpressive:
class: Purple\Libs\Harmony\Xpressive
autowire: true
tags: ['database']
mailer:
class: Purple\Core\Mailer
database:
class: Purple\Core\DbaseConnection
alias: database
tags: ['example']
logger:
class: Purple\Core\FileLogger
arguments:
- '@mailer'
asGlobal: true
asShared: true
addServiceMiddleware: Purple\Core\MiddlewareExamples\LoggingMiddlewares
some_service:
class: Purple\Core\SomeService
method_calls:
- [doSomething, []]
tags: ['example']
autowire: true
#add services from files scanning and adding tagging
discovery:
App\Directory\:
resource: '../core/Db/Example/*'
exclude: '../core/Db/Example/{DependencyInjection,Entity,Migrations,Tests}'
//index.php
// Define the cache configuration
$cacheType = 'memory'; // or 'file', or 'memory' or 'redis'
$cacheConfig = [
'redis' => [
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
],
'maxSize' => 1000,
'evictionPolicy' => 'LRU'
];
// Create the cache instance using the factory
$cache = CacheFactory::createCache($cacheType, $cacheConfig);
// Initialize the DI container with the log path using monolog
$logFilePath = __DIR__ . '/../bootstrap/logs/container.log';
// Initialize the DI container with the log path using monolog
$logFilePath = __DIR__ . '/../bootstrap/logs/container.log';
$configDir = __DIR__ . '/../config';
$resourceDir = __DIR__ . '/../resources';
$eventDispatcher = new EventDispatcher();
// Create the kernel
$kernel = new Kernel('prod', false, $logFilePath, $cache, $configDir, $resourceDir, $eventDispatcher );
// Load environment variables
$kernel->loadEnv(__DIR__ . '/../.env');
// Load service configurations from a file (e.g., YAML)
$kernel->loadConfigurationFromFile(__DIR__ . '/../config/services.yaml');
// Define services in PHP
$kernel->loadConfigurationFromFile(__DIR__ . '/../public/service.php');
//event dispatcher boot
$eventDispatcher->addListener('kernel.pre_boot', function() {
echo "Kernel is about to boot!\n";
});
$eventDispatcher->addListener('kernel.post_boot', function() {
echo "Kernel has finished booting!\n";
});
//$kernel->addExtension(new DatabaseExtension());
//auto discovery of services feature outside the yaml and php file
$kernel->autoDiscoverServices('../core/Db/Example', 'Purple\Core\Db\Example');
// Add any compiler passes
$kernel->addCompilerPass(new CustomCompilerPass('exampletag','examplemethod'));
// Boot the kernel and get the container
$kernel->boot();
$container = $kernel->getContainer();
$container->annotationDiscovery([
'namespace' => [
'Purple\\Core\\AnnotEx' => [
'resource' => __DIR__.'/../core/AnnotEx',
'exclude' => []
]
]
]);
//get service by tag
$databaseServices = $container->getByTag('example');
//get all services with a tag name
$databaseServices = $container->findTaggedServiceIds('purple.core.db.example.autowired');
// Retrieve and use services as usual
$harmony = $container->get('harmony');
$schema = $container->get('schema');
//example compiler pass
<?php
namespace Purple\Core\Services\CompilerPass;
use Purple\Core\Services\Container;
use Purple\Core\Services\Interface\CompilerPassInterface;
use Purple\Core\Services\ContainerConfigurator;
use Purple\Core\Services\Kernel\PassConfig;
use Purple\Core\Services\Reference;
class CustomCompilerPass implements CompilerPassInterface
{
private $tagName;
private $methodName;
public function __construct(string $tagName, string $methodName)
{
$this->tagName = $tagName;
$this->methodName = $methodName;
}
/**
* Modify the container here before it is dumped to PHP code.
*/
public function process(ContainerConfigurator $containerConfigurator): void
{
// Example: Tag all services with a specific interface
$taggedServices = $containerConfigurator->findTaggedServiceIds('example');
//print_r( $taggedServices);
foreach ($taggedServices as $id => $tags) {
// Set the currentservice
$containerConfigurator->setCurrentService($tags);
echo $tags;
// Add a method call to each tagged service
$containerConfigurator->addMethodCall('setTester', [new Reference('logger')]);
// You can also modify other aspects of the service definition here
}
}
/**
* Get the priority of this compiler pass.
*
* @return int The priority (higher values mean earlier execution)
*/
public function getPriority(): int
{
// This compiler pass will run earlier than default priority passes
return 10;
}
/**
* Get the type of this compiler pass.
*
* @return string One of the TYPE_* constants in Symfony\Component\DependencyInjection\Compiler\PassConfig
*/
public function getType(): string
{
// This pass runs before optimization, allowing it to modify service definitions
return PassConfig::TYPE_BEFORE_OPTIMIZATION;
}
}
//config_prod.php
<?php
return [
'host' => 'localhost',
'database' => 'games',
'username' => 'root',
'password' => '',
'driver' => 'mysql',
'cache' => [
'driver' => 'redis',
'host' => 'redis.example.com',
'port' => 6379,
],
'debug' => false,
'log_level' => 'error',
// You can even use environment variables or conditionals
'api_key' => getenv('API_KEY') ?: 'default_api_key',
'feature_flags' => [
'new_feature' => PHP_VERSION_ID >= 70400,
],
];