Basically I have a class that sends a SOAP request for room information receives a response, it can only handle one room at a time.. eg:
class roomParser {
private $numRooms;
private $adults;
private $dailyPrice;
public function parse(){}
public function send(){}
};
$room = new roomParser( $arrival, $departue );
$return = $room->parse();
if ( $return ) { }
Now I have the dilemma of basically supporting multiple rooms, and for each room I have to separately keep information of the dailyPrice, # of adults, so I have to sessionize each rooms information since its a multiple step form..
Should I just create multiple instances of my object, or somehow modify my class so it supports any # of rooms in a rooms array, and in the rooms array it contains properties for each room?
Edit #1: After taking advice I tried implementing the Command pattern:
<?php
interface Parseable {
public function parse( $arr, $dept );
}
class Room implements Parseable {
protected $_adults;
protected $_kids;
protected $_startDate;
protected $_endDate;
protected $_hotelCode;
protected $_sessionNs;
protected $_minRate;
protected $_maxRate;
protected $_groupCode;
protected $_rateCode;
protected $_promoCode;
protected $_confCode;
protected $_currency = 'USD';
protected $_soapAction;
protected $_soapHeaders;
protected $_soapServer;
protected $_responseXml;
protected $_requestXml;
public function __construct( $startdate,$enddate,$rooms=1,$adults=2,$kids=0 ) {
$this->setNamespace(SESSION_NAME);
$this->verifyDates( $startdate, $enddate );
$this->_rooms= $rooms;
$this->_adults= $adults;
$this->_kids= $kids;
$this->setSoapAction();
$this->setRates();
}
public function parse( $arr, $dept ) {
$this->_price = $arr * $dept * rand();
return $this;
}
public function setNamespace( $namespace ) {
$this->_sessionNs = $namespace;
}
private function verifyDates( $startdate, $enddate ) {}
public function setSoapAction( $str= 'CheckAvailability' ) {
$this->_soapAction = $str;
}
public function setRates( $rates='' ) { }
private function getSoapHeader() {
return '<?xml version="1.0" encoding="utf-8"?>
<soap:Header>
</soap:Header>';
}
private function getSoapFooter() {
return '</soap:Envelope>';
}
private function getSource() {
return '<POS>
<Source><RequestorId ID="" ID_Context="" /></Source>
</POS>';
}
function requestXml() {
$this->_requestXml = $this->getSoapHeader();
$this->_requestXml .='<soap:Body></soap:Body>';
return $this->_requestXml;
}
private function setSoapHeaders ($contentLength) {
$this->_soapHeaders = array('POST /url HTTP/1.1',
'Host: '.SOAP_HOST,
'Content-Type: text/xml; charset=utf-8',
'Content-Length: '.$contentLength);
}
}
class RoomParser extends SplObjectStorage {
public function attach( Parseable $obj ) {
parent::attach( $obj );
}
public function parseRooms( $arr, $dept ) {
for ( $this->rewind(); $this->valid(); $this->next() ) {
$ret = $this->current()->parse( $arr, $dept );
echo $ret->getPrice(), PHP_EOL;
}
}
}
$arrive = '12/28/2010';
$depart = '01/02/2011';
$rooms = new RoomParser( $arrive, $depart);
$rooms->attach( new Room( '12/28/2010', '01/02/2011') );
$rooms->attach( new Room( '12/29/2010', '01/04/2011') );
echo $rooms->count(), ' Rooms', PHP_EOL;
Well what you've defined is an object that handles a single room, so naturally, if you wanted to handle multiple rooms, you should create an object that is simply a collection of these single-room objects.
If you intend to interact with your MultiRoomParser in the same way that you do your RoomParsers, this scenario may be a good candidate for the Composite Pattern. Basically, your MultiRoomParser would contain a collection of RoomParsers, and when you call a method such as parse() on your MultiRoomParser, it simply iterates through all RoomParsers in its collection and calls parse() on each element.
Given from the information in the question, I'd probably use a Command Pattern
All Rooms should implement a parse() command
interface Parseable
{
public function parse($arr, $dept);
}
A room instance could look like this
class Room implements Parseable
{
protected $_price;
protected $_adults;
public function parse($arr, $dept) {
// nonsense calculation, exchange with your parse logic
$this->_price = $arr * $dept * rand();
return $this;
}
public function getPrice()
{
return $this->_price;
}
}
To go through them, I'd add them to an Invoker that stores all rooms and knows how to invoke their parse() method and also knows what to do with the return from parse(), if necessary
class RoomParser extends SplObjectStorage
{
// makes sure we only have objects implementing parse() in store
public function attach(Parseable $obj)
{
parent::attach($obj);
}
// invoking all parse() methods in Rooms
public function parseRooms($arr, $dept)
{
for($this->rewind(); $this->valid(); $this->next()) {
$ret = $this->current()->parse($arr, $dept);
// do something with $ret
echo $ret->getPrice(), PHP_EOL;
}
}
// other methods
}
And then you could use it like this:
$parser = new RoomParser;
$parser->attach(new Room);
$parser->attach(new Room);
$parser->attach(new Room);
$parser->attach(new Room);
echo $parser->count(), ' Rooms', PHP_EOL;
$parser->parseRooms(1,2);
Note that the Invoker extends SplObjectStorage, so it implements Countable, Iterator, Traversable, Serializable and ArrayAccess.
I would say that making multiple instances of the object makes sense. It's how the objects works.
Related
I am using a library, and it has the following process to attach many operations to one event:
$action = (new EventBuilder($target))->addOperation($Operation1)->addOperation($Operation2)->addOperation($Operation3)->compile();
I am not sure how to dynamically add operations depending on what I need done.
Something like this
$action = (new EventBuilder($target));
while (some event) {
$action = $action->addOperation($OperationX);
}
$action->compile();
I need to be able to dynamically add operations in while loop and when all have been added run it.
Your proposed solution will work. The EventBuilder provides what is known as a Fluent Interface, which means that there are methods that return an instance of the builder itself, allowing you to chain calls to addOperation as many times as you want, then call the compile method to yield a result. However you are free to ignore the return value of addOperation as long as you have a variable containing an instance of the builder that you can eventually call compile on.
Take a walk with me...
// Some boilerplate classes to work with
class Target
{
private ?string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
class Operation
{
private ?string $verb;
public function __construct(string $verb)
{
$this->verb = $verb;
}
public function getVerb(): string
{
return $this->verb;
}
}
class Action
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target, array $operations)
{
$this->target = $target;
$this->operations = $operations;
}
/**
* Do the things
* #return array
*/
public function run(): array
{
$output = [];
foreach ($this->operations as $currOperation)
{
$output[] = $currOperation->getVerb() . ' the ' . $this->target->getName();
}
return $output;
}
}
Here is a basic explanation of what your EventBuilder is doing under the covers:
class EventBuilder
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target)
{
$this->target = $target;
}
/**
* #param Operation $operation
* #return $this
*/
public function addOperation(Operation $operation): EventBuilder
{
$this->operations[] = $operation;
// Fluent interface - return a reference to the instance
return $this;
}
public function compile(): Action
{
return new Action($this->target, $this->operations);
}
}
Let's try both techniques and prove they will produce the same result:
// Mock some operations
$myOperations = [
new Operation('Repair'),
new Operation('Clean'),
new Operation('Drive')
];
// Create a target
$target = new Target('Car');
/*
* Since the EventBuilder implements a fluent interface (returns an instance of itself from addOperation),
* we can chain the method calls together and just put a call to compile() at the end, which will return
* an Action instance
*/
$fluentAction = (new EventBuilder($target))
->addOperation($myOperations[0])
->addOperation($myOperations[1])
->addOperation($myOperations[2])
->compile();
// Run the action
$fluentResult = $fluentAction->run();
// Traditional approach, create an instance and call the addOperation method as needed
$builder = new EventBuilder($target);
// Pass our mocked operations
while (($currAction = array_shift($myOperations)))
{
/*
* We can ignore the result from addOperation here, just keep calling the method
* on the builder variable
*/
$builder->addOperation($currAction);
}
/*
* After we've added all of our operations, we can call compile on the builder instance to
* generate our Action.
*/
$traditionalAction = $builder->compile();
// Run the action
$traditionalResult = $traditionalAction->run();
// Verify that the results from both techniques are identical
assert($fluentResult == $traditionalResult, 'Results from both techniques should be identical');
// Enjoy the fruits of our labor
echo json_encode($traditionalResult, JSON_PRETTY_PRINT).PHP_EOL;
Output:
[
"Repair the Car",
"Clean the Car",
"Drive the Car"
]
Rob Ruchte thank you for detailed explanation, one thing I did not include was that each operation itself had ->build() call and I needed to move that to each $builder for it to work.
I mostly use Iterators because I return multiple objects (new X(new Y(new Z(new A)));) and without using iterator it could drain a lot memory. My code:
<?php
namespace Bulletpoint\Model\Wiki;
use Nette\Caching\IStorage;
final class CachedBulletpoints implements Bulletpoints {
private $origin;
private $storage;
public function __construct(Bulletpoints $origin, IStorage $storage) {
$this->origin = $origin;
$this->storage = $storage;
}
public function iterate(): \Iterator {
$key = __CLASS__ . '::' . __FUNCTION__;
if($this->storage->read($key) === null) {
$this->storage->write(
$key,
iterator_to_array($this->origin->iterate()),
[]
);
}
return new \ArrayIterator($this->storage->read($key));
}
public function add(string $content, InformationSource $source) {
$this->origin->add($content, $source);
}
}
Then, another class, which can be wrapped by CachedBulletpoints:
<?php
namespace Bulletpoint\Model\Wiki;
use Bulletpoint\Model\Storage;
final class MySqlBulletpoints implements Bulletpoints {
private $database;
public function __construct(Storage\Database $database) {
$this->database = $database;
}
public function iterate(): \Iterator {
$rows = $this->database->fetchAll(
'SELECT ALL'
);
foreach($rows as $row) {
yield new ConstantBulletpoint(
$row['content'],
new \DateTime($row['date'])
);
}
}
public function add(string $content, InformationSource $source) {
$this->database->query(
'JUST INSERT'
);
}
}
Usage:
<?php
$bulletpoints = new CachedBulletpoints(
new MySqlBulletpoints(new PDODatabase),
new MemoryStorage // Cache
);
$bulletpoints->iterate();
$bulletpoints->iterate();
$bulletpoints->iterate();
I am afraid of converting iterator to array, because it does not seem like a good solution and I probably entirely lose the power of Iterators here. So, should I use in this case array rather than Iterator or what should I use to cache Iterator? I have read about CachingIterator, but it does not seem like a solution for me, does it?
Thanks :)
Im a fan of the jackson mapper in Java, and I'm a bit lost without it in php. I would like an equivalent.
So far the closest I have come across is this, however, it requires the fields to be declared as public, and I dont want to do that:
https://github.com/netresearch/jsonmapper
I want something that does everything that that does, with this sort of code:
<?php
class Contact
{
/**
* Full name
* #var string
*/
public $name; //<- I want this to be private
/**
* #var Address //<- and this
*/
public $address;
}
class Address
{
public $street;<- and this
public $city;<- and this
public function getGeoCoords()
{
//do something with the $street and $city
}
}
$json = json_decode(file_get_contents('http://example.org/bigbang.json'));
$mapper = new JsonMapper();
$contact = $mapper->map($json, new Contact());
Json from file_get_contents:
{
'name':'Sheldon Cooper',
'address': {
'street': '2311 N. Los Robles Avenue',
'city': 'Pasadena'
}
}
So I dont want to be writing individual constructors, or anything individual at all.
Im sure there would be something that does this out of the box using reflection?
You can provide a setter method for protected and private variables:
public function setName($name)
{
$this->name = $name;
}
JsonMapper will automatically use it.
Since version 1.1.0 JsonMapper supports mapping private and protected properties.
This can be achieved very easily and nicely using Closures.
There is even no need to create setter functions.
<?php
class A {
private $b;
public $c;
function d() {
}
}
$data = [
'b' => 'b-value',
'c' => 'c-value',
'd' => 'function',
];
class JsonMapper {
public function map( $data, $context ) {
$json_mapper = function() use ( $data ) {
foreach ($data as $key => $value) {
if ( property_exists( $this, $key ) ) {
$this->{$key} = $value;
}
}
};
$json_mapper = $json_mapper->bindTo( $context, $context );
$json_mapper();
return $context;
}
}
$mapper = new JsonMapper();
$a = $mapper->map( $data, new A );
print_r($a);
Sorry, I don't have enough 'reputation' so can't add a comment.
I've only been using Java for a few month, but my understanding is that your classes in Java will all have getters and settings, which is how Jackson is able to set the value of a private property.
To do the same in PHP, I suspect you would need to make your properties private, and create getter and setter methods...
public function setName($name) {
$this->name = name;
}
Then within your Mapper, use reflection to call the setter.
The way I would do this would be to look at the keys you have in the JSON, and try to put together a method name.
For example, if there's a key in the JSON labelled 'name'...
$className = "Contact";
$object = json_decode($jsonResponse);
$classObject = new $className();
foreach ($object as $key => $value) {
$methodName = "set" . ucfirst($key);
if (method_exists($classObject, $methodName)) {
$classObject->$methodName($value);
}
}
The above may not be exactly right, but I hope it gives you an idea.
To expand on the above, I've put together the following example which seems to do what you require?
class Contact {
private $name;
private $telephone;
public function setName($name) {
$this->name = $name;
}
public function setTelephone($telephone) {
$this->telephone = $telephone;
}
public function getName() {
return $this->name;
}
public function getTelephone() {
return $this->telephone;
}
}
class Mapper {
private $jsonObject;
public function map($jsonString, $object) {
$this->jsonObject = json_decode($jsonString);
if (count($this->jsonObject) > 0) {
foreach ($this->jsonObject as $key => $value) {
$methodName = "set" . ucfirst($key);
if (method_exists($object, $methodName)) {
$object->$methodName($value);
}
}
}
return $object;
}
}
$myContact = new stdClass();
$myContact->name = "John Doe";
$myContact->telephone = "0123 123 1234";
$jsonString = json_encode($myContact);
$mapper = new Mapper();
$contact = $mapper->map($jsonString, new Contact());
echo "Name: " . $contact->getName() . "<br>";
echo "Telephone: " . $contact->getTelephone();
There are lots of articles regarding factory method implementation in PHP.
I want to implement such a method for my MongoDB implementation in PHP.
I wrote the code something like below. Please Look at that code.
<?php
class Document {
public $value = array();
function __construct($doc = array()) {
$this->value = $doc;
}
/** User defined functions here **/
}
class Collection extends Document {
//initialize database
function __construct() {
global $mongo;
$this->db = Collection::$DB_NAME;
}
//select collection in database
public function changeCollection($name) {
$this->collection = $this->db->selectCollection($name);
}
//user defined method
public function findOne($query = array(), $projection = array()) {
$doc = $this->collection->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function find($query = array(), $projection = array()) {
$result = array();
$cur = $this->collection->find($query, $projection);
foreach($cur as $doc) {
array_push($result, new Document($doc));
}
return $result;
}
/* Other user defined methods will go here */
}
/* Factory class for collection */
class CollectionFactory {
private static $engine;
private function __construct($name) {}
private function __destruct() {}
private function __clone() {}
public static function invokeMethod($collection, $name, $params) {
static $initialized = false;
if (!$initialized) {
self::$engine = new Collection($collection);
$initialized = true;
}
self::$engine->changeCollection($collection);
return call_user_func_array(array(self::$engine, $name), $params);
}
}
/* books collection */
class Books extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('books', $name, $params);
}
}
/* authors collection */
class Authors extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('authors', $name, $params);
}
}
/* How to use */
$books = Books::findOne(array('name' => 'Google'));
$authors = Authors::findOne(array('name' => 'John'));
Authors::update(array('name' => 'John'), array('name' => 'John White'));
Authors::remove(array('name' => 'John'));
?>
My questions are:-
Is this correct PHP implementation of Factory method?
Does this implementation have any issues?
Are there any better methodologies over this for this scenario?
Thanks all for the answers.
Hmm no, because with your piece of code you make ALL methods on the collection class available for a static call. That's not the purpose of the (abstract) factory pattern.
(Magic) methods like __callStatic or call_user_func_array are very tricky because a developer can use it to call every method.
What would you really like to do? Implement the factory pattern OR use static one-liner methods for your MongoDB implementation?!
If the implementation of the book and author collection has different methods(lets say getName() etc..) I recommend something like this:
class BookCollection extends Collection {
protected $collection = 'book';
public function getName() {
return 'Book!';
}
}
class AuthorCollection extends Collection {
protected $collection = 'author';
public function getName() {
return 'Author!';
}
}
class Collection {
private $adapter = null;
public function __construct() {
$this->getAdapter()->selectCollection($this->collection);
}
public function findOne($query = array(), $projection = array()) {
$doc = $this->getAdapter()->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function getAdapter() {
// some get/set dep.injection for mongo
if(isset($this->adapter)) {
return $this->adapter;
}
return new Mongo();
}
}
class CollectionFactory {
public static function build($collection)
{
switch($collection) {
case 'book':
return new BookCollection();
break;
case 'author':
return new AuthorCollection();
break;
}
// or use reflection magic
}
}
$bookCollection = CollectionFactory::build('book');
$bookCollection->findOne(array('name' => 'Google'));
print $bookCollection->getName(); // Book!
Edit: An example with static one-liner methods
class BookCollection extends Collection {
protected static $name = 'book';
}
class AuthorCollection extends Collection {
protected static $name = 'author';
}
class Collection {
private static $adapter;
public static function setAdapter($adapter) {
self::$adapter = $adapter;
}
public static function getCollectionName() {
$self = new static();
return $self::$name;
}
public function findOne($query = array(), $projection = array()) {
self::$adapter->selectCollection(self::getCollectionName());
$doc = self::$adapter->findOne($query, $projection);
return $doc;
}
}
Collection::setAdapter(new Mongo()); //initiate mongo adapter (once)
BookCollection::findOne(array('name' => 'Google'));
AuthorCollection::findOne(array('name' => 'John'));
Does it make sense for Collection to extend Document? It seems to me like a Collection could have Document(s), but not be a Document... So I would say this code looks a bit tangled.
Also, with the factory method, you really want to use that to instantiate a different concrete subclass of either Document or Collection. Let's suppose you've only ever got one type of Collection for ease of conversation; then your factory class needs only focus on the different Document subclasses.
So you might have a Document class that expects a raw array representing a single document.
class Document
{
private $_aRawDoc;
public function __construct(array $aRawDoc)
{
$this->_aRawDoc = $aRawDoc;
}
// Common Document methods here..
}
Then specialized subclasses for given Document types
class Book extends Document
{
// Specialized Book functions ...
}
For the factory class you'll need something that will then wrap your raw results as they are read off the cursor. PDO let's you do this out of the box (see the $className parameter of PDOStatement::fetchObject for example), but we'll need to use a decorator since PHP doesn't let us get as fancy with the Mongo extension.
class MongoCursorDecorator implements MongoCursorInterface, Iterator
{
private $_sDocClass; // Document class to be used
private $_oCursor; // Underlying MongoCursor instance
private $_aDataObjects = []; // Concrete Document instances
// Decorate the MongoCursor, so we can wrap the results
public function __construct(MongoCursor $oCursor, $sDocClass)
{
$this->_oCursor = $oCursor;
$this->_sDocClass = $sDocClass;
}
// Delegate to most of the stock MongoCursor methods
public function __call($sMethod, array $aParams)
{
return call_user_func_array([$this->_oCursor, $sMethod], $aParams);
}
// Wrap the raw results by our Document classes
public function current()
{
$key = $this->key();
if(!isset($this->_aDataObjects[$key]))
$this->_aDataObjects[$key] =
new $this->sDocClass(parent::current());
return $this->_aDataObjects[$key];
}
}
Now a sample of how you would query mongo for books by a given author
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
// Wrap the native cursor by our Decorator
$cursor = new MongoCursorDecorator($cursor, 'Book');
foreach ($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}
You could tighten it up a bit with a MongoCollection subclass and you may as well have it anyway, since you'll want the findOne method decorating those raw results too.
class MongoDocCollection extends MongoCollection
{
public function find(array $query=[], array $fields=[])
{
// The Document class name is based on the collection name
$sDocClass = ucfirst($this->getName());
$cursor = parent::find($query, $fields);
$cursor = new MongoCursorDecorator($cursor, $sDocClass);
return $cursor;
}
public function findOne(
array $query=[], array $fields=[], array $options=[]
) {
$sDocClass = ucfirst($this->getName());
return new $sDocClass(parent::findOne($query, $fields, $options));
}
}
Then our sample usage becomes
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoDocCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
foreach($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}
How do I know what to load in a constructor and what to set using the set methods later on?
For example, I have a question class which most of the time will call the following vars:
protected $question;
protected $content;
protected $creator;
protected $date_added;
protected $id;
protected $category;
At the moment I have it so only the bare essentials $id, $question, and $content are set in the constructor so I don't start building up a huge list of constructor arguments. This however, means that when I make a new question object elsewhere, I have to set the other properties of that object straight after meaning 'setter code' getting duplicated all over the place.
Should I just pass them all into the constructor right away, do it the way I'm doing it already, or is there a better solution that I'm missing? Thanks.
Depending on the language you can have multiple constructors for any one class.
You could use an array as the parameter to the constructor or setter method.
Just example:
public function __construct($attributes = array()) {
$this->setAttributes($attributes);
}
public function setAttributes($attributes = array()) {
foreach ($attributes as $key => $value) {
$this->{$key} = $value;
}
}
PHP doesn't support traditional constructor overloading (as other OO languages do). An option is to pass an array of arguments into the constructor:
public function __construct($params)
{
}
// calling it
$arr = array(
'question' => 'some question',
'content' => ' some content'
...
);
$q = new Question($arr);
Using that, you're free to pass a variable number of arguments and there is no dependency on the order of arguments. Also within the constructor, you can set defaults so if a variable is not present, use the default instead.
I would pass an array to the constructor with the values I want to set.
public function __construct(array $values = null)
{
if (is_array($values)) {
$this->setValues($values);
}
}
Then you need a method setValues to dynamicly set the values.
public function setValues(array $values)
{
$methods = get_class_methods($this);
foreach ($values as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
For this to work you need setter methods for your properties like setQuestion($value) etc.
A fluent interface is another solution.
class Foo {
protected $question;
protected $content;
protected $creator;
...
public function setQuestion($value) {
$this->question = $value;
return $this;
}
public function setContent($value) {
$this->content = $value;
return $this;
}
public function setCreator($value) {
$this->creator = $value;
return $this;
}
...
}
$bar = new Foo();
$bar
->setQuestion('something')
->setContent('something else')
->setCreator('someone');
Or use inheritance...
class Foo {
protected $stuff;
public function __construct($stuff) {
$this->stuff = $stuff;
}
...
}
class bar extends Foo {
protected $moreStuff;
public function __construct($stuff, $moreStuff) {
parent::__construct($stuff);
$this->moreStuff = $moreStuff;
}
...
}
Or use optional parameters...
class Foo {
protected $stuff;
protected $moreStuff;
public function __construct($stuff, $moreStuff = null) {
$this->stuff = $stuff;
$this->moreStuff = $moreStuff;
}
...
}
In any case, there are many good solutions. Please dont use a single array as params or func_get_args or _get/_set/__call magic, unless you have a really good reason to do so, and have exhausted all other options.