此包最新版本(dev-master)没有可用的许可信息。

dev-master 2016-09-15 15:18 UTC

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使用的默认值,并返回它。

当使用buildImmutablebuildSoftImmutable时,装饰器永远不会返回新对象,而是保持对象实例受保护,并将适当的调用转发到对象。

许可证

本项目发布在MIT许可证下。