Warning: Cannot use a scalar value as an array in /home/admin/public_html/forum/include/fm.class.php on line 757

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/include/fm.class.php on line 770

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/topic.php on line 737
Форумы портала PHP.SU :: Урок №19 - Немного о паттернах

 PHP.SU

Программирование на PHP, MySQL и другие веб-технологии
PHP.SU Портал     На главную страницу форума Главная     Помощь Помощь     Поиск Поиск     Поиск Яндекс Поиск Яндекс     Вакансии  Пользователи Пользователи


 Страниц (7): [1] 2 3 4 5 6 7 » 

> Описание: Архитектура MVC своими руками.
EuGen Администратор
Отправлено: 23 Апреля, 2011 - 11:45:07
Post Id


Профессионал


Покинул форум
Сообщений всего: 9095
Дата рег-ции: Июнь 2007  
Откуда: Berlin


Помог: 707 раз(а)




Пара слов для вступления
Приветствую.
В этом уроке речь пойдет об одном из способов организации веб-приложения. О всем известной хотя бы на слух MVC. Оговорюсь заранее, что для того, чтобы понять содержание урока, вам будет необходимо изучить предыдущие уроки этой ветки форума и, быть может, не раз обратиться к документации http://php.net

Немного терминов
Начинать стоит, конечно же, с самого MVC. Расшифровка её такова: Model-View-Controller. Иными словами, веб-приложение, созданное на основе этой архитектуры, можно логически поделить на 3 группы:

  • Model - часть скриптов, которая отвечает за реализацию работы с исходными ресурсами. Иными словами, этот функционал описывает их поведение в приложении в соответствии с бизнес-логикой. Очень часто к ним относятся различные классы, реализующие работу с данными в БД. Но это далеко не единственный вид моделей. Вообще, модель - это совокупность описывающих какой-то процесс правил и свойств, так что в эту категорию попадает широкий спектр классов реализуемого приложения.
  • View - отвечает за представление данных. Неважно какими способами и из каких источников они получены, как обработаны и хранятся, в конечном итоге цель приложения - отобразить данные пользователю. И архитектура MVC разделяет логику и представление. К этой части относятся шаблоны. В них определяется конечный вид на веб-станицах - определяются html-элементы, подключаются стили и т.п. Однако сейчас все чаще шаблоны начинают отвечать и за функциональную часть приложения. Например, в них встраиваются элементы, выполняющие AJAX-запросы на сервер, являясь таким образом уже частью функциональности. Это не идет вразрез с идеологией MVC, просто расширяет понятие View
  • Controller - служит "мостом" между View и Model. Это тот функционал приложения, который реализует его бизнес-процессы. Он отвечает за прием и обработку пользовательских данных, обращается к моделям (Model) приложения и использует представления (View) для вывода. Классически, контроллер делится на Action - действия. Действие - это часть контроллера, направленная на выполнение конкретного функционала. Например, "добавить пользователя", "сохранить запись" и т.п.

Далее, после того, как мы несколько познакомились с частями MVC, расскажу еще об одной популярной идее по реализации этой архитектуры. А именно - о фронт-контроллере. Фронт-контроллер (Front Controller) это некоторый управляющий контроллер. Через него идут все обращения к веб-приложению, по сути он - единая точка входа. Есть несколько способов реализации этого, однако чаще всего это делается через соответствующую настройку веб-сервера с перехватом запросов и перенаправлением их конкретному скрипту.

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

Реализация
Теперь мы уже можем подойти собственно к самому процессу реализации. Приведу скрипт index.php, на который перенаправляются все запросы, пришедшие приложению:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. define('APPLICATION_PATH', realpath(dirname(__FILE__)));
  4. define('VIEWS_PATH', realpath(dirname(__FILE__)).'/Web/Views/');
  5.         APPLICATION_PATH . '/Lib' . PATH_SEPARATOR .
  6.         APPLICATION_PATH . '/Web' . PATH_SEPARATOR .
  7.         APPLICATION_PATH . '/Core' . PATH_SEPARATOR .
  8.         './bbcode/' .PATH_SEPARATOR. get_include_path());
  9. function __autoload($className)
  10. {
  11.     $fname = str_replace('_', '/', $className) . '.php';
  12.     $result = include_once($fname);
  13.     return $result;
  14. }
  15. $router = new Router();
  16. $router->run();
  17. ?>
  18.  

Пара слов об __autoload. Это - функция, которая служит для динамического подключения скриптов "по требованию". Обычно в имя класса закладывается некоторая логика, чтобы такое подключения могло быть разобрано относительно легко, и класс подключился через эту функцию. Я в этом примере использовал имя класса, разделенное "_", указывающее на подкаталоги.

Для того, чтобы перенаправление происходило, веб-сервер настраивается через .htaccess файл примерно так:
CODE (text):
скопировать код в буфер обмена
  1.  
  2. ErrorDocument 404 /
  3.  
  4. RewriteEngine On
  5. RewriteCond %{REQUEST_FILENAME} -s [OR]
  6. RewriteCond %{REQUEST_FILENAME} -l [OR]
  7. RewriteCond %{REQUEST_FILENAME} -d
  8. RewriteRule ^.*$ - [L]
  9. RewriteRule ^.*$ index.php [L]
  10.  


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

Перейдем к реализации маршрутизатора (который в нашем примере есть еще и фронт-контроллер по сути)

PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. class Router
  4. {
  5.     private $routes;
  6.  
  7.     function __construct()
  8.     {
  9.         $this->_load_defaults();
  10.     }
  11.  
  12.     function getURI()
  13.     {
  14.         $uri=$this->_getURI();
  15.         $webRoot=Config_Handler::getInstance()->getWebpath();
  16.  
  17.         return ltrim(preg_replace('#^'.$webRoot.'#i', '', $uri), '/');
  18.     }
  19.     //
  20.  
  21.     function run()
  22.     {
  23.         $uri = $this->getURI();
  24.         foreach($this->routes as $route=>$destination)
  25.         {
  26.             if(preg_match($route, $uri))
  27.             {
  28.                 $internalRoute = preg_replace($route, $destination, $uri);
  29.                 $segments = explode('/', $internalRoute);
  30.                 $controller = 'Controller_'.ucfirst(array_shift($segments));
  31.                 $method = array_shift($segments).'Action';
  32.                 $parameters = $segments;                
  33.                 //launch controller:
  34.                 if(class_exists($controller))
  35.                 {
  36.                     $obj    = new $controller;
  37.                     if(method_exists($obj, $method))
  38.                     {
  39.                         call_user_func_array(array($obj, $method), $parameters);
  40.                         return;
  41.                     }
  42.                     else
  43.                     {
  44.                         $this->_default_error();
  45.                     }
  46.                 }
  47.                 else
  48.                 {
  49.                     $this->_default_error();
  50.                 }
  51.             }
  52.         }
  53.         return;
  54.     }
  55.  
  56.     protected function _default_error()
  57.     {
  58.         header("HTTP/1.0 404 Not Found");
  59.         exit("<table style='width:100%; height: 100%'><tr><td align='center' valign='center'><h1>404 Not Found</h1></td></tr></table>");
  60.     }
  61.  
  62.     protected function _load_defaults()
  63.     {
  64.         $this->routes=array(
  65.             '#([-_a-z0-9]+)/([-_a-z0-9]+)/([-_a-z0-9]+)[\?]?(.*)#'   => '$1/$2/$3/$4',
  66.             '#([-_a-z0-9]+)/([-_a-z0-9]+)[\?]?(.*)#'                 => '$1/$2/$3',
  67.             '#([-_a-z0-9]+)[\?]?(.*)#'                               => '$1/$2',
  68.         );
  69.         return true;
  70.     }
  71.  
  72.     protected function _getURI()
  73.     {
  74.         if(!empty($_SERVER['REQUEST_URI']))
  75.         {
  76.             return trim($_SERVER['REQUEST_URI'], '/');
  77.         }
  78.         if(!empty($_SERVER['PATH_INFO']))
  79.         {
  80.             return trim($_SERVER['PATH_INFO'], '/');
  81.         }
  82.         if(!empty($_SERVER['QUERY_STRING']))
  83.         {
  84.             return trim($_SERVER['QUERY_STRING'], '/');
  85.         }
  86.     }
  87. }
  88.  
  89. ?>
  90.  


Маршрутизатор этот очень прост. По сути, он ничего не делает с пришедшим запросом, а просто разбивает его по "/" и делит на составляющие, интерпретируя их соответственно как имя контроллера и его действие. То есть /users/list будет приведено к Controller_Users, и вызвано его действие listAction, вот и все.
Для того, чтобы не заботиться о том, откуда берутся маршруты, сделан метод _load_defaults, который содержит в себе их массив. Задача читателям будет преобразовать это к более удобному - настраиваемому - виду.
В маршрутизаторе используется класс Config_Handler, который будет рассмотрен позднее. Надобность в нем обусловлена тем, что для правильной работы необходимо знать каталог от корня, в котором располагается веб-приложение. В самом деле, если виртуальный хост сделан так, что его имя simplemvc.dev и указывает он, например, на

/var/www/projects/simplemvc

А проект мы расположим в

/var/www/projects/simplemvc/we/d ont/care/about/path

То чтобы вызвать его через веб, нам нужно указать

http://simplemvc[dot]dev/we/dont/car[dot][dot][dot]rname/actionname

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

Контроллеры
Сейчас, когда мы уже знаем, как происходит вызов контроллеров, пора их описать. В примере, который используется здесь, все контроллеры наследуются от некоторого базового. Так достигается удобство того, что общие задачи всех контроллеров доступны им всем сразу, и сосредоточены в одном месте. Это достигается базовым классом:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. class Controller_Base
  4. {
  5.     protected $view;
  6.     protected $dbInstance;
  7.     protected $currentUser;
  8.  
  9.     function __construct()
  10.     {
  11.         session_start();
  12.         $rDb    = new Db_Controller();
  13.         $this->view = new View_Base();
  14.         $this->dbInstance   = $rDb->getAdapter();
  15.         /* handle possible auth, for example:
  16.         if(isset($_SESSION['login']))
  17.         {
  18.             $this->currentUser=$_SESSION['login'];
  19.         }
  20.         */
  21.     }
  22.  
  23.     function redirect($url)
  24.     {
  25.         if($url[0]!='/')
  26.         {
  27.             $url[0]='/';
  28.         }
  29.         header('Location: '.Config_Handler::getInstance()->getWebroot().$url);
  30.         exit();
  31.     }
  32.  
  33.     function getParam($param)
  34.     {
  35.         $mValue = $this->_get_param($param, $_GET);
  36.         if(!$mValue)
  37.         {
  38.             $mValue = $this->_get_param($param, $_POST);
  39.         }
  40.         return $mValue;
  41.     }
  42.  
  43.     function loginUser($rgUser)
  44.     {
  45.         /* here we are saving login of user, for example:
  46.         $_SESSION['login']=$rgUser['login'];
  47.         */
  48.     }
  49.  
  50.     function logoutUser()
  51.     {
  52.         /* here we are doing logout of user, for example:
  53.         unset($_SESSION['login']);
  54.         */
  55.     }
  56.  
  57.     protected function _get_param($param, $array)
  58.     {
  59.         if(!array_key_exists($param, $array))
  60.         {
  61.             return null;
  62.         }
  63.         return $array[$param];
  64.     }
  65. }
  66.  
  67. ?>
  68.  


