Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Defining dynamic service's during runtime

Hi everyone!

I can't find answer in docs how to define dynamic service during runtime. First of all, what I need to achieve:

I need to use the outsite API class that in the constructor accepts parameters, like "personalApiKey", that are read from the database after successful login to the application. So I can create object of this class only after login action. But then I want to use this class as Phalcon service in different places of the application.

So how can I do that?

Now I'm trying to define this service as empty object before "$application->handle()" and then redefine with proper constructor after login but I get some errors like "service not defined" or service is empty object. I tried to use shared service but it did not help. Maybe problem is in geting proper DI() object?

My code: index.php

$di = new FactoryDefault();

$di->setShared('Api', function() // I tried $di->set(); and $di->setService() but did not help {

});

AuthControler.php

$Api = new Api(array('apiKey' => '...')); $this->di->setShared('Api', $Api); // I tried $di = Phalcon\DI::getDefault(); and $di = $this->getDI(); but did not help

OtherControler.php

$Api = $this->di->getService('Api'); // error or empty object, I tried $di = Phalcon\DI::getDefault(); and $di = $this->getDI(); but did not help

I begin to lose my senses... please help :/

You need to RETURN $api. Like this in controller code. Also Defining it must be do in service closure.

$this->di->setShared('Api', function(){
    $Api = new Api(array('apiKey' => '...'));
    return $Api;
}); 


468
edited Dec '15

Jurigag:

Thaks for answer! You're right, it was my first syntax, but then I start to experiment and forgot to restore it.

My problem is that in OtherController.php I get:

Api Object ( [options:protected] => Array ( [apiKey] => ) )

with empty "apiKey" value, so it's useless...

Once again my whole code:

index.php

$di = new FactoryDefault();

$di->setShared('Api', function() { $Api = new Api(array('apiKey' => '')); return $Api; });

AuthControler.php

$di->setShared('Api', function() { $Api = new Api(array('apiKey' => 'xyz')); return $Api; });

OtherControler.php

$api = $this->di->getService('Api');

print_r($api); die;

Debug in OtherController.php display:

Api Object ( [options:protected] => Array ( [apiKey] => ) )

ps. how do You format your code in a comment? :)

There is how to format code in comment: use backticks.

Well try to change setShared to just set maybe. If it still dont work then try in AuthControler:

$di->get('Api')->setDefinition(function() { $Api = new Api(array('apiKey' => 'xyz')); return $Api; });

Also are you sure that instruction in AuthControler is called before instructions in OtherControler ?



468

Changing from Shared service to default service did not help and when I try to use setDefinition() as You suggest, I get "Call to undefined method Api::setDefinition()" :/

edited Dec '15

Oh sorry, instead of ->get('Api') try getService('Api') and then setDefinition.

Also are you sure that instruction in AuthControler is called before instructions in OtherControler ?

If you do it like this:

  1. Request in AuthControler
  2. Request in OtherControler without calling authcontroler previously

Then yea apiKey will be empty. You need to set it as some variable in memcache/session/persistent/in config or wherever else and get it in service definition. And pass to Api contructor.



468
edited Dec '15

Wait... what!? So I must define this service in every Controller? :) Ok, now I see my mistake but I thought the service can be defined once and then the service object is stored in memcache automatically ;)

Hmm so in this case defining Phalcon Service is pointless. I can save "apiKey" to Session object and then add $this->api = new Api(array('apiKey' => $this->session->get('apiKey'))); in ControllerBase() right? :)

My goal is to use Api class in every controller with "apiKey" from AuthController()->login() that is fired only once. Maybe is there a way to do that in more proper way?



114.3k
Accepted
answer
edited Dec '15

No its not stored. Service lives only for request time. Exactly. Well then after login() you just $this->session->set("apiKey",$apiKey") and get this apiKey in service definition. This is proper way.

Just think about it - in your case - you would need to store Api Object in memcache FOR EVERY USER.



48.2k

Just check that the conditions are correct within the service and if they are not then throw an exception.

If I get it correctly, you want to pass some arguments to the shared service, and by that way to dynamically change it's default definition?

It seems that this is the only working syntax:

Shared service name: bladeRunner

 $this->di->getBladeRunner([0x0a, 0xff]);

I wanted to avoid refering to DI always and using magic method getSharedService...

so I did it like this:

$di->setShared('bladeRunner', function (){
    class MyOverride extends OriginalClass
    {
        function myCustomOverride($id, $data) {
        //handle data from passed arguments 
        }
    }
    return new MyOverride();
    });

Finally, call the shared service and pass some data to it, this time with much simpler syntax:

 $this->bladeRunner->myCustomOverride([0x0a, 0xff]);

Hope this helps.



48.2k

0x0a, 0xff

You could take that data from the config service or from a model. You could query the database based upon something that has been already determined in the runtime cycle. You just need to figure out where to put that data so that you can access it. If you think that the data is the same throughout the execution then it is better then passing in the constants.

Attach the DI like this:

function () use ($di)

and defining manually specific method No, no, that data is just an example. :) That's why I was playing with hexadecimal notation, real data would be an array, object, etc... something generated during application lifecycle, in other words - dynamic data.

The point is how to pass parameters to the (shared) service and to pass that directly to the parent constructor.

Here's the key:

function ($acceptSomething) use ($di)

But to retain the syntax as:

 $this->bladeRunner->action([1, 2, 3]);

Without extending parent class, this is not possible.