Какова лучшая практика для добавления постоянства в модель MVC? - PullRequest
4 голосов
/ 25 октября 2009

Я нахожусь в процессе реализации сверхлегкого MVC-фреймворка в PHP. Кажется, существует общее мнение, что загрузка данных из базы данных, файла и т. Д. Должна быть независимой от модели, и я согласен. В чем я не уверен - это лучший способ связать этот «слой данных» с MVC.


Хранилище данных взаимодействует с моделью

//controller
public function update()
{

 $model = $this->loadModel('foo');
 $data = $this->loadDataStore('foo', $model);

 $data->loadBar(9); //loads data and populates Model
 $model->setBar('bar');
 $data->save(); //reads data from Model and saves

}

Контроллер является посредником между моделью и хранилищем данных

Кажется немного многословным и требует, чтобы модель знала, что хранилище данных существует.

//controller
public function update()
{

 $model = $this->loadModel('foo');
 $data = $this->loadDataStore('foo');

 $model->setDataStore($data);

 $model->getDataStore->loadBar(9); //loads data and populates Model
 $model->setBar('bar');
 $model->getDataStore->save(); //reads data from Model and saves

}

хранилище данных расширяет модель

Что произойдет, если мы захотим сохранить модель, расширяющую хранилище базы данных, в хранилище плоских файлов?

//controller
public function update()
{

 $model = $this->loadHybrid('foo'); //get_class == Datastore_Database

 $model->loadBar(9); //loads data and populates
 $model->setBar('bar');
 $model->save(); //saves

}

Модель расширяет хранилище данных

Это допускает переносимость модели, но кажется неправильным ее расширение. Кроме того, хранилище данных не может использовать ни один из методов Модели.

//controller extends model
public function update()
{

 $model = $this->loadHybrid('foo');  //get_class == Model

 $model->loadBar(9); //loads data and populates
 $model->setBar('bar');
 $model->save(); //saves

}

РЕДАКТИРОВАТЬ: модель общается с DAO

//model
public function __construct($dao)
{
    $this->dao = $dao;
}

//model
public function setBar($bar)
{
    //a bunch of business logic goes here
    $this->dao->setBar($bar);
}

//controller
public function update()
{
    $model = $this->loadModel('foo');
    $model->setBar('baz');
    $model->save();
}

Любой вклад в «лучшую» опцию - или альтернативу - наиболее ценится.

Ответы [ 5 ]

7 голосов
/ 25 октября 2009

Я не думаю, что он принадлежит контроллеру - это действительно часть представления, которое может приходить и уходить. Бизнес-логика и постоянство не должны зависеть от представления или контроллера.

Отдельный сервисный уровень дает вам возможность представить эту логику в качестве распределенных компонентов для клиентов, не основанных на браузере (например, мобильные представления, пакетная обработка и т. Д.)

Я бы не позволил модели расширить хранилище данных в объектно-ориентированном смысле, потому что наследование - это отношение IS-A. Как вы заметили, модель не является реляционной базой данных; это всего лишь один из многих вариантов сохранения информации.

Я думаю, что это больше композиции. Уровень персистентности может быть отдельным DAO, который сохраняет объекты модели, не требуя, чтобы они знали о том, что они являются более долгоживущими. ИЛИ у вас смешанное поведение, когда модель объявляет тот факт, что у нее есть операции CRUD, но она просто передает запросы реализации, которую они дают.

Обычная идиома Spring имеет служебный уровень, отдельный от контроллера. Он знает о вариантах использования и единицах работы. Он использует объекты модели и персистентности для достижения целей варианта использования.

MVC подразумевает трех актеров. Я бы сказал, что в типичном приложении больше слоев, чем в этом случае, - необходимо добавить постоянство и обслуживание.

ОБНОВЛЕНИЕ:

Если объект модели ничего не импортирует из пакета персистентности, он вообще ничего об этом не знает. Вот простой пример, использующий простой класс модели Person и его интерфейс DAO, каждый в своем собственном пакете:

package persistence.model;

public class Person
{
    private String name;

    public Person(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }
}

Обратите внимание, что интерфейс DAO импортирует пакет модели, но не наоборот:

package persistence.persistence;

import persistence.model.Person;

import java.util.List;

public interface PersonDao
{
    Person find(Long id);
    List<Person> find();