Как видно, ряд общих вещей вынесено в базовый контроллер. У него есть два важных свойства - соединение с БД и текущее представление. Оба этих класса мы тоже рассмотрим, а пока что их инициализацию можно увидеть в конструкторе. Кроме прочего, так как чаще всего у приложения есть часть, предназначенная для авторизованных пользователей, контроллер предоставляет свойство currentUser - содержащее текущего пользователя, в случае, если он авторизован.
Приведу пример контроллера, который является конечным и действия которого уже вызываются:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. class Controller_Test extends Controller_Base
  4. {
  5.     function showAction($url = null)
  6.     {
  7.         if(!$this->currentUser)
  8.         {
  9.             $this->redirect('/test/auth');
  10.         }
  11.         $rgPages=$this->dbInstance->select('some_table', array(
  12.             'id',
  13.             'name'
  14.         ));
  15.         if(!$rgPages)
  16.         {
  17.             $rgPages=array();
  18.         }
  19.         $this->view->rgPages=$rgPages;
  20.         $this->view->render('showview');
  21.     }
  22.  
  23.     function updateAction()
  24.     {
  25.         $id=$this->getParam('id');
  26.         $rgFields=array(
  27.             'name'         => $this->getParam('name')
  28.         );
  29.         if(!$this->currentUser)
  30.         {
  31.             $this->redirect('/test/auth');
  32.         }
  33.         if(!$id)
  34.         {
  35.             $this->redirect('/test/show');
  36.         }
  37.         $this->dbInstance->update('some_table', $rgFields, array('id'=>$id));
  38.         $this->redirect('/test/show');
  39.     }
  40.  
  41.     function addAction()
  42.     {
  43.         if(!$this->currentUser)
  44.         {
  45.             $this->redirect('/test/auth');
  46.         }
  47.         $rgFields=array(
  48.             'name'         => $this->getParam('name')
  49.         );
  50.         $this->dbInstance->insert('some_table', $rgFields);
  51.         $this->redirect('/test/show');
  52.     }
  53.  
  54.     function deleteAction()
  55.     {
  56.         if(!$this->currentUser)
  57.         {
  58.             $this->redirect('/test/auth');
  59.         }
  60.         $id = $this->getParam('id');
  61.         if(!$id)
  62.         {
  63.             $this->redirect('/test/show');
  64.         }
  65.         $this->dbInstance->delete('some_table', array('id'=>$id));
  66.         $this->redirect('/test/show');
  67.     }
  68.  
  69.     function authAction()
  70.     {
  71.         $this->view->render('auth');
  72.     }
  73.  
  74.     function loginAction()
  75.     {
  76.         /*
  77.         Here we log in user. If success, redirect to /test/show
  78.         If fails, redirect to /test/auth
  79.         */
  80.        
  81.     }
  82.  
  83.     function logoutAction()
  84.     {
  85.         $this->logoutUser();
  86.         $this->redirect('/test/auth');
  87.     }
  88. }
  89.  
  90. ?>
  91.  

Этот контроллер делает стандартные вещи - обновление, добавление, удаление данных. Таблица для этого some_test, которая состоит из 2-х колонок id и name. Авторизация пользователя опущена (так как войдет в задачи к уроку). Остальные же методы, уверен, говорят сами за себя.

Представления
Идеология устройства View схожа с контроллерами. Существует некоторый базовый класс. Однако в рамках этого примера используется уже сразу он, так как никаких специальных операций с представлениями не требуется. Все просто. Класс этот предполагает, что в приложении есть некоторый layout - часть представлений, общая для них всех. Это очень удобно - можно сохранять в конкретном представлении только то, что там действительно должно быть, не заботясь о "обвеске". Сам класс:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. class View_Base
  4. {
  5.     function render($template, $rgParams = array())
  6.     {
  7.         echo $this->_fetch($template, $rgParams);
  8.     }
  9.  
  10.     protected function _fetch_segment($template, $rgParams = array())
  11.     {
  12.         extract($rgParams);
  13.         ob_start();
  14.         include VIEWS_PATH.$template.'.phtml';
  15.         return ob_get_clean();
  16.     }
  17.     /*For debug purposes: */
  18.     protected function _render_segment($template, $rgParams = array())
  19.     {
  20.         echo $this->_fetch_segment($template, $rgParams);
  21.     }
  22.  
  23.     protected function _fetch($template, $rgParams = array())
  24.     {
  25.         $content = $this->_fetch_segment($template, $rgParams);
  26.         return $this->_fetch_segment('layout', array('content' => $content));
  27.     }    
  28. }
  29.  
  30. ?>
  31.  

В классе виден существенный недостаток - имя layout зашито жестко. Когда мы рассмотрим класс Config_Handler, думаю, с этой задачей справиться будет легко. Основное назначение - метод render, который подключает требуемый файл представления, layout и выводит пользователю. Для полноты картины стоит привести пример layout:
PHP:
скопировать код в буфер обмена
  1.  
  2. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  3. <html>
  4.   <head>
  5.     <title>Simple MVC</title>
  6.     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7.     <link href="<?=Config_Handler::getInstance()->getWebroot()?>/style.css" rel="stylesheet" type="text/css" />
  8.   </head>
  9.   <body>
  10.     <?=$content?>
  11.   </body>
  12. </html>
  13.  

Обратите внимание на то, что вложенное представление будет выведено через переменную $content

Модели
Теперь мы знаем, как управлять данными и как их представлять пользователю. Но их самих у нас еще нет. Рассмотрим модели, используемые в этом демонстрационном приложении. Первая - это работа с конфигурационным файлом. В коде вызов Config_Handler происходил уже не раз.
В этом классе реализуется паттерн Singleton (Одиночка). Этот паттерн предполагает, что экземпляр одиночки всегда один в пределах веб-приложения. Конфигурационный файл у нас один, логично предположить, что экзепмляр конфигуратора должен быть одиночкой:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. class Config_Handler
  4. {
  5.     static protected $rInstance = null;
  6.     protected $section='main';
  7.  
  8.     protected $rgIni;
  9.  
  10.     public static function getInstance()
  11.     {
  12.         if(!self::$rInstance)
  13.         {
  14.             self::$rInstance    = new Config_Handler();
  15.         }
  16.         return self::$rInstance;
  17.     }
  18.  
  19.     function  __construct($path=null)
  20.     {
  21.         if(!$path)
  22.         {
  23.             $path=realpath(dirname(__FILE__)).'/../../Config/application.ini';
  24.         }
  25.  
  26.         $this->rgIni=parse_ini_file($path, $this->section);
  27.     }
  28.     public function getDb()
  29.     {
  30.         return $this->rgIni[$this->section]['dbtype'];
  31.     }
  32.     public function getCredentials()
  33.     {
  34.         return array(
  35.             'host'      => $this->rgIni[$this->section]['dbhost'],
  36.             'port'      => $this->rgIni[$this->section]['dbport'],
  37.             'user'      => $this->rgIni[$this->section]['dbuser'],
  38.             'password'  => $this->rgIni[$this->section]['dbpassword'],
  39.             'database'  => $this->rgIni[$this->section]['dbname']
  40.         );
  41.     }
  42.  
  43.     public function getWebpath()
  44.     {
  45.         $webRoot    = preg_replace('#^http\:\/\/#i', '', $this->rgIni[$this->section]['web_root']);
  46.         $webRoot    = preg_replace('#^'.$_SERVER['HTTP_HOST'].'#i', '', $webRoot);
  47.         $webRoot    = ltrim($webRoot, '/');
  48.         return $webRoot;
  49.     }
  50.  
  51.     public function getWebRoot()
  52.     {
  53.         return $this->rgIni[$this->section]['web_root'];
  54.     }
  55. }
  56. ?>
  57.  

