Laravel中`app->bind`和`app->singleton`有什么区别?

14

我一直在尝试弄清楚在Laravel中设置服务提供者时app->bindapp->singleton之间的区别。 我的理解是,如果我注册一个singleton,每次调用它都会返回同一个对象实例,而bind则会返回一个新实例。

这里是一个简单的示例:

Facade:

use Illuminate\Support\Facades\Facade;

class DataFacade extends Facade
{
    protected static function getFacadeAccessor() { 
        return 'Data';
    }
}

服务提供商:

use Illuminate\Support\ServiceProvider;

class DataServiceProvider extends ServiceProvider
{
    public function register() {
        $this->app->singleton('Data', function() {
            return new Data;
        });
    }
}

类别:

class Data
{
    public $data = [];

    public function get($key)
    {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }

    public function set($key, $val)
    {
        $this->data[$key] = $val;
    }
}

如果我们做类似以下的事情:

$instance = App::make('Data');
$instance->set('foo', 'foo');

$instance2 = App::make('Data');

echo $instance->get('foo');
echo $instance2->get('foo');

我们将运行这段代码,观察使用 bindsingleton 时的适当行为,分别输出 foo 一次和两次。但是,如果我们通过外观模式运行它,就会有不同的结果:

Data::set('test', 'test');
Data::set('cheese', 'cheese');

当它是单例模式时,我期望testcheese都可用;而当它是一个bind时,我不确定通过门面可以使用什么,但似乎没有区别。

这个门面把一切都视为singleton吗?


你已经知道它们的区别了! - The Alpha
你提到了 bind -- 但是你的问题从未直接调用 bind - Alana Storm
但是外观不是调用服务提供者,然后再调用绑定吗? - Rob
在4.2版本中(之前的版本不确定),看起来门面调用了$app[$service_name],然后调用make。更令人困惑的是,门面还有自己的单例存储层。 - Alana Storm
1个回答

33

您的问题有点混乱,缺少一些信息供他人回答,但这是一个比较棘手的主题,所以不要感到难过。这里是一份简述,可能会帮助您更好地理解并提出您想问的问题(同时,我对Laravel还不太熟悉,所以我可能有些不准确)

  1. make方法用于实例化对象。当您使用 App::make('Data') 时,您告诉 Laravel 实例化一个来自类 Data 的对象。

  2. 关于第一条有一个注意事项。如果您调用make并且已经将字符串Data绑定到服务容器中的某个东西上, Laravel 将返回该服务。这可能意味着 Laravel 实例化一个新的服务对象,也可能意味着 Laravel 返回一个单例服务

  3. 服务是返回单例还是实例取决于服务是如何绑定的

  4. make方法不会绑定任何内容

  5. 您可以使用应用程序对象的 bind 方法来绑定服务,它在容器类中定义,具有以下方法原型:public function bind($abstract, $concrete = null, $shared = false)

  6. 看到第三个 $shared 参数了吗?如果为 true,则您的服务将返回单例。如果为 false,则您的服务将返回实例。

  7. 应用程序对象的 singleton 方法是一种绑定服务的方法

关于 #7,这里是singleton的定义

#File: vendor/laravel/framework/src/Illuminate/Container/Container.php
public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}

在你上面的示例中,你将服务Data绑定到容器中。使用首字母大写的服务名会导致问题--data是一个更好的选择。如果由于某种原因没有调用你的register方法,make仍然会实例化一个带有全局类Data的对象。

关于你的Facade--Facade是一层额外的实例/单例模式。以下是Facade类使用getFacadeAccessor获取的字符串返回静态调用的对象的方法。

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) return $name;

    if (isset(static::$resolvedInstance[$name]))
    {
        return static::$resolvedInstance[$name];
    }

    return static::$resolvedInstance[$name] = static::$app[$name];
}
因此,门面使用$app[$name];从容器中获取服务。这是ArrayAccess,因此如果我们查看offsetGet的定义。
public function offsetGet($key)
{
    return $this->make($key);
}

我们可以看到ArrayAccess将调用make。这意味着,如果您没有绑定服务,门面访问将实例化一个对象。如果您将服务绑定为单例/共享服务,则门面访问将返回该单例。如果您将服务绑定为非单例/非共享服务,则门面访问将实例化一个新对象。

然而,门面本身将任何它实例化的对象存储在static::$resolvedInstance中,后续对门面的调用将返回同一实例。这意味着门面访问引入了第二个单例实现。作为单例绑定的服务将存储在应用程序对象上,通过门面访问的服务将作为单例存储在Facade类上。


1
非常感谢您的详细解释,这对我帮助很大。我之前没有理解到外观模式中单例的作用。如果我理解正确,根据我上面的例子,如果一个外观模式创建了一个新实例,那么它将是一个单例,它将始终只引用通过IoC/服务提供者绑定创建的$instance1?这是否意味着无论您的服务提供者设置为新实例/单例,外观都将始终“充当”单例?如果是这样,是否有一种使用外观的方法来避免这种情况?我还应该补充一点,是否存在我们想要这样做的场景? - Rob
1
@Rob -- 我还远非专家,但是是的,看起来 Facade 会实例化一个对象一次,然后始终使用相同的对象进行方法调用,无论容器如何绑定。您可以通过编写一个执行 return $this 的服务方法,在 Facade 上调用该方法,然后进行状态检查来测试此功能。我怀疑这是/是一个有意的决定,以避免“服务默认不是单例而是实例”的性能问题。 - Alana Storm
我认为这里使用门面模式会造成混淆,它们总是强制使用单例而不是多个实例,即使你将其绑定到 shared false。 - Vedmant

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