MongoDB — Фабричный метод реализации

Есть много статей относительно реализации фабричного метода в PHP.
Я хочу реализовать такой метод для моей реализации MongoDB в PHP.

Я написал код что-то вроде ниже. Пожалуйста, посмотрите на этот код.

<?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. Это правильная реализация PHP метода Factory?
  2. Есть ли у этой реализации какие-либо проблемы?
  3. Есть ли лучшие методологии по этому для этого сценария?

Спасибо всем за ответы.

3

Решение

  1. Хм нет, потому что с помощью вашего кода вы делаете ВСЕ методы класса коллекции доступными для статического вызова. Это не цель (абстрактного) фабричного шаблона.
  2. (Магия) методы, такие как __callStatic или же call_user_func_array очень сложно, потому что разработчик может использовать его для вызова каждого метода.
  3. Что бы вы действительно хотели сделать? Реализовать шаблон фабрики ИЛИ использовать статические однострочные методы для реализации MongoDB ?!

Если реализация коллекции книг и авторов имеет разные методы (скажем, 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'));
2

Другие решения

Имеет ли это смысл для Collection расширить Document? Мне кажется, что Collection мог иметь Document(s), но не быть Document… Так что я бы сказал, что этот код выглядит немного запутанным.

Кроме того, с фабричным методом вы действительно хотите использовать его для создания другого конкретного подкласса: Document или же Collection, Предположим, у вас есть только один тип 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 позволяет вам сделать это из коробки (см. $className параметр PDOStatement::fetchObject например), но нам нужно будет использовать декоратор, так как 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];
}
}

Теперь пример того, как вы бы запросили монго для книг данного автора

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

По вопросам рекламы [email protected]