Пользователь
27 августа 2012 в 07:03

Разработка → Реализация MVC паттерна на примере создания сайта-визитки на PHP

mvc
Как вы уже догадались из названия статьи, сегодня речь пойдет о самом популярном, разве что после Singleton, шаблоне проектирования MVC, хотя такое сравнение не совсем уместно. Понимание концепции MVC может помочь вам в рефакторинге и разрешении неприятных ситуаций в которые, возможно попал ваш проект. Дабы восполнить пробел, мы реализуем шаблон MVC на примере простого сайта-визитки.


Оглавление


Введение
1. Теория
1.1. Front Controller и Page Controller
1.2. Маршрутизация URL
2. Практика
2.1. Реализация маршрутизатора URL
2.2. Возвращаемся к реализации MVC
2.3. Реализация классов потомков Model и Controller, создание View's
2.3.1. Создадаем главную страницу
2.3.2. Создадаем страницу «Портфолио»
2.3.3. Создаем остальные страницы
3. Результат
4. Заключение
5. Подборка полезных ссылок по сабжу


Введение


Многие начинают писать проект для работы с единственной задачей, не подразумевая, что это может вырасти в многопользовательскую систему управления, ну допустим, контентом или упаси бог, производством. И всё вроде здорово и классно, всё работает, пока не начинаешь понимать, что тот код, который написан — состоит целиком и полностью из костылей и хардкода. Код перемешанный с версткой, запросами и костылями, неподдающийся иногда даже прочтению. Возникает насущная проблема: при добавлении новых фич, приходится с этим кодом очень долго и долго возиться, вспоминая «а что же там такое написано то было?» и проклинать себя в прошлом.

Вы можеть быть даже слышали о шаблонах проектирования и даже листали эти прекрасные книги:
  • Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидесс «Приемы объектно ориентированного проектирования. Паттерны проектирования»;
  • М. Фаулер «Архитектура корпоративных программных приложений».

А многие, не испугавшись огромных руководств и документаций, пытались изучить какой-либо из современных фреймворков и столкнувшись со сложностью понимания (в силу наличия множества архитектруных концепций хитро увязанных между собой) отложили изучение и применение современных интсрументов в «долгий ящик».

Представленная статья будет полезна в первую очередь новичкам. Во всяком случае, я надеюсь что за пару часов вы сможете получить представление о реализации MVC паттерна, который лежит в основе всех современных веб-фреймворков, а также получить «пищу» для дальнейших размышлений над тем — «как стоит делать». В конце статьи приводится подборка полезных ссылок, которые также помогут разобраться из чего состоят веб-фреймворки (помимо MVC) и как они работают.

Прожженные PHP-программисты вряд ли найдут в данной статье что-то новое для себя, но их замечания и комментарии к основному тексту были бы очень кстати! Т.к. без теории практика невозможна, а без практики теория бесполезна, то сначала будет чуть-чуть теории, а потом перейдем к практике. Если вы уже знакомы с концепцией MVC, можете пропустить раздел с теорией и сразу перейти к практике.


1. Теория


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

Рассмотрим концептуальную схему шаблона MVC (на мой взгляд — это наиболее удачная схема из тех, что я видел):

mvc-schema

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

Типичную последовательность работы MVC-приложения можно описать следующим образом:
  1. При заходе пользователя на веб-ресурс, скрипт инициализации создает экземпляр приложения и запускает его на выполнение.
    При этом отображается вид, скажем главной страницы сайта.
  2. Приложение получает запрос от пользователя и определяет запрошенные контроллер и действие. В случае главной страницы, выполняется действие по умолчанию (index).
  3. Приложение создает экземпляр контроллера и запускает метод действия,
    в котором, к примеру, содержаться вызовы модели, считывающие информацию из базы данных.
  4. После этого, действие формирует представление с данными, полученными из модели и выводит результат пользователю.

Модель — содержит бизнес-логику приложения и включает методы выборки (это могут быть методы ORM), обработки (например, правила валидации) и предоставления конкретных данных, что зачастую делает ее очень толстой, что вполне нормально.
Модель не должна напрямую взаимодействовать с пользователем. Все переменные, относящиеся к запросу пользователя должны обрабатываться в контроллере.
Модель не должна генерировать HTML или другой код отображения, который может изменяться в зависимости от нужд пользователя. Такой код должен обрабатываться в видах.
Одна и та же модель, например: модель аутентификации пользователей может использоваться как в пользовательской, так и в административной части приложения. В таком случае можно вынести общий код в отдельный класс и наследоваться от него, определяя в наследниках специфичные для подприложений методы.

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

Контроллер — связующее звено, соединяющее модели, виды и другие компоненты в рабочее приложение. Контроллер отвечает за обработку запросов пользователя. Контроллер не должен содержать SQL-запросов. Их лучше держать в моделях. Контроллер не должен содержать HTML и другой разметки. Её стоит выносить в виды.
В хорошо спроектированном MVC-приложении контроллеры обычно очень тонкие и содержат только несколько десятков строк кода. Чего, не скажешь о Stupid Fat Controllers (SFC) в CMS Joomla. Логика контроллера довольно типична и большая ее часть выносится в базовые классы.
Модели, наоборот, очень толстые и содержат большую часть кода, связанную с обработкой данных, т.к. структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения.


1.1. Front Controller и Page Controller


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

Надеюсь, вы уже успели заметить, что у разных сайтов могут быть совершенные разные форматы построения адресной строки. Каждый формат может отображать архитектуру web-приложения. Хотя это и не всегда так, но в большинстве случаев это явный факт.

Рассмотрим два варианта адресной строки, по которым показывается какой-то текст и профиль пользователя.

Первый вариант:
  1. www.example.com/article.php?id=3
  2. www.example.com/user.php?id=4

Здесь каждый сценарий отвечает за выполнение определённой команды.

Второй вариант:
  1. www.example.com/index.php?article=3
  2. www.example.com/index.php?user=4

А здесь все обращения происходят в одном сценарии index.php.

Подход с множеством точек взаимодействия вы можете наблюдать на форумах с движком phpBB. Просмотр форума происходит через сценарий viewforum.php, просмотр топика через viewtopic.php и т.д. Второй подход, с доступом через один физический файл сценария, можно наблюдать в моей любимой CMS MODX, где все обращения проходят через index.php.

Эти два подхода совершенно различны. Первый — характерен для шаблона контроллер страниц (Page Controller), а второй подход реализуется паттерном контроллер запросов (Front Controller). Контроллер страниц хорошо применять для сайтов с достаточно простой логикой. В свою очередь, контроллер запросов объединяет все действия по обработке запросов в одном месте, что даёт ему дополнительные возможности, благодаря которым можно реализовать более трудные задачи, чем обычно решаются контроллером страниц. Я не буду вдаваться в подробности реализации контроллера страниц, а скажу лишь, что в практической части будет разработан именно контроллер запросов (некоторое подобие).


1.2. Маршрутизация URL


Маршрутизация URL позволяет настроить приложение на прием запросов с URL, которые не соответствуют реальным файлам приложения, а также использовать ЧПУ, которые семантически значимы для пользователей и предпочтительны для поисковой оптимизации.

К примеру, для обычной страницы, отображающей форму обратной связи, URL мог бы выглядеть так:
http://www.example.com/contacts.php?action=feedback

Приблизительный код обработки в таком случае:
switch($_GET['action'])
{ 
	case "about" :
		require_once("about.php"); // страница "О Нас"
		break;
	case "contacts" :
		require_once("contacts.php"); // страница "Контакты"
		break;
	case "feedback" :
		require_once("feedback.php"); // страница "Обратная связь"
		break;
	default : 
		require_once("page404.php"); // страница "404"
	break;
}

Думаю, почти все так раньше делали.

С использованием движка маршрутизации URL вы сможете для отображения той же информации настроить приложение на прием таких запросов:
http://www.example.com/contacts/feedback

Здесь contacts представляет собой контроллер, а feedback — это метод контроллера contacts, отображающий форму обратной связи и т.д. Мы еще вернемся к этому вопросу в практической части.

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


2. Практика


Для начала создадим следующую структуру файлов и папок:

project-folders
Забегая вперед, скажу, что в папке core будут храниться базовые классы Model, View и Controller.
Их потомки будут храниться в директориях controllers, models и views. Файл index.php это точка в хода в приложение. Файл bootstrap.php инициирует загрузку приложения, подключая все необходимые модули и пр.

Будем идти последовательно; откроем файл index.php и наполним его следующим кодом:
ini_set('display_errors', 1);
require_once 'application/bootstrap.php';

Тут вопросов возникнуть не должно.

Следом, сразу же перейдем к фалу bootstrap.php:
require_once 'core/model.php';
require_once 'core/view.php';
require_once 'core/controller.php';
require_once 'core/route.php';
Route::start(); // запускаем маршрутизатор

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


2.1. Реализация маршрутизатора URL


Пока что отклонимся от реализации паттерна MVC и займемся мрашрутизацией. Первый шаг, который нам нужно сделать, записать следующий код в .htaccess:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L]

