I need to set a parameter (an integer) in the service container using a service in much the same way that I can create a service using another service as defined in the service config by defining the factory class and method.
The only way I can think of to do this is to wrap the value in a class and create that class as a service using the method above which seems a bit awkward to me.
Any ideas?
EDIT
To clarify. There is an integer value that I need to inject into a number of different services. This value is calculated by a service.
If it were an object then I could define it as a service, and create it using the factory class and definition parameters and then inject this service into the other services where it is required.
Is it possible to do this for an integer value or will I have to wrap it in a Value Object / Class in order to do this?
A better solution than injecting the result of a service calculation is injecting the service itself:
class ProviderService {
public function calculateResult() {
return 42; // your integer result
}
}
class ConsumerService {
private $provider;
public function __construct(ProviderService $provider) {
$this->provider = $provider;
}
public function execute() {
$result = $this->provider->calulateResult();
// business logic
}
}
If the calulation of your integer value is expensive, you can cache it in the ProviderService.
As simple as it may sound, a value cannot be a service. So you cannot inject a value derived from a service. If you keep your configuration as-is, you need to define a value object or you need to inject the "factory" service to the service which need to calculate the value.
If the value ONLY depends from the configuration, you can add a compiler pass which calculate the value once and set it to all services which need it. During a compiler pass, you can alter the arguments for a Service constructor or add a call to a setter method.
If the value need to be determined at runtime, then it make sense to create a new service. That's because there should be a service which "detect" the change and "notify" all the dependant service.
Related
I'm fairly new to dependency injection, and I've come upon a situation where I have a class which is basically just functional programming -- one method in it (createPayment) requires 9 dependencies, and the other method (paypalApiEndpoint) only requires 1 of those dependencies:
class Paypal
{
private $restAPIClient;
private $db;
private $logger;
private $paypalGateway;
private $paymentGateway;
private $ordersGateway;
private $usersGateway;
private $resourcesGateway;
private $configGateway;
public function __constructor($restAPIClient, $db, $logger, // ...6 more, each for the private vars)
{
$this->restAPIClient = $restAPIClient;
$this->db= $db;
$this->logger= $logger;
// ... 6 more
}
// this method uses all 9 dependencies from the constructor
public function createPaypalPayment()
{
// insert purchase info into `payment` db table. Requires $paymentGateway and $db
// make paypal API call. Requires $restAPIClient, $db, $paypalGateway, $logger
// based on API call results, update `orders`, `users`, `payment`, `resources`, and `config` db tables. Requires $db, $logger, $paymentGateway, $ordersGateway, $usersGateway, $resourcesGateway, $configGateway
}
// this method only requires 1 of the dependencies, $paypalGateway
public function paypalApiEndpoint()
{
// get a value from our db table `paypal`, and return it. Requires $paypalGateway
}
}
In many places throughout my codebase I only need to use the paypalApiEndpoint method. The problem is, in order to use that method I have to first create 9 objects (the dependencies of class Paypal), then I can use those to create the Paypal object, and then finally I can use the simple paypalApiEndpoint method. Needing to create 10 objects in order to this very simple method seems overboard.
Is there a better way?
// this method only requires 1 of the dependencies, $paypalGateway
public function paypalApiEndpoint()
{
// get a value from our db table `paypal`, and return it. Requires $paypalGateway
}
If the only requirement is the paypalGateway, then move the implementation to the paypalGateway class or an extension.
That doesn't mean you would have to remove the method from the Paypal class:
public function paypalApiEndpoint()
{
return $this->paypalGateway->paypalApiEndpoint();
}
Further, your constructor has lots of parameters.
I believe it would be better to have a Builder class with all the dependencies, using setters, that would be injected as an argument:
https://jlordiales.me/2012/12/13/the-builder-pattern-in-practice/
Or implement composites of related data reducing the parameters number:
https://refactoring.guru/smells/data-clumps
Taking into account that the Paypal constructor has lots of parameters:
https://refactoring.guru/smells/long-parameter-list
If there are several unrelated data elements, sometimes you can merge
them into a single parameter object via Introduce Parameter Object.
https://refactoring.guru/introduce-parameter-object
By consolidating parameters in a single class, you can also move the
methods for handling this data there as well, freeing the other
methods from this code.
(...) see whether there is any point in moving a part of the method
(or sometimes even the whole method) to a parameter object class. If
so, use Move Method or Extract Method.
This means that if you only need to invoke paypalApiEndpoint, you may use only the paypalGateway class for your purposes, with less dependancies than the Paypal class.
You don't have to inject all your dependencies in the constructor. You can have setter injectors for optional parameters.
public function setRestAPIClient($restAPIClient) {
$this->restAPIClient = $restAPIClient;
};
In your example, paypalApiEndpoint is a good candidate to be turned into either a class constant and/or a static method that would then allow you to use it via:
class PayPal {
private static $paypalGateway = null;
public static setPaypalGateway($paypalGateway) {
self::paypalGateway = $paypalGateway;
}
public __construct(.... all params) {
self::paypalGateway = $paypalGateway;
$this->otherDependency = $otherDependency;
}
}
For static use you just have to call the static setter 1 time, then use as much as you want within script execution.
Paypal::setPaypalGateway($ppgateway);
$endpoint = Paypal::paypalApiEndpoint();
Last but not least, you might want to look into use of a DI Container Component.
Some DIC's now feature autowiring which can determine what they need to load at calltime using typehints.
There is a standard PSR standard for how DI Containers should work and Symfony2's DIC, Laravel's DIC and PHP-DI for example, all conform to those standards and can easily be used to handle class loading within your application.
As mentioned, PHP-DI is another DIC component you might want to look at as well.
I was watching this video and at 7:10 he's adding a db dependency and is using a closure to assign the value.
My question is why not just use direct assignment instead, I mean isn't doing this:
$container['db'] = $capsule;
equivalent to doing this:
$container['db'] = function ($container) use ($capsule) {
return $capsule;
}
If not, what is the difference and which way is better?
TLDR it's because defining dependencies as closures makes it possible for dependency injection container to build them on demand, hence you don't need to worry about their order of definition and managing their dependencies manually.
Pimple is Dependency Injection Container, it is supposed to help you set up your objects by managing their dependencies in an easy and convenient way.
If you directly assign a value to a key, Pimple calls that value a parameter, and when you need to access that key, it simply returns that exact value:
$container['sample-param'] = 'foo';
echo $container['sample-param'];
//output: foo
But the point is, this sample-param does not require any set up, it is simply a value and we're fine with that. But assume the following service setup:
$container['myService'] = function($c) {
$service = new \Some\Complicated\Service();
//this service depends on cache service
$service->setCache($c['cache']);
return $service;
}
$container['cache'] = function($c) {
$cache = new \Cache\Driver();
//our cache driver needs a db connection
$cache->setDbConnection($c['db']);
return $cache;
}
$container['db'] = function($c) {
//our database connection requires some parameters
$dbConnection = new \Database\Connection($c['db-config']);
return $dbConnection;
}
$container['db-config'] = [
'username' => 'foo',
'password' => 'bar',
'host' => 'localhost'
];
//Now we want to user our service:
$container['myService']->doSomething();
Please pay attention to order which I used to define different keys in the container.
myService needs cache but cache definition comes after myService definition. And this is where Pimple is helping, and this is why we pass container to every closure, because Pimple is going to build our dependencies on demand. When we need to access myService Pimple looks at its internal data storage, if it has previously built and stored myService successfully, it will return that same instance, else it will call our closure to build it. And when our closure is called, it will ask Pimple ($c is the Pimple container) to give it its dependecies (which in this case is the cache service). Pimple applies the same thing on cache, if it is not built yet, it will build it and so on...till it gets to the part that requires simple params like db-config which will immediately return. And in this chain of closure calls, our object and all its dependencies are built.
Now imagine what would happen if we used simple values instead of closures? In that case, when we want to build myService we have to take care of its dependencies. We have to make sure its dependencies are defined before we define the service itself and we have to deal with other problems related to managing dependencies. In that case we couldn't just define our myService and assume there is a cache service available, which will be defined later.
In that video he is using Slim framework for the container which by default is using Pimple for it's container.
http://pimple.sensiolabs.org/
In the documentation on that page it mentions the following
Defining Services A service is an object that does something as part
of a larger system. Examples of services: a database connection, a
templating engine, or a mailer. Almost any global object can be a
service.
Services are defined by anonymous functions that return an instance of
an object:
// define some services
$container['session_storage'] = function ($container) {
return new SessionStorage('SESSION_ID');
};
$container['session'] = function ($container) {
return new Session($container['session_storage']);
};
Notice that the anonymous function has access to the current container instance, allowing references to other services or parameters.
By design the containers expect to call a function
Pimple's containers implement the \ArrayAccess interface (seen here), and that means they can make the object act like an array while not necessarily being one. In the source code you'll notice offsetSet and offsetGet, which are what happens when you do:
$container['a'] = function($c){ return new Blah();}
$thing = $container['a'];
tl;dr It's a Pimple container specific thing, not a PHP specific thing.
I have 2 classes: User and Router
In my script, class User is instantiated first to set user data, then class Router is instantiated to set page data.
$user = new User();
$router = new Router();
Inside one of Router's methods, I need to invoke $user->getSuperPrivileges(). This function queries the DB and sets extra parameters in the $user object, then returns them.
I could pass $user as a parameter of Router($user) and save it as a property in the construct function, but I believe this would only create a clone of the real object. Problem with this is that the values set by $this->user->getSuperPrivileges() would only be accessible by the clone, and not in the global script by the real object. In other words, I would need to invoke the getSuperPrivileges() method once again in the real object to set these properties again, which is counterproductive.
What is the best way to achieve what I want (access the real object $user and its methods from inside $router, without having to create a clone passed as a function parameter)?
As pointed out below by #hek2mgl, in php5 every object variable is a reference. The __construct magic method would not work at all prior to that anyway so we can assume that OPs example should work regardless.
http://3v4l.org/6dKL0
The following lines are really pointless given the above example.
have you tried passing the $user object as a reference?
class Router{
function __contruct(&$user){
$this->user=$user;
}
}
new Router($user);
in that case how about a singleton?
function user(&$userO){
static $user;
if(!is_array($user)) $user=array();
if(is_object($userO)) $user[$userO->uid]=$userO;
if(is_string($userO)) return $user[$userO];
}
class Router{
function __construct($user){
$this->uid=$user->uid;
}
function __get($k){if($k=='user') return user($this->uid);}
}
To explain a little more, the user function stored the user objects, keyed by a unique identifier in a static array, the __get magic method allows you to intercept calls to the user property on the router object and return the statically saved object from the singleton function.
You can create the $user object and inject it into $router object using constructor injection. But what you are doing should be just fine. You should be able to use that object for whatever you need within your router class. Especially if the database maintains the privilege state.
If you must use only one instance of the class check out the section on Singleton patterns at: http://www.phptherightway.com/pages/Design-Patterns.html and you can get an idea of how to achieve this.
I'd try and apply the Dependency Injection pattern. The point is that methods should be passed all they need to operate.
Meaning the method in your router which operates on a user should be passed said user.
class Router {
method privilegiateUser(User $user) {
// notice the typehint
// php will enforce that your method receives a User
$user->getSuperPrivileges();
}
}
I'd disapprove passing the User to your Router's __construct() if it's to be used only once and not with each script run. Think about it that way:
Is a User a property of a Router in the same way than a Name is a property of a User?
I write a class which populate DI container with services from yaml file. I have problem with testing a below method:
private function parseServices(array $services)
{
foreach ($services as $name => $attr) {
$this->container[$name] = function() use($attr) {
$reflect = new \ReflectionClass($attr["class"]);
$args = $this->parseArguments($attr["arguments"]);
return $reflect->newInstanceArgs($args);
};
}
}
$services is an array with services:
array(
serviceName => array(
class => className,
arguments => array(...)
)
)
A method parseArguments() returns a simple array of arguments for a constructor. For tests $container is a mock. I want to test that container is called one time for every service witch specific parameters. How can I test this? My idea was something like this:
$this->container
->expects($this->at(3))
->method('offsetSet')
->with('demo',$this->callback());
But this doesn't work.
EDIT
The responsibility of this method and even of whole class is populating a container. So maybe the best way to test it is to just check if container is populate correctly? As I wrote in a comment - just don't mock the container but use a concrete implementation. What do you think about it?
Ok, maybe it's not exactly answer to your question, but could be helpful anyway.
I would design this test different. Since you want to test if proper services are being built it would be easier to use some accessor to get 3rd service and check if it is an object of proper class.
... I just saw EDIT ;) - exactly! With properly designed code it's easier to test class from "outside" point of view. In need such wicked mocking only on legacy systems, when i.e can't inject dependencies.
// Original answer
Could you explain what offsetSet is?
From what I see in your test is that you try to assert that
when the method offsetSet is called on $this->container
for the third time
it should be called with two parameters:
'demo' string and
whatever $this->callback() returns (maybe you meant the callback this->callback should be passed as 2nd param, not result of calling it?)
I am creating an application with Silex and was wondering if it is possible to somehow get the instance of the Silex\Application in a place where I can't do method_name(Application $application) in the method parameters?
For example, say I have a private method on a controller that is not an action. If I put Application $application as the parameter, it throws an error saying I need to pass it in.
I would rather not have to manually pass that method in if I don't have to.
There's really only two ways to do it.
a) Pass Silex\Application as an argument to the constructor of your class and assign it as an instance variable.
b) Pass the Silex\Application to your private method as an argument by hand.
Are you sure you need the full app in your class? The point of dependency injection is to inject dependencies directly, instead of injecting the container (yes, Silex\Application extends \Pimple, which is a Dependency Injection Container.
From your comment on the other answer, your goal of getting at the Silex/Application is to get at the Twig service there. The way that I've solved getting at the current application in another function for my Silex projects is:
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
class myController {
private $a;
// Route defined as:
// $app->get('/foo', 'myController::showPage');
public function showPage(Application $a) {
$this->a = $a;
return $this->doAwesome();
}
private function doAwesome() {
$twig = $this->a['twig'];
return $twig->render('awesomePage.twig');
}
}
Every function that is an endpoint for a route would then save the Application passed to it as a class property, for other functions to get at. It means you have to remember to do $this->a = $a; in every function that uses doAwesome() (before calling doAwesome()), but that's the cleanest way I've come up with to tackle that.