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

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

Проект начинается со структуры. Простая и логичная структура – залог успеха работоспособности проекта и того, что кто-то кроме создателя сможет в нём разобраться.

Подробно, что такое MVC, можно ознакомиться тут и тут.

А если кратко — это разделение кода на Модель(логику), Представление и Контроллер.

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

Модель содержит в себе логику и данные приложения. Некий Сан Саныч, что у него контроллер закажет, то он и со склада достанет, по необходимости доработав напильником. Модель взаимодействует с базой данных, и обрабатывает результат. Важно обрабатывать данные в модели, на эту тему есть статья https://habrahabr.ru/post/175465/ . Если кратко, когда логика в моделях, её проще пере использовать как внутри проекта, так и переносить в другие.

Представление – ну сущий продавец. Не слишком смышлёный и знает только то, что доверил контроллер. Задача представления – только представить информацию. В него входит вёрстка (шаблоны).

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

Плюсы такой архитектуры: можно тестировать компоненты по отдельности – проще искать баги, поправить вёрстку на сайте сможет и дизайнер, логику можно будет перенести и в другой проект.

Я предлагаю в качестве архитектуры простого приложения использовать:


Контроллеры отдельно, модели отдельно, представление сложено по папкам (в папке 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);
    }
}


Вместо великолепного самописного класса велосипеда, можно использовать реализацию с обёрткой на плагин, например db simple.

Код
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>


Код виджета меню в main контроллере, используемого в основном шаблоне
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 название функции и сразу можете представление править, не надо в контроллере искать.

Немного о фронтенде:


Папка с скриптами будет похожа на папку контроллеров:


Также базовый скрипт, который будут наследовать все контроллеры 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();