Этот код перенаправит обработку всех страниц на index.php, что нам и нужно. Помните в первой части мы говорили о Front Controller?!

Маршрутизацию мы поместим в отдельный файл route.php в директорию core. В этом файле опишем класс Route, который будет запускать методы контроллеров, которые в свою очередь будут генерировать вид страниц.

Содержимое файла route.php
class Route
{
	static function start()
	{
		// контроллер и действие по умолчанию
		$controller_name = 'Main';
		$action_name = 'index';
		
		$routes = explode('/', $_SERVER['REQUEST_URI']);

		// получаем имя контроллера
		if ( !empty($routes[1]) )
		{	
			$controller_name = $routes[1];
		}
		
		// получаем имя экшена
		if ( !empty($routes[2]) )
		{
			$action_name = $routes[2];
		}

		// добавляем префиксы
		$model_name = 'Model_'.$controller_name;
		$controller_name = 'Controller_'.$controller_name;
		$action_name = 'action_'.$action_name;

		// подцепляем файл с классом модели (файла модели может и не быть)

		$model_file = strtolower($model_name).'.php';
		$model_path = "application/models/".$model_file;
		if(file_exists($model_path))
		{
			include "application/models/".$model_file;
		}

		// подцепляем файл с классом контроллера
		$controller_file = strtolower($controller_name).'.php';
		$controller_path = "application/controllers/".$controller_file;
		if(file_exists($controller_path))
		{
			include "application/controllers/".$controller_file;
		}
		else
		{
			/*
			правильно было бы кинуть здесь исключение,
			но для упрощения сразу сделаем редирект на страницу 404
			*/
			Route::ErrorPage404();
		}
		
		// создаем контроллер
		$controller = new $controller_name;
		$action = $action_name;
		
		if(method_exists($controller, $action))
		{
			// вызываем действие контроллера
			$controller->$action();
		}
		else
		{
			// здесь также разумнее было бы кинуть исключение
			Route::ErrorPage404();
		}
	
	}
	
	function ErrorPage404()
	{
        $host = 'http://'.$_SERVER['HTTP_HOST'].'/';
        header('HTTP/1.1 404 Not Found');
		header("Status: 404 Not Found");
		header('Location:'.$host.'404');
    }
}


Замечу, что в классе реализована очень упрощенная логика (несмотря на объемный код) и возможно даже имеет проблемы безопасности. Это было сделано намерено, т.к. написание полноценного класса маршрутизации заслуживает как минимум отдельной статьи. Рассмотрим основные моменты…

В элементе глобального массива $_SERVER['REQUEST_URI'] содержится полный адрес по которому обратился пользователь.
Например: example.ru/contacts/feedback

С помощью функции explode производится разделение адреса на составлющие. В результате мы получаем имя контроллера, для приведенного примера, это контроллер contacts и имя действия, в нашем случае — feedback.

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

Таким образом, при переходе, к примеру, по адресу:
example.com/portfolio
или
example.com/portfolio/index
роутер выполнит следующие действия:
  1. подключит файл model_portfolio.php из папки models, содержащий класс Model_Portfolio;
  2. подключит файл controller_portfolio.php из папки controllers, содержащий класс Controller_Portfolio;
  3. создаст экземпляр класса Controller_Portfolio и вызовет действие по умолчанию — action_index, описанное в нем.

Если пользователь попытается обратиться по адресу несуществующего контроллера, к примеру:
example.com/ufo
то его перебросит на страницу «404»:
example.com/404
То же самое произойдет если пользователь обратится к действию, которое не описано в контроллере.


2.2. Возвращаемся к реализации MVC


Перейдем в папку core и добавим к файлу route.php еще три файла: model.php, view.php и controller.php

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

Содержимое файла model.php
class Model
{
	public function get_data()
	{
	}
}

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

Содержимое файла view.php
class View
{
	//public $template_view; // здесь можно указать общий вид по умолчанию.
	
	function generate($content_view, $template_view, $data = null)
	{
		/*
		if(is_array($data)) {
			// преобразуем элементы массива в переменные
			extract($data);
		}
		*/
		
		include 'application/views/'.$template_view;
	}
}

Не трудно догадаться, что метод generate предназначен для формирования вида. В него передаются следующие параметры:
  1. $content_file — виды отображающие контент страниц;
  2. $template_file — общий для всех страниц шаблон;
  3. $data — массив, содержащий элементы контента страницы. Обычно заполняется в модели.

Функцией include динамически подключается общий шаблон (вид), внутри которого будет встраиваться вид
для отображения контента конкретной страницы.

В нашем случае общий шаблон будет содержать header, menu, sidebar и footer, а контент страниц будет содержаться в отдельном виде. Опять же это сделано для упрощения.


Содержимое файла controller.php
class Controller {
	
	public $model;
	public $view;
	
	function __construct()
	{
		$this->view = new View();
	}
	
	function action_index()
	{
	}
}

Метод action_index — это действие, вызываемое по умолчанию, его мы перекроем при реализации классов потомков.


2.3. Реализация классов потомков Model и Controller, создание View's


Теперь начинается самое интересное! Наш сайт-визитка будет состоять из следущих страниц:
  1. Главная
  2. Услуги
  3. Портфолио
  4. Контакты
  5. А также — страница «404»

Для каждой из страниц имеется свой контроллер из папки controllers и вид из папки views. Некоторые страницы могут использовать модель или модели из папки models.

mvc-folders
На предыдущем рисунке отдельно выделен файл template_view.php — это шаблон, содержащий общую для всех страниц разметку. В простейшем случае он мог бы выглядеть так:
<!DOCTYPE html>
<html lang="ru">
<head>
	<meta charset="utf-8">
	<title>Главная</title>
</head>
<body>
	<?php include 'application/views/'.$content_view; ?>
</body>
</html>

Для придания сайту презентабельного вида сверстаем CSS шаблон и интегририруем его в наш сайт путем изменения структуры HTML-разметки и подключения CSS и JavaScript файлов:
<link rel="stylesheet" type="text/css" href="/css/style.css" />
<script src="/js/jquery-1.6.2.js" type="text/javascript"></script>

В конце статьи, в разделе «Результат», приводится ссылка на GitHub-репозиторий с проектом, в котором проделаны действия по интеграции простенького шаблона.


2.3.1. Создадаем главную страницу


Начнем с контроллера controller_main.php, вот его код:
class Controller_Main extends Controller
{
	function action_index()
	{	
		$this->view->generate('main_view.php', 'template_view.php');
	}
}

В метод generate экземпляра класса View передаются имена файлов общего шаблона и вида c контентом страницы.
Помимо индексного действия в контроллере конечно же могут содержаться и другие действия.

Файл с общим видом мы рассмотрели ранее. Рассмотрим файл контента main_view.php:
<h1>Добро пожаловать!</h1>
<p>
<img src="/images/office-small.jpg" align="left" >
<a href="/">ОЛОЛОША TEAM</a> - команда первоклассных специалистов в области разработки веб-сайтов с многолетним опытом коллекционирования мексиканских масок, бронзовых и каменных статуй из Индии и Цейлона, барельефов и изваяний, созданных мастерами Экваториальной Африки пять-шесть веков назад...
</p>

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

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


2.3.2. Создадаем страницу «Портфолио»


В нашем случае, страница «Портфолио» — это единственная страница использующая модель.
Модель обычно включает методы выборки данных, например:
  1. методы нативных библиотек pgsql или mysql;
  2. методы библиотек, реализующих абстракицю данных. Например, методы библиотеки PEAR MDB2;
  3. методы ORM;
  4. методы для работы с NoSQL;
  5. и др.

Для простоты, здесь мы не будем использовать SQL-запросы или ORM-операторы. Вместо этого мы сэмулируем реальные данные и сразу возвратим массив результатов.
Файл модели model_portfolio.php поместим в папку models. Вот его содержимое:
class Model_Portfolio extends Model
{
	public function get_data()
	{	
		return array(
			
			array(
				'Year' => '2012',
				'Site' => 'http://DunkelBeer.ru',
				'Description' => 'Промо-сайт темного пива Dunkel от немецкого производителя Löwenbraü выпускаемого в России пивоваренной компанией "CАН ИнБев".'
			),
			array(
				'Year' => '2012',
				'Site' => 'http://ZopoMobile.ru',
				'Description' => 'Русскоязычный каталог китайских телефонов компании Zopo на базе Android OS и аксессуаров к ним.'
			),
			// todo
		);
	}
}


Класс контроллера модели содержится в файле controller_portfolio.php, вот его код:
class Controller_Portfolio extends Controller
{

	function __construct()
	{
		$this->model = new Model_Portfolio();
		$this->view = new View();
	}
	
	function action_index()
	{
		$data = $this->model->get_data();		
		$this->view->generate('portfolio_view.php', 'template_view.php', $data);
	}
}

В переменную data записывается массив, возвращаемый методом get_data, который мы рассматривали ранее.
Далее эта переменная передается в качестве параметра метода generate, в который также передаются: имя файла с общим шаблон и имя файла, содержащего вид c контентом страницы.