Класс полагается на функцию parse_ini_file. Если Вы планируете модифицировать application.ini, стоит прочесть про ограничения, накладываемые этой функцией. В конструкторе определяется путь по-умолчанию. Это неизбежно, так как если у нас еще нет конфигурационного файла, эти данные нужно откуда-то взять. Но можно делать вызов и с использованием явного указания пути. Предполагается, что директивы, определенные в конфигурационном файле, задаются администратором и потому корректны, однако простейшая проверка все же есть.
Обратите внимание так же на стандартную реализацию паттерна Singleton - это классический подход в PHP для такой реализации.

И, наконец, работа с БД. В рамках этого приложения я решил продемострировать паттерн Adapter (адаптер). Он основывается на том, что есть некоторый абстрактный класс(или же интерфейс) для работы с БД, а реализация нужных методов - определяется его потомками.
Для начала стоит привети класс, определяющий текущий адаптер на основе конфигурационного файла:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. class Db_Controller
  4. {
  5.     function  __construct()
  6.     {
  7.         ;
  8.     }
  9.  
  10.     public function getAdapter()
  11.     {
  12.         $adapter    = Config_Handler::getInstance()->getDb();
  13.         $class      = 'Db_'.$adapter.'_Adapter';
  14.         //this fails in php <5.3:
  15.         /*
  16.         return      $class::getInstance();
  17.          */
  18.         return call_user_func(array(
  19.             $class,
  20.             'getInstance'
  21.         ));
  22.     }
  23.  
  24.     public function getCredentials()
  25.     {
  26.         return Config_Handler::getInstance()->getCredentials();
  27.     }
  28. }
  29. ?>
  30.  

В комментарии есть одна любопытная деталь: до php 5.3 имя класса нельзя было указывать как переменную, если выполнялся вызов статического метода. Остальное - простая работа с классом Config_Handler. Имя соответствующего класса генерируется, ориентируясь на __autoload.
Далее абстрактный класс адаптера:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. abstract class Db_Adapter
  4. {
  5.     static protected $rInstance = null;
  6.     static protected $rLink     = null;
  7.  
  8.     protected $lastInsertId;
  9.     protected $iAffectedRows;
  10.  
  11.     abstract public static function getInstance();
  12.     abstract public function select(
  13.             $rgFrom,
  14.             $rgCols=array('*'),
  15.             $rgWhere=null,
  16.             $rgGroup=null,
  17.             $rgOrder=null,
  18.             $rgHaving=null,
  19.             $rgLimit=null);
  20.     abstract public function update($table, $rgFields, $rgWhere=null);
  21.     abstract public function replace();
  22.     abstract public function insert($table, $rgFields, $sPrimaryField=null);
  23.     abstract public function delete($table, $rgWhere);
  24.     abstract public function query($sql);
  25.     abstract protected function _escape_string($string);
  26.  
  27.     public function lastInsertId()
  28.     {
  29.         return $this->lastInsertId;
  30.     }
  31.  
  32.     public function affectedRows()
  33.     {
  34.         return $this->iAffectedRows;
  35.     }
  36.  
  37.     protected function _generic_escape($string)
  38.     {
  39.         return addslashes($string);
  40.     }
  41.  
  42.     protected function _get_delete_sql(
  43.             $table,
  44.             $rgWhere
  45.             )
  46.     {
  47.         if(!$table)
  48.         {
  49.             return false;
  50.         }
  51.         $rgDelete=array('DELETE FROM');
  52.         $rgDelete[]=$table;
  53.         if($rgWhere)
  54.         {
  55.             $rgDelete[]=$this->_get_sql_where($rgWhere);
  56.         }
  57.         return join(' ', $rgDelete);
  58.     }
  59.  
  60.     protected function _get_insert_sql(
  61.             $table,
  62.             $rgFields
  63.             )
  64.     {
  65.         if(!$table || !is_array($rgFields) || !count($rgFields))
  66.         {
  67.             return false;
  68.         }
  69.         $rgTableKeys=array();
  70.         $rgTableValues=array();
  71.         foreach(array_keys($rgFields) as $field)
  72.         {
  73.             $rgTableKeys[]=$field;
  74.         }
  75.         foreach(array_values($rgFields) as $value)
  76.         {
  77.             $rgTableValues[]="'".$this->_escape_string($value)."'";
  78.         }
  79.         $rgInsert=array('INSERT INTO');
  80.         $rgInsert[]=$table;
  81.         $rgInsert[]='('.join(',', $rgTableKeys).')';
  82.         $rgInsert[]='VALUES';
  83.         $rgInsert[]='('.join(',', $rgTableValues).')';
  84.         return join(' ', $rgInsert);
  85.     }
  86.  
  87.     protected function _get_update_sql(
  88.             $table,
  89.             $rgFields,
  90.             $rgWhere
  91.             )
  92.     {
  93.         if(!$table || !is_array($rgFields) || !count($rgFields))
  94.         {
  95.             return false;
  96.         }
  97.  
  98.         $rgUpdate=array('UPDATE');
  99.         $rgUpdate[]=$table;
  100.         $rgUpdate[]='SET';
  101.         $rgUpdateStr=array();
  102.         foreach($rgFields as $field=>$value)
  103.         {
  104.             $rgUpdateStr[]=$field.'='."'".$this->_escape_string($value)."'";
  105.         }
  106.         $rgUpdate[]=join(',', $rgUpdateStr);
  107.         if($rgWhere)
  108.         {
  109.             $rgUpdate[]=$this->_get_sql_where($rgWhere);
  110.         }
  111.         return join(' ', $rgUpdate);
  112.     }
  113.  
  114.     protected function _get_select_sql(
  115.             $rgFrom,
  116.             $rgCols=array('*'),
  117.             $rgWhere=null,
  118.             $rgGroup=null,
  119.             $rgOrder=null,
  120.             $rgHaving=null,
  121.             $rgLimit=null)
  122.     {
  123.         if(!$rgFrom)
  124.         {
  125.             return false;
  126.         }
  127.         $rgSelect=array('SELECT');
  128.         $rgSelect[]=is_array($rgCols)?join(',', $rgCols):$rgCols;
  129.         $rgSelect[]='FROM';
  130.         if(!is_array($rgFrom))
  131.         {
  132.             $rgSelect[]=$rgFrom;
  133.         }
  134.         if($rgWhere)
  135.         {
  136.             $rgSelect[]=$this->_get_sql_where($rgWhere);
  137.         }
  138.  
  139.         //the rest is TODO
  140.         return join(' ', $rgSelect);
  141.     }
  142.  
  143.     protected function _get_sql_where($rgWhere)
  144.     {
  145.         $strWhere=array();
  146.         if($rgWhere)
  147.         {
  148.             $strWhere[]='WHERE';
  149.             $rgWhereStr=array();
  150.             if(is_array($rgWhere))
  151.             {
  152.                 foreach($rgWhere as $field=>$condition)
  153.                 {
  154.                     if(is_array($condition))
  155.                     {
  156.                         //TODO
  157.                     }
  158.                     else
  159.                     {
  160.                         $rgWhereStr[]='('.$field."='".$this->_escape_string($condition)."')";
  161.                     }
  162.                 }
  163.                 $strWhere[]=join(' AND ', $rgWhereStr);
  164.             }
  165.             else
  166.             {
  167.                 $strWhere[]=$rgWhere;
  168.             }
  169.         }
  170.         return join(' ', $strWhere);
  171.     }
  172. }
  173.  
  174. ?>
  175.  

