Использование MVC паттерна

AlexVN

Новичок
Использование MVC паттерна

Доброго дня.

Приходят в голову идеи, в связи с разработкой SiteFramework.
Предисловие: объекты страницы находятся в древовидной структуре, каждый элемент которой является текстовым или предствляет собой экземпляр контрола. Контрол инкапсулирует в себе свойства для работы и методы рендеринга (составление HTML).
Например:
Код:
class SFLabel extends SFControl {

    var $text;
    var $escapeHTML;

    function SFLabel() {
        $this->escapeHTML = true;
        $this->text = null;
        parent::SFControl();
        $this->nodeName = 'Label';
        $this->statefull = true;
    }

    function render(&$output) {
        if ($this->visible)
            $this->raiseEvent($this->BEFORE_RENDER);
        if ($this->visible)
            $output->append($this->escapeHTML ? htmlspecialchars($this->text) : $this->text);
    }
}
Нальнейшее развитие событий: Начитавшись про MVC есть идея разделить View и Model. В частности, сделать Model полностью основанной на XML DOM от PHP5. Для задания свойств и их возможных значений использовать XSD. Тогда образуются классы вида:
Код:
class LabelRenderer extends SFControlRenderer {
    function render($model, $output) {
        if ($model->visible)
            $this->raiseEvent($this->BEFORE_RENDER);
        if ($model->visible)
            $output->append($model->escapeHTML ? htmlspecialchars($model->text) : $model->text);
    }
}
Т.е. все функции render переходят в отдельные классы. Валидаторы тоже переходя в отельный класс (группу классов).
Аналогичным образом, все вызовы onClick, onSubmit, и т.д. (т.е. server-events) переходят в контроллер.
Теперь, для чего это может быть полезно:
На sitepoint в дискуссии спросили на счет поддержки Smarty. Соответственно, можно для этого переписать только View, правильно? Кроме того, есть еще вывод в XHML/XML, а не только в HTML.

Вопрос: все ли я правильно понял на счет MVC и его можно так применить?

С уважением,
Александр
 

nRay

Guest
1 что касается " Валидаторы тоже переходят в отельный класс": думается, что валидовать себя должна уметь модель, я понимаю это так: модель включает в себя не только данные но методы управления данными, в том числе и валидацию

2 что касается "Соответственно, можно для этого переписать только View": возможно, это не совсем так, я не использую смарти, у меня свой темплейт-движок, но врятли это меняет суть, на вход визуализатора попадают данные и указывается как их отобразить(шаблон). Т.е. данные формируются контроллером и (у меня) эти данные view-зависимы, т.е. контроллер формирует такие данные, которые должен понять рендерер.

3 что касается MVC в веб разработках в целом: я для себя пока увидел только один вариант использования, в котором этот подход оправдывается, это формы.

Форма - это модель. Причем модель в моёй реализации не имеет данных как таковых, имеет только методы работы с моделью(данными).
Контроллер - отбрабатывает юзер-реквест, вызывает те или иные методы формы, тем самым реализует бизнесс логику.
Шаблонизатор - тут всё понятно.

пример (не рабочий, на коленке)
PHP:
// модель
class SimpleForm {

	function get_from_blank(){
	}
	function get_from_post(){
	}
	function get_from_db(){
	}
	function check(){
	}
	function save(){
	}

}

// вид
class Template {
	function render(){
	}
}

// контроллер
// его, как правило я не реализую в качестве класа

$sf	= & new SimpleForm();
$tpl	= & new Template();
$tpf	= array(); // данные, передаются почти всегда по ссылке

$action	= (isset($_POST['action'])) ? $_POST['action'] : '';
$id		= 5; // по разному бывает

switch ($action) {
	case 'add':
	case 'edit':
		if ($_POST['post']) {
			if ($sf->check($_POST, $tpf)) {
				$sf->save($_POST);
				header("Location: ...");
				exit;
			}
		} else {
			if ($id) {
				$sf->get_from_db($id, $tpf);
			} else {
				$sf->get_from_blank($tpf);
			}
		}
		$tpl->render($tpf, 'SimpleForm.tpl');
	break
	case 'del':
		$sf->del($id);
	break;
	default:
	break;
}
$tpl->render($tpf, 'SimpleIndex.tpl');
 

su1d

Старожил PHPClubа
nRay
то, что ты назвал моделью -- это скорее FrontController.
Модель -- контейнер с данными
Шаблон -- View
а класс с методами работы с Моделью -- Контроллер.

или не так?
 

nRay

Guest
по идее так конечно, но специфика веба накладывает свои ограничения, у нас же по сути транзакции, смысл объекта как контейнера с данными мне тут не очевиден.

