purpleharmonie / dependency-injection
Purple Container 是一个轻量级的依赖注入容器,用于管理 PHP 应用中的类依赖和执行依赖注入。
Requires (Dev)
- monolog/monolog: ^3.6
- phpunit/phpunit: ^11.1
- symfony/finder: ^7.1
- symfony/yaml: ^7.1
This package is not auto-updated.
Last update: 2024-09-21 09:35:29 UTC
README
目录
介绍
Purple 是一个全面的 PHP 框架,旨在为构建可扩展和可维护的应用程序提供一个强大的基础。它具有强大的依赖注入容器、模块化架构以及各种工具来提高开发效率。灵感来自像 Symfony 这样的流行框架,但具有自己的独特功能。它提供了一种灵活且可扩展的方式来管理 PHP 应用中的依赖和服务。注意:仍在开发中,但可以在应用程序中使用。
安装
您可以通过 Composer 安装 PHP 容器。在您的终端中运行以下命令
composer require purpleharmonie/dependency-injection
核心组件
依赖注入容器
Purple 框架的核心,负责管理服务实例化和依赖。
特性
- 服务注册和检索
- 自动装配
- 懒加载
- 作用域服务(asGlobal,asShared)
- 别名
- 标记
- 服务装饰
- 中间件
内核
管理应用程序的生命周期并引导框架。
特性
- 环境管理
- 调试模式
- 使用包注册,设置 $configDir 路径。bunlde 类应按数组组织,并且每个类都扩展了 bunlde 接口。在 examples 文件夹中找到一个示例包,这是在 $config 路径中创建 bunlde 注册表的。它将在内核引导期间加载到服务中
<?php // config/bundles.php return [ Purple\Core\Services\Bundles\ExampleBundle::class, ];
- 容器初始化
- 错误处理设置
服务管理
服务注册
set($id, $class)
:注册服务alias($alias, $service)
:为服务创建别名addTag($id, $attributes)
:标记服务配置服务可见性:
(asGlobal,asShared)asGlobal(true)
/asGlobal(true)
:设置服务可见性。默认为 falseasShared(true)
:将服务标记为共享(单例)setLazy(true)
:为服务启用/禁用懒加载
服务配置
addArgument($id, $argument)
:向服务添加参数。arguments($id, array $arguments)
:为服务设置所有参数addMethodCall($id, $method, $arguments)
:向服务添加方法调用factory($id, $factory)
:为服务设置工厂。内联工厂不被缓存,并且只支持 PHP,不支持 yamlimplements($name, $interface)
:指定接口实现extends($id, $abstract)
:将服务设置为扩展另一个autowire($id)
:使用类型提示启用服务自动装配annotwire($id)
:使用注释启用服务自动装配
<?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)
:条件绑定服务callable($abstract, callable $factory)
:使用可调用工厂注册服务
事件系统
允许在应用程序生命周期的各个点上挂钩。
特性
- 事件分发
- 按优先级注册监听器
//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()); <?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; }
配置管理
- 特定环境的配置
- 参数管理
- 配置文件加载(YAML,PHP)
- 支持 .env 文件
- 程序化服务定义
// 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');
缓存
- 服务图缓存
- 缓存容器编译
- 可配置的缓存类型(Redis、文件、内存)
- 缓存大小和驱逐策略设置
服务发现
基于目录结构和命名空间自动发现和注册服务。
//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}'
服务跟踪
监控服务使用并实现基本垃圾回收
- 跟踪服务使用频率和最后使用时间
- 实现可配置的垃圾回收机制
编译器遍历
在编译前修改容器配置的自定义逻辑。下面是使用示例,以及示例文件夹
特性
- 基于优先级的执行
- 访问容器以进行修改
错误处理
可定制的错误处理和日志系统。
本文档提供了Purple框架的主要功能和组件概述。有关详细使用说明和高级配置,请参阅特定组件文档或代码示例。
使用示例
EventDispatcherInterface
定义了分发事件和添加监听器的方法。示例实现允许添加具有优先级的监听器并将事件分发到所有已注册的监听器。
KernelExtensionInterface
定义了一个加载方法,扩展使用该方法来配置容器。示例DatabaseExtension展示了如何设置数据库连接服务。
bindIf()的使用
允许设置默认实现,如果已定义则不会被覆盖。在示例中,我们将FileLogger设置为默认值,随后尝试绑定ConsoleLogger不会覆盖它。
callable()的使用
允许使用可调用工厂定义服务。在示例中,我们定义了一个依赖容器中其他服务的Mailer服务。
// 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'));
别名使用
现在在Container中的setAlias()方法会自动将别名设置为公共的。
// 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
中间件接口
<?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, ], ];