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;
}
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.
Related
I have the following situation:
class Main {
function get() {
return new Query();
}
}
class Order extends Main {
public $id;
public $name;
}
class Query {
/**
* #return Main
*/
function execute() {
/// some code here
return $parsedObject;
}
}
When I use this code to execute() and get Order objects (as parsed object), I'm writing this:
$order = new Order();
$result = $order->get()->execute();
$result->id; /// typehint not works, cuz $result is type of Main, not more concrete type Order
So my question is - is there any way to pass a type of base (abstract class, interface) class implementation to method, that is called from that base class, for getting beautiful typehint of created object. Cuz i can create User, that extends Main class, with his own fields. But calling new User()->get()->excute() will give me the same result - an object of type Main
You can annotate just about anything, here is your code with everything annotated.
/**
* Class Main
*/
class Main {
/**
* #return Query
*/
function get() {
return new Query();
}
}
/**
* Class Order
*/
class Order
extends Main {
/** #var int */
public $id;
/** #var string */
public $name;
}
/**
* Class Query
*/
class Query {
/**
* #return Main
*/
function execute() {
/// some code here
return $parsedObject;
}
}
I'm a little bit confused about how to unit test a constructor, particularly since it returns no value.
Let's assume I have this class:
class MyClass {
/** #var array */
public $registered_items;
/**
* Register all of the items upon instantiation
*
* #param array $myArrayOfItems an array of objects
*/
public function __construct($myArrayOfItems) {
foreach($myArrayOfItems as $myItem) {
$this->registerItem($myItem);
}
}
/**
* Register a single item
*
* #param object $item a single item with properties 'slug' and 'data'
*/
private function registerItem($item) {
$this->registered_items[$item->slug] = $item->data;
}
}
Obviously this is a bit contrived and incredibly simple, but it's for the sake of the question. =)
So yeah, how would I go about writing a unit test for the constructor here?
Bonus question: am I right in thinking that no unit test for registerItem() would be needed in a case such as this?
EDIT
How about if I re-factored to remove the logic from the constructor. How would I test registerItem() in this case?
class MyClass {
/** #var array */
public $registered_items;
public function __construct() {
// Nothing at the moment
}
/**
* Register all of the items
*
* #param array $myArrayOfItems an array of objects
*/
public function registerItem($myArrayOfItems) {
foreach($myArrayOfItems as $item) {
$this->registered_items[$item->slug] = $item->data;
}
}
}
Add a method to look up a registered item.
class MyClass {
...
/**
* Returns a registered item
*
* #param string $slug unique slug of the item to retrieve
* #return object the matching registered item or null
*/
public function getRegisteredItem($slug) {
return isset($this->registered_items[$slug]) ? $this->registered_items[$slug] : null;
}
}
Then check that each item passed to the constructor in the test has been registered.
class MyClassTest {
public function testConstructorRegistersItems() {
$item = new Item('slug');
$fixture = new MyClass(array($item));
assertThat($fixture->getRegisteredItem('slug'), identicalTo($item));
}
}
Note: I'm using the Hamcrest assertions, but PHPUnit should have an equivalent.
For First Code
public function testConstruct{
$arrayOfItems = your array;
$myClass = new MyClass($arrayOfItems);
foreach($arrayOfItems as $myItem) {
$expected_registered_items[$item->slug] = $item->data;
}
$this->assertEquals($expected_registered_items, $myClass->registered_items);
}
I'm having a bit of a problem trying to get a correct autocompletion for the following code example. I'm using PHPStorm 7 on a Win7 machine.
First just a simple class.
/**
* Class myObject
*/
class myObject
{
/**
* some method
*/
public function myMethod()
{
// do something
}
}
This one is the collection class which can contain multiple instances of the prior class and implements the IteratorAggregate interface.
/**
* Class myCollection
*/
class myCollection implements IteratorAggregate
{
/**
* #var myObject[]
*/
protected $_objects = array();
/**
* #param myObject $object
* #return myCollection
*/
public function add(myObject $object)
{
$this->_objects[] = $object;
return $this;
}
/**
* #return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->_objects);
}
}
And here is the code example.
$collection = new myCollection;
$collection->add(new myObject);
$collection->add(new myObject);
foreach ($collection as $object) {
$object->myMethod(); // gets no autocompletion
}
As you may have guessed (and read in the example) the myMethod() call gets not autocompleted and is beeing listed in the code analysis. The only way i found is adding a comment block for $object, which i find, to be honest, extremely annoying.
/** #var $object myObject */
foreach ($collection as $object) {
$object->myMethod(); // gets autocompletion now, but sucks
}
So, any ideas or fundamented knowledge on how to solve this?
/**
* #return ArrayIterator|myObject[]
*/
public function getIterator()
{
return new ArrayIterator($this->_objects);
}
For extended classes (the base class is above):
/**
* #method myObject[] getIterator()
*/
class ExtendedClass extends BaseCollection
{
}
or
/**
* #method iterable<myObject> getIterator()
*/
class ExtendedClass extends BaseCollection
{
}
I think this will be best way to handle such case. at least it works with PHPStorm
Your
/** #var $object myObject */
block is indeed the correct way to accomplish this. The syntax you are expecting to do the work,
/**
* #var myObject[]
*/
is not standard phpdoc notation, although it is in informal use and has some effort ongoing to standardize. Until such standardization does happen, IDEs recognizing it will probably be hit-or-miss. IDE coverage of your $object local var block is also hit-or-miss, actually.
In your myCollection class, override current() as follows:
/** #return myObject */
public function current() {
return parent::current();
}
Possible workaround (also ugly) is to create static "constructor", that will return myObject. At least it works in eclipse. If you want to see collection methods too, then just add myCollection to return as "#return myObject[]|myCollection"
class myCollection implements \IteratorAggregate
{
/**
* #return myObject[]
*/
public function create()
{
return new static();
}
}
Writing group of parsers that rely on one abstract class which implements shared methods and asks to implement addition method which contains per parser logic.
Abstract parser code:
<?
abstract class AbstractParser {
/*
* The only abstract method to implement. It contains unique logic of each feed passed to the function
*/
public abstract function parse($xmlObject);
/**
* #param $feed string
* #return SimpleXMLElement
* #throws Exception
*/
public function getFeedXml($feed) {
$xml = simplexml_load_file($feed);
return $xml;
}
/**
* #return array
*/
public function getParsedData() {
return $this->data;
}
/**
* #param SimpleXMLElement
* #return Array
*/
public function getAttributes($object) {
// implementation here
}
}
Concrete Parser class:
<?php
class FormulaDrivers extends AbstractParser {
private $data;
/**
* #param SimpleXMLElement object
* #return void
*/
public function parse($xmlObject) {
if (!$xmlObject) {
throw new \Exception('Unable to load remote XML feed');
}
foreach($xmlObject->drivers as $driver) {
$driverDetails = $this->getAttributes($driver);
var_dump($driver);
}
}
}
Instantiation:
$parser = new FormulaDrivers();
$parser->parse( $parser->getFeedXml('http://api.xmlfeeds.com/formula_drivers.xml') );
As you can see, I pass the result of getFeedXml method to parse method, basically delegating the validation of result of getFeedXml to parse method.
How can I avoid it, make sure it returns correct XML object before I pass it to parse method?
Increasing instantiation process and amount of called methods leads to the need of some factory method...
Anyway, how would you fix this small issue?
Thanks!
Make parse protected, so that only parse_xml_file calls it:
abstract class AbstractParser {
/*
* The only abstract method to implement. It contains unique logic of each feed passed to the function
*/
protected abstract function parse($xmlObject);
/**
* #param $feed string
* #return [whatever .parse returns]
* #throws Exception
*/
public function parseFile($feed) {
$xml = simplexml_load_file($feed);
if (!$xml) {
throw new \Exception('Unable to load remote XML feed');
}
return $this->parse($xml);
}
/**
* #return array
*/
public function getParsedData() {
return $this->data;
}
/**
* #param SimpleXMLElement
* #return Array
*/
public function getAttributes($object) {
// implementation here
}
}
$parser->parseFile('http://api.xmlfeeds.com/formula_drivers.xml');
<?php
/**
* My codebase is littered with the same conditionals over and over
* again. I'm trying to refactor using inheritance and the Factory
* pattern and I've had some success but I'm now stuck.
*
* I'm stuck because I want to derive a new class from the one
* returned by the Factory. But I can't do that, so I'm obviously
* doing something wrong somewhere else.
*/
/**
* The old implementation was as follows. There's if statements
* everywhere throughout both LayoutView and ItemIndexView and
* SomeOtherView.
*/
class LayoutView { }
class IndexView extends LayoutView { }
class SomeOtherView extends LayoutView { }
/**
* Below is the new implementation. So far I've managed to tidy
* up LayoutView (I think I have anyway). But now I'm stuck because
* the way I've tidied it up has left me not knowing how to extend
* it.
*
* For example's sake, let's say the conditions were relating to a
* type of fish: salmon or tuna.
*/
abstract class LayoutView {
protected function prepareHeader() { echo __METHOD__, "\n"; }
protected function prepareLeftHandSide() { echo __METHOD__, "\n"; }
protected function prepareFooter() { echo __METHOD__, "\n"; }
public function prepare() {
$this->prepareHeader();
$this->prepareLeftHandSide();
$this->prepareFooter();
}
}
class SalmonLayoutView extends LayoutView
{
protected function prepareLeftHandSide() { echo __METHOD__, "\n"; }
}
class TunaLayoutView extends LayoutView
{
protected function prepareLeftHandSide() { echo __METHOD__, "\n"; }
protected function prepareFooter() { echo __METHOD__, "\n"; }
}
class ViewFactory {
public static function getLayoutView($fishType) {
switch($this->$fishType) {
case 'salmon':
return new SalmonLayoutView();
break;
case 'tuna':
return new TunaLayoutView();
break;
}
}
}
/**
* Now LayoutView has been cleaned up and the condition that was once
* scattered through every LayoutView method is now in one place.
*/
$view = ViewFactory::getLayoutView( Config::getOption('fishtype') );
$view->prepare();
/**
* Now here's where I'm stuck. I want to effectively extend whatever
* class $view is an instance of.
*
* The reason being, I wish to derive a view to show an index of
* articles within the appropriate *LayoutView. The IndexView code is
* the same for Salmon and Tuna.
*
* I can do something like this:
*/
class SalmonIndexView extends SalmonLayoutView { }
class TunaIndexView extends TunaLayoutView { }
/**
* But then I'll be writing the same IndexView code twice. What I'd
* like to do is something like this:
*/
$view = ViewFactory::getLayoutView( Config::getOption('fishtype') );
class IndexView extends get_class($view) { }
/**
* But I'm pretty certain that's not possible, and even if it was
* it seems very wrong.
*
* Can someone more experienced in refactoring and OO please let
* me know where I've gone wrong and suggest a clean way to solve
* this?
*/
If the IndexView code really is the same then you don't need inheritance, but composition. Add, in your base LayoutView class, an instance of IndexView that then you'll be able to call from each *LayoutView.
Inheritance is due only when the relationship between objects is is-a. I deduce that an IndexView is not a LayoutView, but rather the LayoutView has an IndexView.
Check this out, I don't agree with everything it says, but still:
http://phpimpact.wordpress.com/2008/09/04/favour-object-composition-over-class-inheritance/
Just pass the template as a parameter to the subviews you'll compose. I don't think that'd be evil in this case. Although if it's a standard framework you might be better asking in their forums, because they might have a functionality we are unaware of for this case (it usually happens)
You could have something like
class LayoutView {
protected View $subview; //Potentially an array of views
public function prepare() {
// empty, to be filled out by derived classes
}
public function setSubView($view) { $this->subview = $view; }
public function display() {
$this->prepare();
$this->subview->prepare($this->template);
$this->template->render();
}
}
class IndexView {
protected View $subview; //Potentially an array of views
public function prepare() {
// empty, to be filled out by derived classes
}
public function prepare($template) {
//operate on it, maybe even assigning it to $this->template
}
public function setSubView($view) { $this->subview = $view; }
public function display() {
$this->prepare();
$this->subview->prepare($this->template);
$this->template->render();
}
}