    void saveOrUpdate(Person p);
}
1 голос
/ 06 июня 2010

RedBeanPHP использует систему Fuse для решения этой проблемы. (подробности в вики: http://redbeanphp.com/community/wiki/index.php/Fuse)

//Controller
$foo = R::load("foo", 9);
$foo->bar = "bar";
R::store( $foo );

Здесь ваша модель на самом деле не более, чем боб. Однако система объединяет компонент с полноценной моделью по требованию.

class Model_Foo extends RedBean_SimpleModel {
  public function update() {
    if ($this->bar!="bar") do something... 
  }
}

Фреймворк связывает Model_Foo с компонентом типа "foo" на лету и вызывает метод update () непосредственно перед сохранением. Здесь вы можете хранить бизнес-правила. Помимо update (), вы также можете использовать open () и delete ().

0 голосов
/ 16 декабря 2009

Я закончил украшать свою модель классом хранилища данных:

//datastore class
public function __call($name, $arguments)
{

    if (!method_exists($this->model, $name))
        throw new Exception('does not exist!');

    return call_user_func_array(array($this->model, $name), $arguments);

}

//controller - decorating
$model = new Model;
$datastore = new Datastore($model);
$datastore->actions();
0 голосов
/ 30 октября 2009

Я думаю, что вы пытаетесь создать структуру, которая слишком тесно связана с именами и понятиями. Я обнаружил, что имена и понятия в идиоме Model-View-Controller не определены жестко. Я также обнаружил, что код, работающий в этой парадигме, должен иногда уметь изменять правила.

@ duffymo имеет очень важное замечание относительно компонента Model: модель не является хранилищем данных, она просто имеет хранилище данных. Если это не ясно, другой способ думать об этом - посмотреть, каким должен быть экземпляр каждого из них. Экземпляр объекта «хранилище данных» представляет ресурс, который обеспечивает доступ к произвольному количеству данных. Обычно это соединение с базой данных. Экземпляр «модельного» объекта обычно представляет собой дискретную идентичность с несколькими частями данных. Обычно он представляет строку базы данных в таблице, но это может быть строка в текстовом файле или один файл в хранилище файлов.

Применив один и тот же вопрос к контроллеру и представлению, вы увидите, что часть модели - это совсем другое животное. Хотя обычно одновременно существует только один объект Controller и один объект View, вполне вероятно, что одновременно существует несколько различных объектов Model разных типов.

К сожалению, я также знаю, что многие MVC-среды определяют «Модель» как уровень API, за которым вы выполняете операторы SQL. В этих средах «Модель» - это статический класс или отдельный экземпляр, и он не делает ничего, кроме как обеспечивает практически бесполезное разбиение пространства имен. И многие программисты считают, что это должно иметь смысл и бороться с этим. Вот почему я рекомендую вам не использовать названия и понятия идиомы MVC.

Мой предпочтительный способ написания структурированного PHP только кивает на парадигму MVC. В верхней части находится логика диспетчера и контроллера, а в нижней части - разметка HTML. Это хорошо работает, потому что контроллеры и представления часто тесно связаны между собой, и помещение их в один и тот же файл очень сильно мешает PHP. Да, требуется дисциплина, чтобы не выполнять действия контроллера в коде представления, но я бы предпочел сделать это сам, чем заставить систему заставить меня это делать. Эти страницы не являются самостоятельными, однако они содержат много общей логики и могут импортировать такие вещи, как облегченная диспетчерская система, или даже вызывать целые серии общей логики контроллера.

Но главное, что обеспечивает общая логика, это уровень абстракции данных - в основном уровень модели, как описано в верхней части моего ответа. Уровень абстракции данных, который я написал, состоит в основном из трех вещей: обработчик базы данных, объект «объект» и объект «коллекция».

Обработчик базы данных - это хранилище данных для всех объектов, которые находятся в базе данных. Его основная цель заключается в том, чтобы все операции с базой данных, которые не выполняют запрос и не получают ответ, были в одном месте. Большинство вызовов базы данных находятся в ядре объекта. Обработчик базы данных ничего не знает о ядре объекта: он просто берет SQL и возвращает наборы результатов.

Два других объекта предназначены для подкласса и не будут работать, если будут созданы сами. Используя наследование и специальное расширенное объявление (сделанное методом статического класса), они знают, как превратить база данных данных в объект. Экземпляр унаследованного объекта «объект» представляет одну строку объявленного класса. Ему присваивается идентификатор при создании экземпляра, и он будет запрашивать базу данных, когда ему необходимо получить одну строку данных. Он также отслеживает изменения и может сделать однорядное ОБНОВЛЕНИЕ, когда ему сказано. API, который он представляет, полностью лишен SQL: у объекта есть поля you -> get () и -> set (), и вы можете -> save (), когда закончите.

Объект "коллекция" знает, как получить отфильтрованный список строк конкретного объекта, и знает, как соответствующим образом превратить его в отдельные объекты, каждый из которых функционирует так же, как они были созданы индивидуально. (Объект коллекции также поддерживает сам объект, но я редко использую эту функцию, поскольку в настоящее время она имеет некоторые ограничения реализации. И я обнаружил, что большинство программистов с трудом понимают эту проблему.)

Большинству объектов требуется только основной код наследования и объявления для их конкретной таблицы.

Конечным эффектом является то, что код контроллера делает очевидные вещи, подобные этому:

$ticket = Ticket::Create($ticket_id);
$ticket->set('queue', $new_queue);
$ticket->set('queue_changed', date('Y-m-d H:i:s'));
$ticket->save();

... вместо чего-то непрозрачного, например:

TicketModel::ChangeTicketQueue($ticket_id, $new_queue);

Этот подход также позволяет вам кодировать вещи в объекте Ticket, которые могут обновлять другие поля или другие объекты при изменении поля queue, и это всегда будет происходить при изменении этого поля, вместо того, чтобы не забывать делать это в каждая функция, которая может изменить очередь заявки. Это также означает, что вы можете добавить другие методы в Билет: вероятно, для $ticket->get_queue() будет иметь смысл сделать return TicketQueue::Create($this->get('queue')); для вас. Объектно-ориентированное программирование во всей красе! :-)

