MVC на phppatterns.com

Fred

Новичок
Автор оригинала: whirlwind
Для простоты совмещаем model и data mapper

PHP:
class model {

   ...

   function findById($id) 
   { 
            $res = $db->query('SELECT '.implode(', ', $this->model_attributes).' * FROM users WHERE id_user = ?', $id); 
            $this->data = $res->fetch_assoc(); 
    }

}
осталось ещё сделать поисковый метод статическим и мы прийдем к эктиврекорду :)

PHP:
class Model {

	protected $data = array();
	
	// ...
	
	static public function findById($id) {
		$data = DB::query('...')->fetchAssoc();
		
		$model = new Model();
		$model->data = $data;
		return $model;
	}
}
 

whirlwind

TDD infected, paranoid
Какие нафик статические методы? Забудьте про это увечье раз и навсегда. Кто-то один раз ошибся, а все подсели.

ЗЫ. нунапиши со статическим методом ActionView для контроллера, который подойдет к любой модели
 

whirlwind

TDD infected, paranoid
дальше классы Request, View и базовый контроллер для просмотра объекта модели, который на основе $request->get('id') сделает $view->set($attr,$value) для каждого атрибута модели. После можно использовать контроллер для отображения этой модели в любом месте сайта, просто передав ему id в Request.
 

Духовность™

Продвинутый новичок
Автор оригинала: whirlwind
дальше классы Request, View и базовый контроллер для просмотра объекта модели, который на основе $request->get('id') сделает $view->set($attr,$value) для каждого атрибута модели. После можно использовать контроллер для отображения этой модели в любом месте сайта, просто передав ему id в Request.
эээ.. whirlwind ты точно нигде не опечатался в последнем посте?

Request, я так понимаю из сказанного тобой - это не класс работы с HTTP/GPC, а некий слой уровня выше контроллера (содержащий объект контроллера)? Не совсем понятно с этим классом... id - это что? ID записи? Я так это понял:

// удалил
 

whirlwind

TDD infected, paranoid
нет, все гораздо проще. Request это тупо враппер $_REQUEST. Примерно что бы было так

PHP:
$ctrl = new ViewController(new User());
$request = new Request($_REQUEST);
$view = new View('template.tpl')
$ctrl->run($request,$view);
$view->display();
 

whirlwind

TDD infected, paranoid
мы пока говорим только о просмотре. соотв и назван что бы было ясно что контроллер делает. Названия классов должны говорить сами за себя. Чтобы глянул на название и понял о чем речь не ковыряясь во внутренностях. Можно так еще
PHP:
$ctrl = new Controller(new User(),new ActionView());
главное что бы было ясно что происходит.

На реквест лучше делать геттеры/сеттеры + hasVar ибо null это тоже значение, а вот exists это совсем другая ситуация. И View по интерфейсу можно сделать cхожим c Request. Тогда можно будет легко тестировать не привязываясь к конкретным шаблонам.
 

Духовность™

Продвинутый новичок
Контроллеры сами так и просятся: регистрация, редактирование, просмотр информации, краткая инфа в топике форума и т.п. и т.д. выбирай два на свое усмотрение
да, а как лучше сделать - один класс-контроллер и действия-методы edit, registration, view (регистрация, редактирование, просмотр информации..)

ИЛИ

на каждое действие -- свой контроллер?

UPD: Это, наверное, ответ на моё вопрос: мы пока говорим только о просмотре. соотв и назван что бы было ясно что контроллер делает. Названия классов должны говорить сами за себя. Чтобы глянул на название и понял о чем речь не ковыряясь во внутренностях.
 

whirlwind

TDD infected, paranoid
Эмм.. как то трудно работать и отвечать в форуме чз 15мин :) Давай пока самый простой вариант MVC рассмотрим. То есть то, что обсуждали в самом начале: есть юзер и нужно его отобразить на 3х страницах в разных вариантах. Иначе говоря - модель одна, контроллер один, а представления разные.

На самом деле, там тонкостей много, если сразу всеми заморачивацо, то моск лопнет.
 

dr-sm

Новичок
triumvirat ну вот например в ZF. группа действии обладающих сходной фунциональностью объединяются в один класс контролеера обычно, но можно сделать и на каждое действие свой контроллер.

===

а вот кстати кто как считает, формы это часть view или controller'a?
 

Духовность™

Продвинутый новичок
Так, вот что получилось. Пример полностью рабочий, без всего лишнего.
Пояснения и вопросы ниже.

PHP:
// Модель пользователя
class User
{
    // имена тех аттрибутов, которые может содержать в себе объект
    private $model_attributes = array();
    // данные пользователя
    private $data;

    public function __construct()
    {
        $this->model_attributes = array('user_name', 'user_age', 'user_sex');
    }

    public function __set($key, $value)
    {
        if (in_array($key, $this->model_attributes))
        {
            $this->data[$key] = $value;
        }
    }