Код функции создающей окошки, дешёвый аналог fancybox, с методами и плюшками.
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)


  1. Sky4eg
    16.02.2017 14:56
    +11

    Вы серьезно? PSR — нет, не слышал. Namespace — зачем, лучше напишем свой загрузчик классов. Название классов одной буквой

    function page_add(){
    

    function widgetAdd(){
    

    вы уже определитесь со стилем написания

    Это явное не best practice для новичков, а скорее наоборот.


  1. lair
    16.02.2017 15:02
    +9

    (омг, нет, только не опять)


    В архитектуре MVC важно, что компоненты ничего не знают друг о друге, а потому – независимы.

    То есть контроллер ничего не знает ни о модели, ни о представлении, а представлении — ничего не знает о модели? Хм… а кто же знает, какое представление показать?


    Каждый контроллер унаследует базовый, в котором реализуем функцию выводящую представление

    … а говорите — контроллер ничего не знает о представлении. Как минимум, знает имя.


    В нём, и каждом другом контроллере создаём ссылку, на его модель.

    … и про модель контроллер знает.


    Я предлагаю в качестве архитектуры простого приложения использовать:

    А почему вы в качестве архитектуры показываете структуру файлов?


    r::g

    Эээ, что?


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

    "Рабочий проект" с обращениями в БД без параметров? Спасибо, нет.


    В приложении используется реестр — реализация паттерна singleton – одиночка, хранящий все инстансы ( инициализированные объекты классов ) всех моделей и контроллеров.

    Реестр и синглтон — это два разных паттерна. Вам, кстати, никогда не говорили, что синглтон — это антипаттерн? Ну и почему у вас модели — это синглтоны?


    Ещё в базовом контролере реализуем реестр для хранения заголовка страницы, с возможностью изменить его из любого контролера.

    Но зачем?


    //Сохранение исходного вида запроса к приложению
    private static $request = null;

    Я так понимаю, никакой многопоточности быть не может?


    В общем, как обычно — зачем? Зачем писать свой велосипед очередной раз? Нет MVC-фреймворков?


    1. retran
      16.02.2017 17:23
      +1

      Это же PHP, там в принципе нет настоящей многопоточности (я знаю про pthreads).


      1. lair
        16.02.2017 17:27
        +1

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


    1. http3
      17.02.2017 12:07
      -2

      Вам, кстати, никогда не говорили, что синглтон — это антипаттерн?

      Поцчему? :)

      Я так понимаю, никакой многопоточности быть не может?

      Покажите мне сайты с многопоточностью :)

      Нет MVC-фреймворков?

      Они унылы. :)
      А фреймворк может быть не MVC? :)


      1. lair
        17.02.2017 12:12

        Поцчему?

        Потому что (а) это разделенное состояние и (б) это статическая зависимость.


        Покажите мне сайты с многопоточностью

        Любой сайт, на котором больше одного одновременного посетителя.


        А фреймворк может быть не MVC?

        Легко.


        1. retran
          17.02.2017 13:47

          Любой сайт, на котором больше одного одновременного посетителя.


          Они запускают несколько процессов и реактивно процессят запросы с асинхронным io. Общий стейт кладут в memcached. Нету там многопоточности на уровне «прикладного» кода.


          1. lair
            17.02.2017 13:50

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

            Ты вот прямо за все сайты в интернете готов сказать?


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


            1. retran
              17.02.2017 13:56

              «Они» = php/python/ruby/nodejs-разработчики. Мне стоило уточнить.


              1. lair
                17.02.2017 14:01

                … а все потому, что неявный контекст — зло.


        1. http3
          17.02.2017 16:58
          -2

          Потому что (а) это разделенное состояние и (б) это статическая зависимость.

          а.1) возможно необходимое именно статичное состояние
          а.2) есть вариант с пулом одиночек
          б.1) чем плоха статика?
          б.2) статика есть и на фреймворках, была где-то статистическая информация о ее проценте в них

          Любой сайт, на котором больше одного одновременного посетителя.

          Шта?
          Это любой сайт автоматом становится многопоточным? :)
          Хотя ниже Вы все же признали, что были не правы и примеры сайтов с многопоточностью привести не смогли :)

          Легко.

          Например?

          П.С.
          У минусующего быдла такие же аргументы?


          1. lair
            17.02.2017 17:24

            возможно необходимое именно статичное состояние

            Ну так и антипаттерны иногда нужны. Просто надо осознавать, что это антипаттерны.


            чем плоха статика?

            Невозможностью подмены.


            статика есть и на фреймворках, была где-то статистическая информация о ее проценте в них

            Ну, это личное дело каждого фреймворка.


            Шта? Это любой сайт автоматом становится многопоточным?

            Я уже уточнил ниже. Формально, не любой. Но вот любой сайт на asp.net — многопоточен.


            Например?

            asp.net webforms, asp.net webapi.


            1. lair
              17.02.2017 17:54

              Но вот любой сайт на asp.net — многопоточен.

              Для зануд: на asp.net под IIS. За другие хосты не отвечаю.


            1. SerafimArts
              17.02.2017 18:34
              +1

              Невозможностью подмены.

              Не факт. В Laravel, например, есть такая штука как "фасады", которые являются прокси на объект контейнера, получаемые через сервислокацию. А это значит что подмена более чем практикуема при условии сохранении интерфейса элемента контейнера.


              Например?

              Любой билд TS php многопоточный и требуется для работы вместе с Apache в режиме mod_apache (как модуль). В противоположность оному существуют NTS билды, которые используются в nginx и iis, например в cgi, fcgi (fpm) режимах. В этом случае fpm процесс спамит процесс под запрос (или всё же процесс + несколько тредов на процесс? Я не уверен).


              Так что пример — 80% сайтов и ынтернетах.


              1. lair
                17.02.2017 18:46

                В Laravel, например, есть такая штука как "фасады", которые являются прокси на объект контейнера, получаемые через сервислокацию.

                Ключевое слово: сервис-локатор. Сервис, полученный через сервис-локатор — это не синглтон, даже если внутри сервис-локатора хранится ровно один экземпляр сервиса.


                (я могу тут порассуждать еще почему сервис-локатор — это тоже антипаттерн, но не буду)


                1. SerafimArts
                  17.02.2017 19:18
                  +1

                  Ключевое слово: сервис-локатор. Сервис, полученный через сервис-локатор — это не синглтон, даже если внутри сервис-локатора хранится ровно один экземпляр сервиса.

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


                  P.S. Оффтоп: Антипаттерн, никто не спорит. Но, признаться, я не вспомню случаев когда он мне мешался или становился камнем преткновения на практике.


                  1. lair
                    17.02.2017 19:28

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

                    Доступ к сервис-локатору, да. Который обычно синглтон и так далее, см. выше. Но сервис, полученный из сервис-локатора — не синглтон. Это такие вот мелочи, которые сильно меняют поведение системы.


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

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


                    1. SerafimArts
                      17.02.2017 21:33
                      +1

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

                      Я про сервис-локацию, а не синглтон. Синглтоны — это вообще открытое зло, единственное место, где он действительно необходим — это контейнер приложения, имхо.


                      1. lair
                        17.02.2017 21:34

                        … а с сервис-локацией я как встретил один раз ситуацию, когда результат тестов зависел от порядка их выполнения (а параллельно они вообще отказывались работать) — так и стал ее избегать.


                        1. SerafimArts
                          17.02.2017 23:03
                          +1

                          Но это не проблема сервис-локации же? Это проблема, подозреваю, рантайма контейнера, когда он билдится и фризится и после этого подмена элемента контейнера проблематична.


                          1. lair
                            17.02.2017 23:25

                            Нет, это проблема ровно сервис-локатора. При параллельном использовании мы, очевидно, не можем выдать две имплементации одной и той же зависимости, а при последовательном — одна ошибка в инициализации/деинициализации приводит к неочевидному поведению.


                            1. SerafimArts
                              18.02.2017 00:54

                              Да, верно, не подумал. Полностью согласен.


                              UPD. Но есть же бектрейс… xD


            1. http3
              17.02.2017 19:07
              -3

              Просто надо осознавать, что это антипаттерны.

              Вообще осознавать нужно всегда, что делаешь :)

              Невозможностью подмены.

              Придется смириться :)
              Ну или подсовывать другую обертку, которая использует другую статику. :)

              Ну, это личное дело каждого фреймворка.

              Но на автора все набросились и поставили в пример фреймворки…

              Но вот любой сайт на asp.net — многопоточен.

              Это как-то относиться к PHP? :)

              asp.net webforms, asp.net webapi.

              Там вывод шаблона и логика в одном файле? :)


              1. lair
                17.02.2017 19:12

                Придется смириться

                Ну и зачем, если есть паттерны, которые позволяют без этого обойтись.


                Но на автора все набросились и поставили в пример фреймворки…

                Никто не идеален. Это не отменяет того, что когда пишешь сам, ошибок надо избегать.


                Это как-то относиться к PHP?

                Нет. Зато относится к сайтам. Вы же не просили показать вам сайты на PHP с многопоточностью.


                Там вывод шаблона и логика в одном файле?

                Нет.


                1. http3
                  18.02.2017 10:26
                  -2

                  Ну и зачем, если есть паттерны, которые позволяют без этого обойтись.

                  Например?
                  Что мне даст ленивую инициализацию и один и тот же экземпляр?

                  Кстати, если приложение одно, то можно поручить хранить экземпляры ему. :)

                  Нет. Зато относится к сайтам. Вы же не просили показать вам сайты на PHP с многопоточностью.

                  Включайте мозг.

                  Нет.

                  Ну, поздравляю. Они изобрели MVC. (Да и Вы опять вспоминаете ASP в ветке PHP :))
                  А то, что его нет в названии — плевать.
                  Это для лохов придумали asp.net mvc. :)


                  1. 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, так что я бы, на вашем месте, воздержался...


                    1. http3
                      18.02.2017 13:34
                      -2

                      Dependency 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;
                      }
                      


                      1. lair
                        18.02.2017 13:36

                        Главное сам принцип разделения.

                        Сам принцип разделения называется Separated Presentation. MVC называется конкретная реализация.


                        Поэтому не пишите в теме PHP информацию, подразумевая ASP, но не указывая этого явно

                        Я как-нибудь разберусь, что мне писать, спасибо.


                        1. http3
                          18.02.2017 14:42

                          MVC называется конкретная реализация.

                          Хм, у этой реализации у самой 100500 реализаций :)
                          Просто, говоря MVC, обычно и понимается Separated Presentation, кмк.

                          Я как-нибудь разберусь, что мне писать, спасибо.

                          Сорри.
                          В первоначальном варианте было с «пожалуйста». :)
                          Но браузер покрашился и коммент удалось не полностью воссоздать :)
                          Просто Вы внесли непонятки упоминанием фич ASP в контексте PHP без упоминания самого ASP. :)


                          1. lair
                            18.02.2017 15:10
                            -1

                            Просто, говоря MVC, обычно и понимается Separated Presentation, кмк.

                            Вам кажется. Это понимание как минимум неточно, как максимум — безграмотно.


                          1. babylon
                            18.02.2017 19:25
                            -1

                            MVC это всего лишь парадигма, обозначающая наличие в ней модели вида и контроллера. Контролер знает про вид и про модель, но модели и виды могут меняться.Иметь разные реализации. Если заменим/добавим V на S сервис, то будет MVCS. Например, сервис с базой данных. Была SQL поменяли на NoSQL вид и контроллер от этого не поменялся, но сервисы, получающие данные поменялись.


                            1. lair
                              18.02.2017 21:21
                              +1

                              Если заменим/добавим V на S сервис, то будет MVCS.

                              Можно ссылку на источник?


                              Например, сервис с базой данных.

                              С точки зрения "канонического" MVC это скрыто в модели, никакой "дополнительной буквы" это не приносит.


                              1. babylon
                                19.02.2017 23:45
                                -2

                                Ты свои проповеди в д/c читай. Сервис должен стоять перед моделью, между ней и контроллером.


                                1. lair
                                  19.02.2017 23:51
                                  +1

                                  Ну так дайте ссылку на источник, который говорит, что в MVC между моделью и контроллером стоит сервис.


  1. ellrion
    16.02.2017 15:06
    +5

    И ещё, дабы защитить свой проект от доступа к скриптам, в папки controller, model и. view надо положить .htacess с содержанием: Deny from all

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


    Вообще всё очень спорно и не понятно зачем оно на хабре.


  1. NorthDakota
    16.02.2017 15:13
    +8

    Шёл 2017 год…


    1. HunterNNm
      16.02.2017 15:15
      +2

      … еще использовали

      <?
      


  1. ellrion
    16.02.2017 15:16
    +2

    Реврайты в .htaccess?! А если у меня nginx+fpm (вернее не если а только так). А если мне нужна иная структура например resources/{resource}/subresources. А еще разная обработка для get И post. Ну есть же концепция роутинга и без нее никуда.


    1. babylon
      16.02.2017 16:46
      +2

      У автора своеобразное непонимание MVC. Да и всего остального. Странно… но на хабре были на эту тему статьи и получше. Ни одной идеи которую хотелось отметить. Полный трэш — в корзину!


      1. ellrion
        16.02.2017 17:05
        +1

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


  1. smple
    16.02.2017 15:23
    +4

    новичкам в php я бы посоветовал http://www.phptherightway.com/

    «вот это»(то что в статье) я бы не советовал, кмк.


  1. ellrion
    16.02.2017 15:23
    +2

    Огромную роль в современном php комьюнити играет PSR и composer. Я очень советую воспользоваться ими. Хотя бы PSR-2 и PSR-4


  1. SerafimArts
    16.02.2017 15:34

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

    Огонь! Невероятно! Но есть одна загвоздка… Вы в своей статье описали почти что классический MVP


  1. 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 году — это перебор.


    1. JhaoDa
      16.02.2017 16:04

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


      1. iit
        16.02.2017 16:12

        ах да — Хабр уже не торт…

        <sarcasm> и у меня до сих пор нет инвайта! </sarcasm>


        1. Skerrigan
          17.02.2017 06:16

          Так покажите «мастер-класс». Потом будете тех, кто не умеет, посылать на свою статью. Или опасаетесь, что вас так же камнями закидают (ибо всегда будут не согласные)?


          1. iit
            17.02.2017 11:37

            Я вчера над этим уже задумался после этого поста, думаю как закончу текущие проекты — попробую собрать простое mvc приложение в виде обучалки. Если будут неточности — пишите на камне что не так и можете кидать =)


            1. Skerrigan
              17.02.2017 13:33

              Ок, тогда как минимум я буду ждать. Мне как раз стоит поучиться.


  1. iborzenkov
    16.02.2017 16:59

    Злые вы, у него хотя бы классы есть :)
    Хоть какая-то структура, логика, фронт-контроллер базовый (index.php) и роутинг уровня кодеигнайтера
    Хоть заюзал вполне адекватную либу для базы данных, хоть и написал сначало свой велосипед.
    Ну хоть уровень выше чем уроки Попова :) Мелкие студии в начале 2000-х и хуже творили.


  1. iit
    16.02.2017 17:41

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

    При этом я считаю свой ACL для laravel 5.2 убогим недоработанным костылесипедом, только потому что работает через middleware a не через guard который появился в 5.3 и пока не допилю его в своем приватном gitlab — публично его мне совесть не позволит выложить, хотя он уже используется в довольно крупном проекте в течении года и вполне себе работоспособен.

    И тут я вижу от взрослого и вроде адекватного, владеющим английским человека статью на хабре про «ЭТО»

    Как говорил мой бывший коллега-питонст, который улетел на работу в чехию:

    Какая криворукая тварь это написала!
    У меня не просто бомбит на этот код, меня полностью и окончательно разорвало!


  1. Skull
    16.02.2017 19:00
    +1

    Такое ощущение что я уже читал эту статью во времена PHP 5.2


  1. nskforward
    17.02.2017 00:10

    Ценность статьи есть — пример новичкам как делать НЕ надо


  1. tytar
    17.02.2017 04:07
    +1

    НЛО, пожалуйста, прекрати выдавать приглашения из песочницы за такие статьи! Простите, не сдержался.


  1. maxru
    17.02.2017 17:46
    -2

    Я джва года ждал такой пост.