Итак, чтобы окончательно ответить на ваш вопрос, я бы рекомендовал none подходов в вашем вопросе. Ваши объекты модели должны представлять унифицированный API для кода контроллера, и они сами должны определить, как они хранят свои данные. Если он находится в базе данных, то для этого им необходимо организовать свои собственные вызовы. Но модель не является хранилищем данных: тем не менее, она может иметь единицу.

0 голосов
/ 25 октября 2009

Мой голос за то, что я пошел по маршруту, который расширяет хранилище данных. Это кажется наиболее естественным, поскольку в действительности ваша модель является расширением основного хранилища данных. Однако вы можете захотеть использовать шаблон проектирования службы, чтобы отделить создание объекта модели от потребления результирующего объекта.

class Model {
   protected $_tablename == __CLASSNAME__; // or some derivative of the classname
   protected $_datastore;
   protected $_typeOfDataStore;

   function __construct( $type == 'mysql' ) { 
      $this->_typeOfDataStore = $type;
      $this->bindToDataStore();
   }

   function bindToDataStore( $ds ) { 
      $this->_datastore = DataStoreFactory::buildDataStore( $this->_typeOfDataStore );
   }

}

class DataStoreFactory {

   function buildDataStore( $type ) {
     switch ( $type ) {
       case 'flatfile' : return new FlatFileDataStore();
       case 'mysql': return new MySQLDataStore();
     }
   }
}

class DataStoreBase {
   function connect() { }
   function disconnect() { }
   function getData() { }
   // ...
} 

class FlatFileDataStore extends DataStoreBase { }
class MySQLDataStore extends DataStoreBase { 
   function runQuery() { }
}

Это строительная часть. Когда вам нужно создать / внедрить новую модель, вы можете просто расширить модель:

class Users extends Model {
   protected $_tablename = 'users';
   protected $_typeOfDataStore = 'flatfile';

   function getAllUsers() {
      return $this->runQuery( "SELECT * FROM " . $this->_tablename );
   }

}

А в вашем контроллере:

$usersModel = new Users();
$usersModel->getAllUsers();

Возражение, которое вы указали о том, что хранилище данных не может использовать какой-либо из методов Модели, является хорошим ограничением для системы. Модель является потребителем хранилища данных, и поэтому хранилище данных не должно иметь возможность напрямую вызывать функции объекта модели. Модель должна поддерживать контроль и использовать службу хранилища данных, когда это необходимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...