1

I wish to create a new instance of SomeService which must be injected with some data which is not known when defining the service in Pimple. The following technically works, but surely cannot be the proper way to do so. How should this be accomplished?

<?php
use Pimple\Container;

class SomeService
{
    private $theNewData;
    public function __construct(MyPdo $pdo, array $theNewData){
        $this->theNewData=$theNewData;
    }
    public function getJsonString():string{
        return json_encode($this->theNewData);
    }
}
class MyPdo{}

function getServiceSometimeInTheFuture(Container $container):SomeService {
    $someFutureData= ['a'=>1,'b'=>2,'c'=>3,];

    /*
    How do I inject this content into SomeService other than using Pimple as a temporary transport?
    */
    $container['temp']=$someFutureData;
    $newInstance=$container['someService'];
    unset($container['temp']);
    return $newInstance;
}

require '../vendor/autoload.php';

$container=new Container();

$container['myPdo'] = function ($c) {
    return new MyPdo();
};

$container['someService'] = $container->factory(function ($c) {
    return new SomeService($c['myPdo'], $c['temp']);
});

$service = getServiceSometimeInTheFuture($container);
echo($service->getJsonString());
user1032531
  • 24,767
  • 68
  • 217
  • 387

2 Answers2

0

How do I inject this content into SomeService other than using Pimple as a temporary transport?

I think the root cause for this problem is that future data is actually data as its name suggests, and it is going to change multiple times over the time, so it should not be passed to the service at the time of service definition, but every time the consumer method in the service (getJsonString here) needs that data.

... which is not known when defining the service

The data is not known, but how about the source of the data? Could you write a new service to act as data provider, so the original service can get required data when required in the future?

I can suggest two solutions.
(I intentionally removed MyPdo from everywhere because it was not used at all, even in the constructor)

If you really need the data to be passed to the service at creation time:

<?php
require __DIR__ . '/../vendor/autoload.php';
use Pimple\Container;

class SomeService
{
    private $theNewData;
    public function __construct(array $theNewData){
        $this->theNewData=$theNewData;
    }
    public function getJsonString():string{
        return json_encode($this->theNewData);
    }
}

$container=new Container();

// Use pimple factory method to create new service instance, instead of creatng a custom function
$container['getServiceSometimeInTheFuture'] = $container->factory(function (Container $c):SomeService {
    return new SomeService($c['futureData']);
});

// `futureData` returns new data every time
$container['futureData'] = $container->factory(function(){
    return ['a' => rand(1, 10), 'b' => rand(1, 10), 'c' => rand(1, 10), ];
});

$service1 = $container['getServiceSometimeInTheFuture'];
$service2 = $container['getServiceSometimeInTheFuture'];

// Demonstrate how two different instances have different, persistent data
echo("\nservice 1:" . $service1->getJsonString());
echo("\nservice 2:" . $service2->getJsonString());
echo("\nservice 1:" . $service1->getJsonString());
echo("\nservice 2:" . $service2->getJsonString());

If you can postpone providing data to the service at the time it needs that data in the future:

<?php
require __DIR__ . '/../vendor/autoload.php';
use Pimple\Container;

// DataProvider decides what data should be provided to the service
class DataProvider {
    public function getData(){
        return ['a' => rand(1, 10), 'b' => rand(1, 10), 'c' => rand(1, 10), ];
    }
}

class SomeService
{
    private $dataProvider;
    public function __construct(DataProvider $dataProvider){
        $this->dataProvider=$dataProvider;
    }
    public function getJsonString():string{
        return json_encode($this->dataProvider->getData());
    }
}

$container=new Container();

// Use pimple factory method to create new service instance, instead of creatng a custom function
$container['getServiceSometimeInTheFuture'] = $container->factory(function (Container $c):SomeService {
    return new SomeService($c['dataProvider']);
});

$container['dataProvider'] = function() {
    return new DataProvider;
};

$service = $container['getServiceSometimeInTheFuture'];

// Demonstrate how THE SAME INSTANCE will have different data every time
echo("\n" . $service->getJsonString());
echo("\n" . $service->getJsonString());
echo("\n" . $service->getJsonString());
echo("\n" . $service->getJsonString());
Nima
  • 3,309
  • 6
  • 27
  • 44
  • Thanks Nima, For both these solutions, however, how can one get the data into the DataProvider? – user1032531 Dec 05 '19 at 15:35
  • In your example, where does `$someFutureData` come from? `DataProvider` should be responsible to fetch data from the same source. To me, this depends on the actual use case. Data might be fetched from a database, or it can be some hardcoded array like your example. May I ask how and when does this data become available? – Nima Dec 05 '19 at 21:50
  • Hi Nima, The application is a socket server and not a http server and as such runs continuously and `$someFutureData` comes from a clients request through a tcp socket connection. – user1032531 Dec 06 '19 at 11:16
  • Shouldn't DataProvider be responsible for getting data in first place in that case? Or maybe another service with more relevant responsibility to get data and pass it (i.e calling another service, instead of being called by that) to the main service that requires that data? I have one question though, is it necessary for this future data to be passed to the constructor? This seems more like actual __data__ than configuration, so it could be passed to the final method that requires the data. Something like`$service->getJsonString($someData)`. – Nima Dec 06 '19 at 18:37
  • Maybe my poor choice on words. Another service is responsible to get the data. That service, eventually needs to perform work on the data and passing this data to a new object's constructor is advantageous. I've read that one should try to define objects early on instead of deep in some script and was trying to do so. – user1032531 Dec 07 '19 at 13:06
  • I think I have an idea of how this could be done the proper way, but I can not show it using current example code. Would you please consider updating your example code to reflect the requirements of the actual problem more precisely? It seems how the data is retrieved and how it is consumed are very important factors here, but the example does not reflect those as it uses a hardcoded array as data and also it does not demonstrate a continuous execution flow. – Nima Dec 07 '19 at 20:01
-1

Use Pimple's raw() method.

$container->raw('someService')($container, $someFutureData);

The service will need to be set up to accept the new data.

$container['someService'] = $container->factory(function ($c, $data) {
    return new SomeService($c['myPdo'], $data);
});
user1032531
  • 24,767
  • 68
  • 217
  • 387