MongoDB - PHP - 工厂方法实现

时间:2015-03-03 05:11:58

标签: php mongodb methodology

PHP中有很多关于工厂方法实现的文章。 我想为PHP中的MongoDB实现实现这样的方法。

我写了类似下面的代码。请查看该代码。

<?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'));
?>

我的问题是: -

  1. 这是Factory方法的正确PHP实现吗?
  2. 此实施是否有任何问题?
  3. 对于这种情况,还有更好的方法吗?
  4. 感谢大家的答案。

2 个答案:

答案 0 :(得分:2)

  1. 嗯,不,因为使用您的代码片段,您可以使集合类上的所有方法都可用于静态调用。这不是(抽象)工厂模式的目的。
  2. (Magic)方法(如__callStaticcall_user_func_array)非常棘手,因为开发人员可以使用它来调用每个方法。
  3. 你真的想做什么?实现工厂模式或为MongoDB实现使用静态单行方法?!
  4. 如果书和作者集合的实现有不同的方法(比如getName()等..)我推荐这样的东西:

    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!
    

    编辑:使用静态单行方法的示例

    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'));
    

答案 1 :(得分:1)

Collection扩展Document是否有意义?在我看来,Collection可以拥有 Document(s),但不是 a Document ...所以我会说这段代码看起来有点纠结。

此外,使用工厂方法,您确实希望使用它来实例化DocumentCollection的不同具体子类。让我们假设您只有一种类型的Collection以便于交谈;那么你的工厂类只需要关注不同的Document子类。

因此,您可能有一个Document类,希望原始array代表单个文档。

class Document
{
    private $_aRawDoc;
    public function __construct(array $aRawDoc)
    {
        $this->_aRawDoc = $aRawDoc;
    }

    // Common Document methods here..
}

然后是给定文档类型的专用子类

class Book extends Document
{
    // Specialized Book functions ...
}

对于工厂类,您需要一些东西,然后在从光标读取时将原始结果包装起来。 PDO让您开箱即可(例如,参见PDOStatement::fetchObject$className参数),但我们需要使用装饰器,因为PHP不会让我们看看Mongo的扩展。

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];
    }
}

现在举例说明如何通过给定作者查询mongo的书籍

$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
}

您可以使用MongoCollection子类将其收紧一点,无论如何您也可以拥有它,因为您还希望findOne方法也能够装饰这些原始结果。

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));
    }
}

然后我们的样本用法变为

$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
}