Здесь снова видна реализация паттерна Singleton. Кроме прочего, объявляются стандартные метода для работы с sql а так же определяется генерация самого SQL-текста запроса. в TODO помечено то, что не реализовано (скажем, GROUP BY, HAVING и т.п.)
Некоторые методы обязаны быть определены в потомках. Собственно, они и могут быть определены только там (как, к примеру, _escape_string - мы заранее не знаем, какой адаптер будет реализовывать работу)
В приложении-примере я использую работу с sqlite 2 БД. И класс, соответствующий такой работе, сделан таким образом:
PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. class Db_Sqlite_Adapter extends Db_Adapter
  4. {
  5.     protected $lastError;
  6.  
  7.     public static function getInstance()
  8.     {
  9.         if(!parent::$rInstance)
  10.         {
  11.             parent::$rInstance    = new Db_Sqlite_Adapter();
  12.         }
  13.         return parent::$rInstance;
  14.     }
  15.  
  16.     function __construct()
  17.     {
  18.         if(!$this->_connect())
  19.         {
  20.             return false;
  21.         }
  22.     }
  23.  
  24.     function select($rgFrom,
  25.             $rgCols=array('*'),
  26.             $rgWhere=null,
  27.             $rgGroup=null,
  28.             $rgOrder=null,
  29.             $rgHaving=null,
  30.             $rgLimit=null)
  31.     {
  32.         if(!parent::$rLink)
  33.         {
  34.             return false;
  35.         }
  36.         $sql=$this->_get_select_sql($rgFrom, $rgCols, $rgWhere, $rgGroup, $rgOrder, $rgHaving, $rgLimit);
  37.         if(!$sql)
  38.         {
  39.             return false;
  40.         }
  41.         $rgResult    = sqlite_array_query(parent::$rLink, $sql);
  42.         if(!$rgResult)
  43.         {
  44.             $rgResult=false;
  45.         }
  46.         return $rgResult;
  47.     }
  48.  
  49.     public function replace()
  50.     {
  51.  
  52.     }
  53.  
  54.     public function update($table, $rgFields, $rgWhere=null)
  55.     {
  56.         if(!is_array($rgFields)||!count($rgFields))
  57.         {
  58.             return false;
  59.         }
  60.         $sql=$this->_get_update_sql($table, $rgFields, $rgWhere);
  61.         $bResult    = $this->query($sql);
  62.         return $bResult;
  63.     }
  64.  
  65.     public function insert($table, $rgFields, $sPrimaryField='id')
  66.     {
  67.         if(!array_key_exists($sPrimaryField, $rgFields))
  68.         {
  69.             $rgFields[$sPrimaryField]=$this->_get_next_id($table, $sPrimaryField);
  70.         }
  71.         $sql=$this->_get_insert_sql($table, $rgFields);
  72.         $bResult    = $this->query($sql);
  73.         return $bResult;
  74.     }
  75.  
  76.     public function delete($table, $rgWhere)
  77.     {
  78.         $sql=$this->_get_delete_sql($table, $rgWhere);
  79.         $bResult    = $this->query($sql);
  80.         return $bResult;
  81.     }
  82.  
  83.     public function query($sql)
  84.     {
  85.         return sqlite_exec($sql, parent::$rLink);
  86.     }
  87.  
  88.     protected function _get_next_id($table, $sPrimaryField='id')
  89.     {
  90.         $rgMax=$this->select($table, array('MAX('.$sPrimaryField.')'));
  91.         return $rgMax[0][0]+1;
  92.     }
  93.  
  94.     protected function _connect()
  95.     {
  96.         $rDb            = new Db_Controller();
  97.         $rgCredentials  = $rDb->getCredentials();
  98.         if(!parent::$rLink)
  99.         {
  100.             try
  101.             {
  102.                 parent::$rLink=sqlite_open($rgCredentials['database'], 0666, $sLastError);
  103.                 if(!parent::$rLink)
  104.                 {
  105.                     $this->lastError=$sLastError;
  106.                     return false;
  107.                 }
  108.             }
  109.             catch(Exception $e)
  110.             {
  111.                 return false;
  112.             }
  113.         }
  114.         return true;
  115.     }
  116.  
  117.     protected function  _escape_string($string)
  118.     {
  119.         return sqlite_escape_string($string);
  120.     }
  121.  
  122. }
  123. ?>
  124.  

