![](https://habrastorage.org/files/26b/3b0/95f/26b3b095f17a4493b8b34c52f3b35ad9.jpg)
Я хочу привести простой и рабочий шаблон проектов, с которым любой новичок в программировании на PHP сможет создать свое веб приложение и заодно втянуться в тему MVC.
Статья ориентирована на новичков, т.е. ничего нового в ней нет, просто несколько идей собраны в рабочий проект, решающий большинство задач.
Проект начинается со структуры. Простая и логичная структура – залог
Подробно, что такое MVC, можно ознакомиться тут и тут.
А если кратко — это разделение кода на Модель(логику), Представление и Контроллер.
Модель содержит в себе логику и данные приложения. Некий Сан Саныч, что у него контроллер закажет, то он и со склада достанет, по необходимости доработав напильником. Модель взаимодействует с базой данных, и обрабатывает результат. Важно обрабатывать данные в модели, на эту тему есть статья https://habrahabr.ru/post/175465/ . Если кратко, когда логика в моделях, её проще пере использовать как внутри проекта, так и переносить в другие.
Представление – ну сущий продавец. Не слишком смышлёный и знает только то, что доверил контроллер. Задача представления – только представить информацию. В него входит вёрстка (шаблоны).
В архитектуре MVC важно, что компоненты ничего не знают друг о друге, а потому – независимы. Если мы захотим поменять внешний вид формы на сайте, это должно затрагивать только представление. Если вносим изменения в базу данных, то это должно касаться только модели.
Плюсы такой архитектуры: можно тестировать компоненты по отдельности – проще искать баги, поправить вёрстку на сайте сможет и дизайнер, логику можно будет перенести и в другой проект.
Я предлагаю в качестве архитектуры простого приложения использовать:
![](https://habrastorage.org/files/a59/9aa/44a/a599aa44a1204304bf06cfe1bef2a136.png)
Контроллеры отдельно, модели отдельно, представление сложено по папкам (в папке base универсальные пере используемые шаблоны, такие как пагинация). В папке s – ресурсы: картинки, JavaScript и стили.
В первую очередь правила для сервера в htacess:
#Кодировка
AddDefaultCharset utf-8
RewriteEngine on
RewriteBase /
#Запрет перенаправления если файл есть
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
#Перенаправление со страниц вида /employee/
RewriteRule ^([A-z]*)\/$ ?type=page&controller=$1&action=main [L,QSA]
#Обращение к контролеру
RewriteRule ^([A-z]*)\/([A-z]*)\/$ ?type=page&controller=$1&action=$2 [L,QSA]
#Ajax
RewriteRule ^ajax\/([A-z]*)\/([A-z]*)\/$ ?type=ajax&controller=$1&action=$2 [L,QSA]
ErrorDocument 404 /404.html
У приложения будет единая точка входа (то есть все запросы к сайту, за исключением файлов, будут перенаправлены на 1 скрипт) – index.php. В нём:
<?
session_start();
//Определяем тип запроса
$type = $_REQUEST['type'];
//Подключаем настройки проекта
include $_SERVER['DOCUMENT_ROOT']. "/config.php";
//Загружаем базовые функции
include $_SERVER['DOCUMENT_ROOT']."/model/functions.php";
//Загружаем класс для работы с базой данных
include $_SERVER['DOCUMENT_ROOT']."/model/db.php";
//Загружаем реестр
include $_SERVER['DOCUMENT_ROOT']."/model/reestr.php";
//Сохранение данных запроса
r::g('Controller_Main')->setRequest($_REQUEST);
if ($type == 'ajax') {
r::g('Controller_Main')->ajax($_REQUEST['controller'], $_REQUEST['action'], $_REQUEST['send']);
} else {
r::g('Controller_Main')->page($_REQUEST['controller'], $_REQUEST['action'],$_REQUEST);
}
У нашего приложения будет 2 типа запросов: страницы и ajax запросы, оба типа обрабатываются в main контролере, соответствующими функциями.
//Настройки подключения к базе MySQl
define(«sql_user», «root»);//Логин
define(«sql_host», «localhost»);//Адрес сервера
define(«sql_pass», "");//Пароль
define(«sql_table», «loumvc»);//Таблица базы данных
define(«title», «Лёгкий MVC»);//Базовый тайтл
Классы будем подгружать при инициализации, с помощью автозагрузчика. Т.е при инициализации класса new Controller_main(), при условии что файл с таким классом не подключен, 'Controller_main' будет передано в нашу функцию загрузчик, а она разобъёт название класса по "_" и попытается подключить файл /сontroller/main.php. Если не удастся — перенаправит пользователя на 404 страницу. Объявление и подключения автозагручика, пропишем в файле /model/function.php, который мы подключили вручную.
<?php
function autoload($class_name) {
$class_name = mb_strtolower($class_name);
$arPath = explode("_", $class_name);
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/" .implode("/", $arPath) . ".php")) {
include_once($_SERVER['DOCUMENT_ROOT'] . "/" .implode("/", $arPath) . ".php");
}else{
//Не найден контроллер
r::g('Controller_main')->error404();
die();
}
}
//Прописываем автозагрузку классов по необходимости
spl_autoload_register('autoload');
//Простейший дебаг, принтит все переменные в него переданные
function pr() {
$args = func_get_args();
foreach ($args as $item) {
echo "<pre>";
print_r($item);
echo "</pre>";
}
}
//Логирование данных в базу
function dlog($name,$text){
if(!is_string($text)){
$text = print_r($text,true);
}
db::insertArr('log',array(
'name'=>$name,
'val'=>$text
),1);
}
Для работы с базой данных, класс со статическими функциями. Его можно будет использовать в любом месте программы. (Это сделано чтобы проще было использовать функцию логирования, обращаться к базе данных вне модели – не стоит.).
<?
class db{
//Соединение с БД
private static $connect = null;
//Последний добавленный id
private static $lastId = 0;
//Логирование
private static $log = 0;
//Запрос к базе данных
static function query($query,$skipLog=0){
if(self::$connect==null){
self::$connect = new mysqli(sql_host, sql_user, sql_pass, sql_table);
if(self::$connect->connect_errno){
die('База данных не доступна');
}
}
//При включённом логировании, id затрётся запписью лога, поэтому вручную его запишем и передадим
self::$lastId = 0;
$res = self::$connect->query($query);
$lastId = self::$connect->insert_id;
//При включённом логировании, логируем каждый запрос,исключая запись лога
if(!$skipLog && self::$log){
dlog('query',$query);
}
self::$lastId = $lastId;
return $res;
}
static function insertArr($table,$arr,$skipLog=0){
$sql = "INSERT INTO `$table` ";
//Экранируем переменные массива
foreach($arr as $key=>$val){
$val = self::$connect->mysqli_escape_string($val);
}
$sql .= "(`".implode("`,`",array_keys($arr))."`) VALUES ('".implode("','",array_values($arr))."')";
self::query($sql,$skipLog);
return self::insertId();
}
//Возвращает id последней добавленной записи
static function insertId(){
return self::$lastId;
}
static function insert($query){
self::query($query);
//Возвращает id последней добавленной записи
return self::insertId();
}
static function selectCell($query){
$row = self::selectRow($query);
if($row){
return reset($row);
}
}
static function selectRow($query){
$res = self::select($query);
if($res){
return reset($res);
}
}
static function select($query){
$res = self::query($query);
$mas = array();
if($res) {
while ($row = $res->fetch_assoc()){
if($row['ARRAY_KEY']){
$key = $row['ARRAY_KEY'];
unset($row['ARRAY_KEY']);
$mas[$key]=$row;
}else{
$mas[]=$row;
}
}
return $mas;
}
}
static function update($table,$mass,$id){
$sql = "UPDATE `$table` SET ";
foreach($mass as $key=>$it){
$it = self::$connect->mysqli_escape_string($it);
$sql .= " $key='$it',";
}
$sql = substr($sql, 0,-1);
$sql .= " where id = $id";
return self::query($sql);
}
}
Вместо
class db {
protected static $connect = null;
static function getConnect(){
if(self::connect == null){
require($_SERVER['DOCUMENT_ROOT'] . '/libs/DbSimple/Generic.php');
self::$connect = DbSimple_Generic::connect("mysql://" . sql_user . ":" . sql_pass . "@" . sql_host . "/" . sql_table);
if(!self::$connect){
die("Нет подключения к бд");
}
self::$connect->query("SET NAMES UTF8");
}
return self::$connect;
}
//Меняем функции query
static function query() {
$res = call_user_func_array(array(self::getConnect(), 'query'), func_get_args());
return $res;
}
//Меняем функции select
static function select() {
$res = call_user_func_array(array(self::getConnect(), 'select'), func_get_args());
return $res;
}
//По умолчанию вызовы пойдут в библиотеку
static function __callStatic($name, $arguments) {
if(method_exists(self, $name)){
return call_user_func_array(array(self, $name), $arguments);
}else{
return call_user_func_array(array(self::getConnect(), $name), func_get_args());
}
}
}
В приложении используется реестр — реализация паттерна singleton – одиночка, хранящий все инстансы ( инициализированные объекты классов ) всех моделей и контроллеров. Благодаря ему каждый класс в проекте будет инициализирован только один раз, и мы легко получим к нему доступ. Для этого обращаться к классам надо так:
r::g("Название класса")
Реализация реестра:
<?
//Реестр для сохранения инстансов объектов
class r {
//Объявление статической переменной доступной только в классе
protected static $instance = array();//Хранилище классов
//Функция получения класса
static function g($name){
if(self::$instance[$name]){
return self::$instance[$name];
}else{
//Создание и сохранение объекта класса
return self::add($name, new $name() );
}
}
//Сохранение объекта класса в хранилище
static function add($name,$val){
return self::$instance[$name] = $val;
}
}
Каждый контроллер унаследует базовый, в котором реализуем функцию выводящую представление
<?php
class controller_base{
function view($name,$args=null,$isBase=0){
ob_start();
if($isBase){
$tpl = $_SERVER['DOCUMENT_ROOT']. "/view/base/{$name}.php";
}else{
//Название вызывающего класса
$class = explode("_",get_class($this));
$tpl = $_SERVER['DOCUMENT_ROOT']. "/view/{$class[1]}/{$name}.php";
}
if(file_exists($tpl)){
if($args){
//Распаковка переданных в шаблонизатор переменных, в область видимости шаблона
extract($args);
}
include $tpl;
}else{
echo "no tpl {$class[1]}/{$name}";
}
return ob_get_clean();
}
Ещё в базовом контролере реализуем реестр для хранения заголовка страницы, с возможностью изменить его из любого контролера.
private static $title = '';
function getTitle(){
if(!self::$title){
//В конфиге прописан базовый
return title;
}
return self::$title;
}
function setTitle($title){
self::$title = $title;
}
//Сохранение исходного вида запроса к приложению
private static $request = null;
function setRequest($param){
self::$request = $param;
}
function getRequest(){
return self::$request;
}
Главный контролер, в него будут первично приходить все вызовы
class Controller_main extends Controller_base{
function __construct(){
$this->model = r::g('Model_main');
}
В нём, и каждом другом контроллере создаём ссылку, на его модель.Обработку запросов на построение страницы доверим функции page:
function page($controller,$action,$param){
//По умолчанию главный контроллер (индексная страница)
if(!$controller){
$controller = 'main';
}
// И метод main
if(!$action){
$action = 'main';
}
$method_name = "page_".$action;
$controller_name = 'Controller_'.$controller;
if(method_exists(r::g($controller_name), $method_name)){
//Создаётся центральная часть
ob_start();
r::g($controller_name)->{$method_name}($param);
$html = ob_get_clean();
//Выводится вся страница
echo $this->view('page',array(
'html' => $html,
'title' => $this->getTitle()
));
}else{
//Перебрасываем пользователя на страницу 404
$this->error404();
}
}
В ней проверятся существования запрошенного метода в запрошенном контроллере, ну и при попытке подключить несуществующий контролер, выпадет 404 (это мы устроили в загрузчике классов).
Тело страницы генерируется методами page_{action}. Так и визуально сразу видно, какие функции отвечают за генерацию странницы, и методы c именем dropDataBase – защищены от вызова извне. Пример такой функции:
function page_main($param){
echo $this->view('main');
}
<html>
<head>
<title> <?= $title ?> </title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="/s/js/base.js"></script>
<script src="/s/js/main.js"></script>
<script src="/s/js/employee.js"></script>
<link href="/s/css/index.css" rel="stylesheet">
</head>
<body>
<div class="menu">
<?= $this->widget_menu() ?>
</div>
<?= $html ?>
</body>
</html>
function widget_menu(){
//Достаём запрос, чтобы определит, где в меню мы находимся
$request = $this->getRequest();
//Из модели получаем структуру меню, с отмеченными пунктами, где мы
$menu = $this->model->getMenu($request['controller'],$request['action']);
//Строим меню и возвращаем
return $this->view('menu',[
'list'=>$menu
]);
}
Ajax запросы обрабатываются функцией:
function ajax($controller,$action,$send){
$method_name = "ajax_".$action;
$controller_name = 'Controller_'.$controller;
if(method_exists(r::g($controller_name), $method_name)){
$res = r::g($controller_name)->$method_name($send);
if($res){
echo "<ja>" . json_encode($res) . "</ja>";
}
}
}
Она работает аналогично page, за небольшим исключением:
- Ищет методы с приставкой ajax_
- Не буферизует вывод, а сразу выводит напечатанное ajax_{action} методом
- Если ajax_{action} метод возвращает данные — сериализует эти данные и обрамляя тегами ja, печатает.
Последнее нужно, если нам надо вернуть ещё и структурированные данные в javascript. В вызываемом ajax_{action} методе надо просто вернуть массив, а дальше функция Controller_main->ajax «сама разберётся», как передать данные фронтенду.
Теги ja нужны, чтобы легко разобрать ответ на фронтенде. Получим отдельно текст, отдельно сериализованный массив. На этом сэкономим трафика и CPU, тем, что не будем кодировать/раскодировать html страницу. И ещё защищены от ошибок, никакой лишний вывод не сломает нам фронтенд.
function ajax_add(){
echo $this->widgetAdd();
return ['status'=>1];
}
А страничный метод выглядит так:
function page_add(){
$this->setTitle('Добавление сотрудника');
echo $this->view('page_add',[
'html' => $this->widgetAdd()
]);
}
Виджет который используется в обоих случаях:
function widgetAdd(){
return $this->view('add',[
'divisions'=>$this->model->getDivisions()
]);
}
Настоятельно рекомендую называть шаблоны по имени той функции которую они исполняют, или того метода, в котором используются. И не только шаблоны, но и функции в js.
Получается: в javascript создаёте метод «addEmployee», он запрос посылает на «ajax_addEmployee», а в функции используется шаблон «addEmployee». Потом намного проще будет их искать. Захотите потом поправить шаблон, посмотрите в js название функции и сразу можете представление править, не надо в контроллере искать.
Немного о фронтенде:
Папка с скриптами будет похожа на папку контроллеров:
![](https://habrastorage.org/files/a59/9aa/44a/a599aa44a1204304bf06cfe1bef2a136.png)
Также базовый скрипт, который будут наследовать все контроллеры JS.
function base(){
this.post = function(method,send,callback){
var controller = this.url ;
var url = "/ajax/"+controller+"/"+method+"/";
return $.post(url,{
send : send
},function (re) {
var res = re.split('<ja>');
if (res[1] != undefined) {
var text = res[0];
res = res[1].split('</ja>');
text += res[1];
re = text;
var mas = $.parseJSON(res[0]);
} else {
var mas = {};
}
if (typeof (callback) == 'function') {
callback(re, mas)
}
})
}
this.createPop = function(title, html) {}
}
}base = new base();
this.createPop = function(title, html) {
//Создаём само окно, при помощи библиотеки jqary
var $win = $("<div/>", {
class: 'Pop'
});
//Создаём box c содержимым окна
var $box = $("<div/>", {
class: 'winBox'
});
//Верхняя часть окна,
var $top = $("<div/>", {
class: "winTop"
});
//Заголовок окна
var $title = $('<div/>', {
class: "winTitle",
html: title
});
//Заголовок будет внутри верхней части
$top.append($title);
//Создаём кнопку закрытия окна, то-же поместим в верхнюю часть
var $close = $("<div/>", {
class: 'winClose',
html: "x"
});
$top.append($close);
//И созадим на кнопке событие - реакцию на клик - закрытия окна
$close.on('click', function () {
$win.remove();
});
//Закрывает окошко
$win.close = function () {
$win.remove();
}
// Если содержимое null, создаст сообщение, о загрузке
if (html == null) {
//Можно сюда впихнуть лоадер, чтоб было веселее
html = "<div class='help'> Загрузка данных </div>";
}
//Вставляем в тело окна содержимое
$box.html(html);
//Вставляем в обёртку окна верхнюю часть и тело
$win.append($top);
$win.append($box);
//Создаём в обёртке функцию, обновления содержимого
$win.update = function (html) {
$box.html(html);
}
//ПРоверяем, есть ли уже на странице блок для окон
if ($('.BoxPop').length == 0) {
//Если нет - создадим его
$('body').append($('<div/>', {
class: 'BoxPop'
}));
}
//Вставляем обёртку в блок для окон, окно появится для пользователя
$('.BoxPop').append($win);
//Возвратим объект окна для дальнейшей работы
return $win;
}
Собственно наследование и использование ajax:
function employee(){
//Контроллер обработчик
this.url = "employee";
//Замыкание на себя
var self = this;
this.add = function(){
var $win = self.createPop('Добавление сотрудника',null);
self.post('add',{},function(re){
$win.update(re);
})
}
}
employee.prototype = base;
employee = new employee();
И ещё, дабы защитить свой проект от доступа к скриптам, в папки controller, model и. view надо положить .htacess с содержанием:
Deny from all
Используя данный шаблон, вы сможете реализовать большинство задач, для веб приложений. И главное, любой другой программист, знакомый с MVC, сможет легко разобраться в вашем проекте.
> Весь исходный код
Комментарии (55)
lair
16.02.2017 15:02+9(омг, нет, только не опять)
В архитектуре MVC важно, что компоненты ничего не знают друг о друге, а потому – независимы.
То есть контроллер ничего не знает ни о модели, ни о представлении, а представлении — ничего не знает о модели? Хм… а кто же знает, какое представление показать?
Каждый контроллер унаследует базовый, в котором реализуем функцию выводящую представление
… а говорите — контроллер ничего не знает о представлении. Как минимум, знает имя.
В нём, и каждом другом контроллере создаём ссылку, на его модель.
… и про модель контроллер знает.
Я предлагаю в качестве архитектуры простого приложения использовать:
А почему вы в качестве архитектуры показываете структуру файлов?
r::g
Эээ, что?
Статья ориентирована на новичков, т.е. ничего нового в ней нет, просто несколько идей собраны в рабочий проект, решающий большинство задач.
"Рабочий проект" с обращениями в БД без параметров? Спасибо, нет.
В приложении используется реестр — реализация паттерна singleton – одиночка, хранящий все инстансы ( инициализированные объекты классов ) всех моделей и контроллеров.
Реестр и синглтон — это два разных паттерна. Вам, кстати, никогда не говорили, что синглтон — это антипаттерн? Ну и почему у вас модели — это синглтоны?
Ещё в базовом контролере реализуем реестр для хранения заголовка страницы, с возможностью изменить его из любого контролера.
Но зачем?
//Сохранение исходного вида запроса к приложению private static $request = null;
Я так понимаю, никакой многопоточности быть не может?
В общем, как обычно — зачем? Зачем писать свой велосипед очередной раз? Нет MVC-фреймворков?
http3
17.02.2017 12:07-2Вам, кстати, никогда не говорили, что синглтон — это антипаттерн?
Поцчему? :)
Я так понимаю, никакой многопоточности быть не может?
Покажите мне сайты с многопоточностью :)
Нет MVC-фреймворков?
Они унылы. :)
А фреймворк может быть не MVC? :)lair
17.02.2017 12:12Поцчему?
Потому что (а) это разделенное состояние и (б) это статическая зависимость.
Покажите мне сайты с многопоточностью
Любой сайт, на котором больше одного одновременного посетителя.
А фреймворк может быть не MVC?
Легко.
retran
17.02.2017 13:47Любой сайт, на котором больше одного одновременного посетителя.
Они запускают несколько процессов и реактивно процессят запросы с асинхронным io. Общий стейт кладут в memcached. Нету там многопоточности на уровне «прикладного» кода.lair
17.02.2017 13:50Они запускают несколько процессов и реактивно процессят запросы с асинхронным io. Общий стейт кладут в memcached.
Ты вот прямо за все сайты в интернете готов сказать?
(Другое дело, что я тоже не вполне прав, и не на любом сайте есть многопоточность, где-то есть многопроцессность, и поэтому нет конкурентности, это да. Но, как мы оба знаем, многопоточные сайты тоже вполне себе существуют.)
http3
17.02.2017 16:58-2Потому что (а) это разделенное состояние и (б) это статическая зависимость.
а.1) возможно необходимое именно статичное состояние
а.2) есть вариант с пулом одиночек
б.1) чем плоха статика?
б.2) статика есть и на фреймворках, была где-то статистическая информация о ее проценте в них
Любой сайт, на котором больше одного одновременного посетителя.
Шта?
Это любой сайт автоматом становится многопоточным? :)
Хотя ниже Вы все же признали, что были не правы и примеры сайтов с многопоточностью привести не смогли :)
Легко.
Например?
П.С.
У минусующего быдла такие же аргументы?lair
17.02.2017 17:24возможно необходимое именно статичное состояние
Ну так и антипаттерны иногда нужны. Просто надо осознавать, что это антипаттерны.
чем плоха статика?
Невозможностью подмены.
статика есть и на фреймворках, была где-то статистическая информация о ее проценте в них
Ну, это личное дело каждого фреймворка.
Шта? Это любой сайт автоматом становится многопоточным?
Я уже уточнил ниже. Формально, не любой. Но вот любой сайт на asp.net — многопоточен.
Например?
asp.net webforms, asp.net webapi.
lair
17.02.2017 17:54Но вот любой сайт на asp.net — многопоточен.
Для зануд: на asp.net под IIS. За другие хосты не отвечаю.
SerafimArts
17.02.2017 18:34+1Невозможностью подмены.
Не факт. В Laravel, например, есть такая штука как "фасады", которые являются прокси на объект контейнера, получаемые через сервислокацию. А это значит что подмена более чем практикуема при условии сохранении интерфейса элемента контейнера.
Например?
Любой билд TS php многопоточный и требуется для работы вместе с Apache в режиме mod_apache (как модуль). В противоположность оному существуют NTS билды, которые используются в nginx и iis, например в cgi, fcgi (fpm) режимах. В этом случае fpm процесс спамит процесс под запрос (или всё же процесс + несколько тредов на процесс? Я не уверен).
Так что пример — 80% сайтов и ынтернетах.
lair
17.02.2017 18:46В Laravel, например, есть такая штука как "фасады", которые являются прокси на объект контейнера, получаемые через сервислокацию.
Ключевое слово: сервис-локатор. Сервис, полученный через сервис-локатор — это не синглтон, даже если внутри сервис-локатора хранится ровно один экземпляр сервиса.
(я могу тут порассуждать еще почему сервис-локатор — это тоже антипаттерн, но не буду)
SerafimArts
17.02.2017 19:18+1Ключевое слово: сервис-локатор. Сервис, полученный через сервис-локатор — это не синглтон, даже если внутри сервис-локатора хранится ровно один экземпляр сервиса.
Ну так доступ же к нему полностью статический и глобальный (привет синглтоны), магия и всякая хурма.
P.S. Оффтоп: Антипаттерн, никто не спорит. Но, признаться, я не вспомню случаев когда он мне мешался или становился камнем преткновения на практике.
lair
17.02.2017 19:28Ну так доступ же к нему полностью статический и глобальный (привет синглтоны), магия и всякая хурма.
Доступ к сервис-локатору, да. Который обычно синглтон и так далее, см. выше. Но сервис, полученный из сервис-локатора — не синглтон. Это такие вот мелочи, которые сильно меняют поведение системы.
признаться, я не вспомню случаев когда он мне мешался или становился камнем преткновения на практике.
А мне регулярно приходится разрывать зависимости-через-синглтон потому что или с видимостью проблемы, или протестировать не удается, или не кастомизируется в достаточной мере.
SerafimArts
17.02.2017 21:33+1А мне регулярно приходится разрывать зависимости-через-синглтон потому что или с видимостью проблемы, или протестировать не удается, или не кастомизируется в достаточной мере.
Я про сервис-локацию, а не синглтон. Синглтоны — это вообще открытое зло, единственное место, где он действительно необходим — это контейнер приложения, имхо.
lair
17.02.2017 21:34… а с сервис-локацией я как встретил один раз ситуацию, когда результат тестов зависел от порядка их выполнения (а параллельно они вообще отказывались работать) — так и стал ее избегать.
SerafimArts
17.02.2017 23:03+1Но это не проблема сервис-локации же? Это проблема, подозреваю, рантайма контейнера, когда он билдится и фризится и после этого подмена элемента контейнера проблематична.
lair
17.02.2017 23:25Нет, это проблема ровно сервис-локатора. При параллельном использовании мы, очевидно, не можем выдать две имплементации одной и той же зависимости, а при последовательном — одна ошибка в инициализации/деинициализации приводит к неочевидному поведению.
http3
17.02.2017 19:07-3Просто надо осознавать, что это антипаттерны.
Вообще осознавать нужно всегда, что делаешь :)
Невозможностью подмены.
Придется смириться :)
Ну или подсовывать другую обертку, которая использует другую статику. :)
Ну, это личное дело каждого фреймворка.
Но на автора все набросились и поставили в пример фреймворки…
Но вот любой сайт на asp.net — многопоточен.
Это как-то относиться к PHP? :)
asp.net webforms, asp.net webapi.
Там вывод шаблона и логика в одном файле? :)lair
17.02.2017 19:12Придется смириться
Ну и зачем, если есть паттерны, которые позволяют без этого обойтись.
Но на автора все набросились и поставили в пример фреймворки…
Никто не идеален. Это не отменяет того, что когда пишешь сам, ошибок надо избегать.
Это как-то относиться к PHP?
Нет. Зато относится к сайтам. Вы же не просили показать вам сайты на PHP с многопоточностью.
Там вывод шаблона и логика в одном файле?
Нет.
http3
18.02.2017 10:26-2Ну и зачем, если есть паттерны, которые позволяют без этого обойтись.
Например?
Что мне даст ленивую инициализацию и один и тот же экземпляр?
Кстати, если приложение одно, то можно поручить хранить экземпляры ему. :)
Нет. Зато относится к сайтам. Вы же не просили показать вам сайты на PHP с многопоточностью.
Включайте мозг.
Нет.
Ну, поздравляю. Они изобрели MVC. (Да и Вы опять вспоминаете ASP в ветке PHP :))
А то, что его нет в названии — плевать.
Это для лохов придумали asp.net mvc. :)lair
18.02.2017 11:48Что мне даст ленивую инициализацию и один и тот же экземпляр?
Dependency Injection.
Ну, поздравляю. Они изобрели MVC. [...] А то, что его нет в названии — плевать.
Но нет. Во-первых, не всякое Separated Presentation — MVC. Во-вторых, в вебформзах даже separated presentation будет только если его тщательно написать руками, а обычно там лапша. А в-третьих, asp.net webapi — вообще не UI-фреймворк.
(в-четвертых, MVC старше всего asp.net вместе взятого, если не asp вообще, так что говорить, что они изобрели MVC — это смешно, конечно)
Это для лохов придумали asp.net mvc
Вы, судя по вопросам, немножко не знаете разницы между asp.net webforms и asp.net mvc, так что я бы, на вашем месте, воздержался...
http3
18.02.2017 13:34-2Dependency Injection.
Да, норм. :)
Это примерно то:Кстати, если приложение одно, то можно поручить хранить экземпляры ему. :)
Во-первых, не всякое Separated Presentation — MVC.
Это все условности.
Главное сам принцип разделения.говорить, что они изобрели MVC — это смешно, конечно
Это типа выражение: «Поздравляю, ты изобрел велосипед (колесо) :)»Вы, судя по вопросам, немножко не знаете разницы между asp.net webforms и asp.net mvc
Не знаю.
Поэтому не пишите в теме PHP информацию, подразумевая ASP, но не указывая этого явно. :)
П.С.
Пока ожидал возможности отправить свой ответ, написал свою реализацию DIC. :)
Проверил хабр, нашел статью https://habrahabr.ru/post/183658/
Написал примерно то же. :)
П.П.С.
А как хабровчане относятся к хранению экземпляров в статической переменной (не статическом члене класса):
function injected($pid = 0) { static $instances = array(); if(array_key_exists($pid, $instances)) { $instance = $instances[$pid]; } else { $instance = $this->get('injected'); $instances[$pid] = $instance; } return $instance; }
lair
18.02.2017 13:36Главное сам принцип разделения.
Сам принцип разделения называется Separated Presentation. MVC называется конкретная реализация.
Поэтому не пишите в теме PHP информацию, подразумевая ASP, но не указывая этого явно
Я как-нибудь разберусь, что мне писать, спасибо.
http3
18.02.2017 14:42MVC называется конкретная реализация.
Хм, у этой реализации у самой 100500 реализаций :)
Просто, говоря MVC, обычно и понимается Separated Presentation, кмк.
Я как-нибудь разберусь, что мне писать, спасибо.
Сорри.
В первоначальном варианте было с «пожалуйста». :)
Но браузер покрашился и коммент удалось не полностью воссоздать :)
Просто Вы внесли непонятки упоминанием фич ASP в контексте PHP без упоминания самого ASP. :)lair
18.02.2017 15:10-1Просто, говоря MVC, обычно и понимается Separated Presentation, кмк.
Вам кажется. Это понимание как минимум неточно, как максимум — безграмотно.
babylon
18.02.2017 19:25-1MVC это всего лишь парадигма, обозначающая наличие в ней модели вида и контроллера. Контролер знает про вид и про модель, но модели и виды могут меняться.Иметь разные реализации. Если заменим/добавим V на S сервис, то будет MVCS. Например, сервис с базой данных. Была SQL поменяли на NoSQL вид и контроллер от этого не поменялся, но сервисы, получающие данные поменялись.
lair
18.02.2017 21:21+1Если заменим/добавим V на S сервис, то будет MVCS.
Можно ссылку на источник?
Например, сервис с базой данных.
С точки зрения "канонического" MVC это скрыто в модели, никакой "дополнительной буквы" это не приносит.
ellrion
16.02.2017 15:06+5И ещё, дабы защитить свой проект от доступа к скриптам, в папки controller, model и. view надо положить .htacess с содержанием: Deny from all
Нет для этого Выделите паблик директорию, которая будет рутовой для вебсервера и что бы все файлы (внутренний код приложения) лежали выше нее. Это единственный нормальный вариант.
Вообще всё очень спорно и не понятно зачем оно на хабре.
ellrion
16.02.2017 15:16+2Реврайты в .htaccess?! А если у меня nginx+fpm (вернее не если а только так). А если мне нужна иная структура например
resources/{resource}/subresources
. А еще разная обработка для get И post. Ну есть же концепция роутинга и без нее никуда.babylon
16.02.2017 16:46+2У автора своеобразное
непонимание MVC. Да и всего остального. Странно… но на хабре были на эту тему статьи и получше. Ни одной идеи которую хотелось отметить. Полный трэш — в корзину!
smple
16.02.2017 15:23+4новичкам в php я бы посоветовал http://www.phptherightway.com/
«вот это»(то что в статье) я бы не советовал, кмк.
ellrion
16.02.2017 15:23+2Огромную роль в современном php комьюнити играет PSR и composer. Я очень советую воспользоваться ими. Хотя бы PSR-2 и PSR-4
SerafimArts
16.02.2017 15:34Используя данный шаблон, вы сможете реализовать большинство задач, для веб приложений. И главное, любой другой программист, знакомый с MVC, сможет легко разобраться в вашем проекте.
Огонь! Невероятно! Но есть одна загвоздка… Вы в своей статье описали почти что классический MVP
iit
16.02.2017 16:00+4Я очень надеюсь что эта статья такой очень тонкий тролинг, такой код я уже много где видел и даже поддерживал а потом с матами рефакторил, я не буду описывать все минусы сего с позволения так сказать «творчества», но вот основные:
1) стандарты и рекомендации — нет не слышал! — php-fig.org
2) роутинг прибит намертво через .htaccess — за это я готов руки оторвать
3) автолоад — что это?
4) переменные и даже классы из одной буквы — замечательно!
5) кастомный шаблонизатор — вообще мечта!
6) mysqli — хорошо хоть не mysql
7) конфиги бд через define — ня!
Для начинающего в 2005-2009 году который изучает php две недели — это проект еще нормален, но для статьи на хабре в 2017 году — это перебор.JhaoDa
16.02.2017 16:04Подобные статьи появляются на хабре минимум раз в квартал, странный троллинг...
iit
16.02.2017 16:12ах да — Хабр уже не торт…
<sarcasm> и у меня до сих пор нет инвайта! </sarcasm>Skerrigan
17.02.2017 06:16Так покажите «мастер-класс». Потом будете тех, кто не умеет, посылать на свою статью. Или опасаетесь, что вас так же камнями закидают (ибо всегда будут не согласные)?
iit
17.02.2017 11:37Я вчера над этим уже задумался после этого поста, думаю как закончу текущие проекты — попробую собрать простое mvc приложение в виде обучалки. Если будут неточности — пишите на камне что не так и можете кидать =)
iborzenkov
16.02.2017 16:59Злые вы, у него хотя бы классы есть :)
Хоть какая-то структура, логика, фронт-контроллер базовый (index.php) и роутинг уровня кодеигнайтера
Хоть заюзал вполне адекватную либу для базы данных, хоть и написал сначало свой велосипед.
Ну хоть уровень выше чем уроки Попова :) Мелкие студии в начале 2000-х и хуже творили.
iit
16.02.2017 17:41Думаю большинство людей писали нечто даже похуже этого, и я не исключение.
При этом я считаю свой ACL для laravel 5.2 убогим недоработанным костылесипедом, только потому что работает через middleware a не через guard который появился в 5.3 и пока не допилю его в своем приватном gitlab — публично его мне совесть не позволит выложить, хотя он уже используется в довольно крупном проекте в течении года и вполне себе работоспособен.
И тут я вижу от взрослого и вроде адекватного, владеющим английским человека статью на хабре про «ЭТО»
Как говорил мой бывший коллега-питонст, который улетел на работу в чехию:
Какая криворукая тварь это написала!
У меня не просто бомбит на этот код, меня полностью и окончательно разорвало!
tytar
17.02.2017 04:07+1НЛО, пожалуйста, прекрати выдавать приглашения из песочницы за такие статьи! Простите, не сдержался.
Sky4eg
Вы серьезно? PSR — нет, не слышал. Namespace — зачем, лучше напишем свой загрузчик классов. Название классов одной буквой
вы уже определитесь со стилем написания
Это явное не best practice для новичков, а скорее наоборот.