smeeckaert / di
This package is not auto-updated.
Last update: 2024-09-23 12:50:57 UTC
README
DI是PHP中依赖注入的一个实验。
实验的目标是允许在PHP类中实现最简单的DI,使其容易在现有类中实现,同时也与旧类协同工作。
没有神级容器,它基于约定。
它在PHP 7.0中工作,希望也能在PHP 5.6中工作。它与理解PHPDoc的任何IDE都能良好协作。
本文档中的所有代码示例都可以在demo文件夹中找到。
概述
使用\FW\DI\DI
特质即可在类中声明以利用DI。
// DBConnection.php <?php class DBConnection { use \FW\DI\DI; /** * Common protected parameters **/ protected $host; protected $user; protected $password; /** * DI contructor (see below) **/ public function __construct($host, $user, $password) { } public function isConnected() { return true; } }
现在您可以使用build
静态方法和with
方法(两种方式工作)来构建对象
<?php // As an array of named parameters $dbCon = DBConnection::build()->with(['host' => 'localhost', 'user' => 'root', 'password' => 'pwd']); // OR a list of chained parameters (first the value, then the name) $dbCon = DBConnection::build()->with('localhost', 'host')->with('root', 'user')->with('pwd', 'password'); var_dump($dbCon);
一旦构建了对象,您就不能通过with
方法更改任何受保护的或私有的属性。
<?php // WithLock.php class DBConnection { use \FW\DI\DI; protected $host; public function __construct($host) { } public function hello() { return 'hello'; } } $model = DBConnection::build()->with('localhost', 'host'); echo $model->hello() . "\n"; try { $model->with('test', 'host'); // This will fail echo $model->hello() . "\n"; } catch (Exception $e) { var_dump($e->getMessage()); }
构造函数和必需的属性
如果您想使属性成为必需的,您必须创建一个接受参数的__construct
方法,这些参数的名称必须与必需的属性匹配。
<?php // Orchard.php class Orchard { use \FW\DI\DI; protected $apple; protected $pear; protected $orange; public function __construct($apple) { } } try { var_dump(Orchard::build()->with(1, 'orange')); // Will generate an error since apple is a required property } catch (Exception $e) { var_dump($e->getMessage()); } var_dump(Orchard::build()->with(1, 'orange')->with(1, 'apple'));
参数类型提示
您可以在参数中添加一些类型提示,以防止注入错误的对象。
您只需将对象类的类作为对象属性的默认值即可。
(这是7.0版PHP中缺少属性类型提示的唯一方法)
<?php // TypeHint.php class DBConnection { use \FW\DI\DI; protected $host; protected $user; protected $password; public function __construct($host) { } } class DBExtend extends DBConnection { } class Model { use \FW\DI\DI; protected $connection = DBExtend::class; protected $table; public function __construct($connection) { } public function hello() { return 'hello'; } } $model = Model::build()->with(DBExtend::build()->with('localhost', 'host')); // it will automatically match the $connection parameter echo $model->hello() . "\n"; try { $model = Model::build()->with(DBConnection::build()->with('localhost', 'host')); // This will fail echo $model->hello() . "\n"; } catch (Exception $e) { var_dump($e->getMessage()); }
with
方法将自动检测传递给它的实例化对象的类型,并尝试将它们放置在正确的属性中。
但是,如果有两个或多个属性具有相同的类型或都是标量,您必须指定属性名称作为第二个参数。
自动构建
重复相同的默认参数很麻烦。
为了改变这一点,有一个自动构建工具。
它分为两步
- 首先,使用
\FW\DI\AutoBuild::register
方法注册您的方法和参数。 - 然后,调用构建对象的
auto
方法。
<?php // We register the DBConnection class in the AutoBuild \FW\DI\AutoBuild::register(DBConnection::class, ['host' => 'localhost']); // The Model_Post uses the AutoBuild to inject the dependancy $post = Model_Post::build()->auto();
自动构建是级联的,这意味着如果您的类依赖项已经注册,您不需要在类参数中添加它。
<?php // You can do that way, but we are adding an instanciated object into the AutoBuild mechanism \FW\DI\AutoBuild::register(DBConnection::class, ['host' => 'localhost', 'dependancy' => Dependancy::build()]); // Or you can register the dependancy first \FW\DI\AutoBuild::register(Dependancy::class, []); // And omit it in subsequent registrations \FW\DI\AutoBuild::register(DBConnection::class, ['host' => 'localhost']);
以下是自动构建的用法示例
<?php // AutoBuild.php class Dependancy { use \FW\DI\DI; } class DBConnection { use \FW\DI\DI; protected $host; protected $user; protected $password; protected $dependancy = Dependancy::class; public function __construct($host, $dependancy) { } public function getHost() { return $this->host; } } class Model { use \FW\DI\DI; protected $connection = DBConnection::class; protected $table; public function __construct($connection) { } public function getHost() { return $this->connection->getHost(); } } class Model_Post extends Model { protected $table = 'table'; } \FW\DI\AutoBuild::register(Dependancy::class, []); \FW\DI\AutoBuild::register(DBConnection::class, ['host' => 'localhost']); // $dependancy will be autoloaded since it's registered already try { $post = Model_Post::build()->auto(); var_dump($post->getHost()); } catch (Exception $e) { var_dump($e->getMessage()); }
您只能在自动构建中为类注册一组默认参数。
您可以通过在auto
方法之前使用with
来覆盖自动构建的默认参数;
<?php // AutoBuildOverride.php class Dependancy { use \FW\DI\DI; public $name; public function __construct($name) { } } class DBConnection { use \FW\DI\DI; public $host; public function __construct($host) { } } class Model { use \FW\DI\DI; public $connection = DBConnection::class; public $dependancy = Dependancy::class; public function __construct($connection, $dependancy) { } } \FW\DI\AutoBuild::register(Dependancy::class, ['name' => 'AutoName']); \FW\DI\AutoBuild::register(DBConnection::class, ['host' => 'localhost']); // $dependancy will be autoloaded since it's registered already try { $post = Model::build()->auto(); var_dump($post->connection->host); // We override the connection's parameter while still automatically building the Dependancy $overridenPost = Model::build()->with(DBConnection::build()->with('127.0.0.1', 'host'), 'connection')->auto(); var_dump($overridenPost->connection->host); } catch (Exception $e) { var_dump($e->getMessage()); }
注意:作为数组提供的所有AutoBuilder参数都是静态的,因此它们永远不会被GC清理。这对某些事物(如字符串、整数、文件名等)很好,但请避免在其中放置实例化对象。
如果您想实例化对象,您可以使用回调而不是数组。此回调必须返回与之前相同的数组。
<?php \FW\DI\AutoBuild::register(DBConnection::class, function () { return ['host' => 'localhost']; });
不可变性
您可以通过调用buildImmutable
而不是build
来构建不可变对象。
不可变对象的属性不能通过外部调用或内部调用进行更改。
<?php // Car.php class Car { use \FW\DI\DI; public $window = Window::class; public function __construct($window) { } public function setWindow($window) { $this->window = $window; } } class Window { use \FW\DI\DI; public $name; public function __construct($name) { } } $car = Car::buildImmutable()->with(Window::build()->with('win1', 'name')); var_dump($car->window->name); $carMutable = Car::build()->with(Window::build()->with('win2', 'name')); var_dump($carMutable->window->name); $carMutable->window = Window::build()->with('win3', 'name'); // Will work var_dump($carMutable->window->name); $newWindow = Window::build()->with('win4', 'name'); $carMutable->setWindow($newWindow); var_dump($carMutable->window->name); try { echo "Changing public property\n"; $car->window = Window::build(); // Will throw an error } catch (Exception $e) { var_dump($e->getMessage()); } try { echo "Using method\n"; $car->setWindow($newWindow); // Will throw an error } catch (Exception $e) { var_dump($e->getMessage()); } var_dump($car->window->name); // Will still be win1
或者,您可以使用buildSoftImmutable
方法。它的工作方式相同,但仅防止外部更改对象。
<?php // SoftImmutable.php class Car { use \FW\DI\DI; public $window = Window::class; public function __construct($window) { } public function setWindow($window) { $this->window = $window; } } class Window { use \FW\DI\DI; public $name; public function __construct($name) { } } $car = Car::buildSoftImmutable()->with(Window::build()->with('win1', 'name')); $newWindow = Window::build()->with('win4', 'name'); var_dump($car->window->name); try { $car->window = Window::build()->with('win2', 'name'); // Will throw an error } catch (Exception $e) { var_dump($e->getMessage()); } try { $car->setWindow($newWindow); // Will work in soft mode } catch (Exception $e) { var_dump($e->getMessage()); } var_dump($car->window->name); // Will be win 4
已知问题
尝试调试未构建的对象时会出现错误,这会导致致命错误,至少在PHP 7.0中是这样的。(检查demo/BugDebugInfo.php)。这个问题已在以下链接修复:http://git.php.net/?p=php-src.git;a=commit;h=2d8ab51576695630a7471ff829cc5ea10becdc0f
目前,由于类属性缺少类型提示,您无法将属性的默认值设置为实际类的名称。
工作原理
DI::build方法将返回一个装饰器实例,该实例继承DI特性。
装饰器表现得像对象,但阻止访问方法或属性。
每次通过with
调用在装饰器中更改属性时,它将调用装饰器构造器,以检查是否找到了所有对象必需的参数。
如果找到了所有必需的参数,它将实例化新对象,清除DI使用的默认值,并返回它。
当使用buildImmutable
或buildSoftImmutable
时,装饰器永远不会返回新对象,而是保持对象实例受保护,并将适当的调用转发到对象。
许可证
本项目发布在MIT许可证下。