Метод replace не реализован вовсе, сделан он потому, что должны быть реализованы все абстрактные методы.

Об организации
Итак, мы рассмотрели все три части архитектуры MVC. Осталось сказать лишь несколько слов об организации - том, как хранятся те или иные ресурсы в этом примере. Начну с конфигурационного файла:
CODE (text):
скопировать код в буфер обмена
  1.  
  2. [main]
  3. dbtype  = Sqlite
  4. dbname  = /var/www/data/simplemvc/Db/appdb
  5. dbhost  = 127.0.0.1
  6. dbuser  = NOUSER
  7. dbpassword  = NOPASSWORD
  8. dbport  = NOPORT
  9.  
  10. web_root    = http://simplemvc.dev
  11.  


Соответственно, для правильно работы нужно создать виртуальный хост так, как видно из файла.
Далее, о структуре каталогов. Здесь предполагается, что "/" есть корень расположения проекта:
CODE (text):
скопировать код в буфер обмена
  1.  
  2. Config/ - каталог с конфигурационными файлами (здесь расположен application.ini)
  3. Db/ - каталог с БД. Т.к. Sqlite хранит данные в файле, то этот каталог необходим
  4. Lib/ - каталог для хранения моделей и базовых классов
  5. Web/ - каталог для хранения контроллеров и представлений
  6. images/ - каталог с изображениями
  7. index.php - файл-обработчик запросов
  8. style.css - таблица стилей
  9.  

Дальнейшая структура определяется исходя из того, как __autoload ожидает их найти. Например, в /Lib структура такова:
CODE (text):
скопировать код в буфер обмена
  1.  
  2. Config/
  3. Controller/
  4. Db/
  5. View/
  6. Router.php
  7.  

Дальнейшее, думаю, уже понятно - в этих каталогах располагаются свои подкаталоги и т.п.

Итоги
Располагая моделями, представлениями и контроллерами, уже можно говорить о том, что структура, отвечающая MVC, реализована. Может показаться, что это несколько сложно, однако это не так - более того, масштабировать эту структуру очень просто.
К примеру, если нужна работа с MySQL - нужно всего пронаследовать адаптер и реализовать в нем работу с этой СУБД. Остальные части приложения останутся незатронутыми.
Необходимые директивы можно добавлять в конфигурационный файл и использовать по требованию.
И не стоит забывать, что MVC - не панацея, а просто способ организации архитектуры. На текущий момент он один из наиболее удачных, и я надеюсь, что данный урок раскрывает необходимые для её понимания аспекты.

Задачи к уроку
0. Реализовать разделение маршрутизатора и фронт-контроллера в приложении
1. Добавить возможность настройки маршрутов для маршрутизатора
2. Реализовать в базовом контроллере авторизацию пользователя
3. Сделать настраиваемым имя layout
4. Реализовать метод replace для Sqlite


-----
Есть в мире две бесконечные вещи - это Вселенная и человеческая глупость. Но насчет первой .. я не уверен.
 
 Top
Alex_pac
Отправлено: 03 Мая, 2011 - 21:00:48
Post Id



Новичок


Покинул форум
Сообщений всего: 41
Дата рег-ции: Май 2011  


Помог: 0 раз(а)




Краткое теоретическое описание архитектуры MVC

схема из википедии является эталонной MVC

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



представление

набор статичных шаблонов

модель

набор методов и функций для генерации динамичных значений для "представления"

контроллер

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

принцип работы

Результат работы контроллера к итоге сводится к загрузке шаблона и вставке туда данных полученных с помощью методов из модели

Цитата:
И не стоит забывать, что MVC - не панацея


предлОжите иную, столь же удачную архитектуру?
 
 Top
EuGen Администратор
Отправлено: 03 Мая, 2011 - 21:07:14
Post Id


Профессионал


Покинул форум
Сообщений всего: 9095
Дата рег-ции: Июнь 2007  
Откуда: Berlin


Помог: 707 раз(а)




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


-----
Есть в мире две бесконечные вещи - это Вселенная и человеческая глупость. Но насчет первой .. я не уверен.
 
 Top
Alex_pac
Отправлено: 03 Мая, 2011 - 21:25:28
Post Id



Новичок


Покинул форум
Сообщений всего: 41
Дата рег-ции: Май 2011  


Помог: 0 раз(а)




EuGen пишет:
Не понял если честно, что Вы желали рассказать этим комментарием.
Да, есть митериал из википедии.
В данном уроке предоставляется один из вариантов реализации, не более.
MVC - и в самом деле паттерн, не более, это не панацея. Например, PHP может использоваться для веб-сервисов, разных демонов и прочего. Там уж точно не пригодится такая модель
Я умышленно не рисовал здесь схем - посетители могут почерпнуть такую теорию в той же википедии. С другой стороны, краткое, но более-менее полное теоретическое представление о понятиях, используемых в архитектуре, здесь дано.


панацея это или нет, MVC применяется во всех проектах де-факто.

можно по признакам это увидеть
1) шаблонизация
2) отделение модулей модели вычислений от систем вывода контента

то есть проект либо сделан как MVC, либо он есть смешение кода php и html,
что уже, впоследствии, не подлежит ни рефакторингу ни расширению ни изменению.
То есть архитектура отсутствует вовсе.

MVC довольно абстрактен и в тоже время логичен.

Цитата:
Кроме прочего, здесь есть еще и другие паттерны, которые тоже рассматриваются на практике - Синглтон к примеру, или Адаптер.


не надо все в одну кучу мешать

MVC можно реализовать и без применения ООП вообще, правда будет не так красиво и кратко и не совсем гибко.

это я к тому что каждый паттерн надо рассматривать отдельно, а не все вместе на примере реализации другого паттерна.
 
 Top
EuGen Администратор
Отправлено: 03 Мая, 2011 - 21:36:56
Post Id


Профессионал


