Reputation: 26301
I have a set of objects (MainObject
) which are uniquely defined by two objects (SubObject1
, SubObject2
) and a string (theString
). I with to retrieve a MainObject
from the set by returning an existing object based on the two subobjects and string should it exist, else creating a new one, adding it to the set, and returning that object.
The following pseudo code demonstrates this in the make believe world where a standard array can use objects as keys.
class SubObject1{}
class SubObject2{}
class MainObject{
private $subObject1, $subObject2, $theString;
public function __construct(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
$this->subObject1=$subObject1;
$this->subObject2=$subObject2;
$this->theString=$theString;
}
}
class ObjectCollection
{
private $map=[];
public function getObject(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
if(isset($this->map[$subObject1][$subObject2][$theString])) {
$mainObject=$this->map[$subObject1][$subObject2][$theString];
}
else {
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$this->map[$subObject1][$subObject2][$theString]=$mainObject;
}
return $mainObject;
}
}
$objectCollection=new ObjectCollection();
$subObject1_1=new SubObject1();
$subObject1_2=new SubObject1();
$subObject2_1=new SubObject2();
$subObject2_1=new SubObject2();
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'hello'); //returns a new object
$o=$objectCollection->getObject($subObject1_2, $subObject2_1, 'hello'); //returns a new object
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'goodby'); //returns a new object
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'hello'); //returns existing object
How should this be best implemented?
One possibility is something like the following untested code, however, it is a little verbose and am interested if there is a cleaner solution.
public function getObject(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
if(isset($this->map[$theString])) {
if($this->map[$theString]->contains($subObject1)) {
$subObject1Storage=$this->map[$theString][$subObject1];
if($subObject1Storage->contains($subObject2)) {
$mainObject=$subObject1Storage[$subObject2];
}
else {
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
}
else {
$subObject1Storage = new \SplObjectStorage();
$this->map[$theString][$subObject1]=$subObject1Storage;
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
}
else {
$this->map[$theString] = new \SplObjectStorage();
$subObject1Storage = new \SplObjectStorage();
$this->map[$theString][$subObject1]=$subObject1Storage;
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
return $mainObject;
}
Upvotes: 0
Views: 53
Reputation: 3598
The logic I had in mind was as follows:
A factory(or abstract factory in case of too many objects) will take care of creating the object itself.
A container will map unique identifiers with objects created by the factory. And can retrieve objects based on those identifiers.
That's the easy part, the custom part should be even easier, you can add your own methods to do whatever magic you need with aliases and such.
namespace Example;
/**
* Class ObjectFactory
*
* @package Example
*/
class ObjectFactory {
/**
* This is obviosuly not ideal but it can work
* with a limited amount of objects. Otherwise use an
* abstract factory and let each instance take care of a few
* related objects
*
* @param string $objectAlias
*
* @throws \Exception
*/
public function make(string $objectAlias) {
switch($objectAlias) {
case 'object_unique_id_1':
try{
$instance = new $objectAlias;
}catch (\Exception $exception) {
// log or whatever and rethrow
throw new \Exception("Invalid class? maybe, I dunno");
}
// return $instance
// etc
}
}
}
You can also use Reflection here to recursively get the arguments for the object and dump new instances of the object in the current object based on the arguments in the construct esentially make your own little DI container.
But if you want to keep your sanity use something like Pimple.
Container:
<?php
namespace Example;
/**
* Class Container
*
* @package Example
*/
class Container {
/**
* @var array
*/
private $map = [];
/**
* @param $objectAlias
* @param $objectInstance
*
* @throws \Exception
*/
public function set($objectAlias, $objectInstance) {
// You can use a try catch here, I chose not to
if(isset($this->map[$objectAlias])) {
throw new \Exception("Already exists");
}
$this->map[$objectAlias] = $objectInstance;
}
/**
* @param $objectAlias
*
* @return bool|mixed
*/
public function get($objectAlias) {
if(isset($this->map[$objectAlias])) {
return $this->map[$objectAlias];
}
return false;
}
}
Specific container which will hold your own methods
<?php
namespace Example;
/**
* Class ContainerHashMapThingy
*
* @package Example
*/
class ContainerHashMapThingy extends Container {
// Your methods go here
}
And an example object:
<?php
namespace Example;
/**
* Class ExampleObject1
*
* @package Example
*/
class ExampleObject1 {
/**
* @return string
*/
public function alias() {
// This is just for example sake
// You can just as well have a config, another class to map them or not map them at all
return 'example_object_1';
}
}
And an actual example
<?php
$factory = new \Example\ObjectFactory();
$container = new \Example\Container();
$objectOne = $factory->make('example_object_1');
$container->set('first_object', $objectOne);
The idea here is to give you a clean slate for a container + factory.
If you extend the container you can implement your own methods, remove stuff from the map
array, even rewrite the set
method to suit your own needs.
While this is not a complete answer it's very hard to give one since, as I said, your needs may vary.
I do hope this gets you on the right track.
Upvotes: 0