We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

How exactly does magic 'get' method of injectable work?

Yeah, that's clear that

$this->userService 

always returns the same instance, but this instance is a part of $this but not of DI. How does get method of injectable work? Does it create an instance right when it calls property? If yes than it should be no difference with

$this->di->get(...) 

at the first call.

What we do is:

Create DI and define injections etc.

$di->set('userService', array(.....)) 

We inject dependencies as properties.

$ctrl = new MyController(); 
$ctrl->setDI($di) (our $di)

$ctrl->someMethod();

function someMethod() // of MyController 
{ 
        $this->userService->; 
        and 
        $this->di->get('userService')->
}

In this case, when we write

$this->userService

shouldnt it create new instance? Yes, if we will write the smae $this->userService it's the same instance. So what is the difference between

$this->userService 

and

$this->di->get('userService') 

in this case?



98.9k
Accepted
answer

$this->userService returns the same instance created the first time the property is accesed. You can see the code here: https://github.com/phalcon/cphalcon/blob/2.0.0/phalcon/di/injectable.zep#L99

$this->di->get('userService') calls Phalcon\Di::get which always creates a new instance according on how the service was defined in the service container. https://github.com/phalcon/cphalcon/blob/2.0.0/phalcon/di.zep#L200

edited Jul '14

Yes, but when does 'get' create instance? I will try to explain our issue in details.

We have «UserService» which is defined in DI as a service. One of injectable property of UserService — is UserModel, which is also defined in the same DI as a function which unpacks current user from the envirement (it's not matter). According to UserModel is defined like a function it should be called each time when new UserService is created.

$di->set('userService', array(
         .....
    'userModel' => array(...., 'service' => 'userModel')
));

$di->set('userModel', function() use (...) 
{
          unpackin current user
          return $user;
}

Then we add some log to this function, and before

class MyController extends ... {

        function someMethod()
        {
             ... logging here ... 
            $this->userService->someServiceMethod();
        }

Then we create this controller

$ctrl = new MyController();
$ctrl->setDI($di);
$ctrl->someMethod()

So what we have

  1. Set di to controller
  2. Call controller's method
  3. Controller's method user $this->userService
  4. Controller doesn't have userService property then it looks it up be calling magic 'get'
  5. Drill down to parent Phalcon classes, and how I understand it should take the di from the first step and ask him to create 'userService' service, which should inject userModel which should call definded in the di method.

We have logging in the userModel method and controller's someMethod, and logging records have to be in the following order:

  1. From someMethod of MyController
  2. From userModel function

Right? But what we get is the vice verse — logger shows that userModel function is called somewhere before ' someMethod of MyController'. And if change

$this->userService 

to service locator style

$this->di->get('userService')

it gets to work as we expect. Any ideas?

btw, we are facing this issue in our test, where we have to create controllers manually. And it works properly in our web application, where controllers used to be created automatically by phalcon core

maybe when you use $this->userService it returns already created static instance, and when you use $this->getDI()->get('UserService') it creates new instance, i suppose you are having problems with mocking because of that ...

just a guess ?

Checking out zephir source code makes sense now:

    /**
     * Fallback to the PHP userland if the cache is not available
     */
    if dependencyInjector->has(propertyName) {
        let service = dependencyInjector->getShared(propertyName);
        let this->{propertyName} = service;
        return service;
    }

Will getShared return singleton even if a service is declared as simple set (non shared) in DI? Another words $this->anything uses 'anything' as shared di service. If yes, then it becomes clear.