тогда уж, то что я назвал моделью - Контроллер
то что я назвал контроллером - Фронт контроллер
шаблон - вид
массив($tpf) - модель
 

AlexVN

Новичок
1 что касается " Валидаторы тоже переходят в отельный класс": думается, что валидовать себя должна уметь модель, я понимаю это так: модель включает в себя не только данные но методы управления данными, в том числе и валидацию
Дело в том, что валидаторы - это нечто вроде .NET или Struts валидаторов. Т.е. они отдельные контролы, которые расположены на форме.
Например:
<!-- SF:RegularExpressionValidator Control=... RegExp="..." / -->
Cоответственно, у класса SFRegularExpressionValidator есть метод Validate. Если модель страницы переносится в XML, то получается, что сама по себе модель не может иметь методов.
Тогда получается отдельный блок классов или функций для обеспечений валидации.

2 что касается "Соответственно, можно для этого переписать только View": возможно, это не совсем так, я не использую смарти, у меня свой темплейт-движок, но врятли это меняет суть, на вход визуализатора попадают данные и указывается как их отобразить(шаблон). Т.е. данные формируются контроллером и (у меня) эти данные view-зависимы, т.е. контроллер формирует такие данные, которые должен понять рендерер.
А рендерер пишется для каждой страницы отдельно? По коду мне кажется, что это именно так. Я думаю, что у нас немного различается терминология тогда. Я под view понимаю набор render методов для каждого контрола.
Тогда для подержки другой темплейтно либы мне вместо одного механизма прорисовки надо будет задать другой... Кстати, пришла в голову мысль - мой view будет похож на XSL.
Соответственно, при желании его можно будет непосредственно на нем и написать! :)
3 что касается MVC в веб разработках в целом: я для себя пока увидел только один вариант использования, в котором этот подход оправдывается, это формы.
Согласен. Именно с целью быстрого создания форм я и затеял работу с SF. :)

Кстати, есть такая интересная идея - мне кажется, что в технологии PHP уже есть часть, которую можно рассматривать, как простой MVC. Это связка apache-php.
При этом моделью является $_GET[]/$_POST[]/etc, контроллером - apache и view - сам PHP :)
Например:
1.php
<?=$_GET['test']?>
вызываем - 1.php?test=1
Тогда: контроллер apache вызывает по этому запросу action, который находится в 1.php. Перед вызовом собственно кода заполняется модель $_GET. Код view <?=$_GET['test']?> рендерит модель.
Это, конечно, немного притянуто за уши, но, как мне кажется, слова поставлены в верном порядке :)
 

nRay

Guest
Cоответственно, у класса SFRegularExpressionValidator есть метод Validate. Если модель страницы переносится в XML, то получается, что сама по себе модель не может иметь методов.
Тогда получается отдельный блок классов или функций для обеспечений валидации.
Да. Но при этом, возможно, валидация получится не совсем гибкая: не совсем очевидно как валидовать зависимые значения. Например: поле1 - принимает значение 0 или 1. Поле2 в зависимости от значения поле1 имеет свои ограничения. Думаю, что валидаторы типа "является ли числом", "подходит ли под паттерн" нужно вообще писать статическими методами общего класса: Validator::isRegEx($val, $reg), а собирать уже их в группы взависимости от контекста проверки, которая в свою очередь как правило имеет очень небольшой процент реюза и отрывать её от модели выгоды не вижу.

А рендерер пишется для каждой страницы отдельно?
Нет конечно, если проводить аналогии с твоим фреймворком, то мой рендерер знает все "контроллы", их порядок и набор не важен, они понатыканный в шаблоне в перемешку с хтмл-ем, их рендеринг заложен в ядро визуализатора. Они довольно простые и контролами в твоём понимании не являются, суть их - набор данный необходимый для построения контролла сформированный на языке визуализатора.

Я под view понимаю набор render методов для каждого контрола...
Что-то мне не нравится в таком подходе. Мне кажется, что задача визуализатора принимать данные, пусть и жёстко структурированные, но данные о природе которых визуализатор ничего не знает, иначе мы теряем разделение данных от представления. Любой контрол у тебя должен иметь соответсвующую функцию визуализатора, которая сможет его нарисовать - не гибко. Быть может, тебе стоит добавить ещё один уровень между ними. Например у каждого контрола будет метод - get_as_xml_node(), контроллер соберёт все необходимые ноды и отдаст их xml/xslt парсеру. Тогда при переходе на другой визуализатор (smarty) каждый контрол получит(допишешь ручками) метот get_as_smarty_obj(). Методы пишутся в родительских классах, твой внутренний формат объекта никого кроме тебя не касается. Ещё добавишь враппер get_obj(), который взависимости от настроек сам знает что отдавать визуализатору get_as_smarty_obj() или get_as_xml_node(). А сам рендеринг в этом случае - абсолютно отдельный уровень и реализуется родными методами шаблонного движка.


