Reputation: 5308
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:-
Thanks all for the answers.
Upvotes: 3
Views: 897
Reputation: 69611
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
}
Upvotes: 1
Reputation: 6630
__callStatic
or call_user_func_array
are very tricky because a developer can use it to call every method.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'));
Upvotes: 2