Вид содержащий контент страницы находится в файле portfolio_view.php.
<h1>Портфолио</h1>
<p>
<table>
Все проекты в следующей таблице являются вымышленными, поэтому даже не пытайтесь перейти по приведенным ссылкам.
<tr><td>Год</td><td>Проект</td><td>Описание</td></tr>
<?php

	foreach($data as $row)
	{
		echo '<tr><td>'.$row['Year'].'</td><td>'.$row['Site'].'</td><td>'.$row['Description'].'</td></tr>';
	}
	
?>
</table>
</p>

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


2.3.3. Создаем остальные страницы


Остальные страницы создаются аналогично. Их код досутпен в репозитории на GitHub, ссылка на который приводится в конце статьи, в разделе «Результат».


3. Результат


А вот что получилось в итоге:

Скриншот получившегося сайта-визитки
site

Ссылка на GitHub: https://github.com/vitalyswipe/tinymvc/zipball/v0.1

А вот в этой версии я набросал следующие классы (и соответствующие им виды):
  • Controller_Login в котором генерируется вид с формой для ввода логина и пароля, после заполнения которой производится процедура аутентификации и в случае успеха пользователь перенаправляется в админку.
  • Contorller_Admin с индексным действием, в котором проверяется был ли пользователь ранее авторизован на сайте как администратор (если был, то отображается вид админки) и действием logout для разлогинивания.

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


4. Заключение


Шаблон MVC используется в качестве архитектурной основы во многих фреймворках и CMS, которые создавались для того, чтобы иметь возможность разрабатывать качественно более сложные решения за более короткий срок. Это стало возможным благодаря повышению уровня абстракции, поскольку есть предел сложности конструкций, которыми может оперировать человеческий мозг.

Но, использование веб-фреймворков, типа Yii или Kohana, состоящих из нескольких сотен файлов, при разработке простых веб-приложений (например, сайтов-визиткок) не всегда целесообразно. Теперь мы умеем создавать красивую MVC модель, чтобы не перемешивать Php, Html, CSS и JavaScript код в одном файле.

Данная статья является скорее отправной точкой для изучения CMF, чем примером чего-то истинно правильного, что можно взять за основу своего веб-приложения. Возможно она даже вдохновила Вас и вы уже подумываете написать свой микрофреймворк или CMS, основанные на MVC. Но, прежде чем изобретать очередной велосипед с «блекджеком и шлюхами», еще раз подумайте, может ваши усилия разумнее направить на развитие и в помощь сообществу уже существующего проекта?!

P.S.: Статья была переписана с учетом некоторых замечаний, оставленных в комментариях. Критика оказалась очень полезной. Судя по отклику: комментариям, обращениям в личку и количеству юзеров добавивших пост в избранное затея написать этот пост оказалось не такой уж плохой. К сожалению, не возможно учесть все пожелания и написать больше и подробнее по причине нехватки времени… но возможно это сделают те таинственные личности, кто минусовал первоначальный вариант. Удачи в проектах!


5. Подборка полезных ссылок по сабжу


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

Ссылки

5.1. MVC и другие паттерны




5.2. Шаблонизация


PHP сам по себе является шаблонизатором, но все же…


5.3. Маршрутизация URL




5.4. Сессии




5.5. Микрофреймворки