Кстати, есть такая интересная идея - мне кажется
Всё верно, главное не утонуть в этом бесконечном уровне абстракций.
 

AlexVN

Новичок
Да. Но при этом, возможно, валидация получится не совсем гибкая: не совсем очевидно как валидовать зависимые значения. Например: поле1 - принимает значение 0 или 1. Поле2 в зависимости от значения поле1 имеет свои ограничения.
Это предполагалось в SF реализовать в виде
<!-- SF:CustomValidator FunctionName="checkIfUserValid" php:Visible="false" / --> в темплейте и
function checkIfUserValid(&$sender) {
$parent = &$sender->getParentNode();
$controls = &$parent->getControls();
$c =& $GLOBALS['SFApplication']->getClient();
return (... true or false...);
}
в коде.

Думаю, что валидаторы типа "является ли числом", "подходит ли под паттерн" нужно вообще писать статическими методами общего класса: Validator::isRegEx($val, $reg), а собирать уже их в группы взависимости от контекста проверки, которая в свою очередь как правило имеет очень небольшой процент реюза и отрывать её от модели выгоды не вижу.
Да, тем более в моем случае реюза вообще почти нет - у меня Database классы автоматически валидируют данные по описаниям таблиц.

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

А... Кстати, забыл сказать о формировании модели - модель строится по шаблону. Т.е. если есть <!-- SF:Textbox...> то это и есть элемент модели. Поэтому валидаторы и задаются на странице тоже и добавляются в модель конкретной страницы.

В общем я, кажется, понял, что мне нужно. Раньше надо было сказать Model->Validate(), то теперь мне нужно сказать Validator::Validate(Model). В этом и все отличие :)

-----------
С рендерами разобрались, как мне кажется.
-----------
...Мне кажется, что задача визуализатора принимать данные, пусть и жёстко структурированные, но данные о природе которых визуализатор ничего не знает, иначе мы теряем разделение данных от представления. Любой контрол у тебя должен иметь соответсвующую функцию визуализатора, которая сможет его нарисовать - не гибко. Быть может, тебе стоит добавить ещё один уровень между ними. Например у каждого контрола будет метод - get_as_xml_node(), контроллер соберёт все необходимые ноды и отдаст их xml/xslt парсеру. Тогда при переходе на другой визуализатор (smarty) каждый контрол получит(допишешь ручками) метот get_as_smarty_obj()....
Я, кажется, понял. Представление должно быть отделенно от данных в том смысле, что model и view должны всегда использовать единые интерфейсы. Т.е. должна быть возможность заменить только модель или view не изменяя ничего другого. Но поскольку я хочу использовать XML для model, то это накладывает такое ограничение, что view должен всегда общаться с XML (это природа данных?). Но тогда это может быть действительно не гибко. Можно определить некоторые минимально достаточные интерейсы модели и написать wrapper (адаптор паттерн?) для XML. Таким образом, при изменении модели надо будет переписать только этот адаптор.

Тогда при добавлении нового view или замены модели ничего большого делать не придется.
Ок - Render делается отдельным уровнем, который общается с моделью через маленькое окошко, получает данные и выводит
(Label):
XSL view : <xsl:value-of select="<!-- интересно, можно ли сюда поставить непосредственно PHP код?--> $model->getCurrent()->getText()"/> :)
PHPLib: $tpl->setVar('Label', $model->getCurrent()->getText());
Smarty: $tpl->assign('Label', $model->getCurrent()->getText());
?

Как такой подход?

-~{}~ 01.06.04 13:30:

Доброго!

В общем, обработал я всю информация и немного подшаманил SF.
Получилась такая схема:
1. В классах модели остались только свойства и методы установки значений из request (надо бы их будет вынести или переделать для того, что бы можно было изменить методы инициализации модели).
2. Все блоки, вырисовывающие модель вынесены в Renderers.php. Такой большой view получился. Далее можно сделать его варианты для XHTML, WebServices, RDF.
3. Контроллеры вынесены в Controllers.php - фактически есть PageContoller, FormController, ButtonController. Page вызывает контроллеры своих children и т.д.

Это можно назвать реализацией MVC? Соответствует? :)

Ссылки по теме (at sourceforge.org):
Controllers.php
Renderers.php
Textbox.php - model class example
 
Сверху