diff --git a/Dice.php b/Dice.php
index 9f18535..e1f7b92 100644
--- a/Dice.php
+++ b/Dice.php
@@ -21,6 +21,11 @@ class Dice {
*/
private $instances = [];
+ /**
+ * @var Stores any instances marked as 'sharedConstructor' so create() can return the same instance
+ */
+ private $sharedConstructor = [];
+
/**
* Add a rule $rule to the class $name
* @param string $name The name of the class to add the rule for
@@ -61,6 +66,12 @@ public function create($name, array $args = [], array $share = []) {
// Is there a shared instance set? Return it. Better here than a closure for this, calling a closure is slower.
if (!empty($this->instances[$name])) return $this->instances[$name];
+ $rules = $this->getRule($name);
+ if (!empty($rules['sharedConstructor']) && isset($this->sharedConstructor[$name])) {
+ $snapshot = $this->snapshotArray($args);
+ if (!empty($this->sharedConstructor[$name][$snapshot])) return $this->sharedConstructor[$name][$snapshot];
+ }
+
// Create a closure for creating the object if there isn't one already
if (empty($this->cache[$name])) $this->cache[$name] = $this->getClosure($name, $this->getRule($name));
@@ -91,6 +102,18 @@ private function getClosure($name, array $rule) {
if ($constructor) $constructor->invokeArgs($this->instances[$name], $params($args, $share));
return $this->instances[$name];
};
+ else if (!empty($rule['sharedConstructor'])) $closure = function (array $args, array $share) use ($class, $name, $constructor, $params) {
+ //Shared instance: create the class without calling the constructor (and write to \$name and $name, see issue #68)
+ $snapshot = $this->snapshotArray($args);
+ if (!isset($this->sharedConstructor[$name])) {
+ $this->sharedConstructor[$name] = [];
+ }
+ $this->sharedConstructor[$name][$snapshot] = $this->sharedConstructor[ltrim($name, '\\')][$snapshot] = $class->newInstanceWithoutConstructor();
+
+ //Now call this constructor after constructing all the dependencies. This avoids problems with cyclic references (issue #7)
+ if ($constructor) $constructor->invokeArgs($this->sharedConstructor[$name][$snapshot], $params($args, $share));
+ return $this->sharedConstructor[$name][$snapshot];
+ };
else if ($params) $closure = function (array $args, array $share) use ($class, $params) {
// This class has depenencies, call the $params closure to generate them based on $args and $share
return new $class->name(...$params($args, $share));
@@ -184,4 +207,26 @@ private function getParams(\ReflectionMethod $method, array $rule) {
return $parameters;
};
}
+
+ /**
+ * Creates an unique hash for an array
+ * @param array $args
+ * @return string unique hash for an array
+ */
+ private function snapshotArray(array $args = [])
+ {
+ $res = "";
+ foreach ($args as $key => $arg) {
+ if (is_object($arg)) {
+ $res .= spl_object_hash($arg);
+ } elseif (is_array($arg)) {
+ $res .= $this->snapshotArray($arg);
+ } else {
+ $res .= $arg;
+ }
+ $res = $key . $res;
+ }
+ return $res;
+ #return md5($res);
+ }
}
diff --git a/tests/ShareInstancesTest.php b/tests/ShareInstancesTest.php
index 0260246..92cc07d 100644
--- a/tests/ShareInstancesTest.php
+++ b/tests/ShareInstancesTest.php
@@ -86,4 +86,4 @@ public function testShareInstancesMultiple() {
$this->assertNotEquals($shareTest->share1->shared->uniq, $shareTest2->share2->shared->uniq);
}
-}
\ No newline at end of file
+}
diff --git a/tests/SharedConstructorTest.php b/tests/SharedConstructorTest.php
new file mode 100644
index 0000000..df64d34
--- /dev/null
+++ b/tests/SharedConstructorTest.php
@@ -0,0 +1,96 @@
+dice->addRule('Test\SharedConstructor\Greeter', $rule);
+
+ $greeter = $this->dice->create('Test\SharedConstructor\Greeter');
+ $actual = $greeter->greet("John Doe");
+ $expected = "Hi, John Doe!";
+ $this->assertEquals($expected, $actual);
+ $actual = $greeter->greet("John Doe");
+ $expected = "From cache: Hi, John Doe!";
+ $this->assertEquals($expected, $actual);
+
+ $greeter2 = $this->dice->create('Test\SharedConstructor\Greeter');
+ $actual = $greeter2->greet("John Doe");
+ $expected = "From cache: Hi, John Doe!";
+ $this->assertEquals($expected, $actual);
+ $this->assertSame($greeter, $greeter2);
+ }
+
+ public function testSharedConstructorComplex()
+ {
+ $sharedConstructor = ['sharedConstructor' => true];
+ $singleton = ['shared' => true];
+ $this->dice->addRule('Test\SharedConstructor\BarBazShared', $sharedConstructor);
+ $this->dice->addRule('Test\SharedConstructor\BarBaz', $sharedConstructor);
+ $this->dice->addRule('Test\SharedConstructor\Baz', $singleton);
+
+ $bar = $this->dice->create('Test\SharedConstructor\Bar', ['I am bar']);
+ $baz = $this->dice->create('Test\SharedConstructor\Baz', ['I am baz']);
+ $barBaz = $this->dice->create('Test\SharedConstructor\BarBaz', [$bar, $baz]);
+ $strongWrapper = function ($str) {
+ return "$str";
+ };
+ $bWrapper = function ($str) {
+ return "$str";
+ };
+ $barBazShared = $this->dice->create(
+ 'Test\SharedConstructor\BarBazShared',
+ [$barBaz, $strongWrapper]
+ );
+ $barBazShared2 = $this->dice->create(
+ 'Test\SharedConstructor\BarBazShared',
+ [$barBaz, $strongWrapper]
+ );
+
+ $this->assertEquals($barBazShared->id, $barBazShared2->id);
+
+ # Bar is not shared, so Dice will create new instance
+ # Despite that the argument to Bar is the same
+ $anotherBar = $this->dice->create(
+ 'Test\SharedConstructor\Bar',
+ ['I am bar']
+ );
+ $anotherBarBaz = $this->dice->create(
+ 'Test\SharedConstructor\BarBaz',
+ [$anotherBar, $baz]
+ );
+ $barBazShared3 = $this->dice->create(
+ 'Test\SharedConstructor\BarBazShared',
+ [$anotherBarBaz, $strongWrapper]
+ );
+ $this->assertFalse($barBazShared3->id == $barBazShared->id);
+ $this->assertFalse($barBazShared3->id == $barBazShared2->id);
+
+ $this->assertEquals($barBazShared3->getBarBazWrapped(), $barBazShared->getBarBazWrapped());
+ $this->assertEquals($barBazShared3->getBarBazWrapped(), $barBazShared2->getBarBazWrapped());
+
+ # Baz is the same always even if we pass different param to the constructor
+ $sameBaz = $this->dice->create(
+ 'Test\SharedConstructor\Baz',
+ ['I am another baz']
+ );
+ $sameBarBaz = $this->dice->create(
+ 'Test\SharedConstructor\BarBaz',
+ [$bar, $sameBaz]
+ );
+ $barBazShared4 = $this->dice->create(
+ 'Test\SharedConstructor\BarBazShared',
+ [$sameBarBaz, $strongWrapper]
+ );
+ $this->assertEquals($barBazShared4->id, $barBazShared->id);
+
+ # test change closure
+ $barBazShared5 = $this->dice->create(
+ 'Test\SharedConstructor\BarBazShared',
+ [$sameBarBaz, $bWrapper]
+ );
+ $this->assertFalse($barBazShared5->id == $barBazShared->id);
+ $this->assertFalse($barBazShared5->id == $barBazShared4->id);
+ }
+}
diff --git a/tests/TestData/SharedConstructor.php b/tests/TestData/SharedConstructor.php
new file mode 100644
index 0000000..4cc2a64
--- /dev/null
+++ b/tests/TestData/SharedConstructor.php
@@ -0,0 +1,109 @@
+greeting = $greeting;
+ }
+
+ public function greet($name)
+ {
+ if (!empty($this->cache[$name])) {
+ return "From cache: " . $this->cache[$name];
+ }
+ $this->cache[$name] = sprintf("%s, %s!", $this->greeting, $name);
+ return $this->cache[$name];
+ }
+}
+
+# Second test case. We are interested in more complex sharedConstructor usage.
+# Namely, we want constructor to receive objects and closures so we can test
+# arguments are hashed correctly
+class BarBazShared
+{
+ private $barBaz;
+ private $clo;
+ public $id;
+
+ public function __construct(BarBaz $barBaz, \Closure $clo)
+ {
+ $this->barBaz = $barBaz;
+ $this->clo = $clo;
+ $this->id = microtime(); // should stay the same if instance is the same
+ }
+
+ public function getUid()
+ {
+ }
+
+ public function getBarBazWrapped()
+ {
+ $clo = $this->clo;
+ return $clo(sprintf(
+ "Bar: %s, baz: %s",
+ $this->barBaz->getBar()->value(),
+ $this->barBaz->getBaz()->value()
+ ));
+ }
+}
+
+class BarBaz
+{
+ private $bar;
+ private $baz;
+
+ public function __construct(Bar $bar, Baz $baz)
+ {
+ $this->bar = $bar;
+ $this->baz = $baz;
+ }
+
+ public function getBar()
+ {
+ return $this->bar;
+ }
+
+ public function getBaz()
+ {
+ return $this->baz;
+ }
+}
+
+class Bar
+{
+ private $bar;
+
+ public function __construct($bar)
+ {
+ $this->bar = $bar;
+ }
+
+ public function value()
+ {
+ return $this->bar;
+ }
+}
+
+class Baz
+{
+ private $baz;
+
+ public function __construct($baz)
+ {
+ $this->baz = $baz;
+ }
+
+ public function value()
+ {
+ return $this->baz;
+ }
+}