Vitaly Swipe @vitalyswipe
карма
0,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (175)

  • +2
    Вместо require может лучшим будет require_once использовать?
    • +7
      Лучше использовать автоматическую загрузку классов, иначе все require будет отслеживать уже нереально.
      • +1
        А еще лучше начать использовать namespace'ы с автомотической загрузкой классов и тогда в этом плане наступит полная эйфория :). Хотя я сам и пользуюсь этим способом, код от этого лучше выглядеть не начинает :( вместо конструкции require*/include* появляется обратный слеш :(
        • +1
          Ну, код

          use Vendor\App\Models;
          
          $blog = new Blog();
          $blog->posts[] = new Post();
          

          выглядит лучше чем

          require_once 'lib/vendor/app/models/blog.php';
          require_once 'lib/vendor/app/models/post.php';
          
          $blog = new Vendor_App_Models_Blog();
          $blog->posts[] = new Vendor_App_Models_Post();
          

          Или нет? :)
          • 0
            100% у меня он точно такой же

            только вот эту мелочь стоит гда нить ранее объявить

            spl_autoload_extensions(".php"); // comma-separated list
            spl_autoload_register();

            но вот посмотришь на код глубже и порой от такого количества обратных слешей иногда берет дрожжжж :)
            но это больше проблема namespace'ов нежли способа загрузки файлов :)
            • 0
              еще кое что забыл :)

              define(«APP_PATH», __DIR__. DS. '..'. DS. 'app'. DS);
              set_include_path(APP_PATH); // adding new path to include_path
              spl_autoload_extensions(".php"); // comma-separated list
              spl_autoload_register();

              вот теперь все выглядит как надо.
          • +1
            От require можно избавиться, и останется только new Vendor_App_Models_Blog().
            Это лучше, чем use Vendor\App\Models; new Blog(); по двум причинам:
            1. В любом месте листинга видно, что за класс создается
            2. Создание объекта класса выглядит всегда одинаково, независимо от use в начале файла.

            • +1
              думаю это на 100% зависит от выбранного вами подхода при «строительстве» вашего проекта.

              т.е. все полностью упирается в вашу архитектуру, в некоторых уже готовых приложениях лучше убиться чем начинать использовать namespace'ы.

              Если же вы делаете все с нуля то нет причин не использовать разумный подход, и лучше планировать свою архитектуру.
            • 0
              Даже в таких случаях я предпочту new \Vendor\App\Models\Blog();, а написав new \Vendor\App\Models\Post();, сделаю небольшой рефакторинг и введу use ради DRY.
              • 0
                Вводя этот use, вы теряете в понимании кода. Я не могу сказать, что хуже — нагромождение из \ и нэймспейсов, не понятно, к какому нэймспэйсу принадлежащие классы или же PSR-именованные классы. Мне все кажется плохим ((

                ЗЫ: В Google C++ Style Guide конструкция use запрещена.
                • 0
                  К нэймспэйсам так или иначе приходят в сложных приложениях, вопрос в том использовать их нативные или эмуляцию через _. Что «всё плохо» почти согласен, но вариант с use мне кажется оптимальным из трёх. Кроме всего прочего он явно показывает (если в в соглашениях принять что он обязателен даже при единичном использование, а не только ради DRY) зависимости текущего файла.
  • +12
    Неожидал увидеть Ололошу на хабре.
  • +2
    >> Подход с множеством точек взаимодействия
    это называется точка входа в приложение.
    >>В общем, сегодня речь пойдет о самом популярном (разве что после Singleton) шаблоне проектирования MVC
    думаю такое сравнение некорректно, т.к. MVC это шаблон другой, и он не входит в список тех 27 из книги GoF.
    Картинка порадовала, правда на ней я бы руль обозвал контроллером) Кстати еще, поспешите добавить в статью дисклеймер про то что эта статья для совсем новичков, а то можете рассчитывать что вас раскритикуют) я тоже сначала хотел, но потом подумал, что это будет выглядеть как-то глупо) кому надо из новичков тот посмотрит, а те кто используют fw конечно так пролестают просто на предмет грубых или глупых каких-то моментов. Я смотрю вы тут уже 3 части написали, оперативно однако.
    • 0
      да вроде из контекста очевидно, что для новичков
      • +3
        Библиотека профессионала: «новичкам — от новичков»
    • 0
      думаю такое сравнение некорректно, т.к. MVC это шаблон другой, и он не входит в список тех 27 из книги GoF.
      Потому что в статье написано неправильно, MVC — это не шаблон, а парадигма. И построена может быть на разных шаблонах или без них вообще. А шаблоном может назвать тот, кто не очень понимает что такое шаблон. Просто помешались уже на паттернах кругом все.
      • 0
        Да вы абсолютно правы :)
      • 0
        Формально говоря, MVC является архитектурным шаблоном приложения, а тот же Singleton шаблон более низкоуровневый. Или Фаулер, по-вашему, не понимает что такое шаблоны?
        • 0
          Полагаю, что понимает. Но это уже просто вопрос терминов. Суть MVC в идее «разделение V и C (и M)», и на самом деле это всё. Сложно это «шаблоном» назвать, имхо. То, что MVC — это может быть несколько каких-либо «классических» шаблонов Вы согласны?
          • 0
            Отнюдь, это именно шаблон. Он, кстати, описан в книге «Банды четырех», только не в каталоге, а в первой части, где идет рассказ о Smalltalk.
            • 0
              В новой редакции он описан как составной шаблон.
              • 0
                Не читал еще новую ;)
      • 0
        • 0
          Ну, я рад за автора сайта «Справочник паттерны проектирования», но к чему это? И MVC там стоит в одном разделе с «шаблонизатором» и «контроллером страницы», обозванным «паттерны веб-представления», что, если даже посчитать это более-менее корректным, подтверждает как раз мои слова выше.
  • +1
    Эх, такие бы статьи да во времена, когда я только начинал изучать PHP.
    Новичкам, думаю, очень поможет.

    Для меня, к счастью это уже не актуально. Сменил профу на JS.
  • +4
    Для новичков расжовано хорошо, пару замечений:
    — Почему без namespace?
    — Зачем закрывающий тег ?>, потенциальны проблемы с сессиями
    • +1
      Я не знаю php. Подскажите по вашему 2-му замечанию:
      — почему не должен стоять закрывающий тег ?>
      — почему потенциальные проблемы с сессиями в приведеном примере.

      Спасибо!
      • 0
        Закрывающий тег в PHP вообще можно не ставить, если в файле только PHP код.
      • +4
        1. Всё что будет после ?> окажется контентом.
        2. Некоторые редакторы могут добавлять последнюю пустую строку. Ну или случайно > может оказаться не последним байтом в файле.
        3. Когда этот файл будет за'require'ен, этот хвост будет отправлен на клиент.
        4. После начала отправки контента нельзя установить заголовки, в т.ч. куки, в т.ч. сессионные куки.

        Вывод — особенно в подключаемых файлах, а лучше вообще всегда, нужно убирать ?>, если после него нет осмысленного контента.
        • 0
          Спасибо большое за развернутый ответ! Почерпнул для себя полезную информацию.
  • +1
    И почему в сотне таких одинаковых статей про php и mvc — нет ни слова как убрать код php из вида и прикрутить какой либо шаблонизатор. Помоему если вы это сделаете — Ваша статья будет качественно отличаться от других.
    • 0
      Я планировал написать про шаблонизацию, но понял, что текст статьи в таком случае будет сильно раздут и к тому же может породить очередной спор на тему, а стоит ли использовать сторонний шаблонизатор, ведь PHP сам по себе является шаблонизатором. О реализации простого шаблонизатора вы можете почитать в следующих статьях: "Мой родной PHP шаблонизатор " и "Делим код пополам или представление по шаблону в PHP ". Кстати, эти ссылки приведены в конце статьи!
    • +7
      Может быть потому, что шаблонизаторы к ПХП прикручивать не надо?
      Или потому, что в одной статье лучше разбирать одну тему, а не две?
      • 0
        В данном случае у автора получилась уже не одна статья, так что это не ограничение :-)

        А шаблоны к PHP прикручивать надо — как раз по причине того, что из хорошего шаблонизатора сделали посредственный язык программирования. За двумя зайцами погонишься…
    • 0
      Достаточно отделить вид от всего остального, инкапсулировать в него шаблонизатор (или использовать шаблонизатор в качестве вида) и обеспечить более-менее слабую связанность, чтобы заменить один шаблонизатор на другой.
      • 0
        PHP сам по себе чем плох в роли шаблонизатора?

        Для каких задач нужно что-то еще?
        • 0
          Нет автоэкранирования вывода для HTML, приходит писать минимум <?= e($var) ?> вместо {{var}}.

          Нетривиальна реализация наследования шаблонов.

          Не предоставляет изоляции шаблона от остальной программы и глобального нэймспэйса. То есть в шаблоне вы можете сделать ровно то же самое, что и в самой программе, например выполнить eval($_GET['expr']);

          • 0
            Не очень понял.

            1. Что за автоэкранирование и зачем? Если данные не готовы для вывода, разве «модель» не должна их подготовить? Или «модель представления»?

            2. Видимо я не сталкивался еще с такой проблемой… С другой стороны реализация разве не зависит от вас?

            3. А зачем специально изолировать шаблон? Это же ваша программа, просто не используйте там eval($_GET['expr']);.

            Правильно ли я понял, что надо сначала изобрести себе кучу проблем, и тогда сторонний шаблонизатор обязательно поможет их решить?
            • 0
              1. Чтобы не приходилось практически в каждом месте html-шаблона писать писать htmlspecialchars($var, ENT_QUOTES, 'UTF-8');. Нет, не модель, модель не знает будут данные в html выданы или, например в JSON. И даже не модель представления, имхо, хотя тут могут быть варианты.

              2. Зависит-то то она зависит, но решать её средствами PHP «в лоб» нетривиально, а код шаблона не очень читабельный, например
              <?php $view->extend('::base.html.php') ?>
              
              <?php $view['slots']->set('title', 'My cool blog posts') ?>
              
              <?php $view['slots']->start('body') ?>
                  <?php foreach ($blog_entries as $entry): ?>
                      <h2><?php echo $entry->getTitle() ?></h2>
                      <p><?php echo $entry->getBody() ?></p>
                  <?php endforeach; ?>
              <?php $view['slots']->stop() ?>
              

              по сравнению с
              {% extends '::base.html.twig' %}
              
              {% block title %}My cool blog posts{% endblock %}
              
              {% block body %}
                  {% for entry in blog_entries %}
                      <h2>{{ entry.title }}</h2>
                      <p>{{ entry.body }}</p>
                  {% endfor %}
              {% endblock %}
              


              3. Не всегда шаблон пишет разработчик движка, это может быть верстальщик, веб-мастер, а то и конечный пользователь (индивидуальное оформление своей странички, например). В общем лучше иметь возможность включить режим «песочницы», чем не иметь.
              • +1
                1. Спорно. Во-первых htmlspecialchars($var, ENT_QUOTES, 'UTF-8'); можно сделать и так:
                foreach ($data as $k=>$v)  $data[$k] = htmlspecialchars($v, ENT_QUOTES, 'UTF-8');
                

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

                Да и в модели она может быть. Модель не знает, как будут использованы данные. Но вы-то знаете :) Вызывайте метод типа getDataForHTML() или getDataForJSON().

                2. Для меня удивительно нечитабельны оба ваших примера :).

                У меня например, в шаблоне страницы может быть конструкция <?=show("some_block_template", $page);?>, которая значит, что надо в этом месте надо вставить шаблон некоего блока страницы. У каждого раздела сайта этот блок («заголовок, меню, строка поиска, whatever) может иметь разный шаблон. Если у данного раздела такой шаблон не определен используется базовый, если базового нет, то используется вшитый шаблон по-умолчанию (где просто написано, что шаблон такого-то блока для страницы не определен).

                Я не уверен, что это то самое наследование шаблонов про которое вы говорите. Если что вы меня поправите.

                3. Тут согласен. В описанной вами ситуации разумнее использовать сторонний шаблонизатор, а не php.

                • 0
                  1. Это сработает в случае если все данные нужно экранировать, а если часть надо, а часть не надо? Велик риск пропустить что-то в белом или чёрном списке.

                  2. Интеллектуальный include, который в зависимости от контекста возвращает разные результаты? То есть у вас каждая страница состоит, утрируя, из <html><?=show(«head_template», $page);?><?=show(«body_template», $page);?></html>? Я правильно понял? Если так, то вы наследуете блоки, но не наследуете основной layout.

                  • 0
                    1. Ваш изначальный посыл был в том, что все надо экранировать и запись {{var}} лучше, чем php-fаналог. Разве нет?
                    2. Я не делю основной layout и блоки, layout это просто блок «page», который включает в себя другие блоки (или не включает, если это не надо).
                    • 0
                      1. Не всё, но по умолчанию всё экранировано, если нужны raw данные, так и пишем {{var|raw}}

                      2. А как вы из шаблона ссылаетесь на layout?
                      • 0
                        1. ок. Поэтому вопросу делаю для себя вывод: без шаблонизатора не обойтись, когда надо предоставить возможность создания/изменения шаблонов человеку со стороны (не разработчику) и особенно пользователю. В других случаях выбор использовать или не использовать шаблонизатор (а не сам PHP) это вопрос технических требований и личного вкуса разработчика.

                        2. Если я правильно понял, что такое layout (<html><head></head><body><header><div class="content"></div><footer></footer></body></html>), то я никак не ссылаюсь на него из шаблонов других блоков. Я придерживаюсь концепции независимых блоков.
                        • 0
                          1. Я бы еще добавил, если велика вероятность, что код придётся поддерживать через некоторое время, когда детали забудутся. Или на все проекты должны быть единые соглашения.

                          2. Вот есть, допустим, блок user, есть post, есть admin, есть layout базовый (для user и post), куда блоки должны вставиться в .content и есть расширенный для админки. Как вы задаёте, что для uri /user/ нужно использовать базовый layout, для /post/id тоже, а для /admin — расширенный?
                          • 0
                            1. Не могу согласиться. Сам долго работал на поддержке чужих проектов (к счастью простых). И тут чем меньше технологий используется, тем проще поддерживать.

                            2. Какой layout использовать я определяю по uri, соответствующие файлы будут лежать, например, в templates/user/page.block.user.htm, templates/post/page.block.post.htm, templates/admin/page.block.admin.htm. Свой файл для каждого раздела/объекта. если файла по такому пути нет, используется глобальный.

                            Т.к. layout'ы страниц весьма похожи, то общие фрагменты (header, footer) хранятся в отдельных файлах и «инклюдятся» в page.block.htm.
                            • 0
                              1. ну как долго… всего пару лет. Так что мой опыт ничтожно мал. Я могу быть не прав.
              • 0
                {% extends '::base.html.twig' %}

                Вот за это я и не люблю наследование. Не дело знать шаблону контента, где этот контент будут использовать. Один и тот же контент может быть как показан на странице, так и отправлен клиенту по почте. Как это решать при наследовании?
                • 0
                  Ну, если допустить, что у нас есть один layout для html представления контента на странице, а другой для почты, то можно поступить так:

                  {# /content_http.twig.html #}
                  
                  {% extends '::base.html.twig' %}
                  
                  {% block body %}
                    {% include '::content.html.twig' %}
                  {% endblock %}
                  
                  {# http specific #}
                  


                  {# /content_mail.twig.html #}
                  
                  {% extends '::mail.html.twig' %}
                  
                  {% block body %}
                    {% include '::content.html.twig' %}
                  {% endblock %}
                  
                  {# mail specific #}
                  
                  


                  То есть content.html.twig отрисовывает себя одинаково для http и почты, не зная в каком конткесте он вызывается, а контроллер вызывает разные шаблоны для разных контекстов, в которых установлена связь между шаблоном контента и соотвествующего контенту лэйаута.
                  • 0
                    Т.е. те же самые инклуды контента, что и в случае без наследования, только более хитрые, да еще и с дублированием кода.
                    Мне это не нравится.
                    • 0
                      Может я слишком широко понял задачу и в конкретном случае можно выкрутиться покрасивее.
          • +1
            1. Не думаю, что задача экранирования должна лежать на шаблоне. В конце-концов, это защита сайта от XSS, её нужно решать выше.
            2. Наследование шаблонов — далеко не всегда нужно, но ладно, запишем в недостатки
            3. Как обучение MVC и его принципам — изоляция полезна. Но в повседневной разработке — нет. Как только вы поняли, что такое MVC, и почему не стоит обращаться из шаблона куда попало — это для вас не проблема. А если у вас в команде студенты, то доступ к репозиторию давать только через Code Review.
            • +1
              1. Только шаблон в идеале знает, в каком виде будут представлены данные, скажем правила экранирования немного да отличаются для html4 и html5, не говоря об xml.

              2. Практически всегда нужно, если следовать принципу DRY. Многие сайты/приложения состоят из многих страниц с однотипной вёрсткой, различающейся только блоком контента. Есть вариант обойтись без дублирования с помощью конструкций типа include "header.phtml"/include "footer.phtml", но он не очень удобен для разработки и, особенно, поддержки.

              3. Не всегда разработчик контролирует код приложения.
              • +1
                1. Шаблон не должен знать, каким данным можно доверять, а каким нет! Это не его прерогатива.
                2. Вариант с layout отдельно, content отдельно, на мой взгляд лучше для таких сайтов. Наследование — для более сложных ситуаций.
                3. И что? Защита от дурака, это, конечно, хорошо, но нельзя же везде поставить такие препоны — быдлокодеры всегда найдут где в код нагадить ;)
                • +1
                  1. Вопрос экранирования данных это не вопрос защиты от XSS или ещё чего, это лишь вопрос корректного вывода данных в конкретном представлении. И это область ответственности шаблона (вернее вида). Он должен знать какие данные ему выводить как есть, а какие экранировать согласно своему типу вывода, чтобы получить корректный вывод. Защита тут лишь побочный эффект. Как у экранирование с помощью mysql_real_escape_string в параметрах или использование подготовленных варажений побочный эффект защита от некоторых видов SQL-инъекций, но защитой они не являются.

                  2. Это частный случай наследования, одноуровневое. Не всегда его хватает. Да и всё равно обычно не так читаемо получается.

                  3. Если это нам ничего не стоит, максимум строчка в конфиге, почему бы не ставить? :)
                  • +1
                    1. Правильно, это область ответственности вида. Шаблону об этом знать не нужно.
                    2. По-моему это читаемее, и, что важно — более гибко. Наследование — слишком сильно связывает шаблоны друг с другом.
                    3. Это не строчка в конфиге, это возросшая сложность приложения. KISS не соблюдается.
                    • 0
                      1. Сложно разделить ответственность между видом и шаблоном, шаблон — составная часть вида и вполне, по-моему, может знать о данных столько же сколько вид. В конце-концов только разработчик шаблона знает в каком месте данные нужно экранировать, а в каком нет, а вид, не говоря о контроллере и, тем более, модели должен быть способен работать с любым шаблоном. А в модели для каждого поля делать не только нативный геттер, но геттеры для каждого формата по-моему очень неразумно. Выбор, имхо, стоит только в том экранировать по умолчанию или нет, основной набор данных передаваемых в шаблон. Исходя из того, что проще забыть заэкранировать, чем разэкранировать, я выбираю экранирование по дефолту, которое PHP без костылей реализовать не может.

                      2. О вкусах не спорят :)

                      3. Сложность инкапсулирована в лучших традициях ООП :)
                      • 0
                        1. Как это шаблон и вид могут знать о данных одинаково?
                        «Вид» это код, который пишите вы, шаблон — разметка, которую пишет верстальщик или пользователь. Вы сами говорили об этом выше. Я думаю, в таком случае, разработчик знает о данных не очень много.
                        • 0
                          Вид предназначен для представления данных, шаблонизация лишь один из способов его реализации. Вид, вернее его разработчики, должны знать какие данные ему идут на вход и как их корректно вывести. В одних реализациях разработчику кода достаточно знать какие данные, в других — только верстальщику, в третьих — обоим необходимо знать и синхронизировать свои знания.
  • 0
    Я не знаю PHP. Вопрос. Из модели вернули массив. В представлении использовали обращение не по ключам массива, а «напрямую». Как это возможно?
    • 0
      С массивом из модели должен работать контроллер, а не представление
      • 0
        Пусть бы и так. Только это ничего не изменило. Контроллер получил данные из модели в виде массива. Котроллер передал данные в представление в виде массива. В представлении используется не массив.
        • 0
          Правильно, потому что в классе load.php, в методе view есть строчка extract($data)
          Каждый ключ массива превращается в переменную.
          php.net/manual/ru/function.extract.php
          • 0
            Я был невнимателен.
            • 0
              Наоборот. Вы внимательный :)

              Ведь где-то могут существовать переменные с такими же именами, как и ключи массива.
              Пусть в примере их нет. Но в жизни бывает всякое. :)

      • 0
        С чего вы так решили?
    • 0
      Я нашел ответ на вопрос. Функция extract.
      • 0
        extract не безопасная функция, попробуйте почитать про ActiveRecord или DataMapper
        • 0
          Простите, причём здесь ActiveRecord или DataMapper?
          Речь идёт о массиве в представлении, не о базах данных.
          • 0
            При том, что можно отдать представлению модель и обращаться к ее свойствам из представления.
            • +3
              Всё равно считаю, что в представлении должно быть представление, а работать с моделью должен контроллер.
              • 0
                Спорить не буду, лишь отмечу, что концепции MVC это не противоречит. Можно в контроллере вбить передать значения свойств в представление, можно получать эти значения сразу в представлении.
                • 0
                  Просто в зависимости для кого это делать.
                  Если для себя, то никаких проблем, а если для кого-то, то в один прекрасный день кто-то, скорее всего, это сломает, пытаясь заменить что то в дизайне.
                  • 0
                    Даже для приведенного примера. В чем разница между

                    <?php foreach ($someArr as $item) : ?> ... здесь какой-то вывод ... <?php endforeach; ?>

                    и

                    <?php foreach ($model->getSomeData() as $item) : ?> ... здесь какой-то вывод ... <?php endforeach; ?>

                    Представление также инкапсулировано от модели геттерами как если бы через эти же геттеры нужно было получить данные в контроллере
                    • 0
                      Если так, то никаких проблем.
                      Я Вас, скорее всего, не правильно понял. Простите.
                      • 0
                        Причем использование геттеров, на мой взгляд, более оправдано, так как позволяет гораздо быстрее делать (кодить представления). Написал геттер, который через LazyLoad получает нужные объекты и сразу можешь к нему обратиться в представлении без необходимости вручную передавать контроллером — модель подгружает себе данные по мере обращения к ним.
                        • 0
                          На каком этапе реализуется ACL?
                          • +1
                            ACL — задача контроллера.
                    • 0
                      Использую шаблоны и все выглядит примерно {data}, мне так кажется удобнее.
                      • 0
                        Само собой, тот же Smary вполне положительно относится к тому, что я отдаю ему объект и в представлении обращаюсь к его свойствам.
                    • 0
                      В том, что можно сделать foreach ($model->setSomeData())
          • 0
            т.е. в представление нельзя передавать обьекты?[сарказм] Я же не говорил использовать эти паттерны, а взять идею. К примеру передав в метод объекта setFromArray() или populate() этот массив, понятно что произойдет, А представьте мы получаем массив в котором есть индекс _SESSION и extract делаем, круто правда?
            • 0
              Не знаю как остальные, а у меня представление только выводит данные, всей их обработкой занимается контроллер.
              • 0
                Контроллер не должен заниматься из обработкой, это задача либо модели, либо сервисного слоя. Контроллер передает вызовы от одного к другому, максимум — содержит простую логику, максимум, собрать все необходимые объекты чтобы отдать их модели.

                Толстый контроллер не айс.
                • 0
                  Непонятно я выразился, я имею ввиду, что он получает данные от модели\пользователя, в первом случае смотрит на данные и вывод их, например в случае с входом пользователя в систему, только саму форму, а если от модели пришло указание, что данные неверные\содержат недопустимые символы, выводит ошибку. В этом плане.
            • 0
              ну вообще-то на такие моменты используют префиксы, хватит уже попроть чушь. если посмотреть как делаю во фреймворках, то тма как раз через экстрактинг и делают с префиксом если надо. а вы тут чушь и демагогию развели.
          • 0
            вот уже и минус успели нажать, вместо того, что бы немного подумать, что я написал
            • 0
              Я Вас не минусовал, Вы правы, спорить не буду.
              Но, как по мне, проще не пихать в extract то, что нельзя, чем делать хардкор или работать с моделью через вид. Или же просто передавать массив в вид.
              Опыта у меня явно меньше Вашего, так что я просто прислушаюсь. :)
              • 0
                вот примерный класс view который избавит вас от extract: simpleView
                а использовать просто:
                в скрипте
                $anyArrayWithData['title']='Mega Title';
                $view->assign($anyArrayWithData);
                $view->render('my/mega/template.phtml');
                

                в представлении:
                <h1><?php echo $this->title ?></h1>
                

                вот и все
  • 0
    Когда работал с Opencart очень понравилась её архитектура, считаю одной из самых простых и гибких реализаций MVC.
    • –3
      OpenCart не смотрел, но считаю архитектуру Yii одной из самых приятных.
  • 0
    Сюда бы еще автозагрузчик классов, сами имена классов согласно паттерну MVC и шаблонизатор, было бы самое то.
    • +2
      И давно MVC как-то определяет имена классов? ))
      А то я и не в курсе :)
    • 0
      шаблонизатор тут как бы есть, его роль играет сам php.
    • 0
  • 0
    Коллеги, раз уж собрались специалисты по архитектуре PHP приложений, у меня вопрос.

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

    Поскольку задачи из проекта в проект остаются похожими, желание сделать быстро — преодолевает желание переделать все с нуля.
    • 0
      Для начала стоит написать задачи, которые выполняет ваш фреймворк. Глядишь, посоветуют подходящую для вас замену.
      Если не поможет — выложить на Github, рассказать про проблемы (и необходимые костыли), задачи, планы и т.д. Либо опять же — посоветуют замену, либо могут решить проблемы с костылями.
      • 0
        Задачи нехитрые, тот же Yii их выполняет прекрасно, не считая некоторых специфичных вещей. В других проектах которые поддерживаю есть и Zend и Kohana.

        Проблема не в том, что я не знаю на что перейти, а в том, что это поддерживать пока проще, чем писать с нуля. Я видимо некорректно объяснил. На базе этого «фреймворка», уже написано довольно большое приложение, которое собственно и тянется видоизменяясь.
        • 0
          Я сталкивался с похожей ситуацией, и перешел на Zend. Время, потраченное, на переход, вполне себе окупилось.
    • 0
      Собрать волю в кулак, обуздать лень и перейти на Yii/Symfony/Zend/etc, что ваша религия примет больше.
      Старые проекты поддерживать на старом, а новые писать уже на любом общепринятом fw.
      Мне кажется, что если вы отчетливо понимаете, что в вашем фреймоврке есть архитектурные проблемы, то не зачем делать на нем новые проекты, незачем эти проблемы плодить.
      А касаемо скорости разработки — если возьмете Yii/Code Igniter/Kohana — то это фреймворки с простым уровнем входа, в них легко разобраться и начать писать. Вы не много потеряете времени — попробуйте, вам понравится)
      • +1
        Вот если честно как не пытался себя заставить перейти на какой-либо популярный фреймворк — не смог. Не вижу смысла. Стрелять из пушки по воробьям. Применяю свой микрокаркасс для MVC.

        Прошедший год занимался разработкой сайтов на Joomla, там свой фреймворк, конечно изучить его был смысл, но точно не Yii или Zend.
        А сейчас работаю над проектом, который представляет собой вавилонскую башню из говнокода, накопленного наверное лет за 5, не меньше. И в команде к тому же нет единого мнения о целесообразности ООП, не то, что бы даже про фреймворк.

        Так что не пойму я этой повальной моды на фреймворки.
        • 0
          Схожая ситуация, но постепенно перевожу код на ООП и MVC с использованием тестов, ORM, шаблонизаторов, библиотек имея в виду переход полноценный фреймворк.
          • 0
            Я тоже считаю это направление развития правильным.
            Но не всегда это оказывается возможным или приемлимым с точки зрения временных затрат.
            • 0
              Проблема еще в том, что мода на фреймворки это прежде всего мода.
              Которая проходит, а проекты остаются.
          • 0
            Другими словами, создаете ад? :)
        • +1
          Фреймворки — как стандарт, экономят время сотрудников в крупных проектах на изучение фреймворка.
          • 0
            В случае PHP это работает не очень :( В вакансиях чаще видишь «опыт работы с MVC-фреймворками: ..., ..., ..., и т. п.», чем «опыт работы с ...». Работодатели как бы не надеются, что достаточно соискателей знают конкретный фреймворк, их устраивает, чтобы хоть какой-то знали, ведь переход с одного на другой займёт, в общем случае, куда меньше времени чем принятие концепций фреймворков, MVC и т. п. с нуля.
            • +1
              Чтобы разрабатывать с использованием фреймворка нужно гораздо больше знаний и опыта, чем написание простых функций, раскиданных по сотне файлов. Все просто — чтобы использовать фреймворк нужно хотя бы иметь представление о принципах, которые лежат в его основе, поэтому требование опыта работы с фреймворком вполне обосновано.
              • 0
                Я к тому что единого стандарта де-факто, как Django в Python, RoR в Ruby или ASP .NET MVC в C#, нет (я в курсе что есть и другие фреймворки в них, но эти явно доминируют). И работодатели хотят чтоб хоть с каким-нибудь был опыт, чтобы сократить время на изучение, но на то, что оно вообще не понадобится и новому сотруднику нужно будет изучать только проект, но не сам фреймворк, особо не рассчитывают. RoR-разработчик вакансию можно увидеть, а Yii- или ZF-разработчик как-то не встречались.

                Насчёт количества знаний необходимых для разработки вопрос спорный. Фреймворки многие детали скрывают.
              • 0
                Все верно. Это нужно для того, чтобы разрабатывать с использованием фреймворка.

                Но не обязательно для того, чтобы решать прикладные задачи.
          • +1
            У меня сложилось впечатление, что это как стандарт только читая Хабр.

            В вакансиях сейчас часто конечно встречается пожелание знать популярный фреймворк, но также полно еще предложений без этого требования.
            Я в этом убедился сам, недавно выбирая работу, ходя по собеседованиям. А мой знакомый, который даже ООП толком не понимает, но имеет стаж лет 5, устроился на ведущего программиста.
    • +1
      Если уж есть какой-то готовый проект, который до тебя писала орда говнокодеров, то обладая какими-то навыками построения приложений есть смысл как-то сбоку прикрутить свой фреймворк, аккуратно его подцепить, чтобы и старое работало и новое писать было удобнее. В случае, когда есть уже существующая система к ней гораздо легче прицепить свой фреймворк, чем прицепить Yii/Cohana/CI/etc. Причина очень проста — каждый разработчик знает свой фреймворк гораздо лучше, чем любой из готовых, знает, как с минимальными изменениями адаптировать его под нужны уже имеющегося приложения.

      У меня у самого была такая же ситуация. Есть проект, который просто написан стихийно на коленке, достаточно неплохо работает, но к нему нужно написать много сложных дополнений. Комментарий в коде практически никаких, зато есть собственный фреймворк, который можно урезать и как основу новых модулей использовать. Вроде бы и старый код продолжает работать и рефакторить его не надо, с другой стороны и зависимость нового кода от старого минимальна.

      А переход на распространенный фреймворк обоснован, как минимум, тем, что он распространенный. Узких мест в нем найдено гораздо больше, багов и вариантов использования протестировано также гораздо больше. Да и база готовых модулей сильно сокращает время разработки.
    • 0
      Я выбрал способ вялотекущего рефакторинга с использованием Symfony Components, Doctrine 2 и Twig, имея в виду полный переход на Symfony 2, Doctrine 2 и Twig. Вялотекущего потому что код не мой и для написания тестов фактически его реверс-инженерю, толком даже не понимая где баг, а где фича.
      • 0
        У меня вот полного перехода не получается — к проекту пришлось написать собственный велосипед с почти полным копированием возможностей Yii.
  • 0
    Как только увидел стрелку от View к Контроллеру с пояснением «Вызов метода контроллера», то решил, что дальше читать не стОит :)
    • 0
      Правильно, от View не должно быть стрелок!
      • 0
        Серьёзно? Ничего что именно View отвечает за взаимодействие с пользователем? Двухстороннее взаимодействие, а не одностороннее. В случае веба стрелки от View — это ссылки, формы, ajax-запросы и т. п.
        • 0
          Серьезно. Двухстороннее взаимодействие — а какого хрена мы тогда говорим о разделении визуализации и бизнес-логики? Она тогда не разделена по определению, и все это тогда наука не о чем.

          Вместо этой классической хрени MVC — контроллер может элементарно подписать на события View, нужные методы Модели — и устранить это излишние двухстороннее взаимодействие.
          • 0
            Бизнес-логика — логика модели предметной области, в которой существуют термины, например, «перевести сумму S со счёта A на счёт B», но не существуют «нажать на ссылку „перевод“».

            Подписка на события — один из вариантов реализации MVC, но двухсторонние взаимодействия никуда не деются.
            • 0
              «Подписка на события — один из вариантов реализации MVC, но двухсторонние взаимодействия никуда не деются.»

              Деются. В этом случае у View нет ссылки ни на контроллер, ни на модель.
              • 0
                Они просто идут через посредников, диспетчера событий и модель представления.
                • 0
                  Но эта связь скрыта, и не влияет на повторное использование, в отличии от других где необходима непосредственная ссылка на объект. Такую скрытую связь на UML диаграмма не отображают, и она не может считаться двухсторонней.
                  • 0
                    Это ещё хуже, имхо. Связь есть, но она неявна, скажем модель Blog изменила своё состояние и оповещает слушателей, вызывая метод raiseEvent(new BlogState($this)). Какие классы зависят от BlogState узнать не так просто.
                • 0
                  Часто это зашито собственно язык программирования.
                  • 0
                    Часто это зашито собственно в язык программирования.
  • +2
    >Для сайта domain1.com
    Поскольку статья больше ориентируется на новичков, то давай будем последовательны и не прививать плохие привычки в духе той, что я процитировал. Забудьте вы про всякие domain.com, site.ru и прочих доменных именах которые приводятся в качестве примеров. Следуйте RFC 2606 (IANA — Example domains, Домены для примеров) и прочим полезным спецификациям. Уверяю вас, это не сложно ;)
  • –1
    Лучше бы автор itdumka.com.ua/ на хабре написал ;) чем Вы. Вы взяли у него всю инфу и выложили сюда, в чем смысл?
    Те кто знает про MVC это не нужно, тем кто не знает — материала полно. Collection топики это круто, но они должны ИМХО быть крутого качества!
  • –1
    Классическое заблуждение о пригодности MVC продолжает жить, поразительно. А может ли мне кто-то сказать для чего нужно MVC? Разве не для разделения бизнес логики от визуализации? Нет? А Вы думаете представленная модель MVC с этим справляется? Вот оно заблуждение…

    Как правильно организовать разделение визуализации и бизнес-логики я писал ранее тут.
    • 0
      Не только, трехкомпонентная сущность MVC предполагает, как минимум, разделение логики на три части: вида (то, что вы называете визуализацией), модели (бизнес-логика) и контроллера (всей остальной, логики приложения, как-то: логика хранения, логика контроля прав доступа).
      • 0
        *доступа и т. п.
      • 0
        Логика приложения — это другой слой, как правило оформляемый как ядро системы и используемый напрямую в бизнес-логике, делать ответственным за это контроллер — несерьезно и незачем. Это лишь запутывает систему взаимодействий — у вас тогда никакая логика без обращения к контроллеру не будет работать.
        • 0
          Я привык к подходу где бизнес-логика не зависит от других частей приложения. Контроллер должен знать о модели, вид может знать о модели, но модель, имхо, должна быть самодостаточной, максимум содержать логику хранения (а-ля ActiveRecord), но и то, имъо, это будет лишняя связанность, запутвыающая систему.
          • 0
            В том то и дело, что в MVC модель не самодостаточна. Легко проверить — часто бывает, что модель нужно использовать без визуализации — теневое выполнение. Тогда ни представление, ни контроллер не нужны — проверьте сможет ли заработать без этих классов модель — в общем случае нет, т.к. будет обращаться к представлению, чтобы отправлять обновленные данные.
            • 0
              Имхо, либо представление должно получать данные от модели непосредственно (свойства, геттеры), либо ему данные должен предоставлять контроллер (возможно подписывая представление на события модели). А модель ведёт себя либо пассивно, либо посылая сообщения об изменении своих данных абстрактному подписчику на них.
              • 0
                Я тут открыл Фаулера, там черному по белому написано «В основе MVC лежит разделение кода пользовательского интерфейса (представления, называвшегося раньше view, а сейчас чаще presentation) и логики предметной области (модели)». Поэтому не путайте меня «логикой приложения в контроллере» — это частные додумки, и далеко не эффективные.

                Что касается первого варианта «представление должно получать данные от модели непосредственно (свойства, геттеры)» — это вообще не вариант — это того ради чего вообще затевается MVC с его тяжеловесной конструкцией. У Фаулера опять же описан более классический вариант под названием «Дублирование видимых данных» — это тот рефакторинг который нужно осуществить если в программе есть реализация вашего первого варианта.

                А вот один из вариантов реализации «Дублирования видимых данных» — это через события, но в C# + WPF проще — там есть технология binding
                • 0
                  Всё правильно. Логика UI находится во вью, логика предметной области — в модели. Всему остальному остаётся место только в контроллере (декомпозцию его функций никто не отменял).

                  • 0
                    Всего остального — НЕТ.
                    • 0
                      Как нет? Логика хранения, например, контроль прав и т. п., это и не UI, и не логика предметной области.
                      • 0
                        Как мы говорили ниже — это логика предметной области, только другой.
          • 0
            И да если модель не а-ля ActiveRecord, то как же её можно считать самодостаточной, если она не сохраняется?
            • 0
              Сохранение данных между запусками приложения (для PHP аналогично «между запросами») — задача логики приложения в общем случае, задача модели — моделировать предметную область, как ни странно. Скажем в бухучёте нет терминов «сохранить документ» или «загрузить документ», есть «создать документ» и «уничтожить документ» по каким-то событиям предметной области (приход товара, истечение срока хранения), в которую не входит «авария в электросети, компьютеры выключились» или «PHP сбрасывает среду выполнения для каждого запроса». А так модель самодостаточна, она как бы содержится в памяти с первого запуска приложения до последнего и лишь в силу неидеальности этого мира приложению приходится заботиться о сохранении и загрузки.
              • 0
                Так то оно так, но тогда это надо решать чище. Работа с базой данных — это по сути тоже бизнес-логика, только не бухгалтерии например, а именно так и называемое «хранение данных». Тогда в модели бухучета должны быть события «ОкончаниеСозданияДокумента» и «ОкончаниеУничтоженияДокумента», тогда контроллер должен подписать методы бизнес-логики базы на эти события. Но это слишком чисто для не совершенного мира программирования, и часто вместо этого у модели стоит прямо вызов метода ядра предназначенный для сохранения данных (а-ля ActiveRecord).
                • 0
                  Работа с базой данных — это по сути тоже бизнес-логика

                  Нет, это не бизнес-логика, это техническое решение.
                  • 0
                    Блин, (чего тупим ;) ) — пусть Вы работаете в Microsofte и разрабатываете SQL Server Enterprice Manager, какая у Вас предметная область? В чем состоит бизнес-логика?
                    • 0
                      Я говорил, что в общем случае. Но опять же если взять, например, phpmyadmin то он взаимодействует с БД двумя способами — и как с предметной областью, моделируя в памяти структуру и данные БД, и как со средством хранения части своего состояния между запросами.
                      • 0
                        ИМХО, вот это и является проверкой решения на прочность, если от изменения предметной области (а работа в базой как мы выяснили в частном случае может быть бизнес-логикой) — меняется вся архитектура приложения — значит в такой архитектуре проблемы. Поэтому лучше всегда одинаково относится к бизнес-логике, и лишь разные предметные области разделять по слоям. Тогда бухгалтерия — это высокоуровневая бизнес-логика, а работа с базой — это несколько вырожденная, но все равно бизнес-логика, но уже низкоуровневая.
                        • 0
                          Не меняется от изменения предметной области вся архитектура. Если я буду писать SQL Manager, то в модели предметной области у меня будут классы типа Database, Table, Column, Relations и т. п. для моделирования предметной области, при этом Database, Table и Column будут получаться из SQL-запроса SHOW ... аналогично другим классам, зависящим от внешних сервисов. А Relation будет храниться архитектурно отдельно, на физическом уровне это может быть та же БД, может быть другая, а может вообще быть не БД.
          • 0
            И да если модель не может решить, визуализировать себя или нет — как она может быть самодостаточной? А такое решение и есть бизнес-логика — вида взаимодействовать с пользователем, или работать в автономном режиме. И вот только в случае необходимости GUI возникает отделение деталей визуализации от бизнес-логики.
            • 0
              В предметной области нет, как правило, и термина «визуализация», есть бизнес-процессы в различном состоянии и правила изменения этих состояний. Сохранность этих состояний между запусками приложения или их визуализация — не здача модели. Возлагая на неё эти задачи мы, как минимум, нарушаем принцип единственной ответственности искусственно усложняя приложение.
              • 0
                Вы не поняли. Я же не говорю, что в предметной области есть термин «визуализация». Но именно бизнес-логика решает взаимодействовать с пользователем или нет, например, один платеж можно провести автоматически, а второй нужно показать для утверждения пользователю. И вот в терминах предметной области и возникает необходимость в GUI. Таким образом, решение на верхнем уровне о визуализации диктуется именно бизнес-логикой — и это первейшая задача модели, решить нужен ей пользователь или она может работать автономно и все это в зависимости от определенных бизнес-правил.
                • 0
                  Имхо, в терминах предметной области «нужно показать для утверждения пользователю» означает переход в состояние типа «ожидание подтверждения» или «черновик». «Показать» — логика приложения, «как показать» — логика представления.
                  • 0
                    Можно и так. Но это будет именно то чистое решение, о котором я говорил выше.
                  • 0
                    По сути наличие «ожидание подтверждения» означает, что есть некий диспетчер, который выбирает такие документы из очереди, но на практике многие ленятся так полноценно реализовывать.
                    • 0
                      Полноценно обычно избыточно, хотя и доставляет неудобства, если правила меняются.
    • 0
      А Вам не приходила идея, что «логик», так же как и представлений может быть много? Логика хранилища данных, логика бизнеспроцессов, логика пользовательского интерфейса (да-да, интерфейс это не просто статичная страничка «отчёта» со ссылками и формочками). Всё это разные уровни единой системы и MVC прекрасно применяется на каждом.

      Соотношение примерно такое же как между транзакциями базы данных и транзакциями бизнес-модели. Вроде бы и похожие вещи, но не одно и то же и одно через другое не реализуется.
      • 0
        «логик», так же как и представлений может быть много — естественно, только если Вы от View обращаетесь и к модели и к контроллеру — вы тем самым зацементировали эту связку 3 классов. Попробуйте теперь полностью выкинуть ваш контроллер и модель и подсунуть представлению совершенно другую бизнес-логику и контролер — у них не будет того, что вызывает View и все у вас захлебнется…
        • 0
          View не подразумевает независимости от моделей и контроллеров в общем случае. Можно построить архитектуру, чтобы не зависело, если задаться такой целью, но в общем случае это не подразумевается. Отделение не значит независимость.
          • 0
            > Отделение не значит независимость.

            Тогда грош цена такому отделению. И именно поэтому я начал с того, что «Классическое заблуждение о пригодности MVC продолжает жить, поразительно. „
            • 0
              Представление должно знать, что оно представляет. Иначе возможны трудноуловимые ошибки, которые статически не выявишь.
              • 0
                Сомнительное утверждение.
  • +1
    Вот эту функцию можно повредить передачей $data['file_name']

    function view($file_name, $data = null)
    {
    if(is_array($data)) {
    // преобразуем элементы массива в переменные
    extract($data);
    }
  • +1
    О… прочел теорию. Дайте-ка ругнусь, пока не приступил к практике.

    Сам я не особо силен в MVC и прочих поименованных паттернах. Иногда думаю, почему?
    Да потому что в подобных статьях всегда непойми как объясняется теория.

    Вот, концептуальная диаграмма с кучей связей между тремя элементами. Хотя ниже в сопроводительном тексте сказано, что контроллер нужен для для связи модели и представления. Минуточку… Но на концептуальной схеме мы видим, что модель и представление могут взаимодействовать между собой.

    Чему верить? Уже в этот момент у новичка начинаются трудности с пониманием.

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

    Я например, знаю, что жизненный цикл веб-приложения примерно такой:

    1. — клиент посылает HTTP-запрос серверу (метод, uri).
    2. — приложение запускается, определяет метод и uri
    3. — выполняет код в соответствии с uri и методом.
    4. — возвращает сгенерированный HTML (XML, JSON, GIF, whatever)
    5. — приложение завершается.

    Просто, понятно, знакомо. (IMHO. Допускаю, что для кого-то не просто, не понятно и не знакомо).

    И где тут что. Например, п.2. это контроллер, п.3 — модель, п.4 — представление.

    Прав я или нет? Верно я понял концепнию или не совсем?

    Я пока не знаю, что мы будем делать в Практике (не дочитал топик и не знакомился еще с другими комментами), надеюсь будет интересно.

    Но вот как я вижу мини-фреймворк после прочтени теории.

    <?php
    // контролер
    $method = $_SERVER["REQUEST_METHOD"];
    $uri = $_SERVER["REQUEST_URI"];
    
    // обращаемся за данными к модели
    $model = getModel($uri);
    $data = getModelData($model, $method, $uri);
    
    // обращаемся за HTML-кодом к представлению.
    $view = getView($method, $uri, $data["result"]); // вид зависит от того, как отработала модель
    $output = getViewOutput($view, $data);
    
    echo $output;
    ?>
    


    Как-то так, да?

    Ну все, пошел читать дальше.

    Спасибо за статью, вызвала неподдельный интерес.

    • 0
      1. — клиент посылает HTTP-запрос серверу (метод, uri).
      2. — приложение запускается, определяет метод и uri
      3. — выполняет код в соответствии с uri и методом.
      4. — возвращает сгенерированный HTML (XML, JSON, GIF, whatever)
      5. — приложение завершается.


      С использованием MVC будет скорее так:
      1. — клиент посылает HTTP-запрос серверу (метод, uri).
      2. — приложение запускается, определяет метод и uri, вызывает контроллер
      3.1 — получает данные (как правило один или несколько объектов/массивов) в соответствии с запросом из модели
      3.2 — передаёт данные в вид для генерации HTML,…
      4. — возвращает сгенерированный HTML (XML, JSON, GIF, whatever)
      5. — приложение завершается.

      Важно понимать, что пункт 2 это ещё не контроллер, обычно это называют маршрутизацией, осуществляется или веб-сервером, или фронт-контроллером. То есть в вашем примере контроллер это, как минимум,
      $model = getModel($uri);
      $data = getModelData($model, $method, $uri);
      
      // обращаемся за HTML-кодом к представлению.
      $view = getView($method, $uri, $data["result"]); // вид зависит от того, как отработала модель
      
      , а то и остальные две строчки. Ну и, как правило, uri и method не передаются в модель и представление напрямую, а из них извлекаются необходимые для данного запроса параметры и определяется какие модели вызывать и какие вью использовать. Модель и вью как бы изолируются от HTTP, не User::getByUri($uri), а User::getById($_GET['user_id'])
      • 0
        Тут вроде понятно… весь мой пример скрипта — контроллер.

        Модель и представление, типа, определены в других файлах.

        Насчет uri не совсем понятно. С учетом того, что Uri — universal resourse identifier, чем он хуже любого другого идентификатора?

        Впрочем, я тоже не всегда использую uri, а извлекаю из него параметры. Но иногда (в удачный день :) получается использовать uri без дополнительной обработки.
        • 0
          В принципе да, весь код контроллер, но когда таких контроллеров будет два, три и т. п. вы наверняка выделите часть кода в один, скажем, index.php, в котором будете инклудить зависящий от запроса конкретный контроллер.

          Слишком универсальный в контексте контроллера, Допустим, для запроса GET /users/volch к моменту получения объекта с id volch уже ясны тип объекта, что нужно его именно получить (а не удалить и т. п.) и достаточно вызова $users->getById('volch'), а не $users->getByIUri('/users/volch'), который придётся дополнительно разбирать. Хотя, конечно, это вопрос удобства и архитектуры могут быть разными, но как-то наиболее популярна при которой при разборе метода и части uri определяется собственно обработчик (контроллер), а при разборе оставшейся части uri и post/put данных определяются параметры, которые ему передаются, часть которых он передаёт в модель, а часть использует сам например для выбора формата представления.
          • 0
            Разобрать uri не так и сложно, если заранее о его (или ее? мне нравится «она», правильнее, особенно по-английски — «он») структуре подумать.

            list($empty, $db_name, $id) = explode("/",$uri);
            


            Да и вообще сам uri может выступать идентификатором ресурсе в БД, XMLфайле или key-value хранилище.

            Конечно, не всегда это может быт правильно или удобно.
            • 0
              Разобрать, конечно, не сложно, вопрос в том, где это делать и сколько раз. Особенно если есть вероятность, что правила разбора могут меняться.
    • 0
      Ругаться тут не надо. Представьте свою схему и пояснения к ней и почему она более актуальна чем приведенная в статье.
      • 0
        Вы — учитель. Я лишь заметил странное несоответствие схемы и ее описания.
  • 0
    Пешил попробовать.
    Fatal error: Call to undefined method Load::veiw() in /Applications/MAMP/htdocs/backoffice/application/controller.php on line 19
    

    И дальше никак :(
    • 0
      *Решил
      Сорри
  • +1
    Модель, ВИD, контроллер… Оригинально!
  • 0
    Спасибо за статью!
  • 0
    Занялся изучением Yii. Решил начать с повторения основ ООП и изучения паттерна MVC. Ваша статья ОЧЕНЬ помогла! Большое спасибо!
  • 0
    Видео вариант статьи


    mvcphp.ru

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