Покинул форум
Сообщений всего: 9095
Дата рег-ции: Июнь 2007  
Откуда: Berlin


Помог: 707 раз(а)




Никак не пойму. Вы против размещения данного урока?
Либо же Вам не нравится его структура?
Да, я не являюсь профессиональным преподавателем, но надеюсь информация поможет посетителям форума. Более того, весь код я написал от и до сам, не копируя ничего из фреймворков и предоставив свое виденье.
Alex_pac пишет:
то есть проект либо сделан как MVC, либо он есть смешение кода php и html,

А знаете, есть проекты (и не маленькие), обходящиеся вообще без html
Alex_pac пишет:
не надо все в одну кучу мешать

Пожалуй, стоит обратить внимание на заголовок урока. Он не направлен именно и исключительно на MVC - просто паттерны помогают реализовать именно это. Можно было бы взять и другой пример, однако я посчитал, что в учебных целях лучше показать, как одни паттерны могут помогать реализовать другие, более сложные.
И
Alex_pac пишет:
это я к тому что каждый паттерн надо рассматривать отдельно, а не все вместе на примере реализации другого паттерна.

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


-----
Есть в мире две бесконечные вещи - это Вселенная и человеческая глупость. Но насчет первой .. я не уверен.
 
 Top
Alex_pac
Отправлено: 03 Мая, 2011 - 22:11:21
Post Id



Новичок


Покинул форум
Сообщений всего: 41
Дата рег-ции: Май 2011  


Помог: 0 раз(а)




я могу вроде с относительной легкостью читать код и понимать его смысл. Идеи у вас там описываются интересные.

ОДнако кто занялся php недавно вообще ничо не поймет что вы хотели донести.
У вас даже нету готового работающего образца вашей модели приложения что бы ее можно было
"препарировать в лабораторных условиях"

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

Да вообще зачем оно нужно такое?

Вы не слова не сказали о шаблонизации , кроме html кода странички с одной переменной $content

мол догадайся сам что вот это шаблон и он используется именно так потому как и тд.
этого нет.
хоть в гугл лезь после каждой вашей строчки

посему, ваш урок предназначен для чтения проффесионалами таким же как вы и абсолютно не понятен "новичкам", а следовательно ценность его стремится к 0.
 
 Top
EuGen Администратор
Отправлено: 03 Мая, 2011 - 22:16:45
Post Id


Профессионал


Покинул форум
Сообщений всего: 9095
Дата рег-ции: Июнь 2007  
Откуда: Berlin


Помог: 707 раз(а)




EuGen пишет:
Оговорюсь заранее, что для того, чтобы понять содержание урока, вам будет необходимо изучить предыдущие уроки этой ветки форума и, быть может, не раз обратиться к документации http://php.net

Размещено в первом абзаце, чтобы читатели заранее сориентировались, на что нужно расчитывать. Имеется ввиду уровень знаний.
Alex_pac пишет:
У вас даже нету готового работающего образца вашей модели приложения что бы ее можно было

Естественно. Расчет на то, что вдумчивые читатели соберут это, следуя описанию и комментариям. Это же относится и к каталогам - так как (если Вы читали содержание) они являются частью архитектурного построения. Проблем с запоминанием их названий не будет вообще, если Вы прочитаете листинг с функцией __autoload И описание к нему. Добавлю, что листинги взяты с готового и работающего проекта. Так что можете не сомневаться в работоспособности представленных учебных образцов.
Alex_pac пишет:
Да вообще зачем оно нужно такое?

Вы не слова не сказали о шаблонизации , кроме html кода странички с одной переменной $content

Не уверен, что можно рассуждать о нужности урока за всех посетителей форума. Если Вам он оказался неинтересен - что ж, жаль. Возможно, я чего-то не учел.
О шаблонизации здесь не говорится потому, что я использую формат php+html в данном примере, не видя особенного смысла в придумывании всяких псевдо-языков.
Ну и по поводу того, что Вы сказали о профессионалах - опять же прочтите, пожалуйста, первый абзац урока. Это же относится и к иронии о поиске в google.com


-----
Есть в мире две бесконечные вещи - это Вселенная и человеческая глупость. Но насчет первой .. я не уверен.
 
 Top
Alex_pac
Отправлено: 03 Мая, 2011 - 22:43:23
Post Id



Новичок


Покинул форум
Сообщений всего: 41
Дата рег-ции: Май 2011  


Помог: 0 раз(а)




Цитата:
Расчет на то, что вдумчивые читатели соберут это, следуя описанию и комментариям.

ваше нежелание размещать собранный исходник говорит о неуважении к читателям.
Цитата:
О шаблонизации здесь не говорится потому, что я использую формат php+html в данном примере, не видя особенного смысла в придумывании всяких псевдо-языков.


Любой шаблон это и есть "формат php+html" вопрос в том откомпилирован он или нет каким либо фреймворком-шаблонизатором со своим синтаксисом
шаблонизация есть составляющая архитектуры MVC
 
 Top
EuGen Администратор
Отправлено: 03 Мая, 2011 - 22:50:01
Post Id


Профессионал


Покинул форум
Сообщений всего: 9095
Дата рег-ции: Июнь 2007  
Откуда: Berlin


Помог: 707 раз(а)




Alex_pac пишет:
ваше нежелание размещать собранный исходник говорит о неуважении к читателям.

Это не мое желание а построение урока. Если хотите - дополнительная задача к уроку, которая явно не перечислена. По-моему, довольно очевидный ход с целью чему-либо научить.
Alex_pac пишет:
шаблонизация есть составляющая архитектуры MVC

В этом Вы не правы. MVC - это паттерн, он имеет три составляющих. И каким образом реализуется каждая из этих частей - этот паттерн это не определяет.
Я пояснил свою точку зрения - о том, что не вижу смысла в изобретении псевдо-языков. Могу по пунктам, почему:
- Функционал вставок php такой же самый
- Нужно еще изучить этот псевдо-язык, тогда как php - нет
- Замена разной сложности в шаблоне может сильно замедлить производительность
Кроме того, присутствие такого языка еще бы и усложнило урок (раз уж Вы беспокоитесь о его простоте)
Если угодно, я не стал придумывать шаблонизатора потому что урок - это один из подходов к реализации. И в рамках его я выбрал такое решение.


