Laravel: Difference App::bind and App::singleton

后端 未结 3 369
無奈伤痛
無奈伤痛 2020-12-23 13:15

I get a bit confused over all the nice things laravel has to offer in terms of the IOC container and facades. Since I\'m not an experienced programmer it gets overwhelming t

相关标签:
3条回答
  • 2020-12-23 13:37

    But somewhere I read that Laravel treats classes called via facades always as singletons?

    Thereby, I encountered this problem:

    I have a demo class normally bound via

    $this->app->bind('demo', function() { return new Demo(); }

    An sett up a facade

    protected static function getFacadeAccessor() { return 'demo'; }

    The class itself looks like this

    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;
            }   
    
        }

    You told me that if I would use a facade on this class, it would instantiate an object of the class and then call the method on that object.

    Butt I tested some more and found this very strange (at least to me) behavior:

    If I do

    Demo::setVal1('13654');
    and

    Demo::setVal2('random string')

    I shouldn't be able to use Demo::getVals() to retrieve the values I just created, should I? Since every time a facade method is used a new object will be instantiated and how can one object retrieve properties of another object? There should be three different instances but still I'm able to retrieve the properties from those other instances...

    0 讨论(0)
  • 2020-12-23 13:40

    It's exactly like that.

    A very simple proof is to test out the behavior. Since the Laravel Application simply extends Illuminate\Container\Container, we'll use just the container (in my case I even only added the container as a dependency to my composer.json) to test.

    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";
    

    The result is as expected:

    Bind: test vs. test2

    Singleton: test2 vs. test2

    Might be a dirty proof, but indeed it is one.

    All the magic lies in the Container::make method. If the binding is registered as shared (which means as singleton), the class instance is returned, otherwise a new instance every time.

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

    BTW, Container::singleton is the same as Container::bind with the third parameter set to true.

    0 讨论(0)
  • 2020-12-23 13:44

    Facades do work as singleton, even if the underlying binding is not a singleton.

    Let's say you have:

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

    and:

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

    Then this will create 2 instances of FooConcrete, as usual:

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

    But this will create only one instance of FooConcrete and reuse it:

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

    It is because resolveFacadeInstance() stores the resolved instances.


    There is an exception though. Most of the time the defined getFacadeAccessor() returns a string, as shown above, but it can also return an object. Example from the Schema Facade:

    protected static function getFacadeAccessor() {
        return static::$app['db']->connection()->getSchemaBuilder();
    }
    

    In such a case, resolveFacadeInstance() doesn't store the instance.

    So if getFacadeAccessor() returns a new instance, each call to the Facade creates a new instance as well.

    0 讨论(0)
提交回复
热议问题