    public function __get($key)
    {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }

    public function getAttributes()
    {
        return $this->data;
    }

    public function getObjectById($id)
    {
        if ($this->data == null)
        {
            // поскольку объекта БД и самой БД в примере не предусмотрено,
            // мы делаем заглушку получения результата:

            // $res = $db->query('SELECT '.implode(', ', $this->model_attributes).
            // ' * FROM users WHERE id_user = ?', $id);
            // $this->data = $res->fetch_assoc();

            // Исключительно для тестирования.
            $this->data = array('user_name' => 'Вася', 'user_age' => 15, 'user_sex' => 'M');
        }

        return $this->data;
    }
}


// Оболочка $_REQUEST
class Request
{
    private $data;

    public function __construct($request)
    {
        $this->clear($request);
    }

    // Очищаем request от пробелов и слэшей.
    private function clear($in)
    {
        foreach ($in as $key => $value)
        {
            if (is_array($value)) {
                $this->clearREQUEST($in[$key]);
            }
            else
            {
                $value = trim($value);

                if (get_magic_quotes_gpc()) {
                    $value = stripslashes($value);
                }

    			$in[$key] = $value;
            }
        }

        $this->data = $in;
    }

    public function __get($key)
    {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }

    public function __set($key, $value)
    {
        $this->data[$key] = $value;
    }

    public function __isset($key)
    {
        return isset($this->data[$key]);
    }
}


class ViewController
{
    private $model;
    private $view;
    private $request;

    private $error;
    private $bufer; // всё, что потом пойдет в view в качестве данных для вывода

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

    public function run(&$request, &$view)
    {
        $this->request = $request;
        $this->view    = $view;

        if (!$request->__isset(id_user))
        {
            $this->bufer['error'] = 'Не указан идентификатор пользователя!';
        }
        else
        {
            $user_data = $this->model->getObjectById($request->id_user);
            
            if (!$user_data)
            {
                $this->bufer['error'] = 'Пользователь с ID '.$request->id_user.' не найден';
            }
            else
            {
                foreach ($user_data as $key => $value)
                {
                    $this->bufer[$key] = $value;
                }
            }
        }

        // отдаем всё в view
        foreach ($this->bufer as $key => $value)
        {
            $this->view->$key = $value;
        }
    }
}


class View
{
    private $tpl; // путь до шаблона 
    private $data = array(); // данные для подстановки
    private $out; // скомпилированный шаблон 

    public function __construct($tpl)
    {
        $this->tpl = $tpl;
    }

    public function __get($key)
    {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }

    public function __set($key, $value)
    {
        $this->data[$key] = $value;
    }

    public function display()
    {
        extract($this->data);

        ob_start();
        include($this->tpl);
        $this->out = ob_get_contents();
        ob_end_clean();

        return $this->out;
    }
}


$_REQUEST = array('id_user' => 123);

$request = new Request($_REQUEST);
$ctrl = new ViewController(new User());
$view = new View('template.tpl');
$ctrl->run($request, $view);
echo $view->display();
Шаблон template.tpl:
PHP:
<? if (!$error): ?>
    Привет <?=$user_name?>!
    Тебе <?=$user_age?> лет, ты пола <?=$user_sex?>.
<? else: ?>
    Ошибка: <span style="color:red"><?=$error?></span>
<? endif; ?>


Что смущает:

- Метод контроллера run всего один. Получается, что 1 контроллер = 1 метод. При больших сценариях он чертовски разрастется.
- Сама модель не возвращает информативный результат - модель возвращает только значения. Определением того, что пришло от модели занимается контроллер.
- Не вижу гибкости. Как, например, несколько различных шаблонов прицепить к этой структуре?...

:confused: :mad:
 

whirlwind

TDD infected, paranoid
Давайте конкретные примеры

> Как, например, несколько различных шаблонов прицепить к этой структуре?...

Я не вижу где связь между шаблоном и набором данных View.

Если сделать хотя бы так
PHP:
 public function run(&$request, &$view) 
    { 
        $this->request = $request; 
        $this->view    = $view; 

        if (!$request->__isset(id)) 
        { 
            $this->bufer['error'] = 'NO_OBJECT_ID'; 
        } 
        else 
        { 
            $user_data = $this->model->getObjectById($request->id); 
             
            if (!$data) 
            { 
                $this->bufer['error'] = 'OBJECT_NOT_FOUND'; 
            } 
            else 
            { 
                foreach ($user_data as $key => $value) 
                { 
                    $this->bufer[$key] = $value; 
                } 
            } 
        } 

        // отдаем всё в view 
        foreach ($this->bufer as $key => $value) 
        { 
            $this->view->$key = $value; 
        } 
    } 
}
то этот контроллер подойдет для класса любой модели, удовлетворяющей интерфейсу User. Вынеси из User общее в клас Model, а в самом User оставь специфику. Если брать из примера, то специфика User заключается в двух строчках