-----
Есть в мире две бесконечные вещи - это Вселенная и человеческая глупость. Но насчет первой .. я не уверен.
 
 Top
OrmaJever
Отправлено: 03 Мая, 2011 - 22:50:52
Post Id



Активный участник


Покинул форум
Сообщений всего: 7540
Дата рег-ции: Янв. 2010  
Откуда: Чернигов


Помог: 299 раз(а)




Alex_pac пишет:
ОДнако кто занялся php недавно вообще ничо не поймет что вы хотели донести.
У вас даже нету готового работающего образца вашей модели приложения что бы ее можно было

Тем кто недавно занялся php mvc не нужен.
Если вам не нравится статья то можите написать свою вам не кто не мешает!
(Добавление)
http://habrahabr[dot]ru/blogs/php/31270/ - вот тема с хабра про mvc в php, там что понятнее написано? Однако Но эта статья понравилась читателям


-----
Если вы хотя бы 3-4 раза не решите всё выкинуть и начать заново - вы явно что-то делаете не так.
 
 Top
evgenijj
Отправлено: 03 Мая, 2011 - 22:58:43
Post Id



Участник


Покинул форум
Сообщений всего: 1212
Дата рег-ции: Авг. 2006  
Откуда: Москва


Помог: 10 раз(а)




Цитата:
Не уверен, что можно рассуждать о нужности урока за всех посетителей форума.

Разумеется, ценность этого урока оценит (в лучшем случае) 1%. В худшем - и того меньше. Но это ни в коем случае не уменьшает ценности урока. Больше учителей - хороших и разных!

Цитата:
ваше нежелание размещать собранный исходник говорит о неуважении к читателям

Вовсе нет. Я тоже так делаю - просто некоторый набор кода, который (при желании) можно собрать. Но не "тупо", а немного подумав. Если не в состоянии собрать - значит рано тебе это еще читать.

Цитата:

ОДнако кто занялся php недавно вообще ничо не поймет что вы хотели донести.

Кто недавно занялся PHP - точно. 99% на этом форуме не поймет. Eugen и писал для того самого 1%.
 
 Top
Alex_pac
Отправлено: 03 Мая, 2011 - 22:59:55
Post Id



Новичок


Покинул форум
Сообщений всего: 41
Дата рег-ции: Май 2011  


Помог: 0 раз(а)




Цитата:
Это не мое желание а построение урока. Если хотите - дополнительная задача к уроку, которая явно не перечислена. По-моему, довольно очевидный ход с целью чему-либо научить.

не в этом случае. вы затрагиваете серьзнейшие методы построения архитектуры веб приложения.
А не рассказываете как построить велосипед из циклов for и foreach
Наличие исходника необходимо для понимания всей архитектуры
Или вы считаете что без понимания этого , тот кто скачал исходик сразу построит веб приложение? да НИКОГДА.
 
 Top
EuGen Администратор
Отправлено: 03 Мая, 2011 - 23:06:42
Post Id


Профессионал


Покинул форум
Сообщений всего: 9095
Дата рег-ции: Июнь 2007  
Откуда: Berlin


Помог: 707 раз(а)




Alex_pac пишет:
Или вы считаете что без понимания этого , тот кто скачал исходик сразу построит веб приложение?

Прочитав то, что рекомундуется вначале урока и вчитавшись в сам урок, поняв его код - да, уверен.
Лично я так и сделал в свое время. Я не был новичком в php на момент изучения этой архитектуры, но и урок расчитан на подкованых читателей.
Alex_pac пишет:
А не рассказываете как построить велосипед из циклов for и foreach

Вот как раз построение хитрых псевдо-языков, это и есть изобретение велосипеда с моей точки зрения. Почему - читайте выше.
Я затрагиваю определенную тему. Да, это серьезная архитектура. Но суть в том, чтобы объяснить её принципы на простом примере. И, надеюсь, это мне удалось.
По крайней мере, до сих пор я не увидел достойной аргументации свой неправоты. Оговорюсь, достойной с моей точки зрения.


-----
Есть в мире две бесконечные вещи - это Вселенная и человеческая глупость. Но насчет первой .. я не уверен.
 
 Top
Alex_pac
Отправлено: 03 Мая, 2011 - 23:10:46
Post Id



Новичок


Покинул форум
Сообщений всего: 41
Дата рег-ции: Май 2011  


Помог: 0 раз(а)




EuGen пишет:
По крайней мере, до сих пор я не увидел достойной аргументации свой неправоты. Оговорюсь, достойной с моей точки зрения.

нет того что критиковать. Всем банально лень собирать ваш пазл, пусть даже на выходе будет что то гениальное и интересное.
 
 Top
EuGen Администратор
Отправлено: 03 Мая, 2011 - 23:13:02
Post Id


Профессионал


Покинул форум
Сообщений всего: 9095
Дата рег-ции: Июнь 2007  
Откуда: Berlin


Помог: 707 раз(а)




Alex_pac пишет:
Всем банально лень собирать ваш пазл

Вынужден предупредить, что подмена своего мнения мнением "всех" есть не что иное, как троллинг.
http://ru[dot]wikipedia[dot]org/wiki/%D0[dot][dot][dot]0%B8%D0%BD%D0%B3
В этой теме Вы злоупотребляете таким приемом ведения беседы уже в 4-й раз.
В остальном, читайте выше - не вижу аргументации. А, значит, и смысла в дальнейшей полемике.


-----
Есть в мире две бесконечные вещи - это Вселенная и человеческая глупость. Но насчет первой .. я не уверен.
 
 Top
Страниц (7): [1] 2 3 4 5 6 7 »
Сейчас эту тему просматривают: 0 (гостей: 0, зарегистрированных: 0)
« Уроки php »


Все гости форума могут просматривать этот раздел.
Только зарегистрированные пользователи могут создавать новые темы в этом разделе.
Только зарегистрированные пользователи могут отвечать на сообщения в этом разделе.



Powered by PHP  Powered By MySQL  Powered by Nginx  Valid CSS  RSS

 
Powered by ExBB FM 1.0 RC1. InvisionExBB