Laravel:App::bind和App::singleton的区别

79
我对 Laravel 提供的 IOC 容器和 Facades 所提供的优点感到有些困惑。由于我不是经验丰富的程序员,学习起来也很难以承受。
我想知道这两个示例之间的区别:
1. 通过 `App::bind()` 注册到容器中的'Foo'的外观 2. 通过 `App::singleton()` 注册到容器中的 'Foo' 的外观
据我所知,`Foo::method()` 将被重写为 `$app->make['foo']->method()`,因此在第一个示例中,将创建 `Foo` 类的多个实例,在第二个示例中,由于它使用 `App::singleton()` 绑定,每次调用该对象上的方法时都会返回相同的 `Foo` 实例。
如果这个问题的答案太显然了,我很抱歉,但我找不到任何证实这一点的地方,也没有清晰地解释过。

58
不要因为不理解某件事而道歉。相信我,你不是唯一一个! - Relaxing In Cyprus
3个回答

88

就是这样。

一个非常简单的证明方法是测试它的行为。由于 Laravel 应用程序只是扩展了 Illuminate\Container\Container,我们将仅使用容器来测试(在我的情况下,我甚至只将容器添加为依赖项到我的 composer.json 文件中)。

require __DIR__ . '/vendor/autoload.php';

class FirstClass
{
    public $value;
}

class SecondClass
{
    public $value;
}

// Test bind()
$container = new Illuminate\Container\Container();

$container->bind('FirstClass');

$instance = $container->make('FirstClass');
$instance->value = 'test';

$instance2 = $container->make('FirstClass');
$instance2->value = 'test2';

echo "Bind: $instance->value vs. $instance2->value\n";

// Test singleton()
$container->singleton('SecondClass');

$instance = $container->make('SecondClass');
$instance->value = 'test';

$instance2 = $container->make('SecondClass');
$instance2->value = 'test2'; // <--- also changes $instance->value

echo "Singleton: $instance->value vs. $instance2->value\n";

结果如预期:

绑定:test vs. test2

单例:test2 vs. test2

可能是一个简陋的证明,但确实是一个。

所有的魔力都在Container::make方法中。 如果绑定被注册为共享(也就是单例),那么类实例将被返回,否则每次都会返回一个新的实例。

来源:https://github.com/laravel/framework/blob/4.2/src/Illuminate/Container/Container.php#L442

顺便说一下,Container::singleton与第三个参数设置为true的Container::bind相同。


1
非常感谢,这正是我在寻找的!是否可以请教一个好的资源,关于何时使用单例模式以及何时更好地实例化多个对象? - Luuk Van Dongen
如果您需要在整个请求期间使用同一类的实例,您应该考虑使用单例方法(例如购物车),对于其他所有情况,绑定应该就可以了。 - niclasleonbock
请纠正我,如果在通过App::bind()绑定的基础类上使用Facade,那么Laravel会实例化一个新的基础类对象,对吗?如果是这样,那么这个对象本身是否可访问,因为每次使用Facade上的方法时都会实例化它? - Luuk Van Dongen
抱歉回复晚了。正如文档所指出的,Facades只提供了一个“静态”的接口给应用程序中IoC容器中可用的类(来源)。 - niclasleonbock
你能举一个关于bind和singleton区别的现实生活例子吗? - subdesign
显示剩余5条评论

16

即使底层绑定不是单例,外观模式也可以作为单例运行。

假设你有:

$app->bind('foo', 'FooConcrete'); // not a singleton

和:

class Foo extends \Illuminate\Support\Facades\Facade {
    protected static function getFacadeAccessor() { return 'foo'; }
}

那么这将像往常一样创建2个FooConcrete实例:

app('foo');
app('foo');

但这只会创建一个 FooConcrete 实例并重复使用它:

Foo::someMethod();
Foo::someMethod();

这是因为resolveFacadeInstance()存储了解析的实例。


但有一种情况是异常的。大多数情况下,定义的getFacadeAccessor()返回一个字符串(如上所示),但也可以返回一个对象。例如:Schema门面的示例:

protected static function getFacadeAccessor() {
    return static::$app['db']->connection()->getSchemaBuilder();
}
在这种情况下,resolveFacadeInstance()不会存储该实例。
因此,如果getFacadeAccessor()返回一个新实例,则每次调用Facade时都会创建一个新实例。

这就是为什么如果需要的话,您可以使用 clearResolvedInstances() 方法来删除存储的实例,因此如果您想在 Facade 的每次调用中获取一个新对象,您可以在 getFacadeAccessor() 方法中返回具体字符串之前使用该方法。 - Mohyaddin Alaoddin
1
你应该使用 clearResolvedInstance()(没有 "s")来清除你需要清除的内容,而不是全部清除。尽管如此,最好还是返回一个实例。 - Gras Double

4
但我在某处读到,Laravel总是将通过facade调用的类视为单例?
因此,我遇到了这个问题:
我有一个演示类,通常通过以下方式绑定 $this->app->bind('demo', function() { return new Demo(); }
并设置一个facade protected static function getFacadeAccessor() { return 'demo'; }
该类本身如下所示
class Demo { private $value1; private $value2;
public function setVal1($value) { $this->value1 = $value; }
public function setVal2($value) { $this->value2 = $value; }
public function getVals() { return 'Val 1: ' . $this->value1 . ' Val 2: ' . $this->value2; } }
你告诉我,如果我在这个类上使用facade,它将实例化该类的对象,然后调用该对象上的方法。
但是,我进行了更多测试,并发现这种非常奇怪的(至少对我来说)行为:
如果我执行Demo::setVal1('13654');和
Demo::setVal2('random string')
我不应该能够使用Demo::getVals()来检索我刚创建的值,对吗?由于每次使用facade方法都会实例化一个新对象,一个对象如何检索另一个对象的属性?应该有三个不同的实例,但仍然可以从那些其他实例中检索属性...

1
Gras Double在他的解决方案中很好地回答了这个问题...“即使底层绑定不是单例,Facade也可以作为单例工作。”因此,当您使用Facade时,无论您使用bind()还是singleton(),行为都是相同的。唯一的区别在于,如果您正在创建新对象,例如使用App::make()。 - orrd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接