model_attributes = ...
table = ...

-~{}~ 13.11.08 19:19:

PS. Я кажется понял что тебя смущает.

PHP:
class IndexPageController implements IController {
   
   function run($request,$view){
      $ctrl = new MyNewsControler();
      // $view->pushPrefix('news_'); 
      $ctrl->run($request,$view);

      $ctrl = new LastForumTopicsControler();
      // $view->pushPrefix('forum_'); 
      $ctrl->run($request,$view);

      $ctrl = new LoggedUserControler();
      // $view->pushPrefix('user_'); 
      $ctrl->run($request,$view);
   }

}
разве так нельзя сделать из сложного сценария простой?
 

fixxxer

К.О.
Партнер клуба
Перенес тред в теорию, негоже хорошей теме в оффтопе болтаться :)
 

Духовность™

Продвинутый новичок
Продолжим. На 2 страницы темы HraKK на меня нахеал, ему не понравилась моя изначальная модель:
PHP:
class user
{
    // Принимает массив данных вида 
    // 'id_user' => 12
    // 'user_name' = 'вася'
    // .....
    public function __construct($data) {}

    // возвращает объект user по числовому идентификатору.
    public static function getObjectById($id_user) {}

    /**
    * Возвращает TRUE, если пользователь является админом.
    * В контексте объекта user всегда возвращает FALSE.
    */
    public function isAdmin() {}

    // ....

    /**
    * Обновляем данные пользователя.
    */
    public function update($data) {}

    /*
    * Удаление пользователя.
    */
    public function delete() {}

    /**
    * Устанавливает "активность" пользователя.
    * Применяется при подтверждении регистрации, 
    * а также при банах.
    */
    public function setActive($active) {}

    /*
    * Когда зарегестрированный пользователь вхожит в систему, 
    * обновляем его данные - время посещения и IP.
    */
    public function setActualInfo() {}
}
так я и не понял, а в чем тут была моя ошибка? Ведь
Модель предоставляет данные (обычно для View), а также реагирует на запросы (обычно от контролера ), изменяя свое состояние.
данный класс как раз и занимается всем тем, что входит в сферу понятия "пользователь" - изменяет состояние данных.

HraKK написал, что как по мне это Controller а не Model. Ок! Если например мы метод setActive() который либо банит пользователя, либо нет, перенесем в контроллер, то зачем нам нужна модель без этого метода? Если мы в любом другом контроллере захотим забанить пользователя, а в инстанцируемой модели user не окажется такого метода, то нафига нам вообще нужна такая модель?

UPD: Неплохо было бы почистить тему, удалить всё что до http://phpclub.ru/talk/showthread.php?postid=822808#post822808
 

AmdY

Пью пиво
Команда форума
кстати, у тебя до сих пор не готова модель до этого действия
public function setActive($active) {
$this->active = (bool)$active;
return $this;
}
public function save() {}

$user->getObjectById($request->id_user)->setActive(true)->save();
 

Духовность™

Продвинутый новичок
AmdY
1. почему return $this?

2. save(), насколько я понимаю, сохранит ВСЕ данные находящиеся в модели. Может лучше в самом методе setActive() сделать запрос к БД на сохранение?

2. $user->getObjectById у нас в примере возвращает не объект, а массив. Это так, к слову..
 

Black Raven

Новичок
пожалуй попробую поучаствовать, только сильно ногами не бейте :)

triumvirat
1. наверно чтобы строить цепочки из методов, или я не так понял твой вопрос :)
2. save должен сохранять только измененные данные, для этого можно завести массив modified_fields, в который сеттеры будут добавлять имена измененных полей
3. метод getObjectById должен возвращать один объект, просто потому что он так называется :)

а насчет замечания HraKK мне тоже непонятно почему он в том коде увидел контроллер, а не модель

-- update

и еще не знаю уж к кому вопрос - сегодня не смог найти сообщения, но смысл сводился к тому, что использование статических методов как в пропеле Peer::doSelect это не очень правильно. тогда как правильно? ObjectsFactory::getInstance()->doSelect() или я вообще что-то не понимаю?
 

whirlwind

TDD infected, paranoid
>тогда как правильно?

ну, хотя бы так и то лучше

PHP:
class MPS_UI_A_terminal_edit implements MPS_IController {

    function run(MPS_RequestEnvironment $env,MPS_DataMapper_IRegistry $dmf){
        $request = $env->getRequest();
        $view = $env->getView();
        $mapper = $dmf->getMapper('Terminal');
        
        $id = $request->getInt('id',null);
        if ( !$id ){
            $obj = $mapper->createInstance();
        }else{
            $obj = $mapper->findById($id);
        }
        
        if ( $request->hasVar('save') ){
            $this->update($obj,$mapper,$request,$view);
        }
...
 
Сверху