Единая точка входа в web-приложение

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

Приведенные примеры актуальны для конфигурации web-сервера apache.

Концепция единой точки входа в реализации сводится к тому, что необходимо указать web-серверу перенаправлять все поступающие к нему запросы к файлу, который будет нашей единственной точкой входа, пусть к примеру это будет файл index.php в корневой директории приложения. Для этих целей у web-сервера Apache есть директива RewriteRule, находящаяся в модуле mod_rewrite. Синтаксис директивы следующий:

RewriteRule Шаблон Подстановка Флаги

Шаблон — это perl-совместимое регулярное выражение, которое применяется к текущему URL, причем под текущим подразумевается значение URL в момент применения этого правила. Этот URL не обязательно совпадает с первоначально запрошенным URL, потому что до этого момента возможно уже были применены другие правила к этому URL и соответственно преобразовали его.
Подстановка — это строка, которая будет заменять оригинальный URL, для которого есть совпадение Шаблону. Кроме текста в подстановке можно использовать много чего, но нас интересуют пока только лишь переменная сервера REQUEST_URI.
Из флагов воспользуемся лишь двумя — QSA и L. Первый указывает механизму преобразований на то, что нужно добавить, а не заменить, строку запроса из URL к существующей, в строке подстановки. Флаг L указывает серверу остановить процесс преобразования на этом месте и не применять больше никаких правил преобразований.
Учитывая все выше сказанное, допишем в файл .htaccess в корневой директории приложения следующую строку:

RewriteRule ^(.*)$ index.php?%{REQUEST_URI} [QSA,L]

Но не следует забывать о том, что кроме обращения непосредственно к скрипту, браузер будет запрашивать у сервера различные файлы ресурсов, такие как каскадные таблицы стилей, изображения, файлы со скриптами и т.п. Для того, чтобы сервер дал доступ браузеру к желаемому, нужно задать дополнительные условия директиве RewriteRule. Эти условия описываются директивой RewriteCond, которая определят когда следует делать преобразования в URL или когда необходимо оставить его без изменений.
Не вдаваясь глубоко в подробности, приведу несколько примеров, а более подробно об упомянутых директивах можно прочитать по ссылкам приведенным в конце статьи.

RewriteCond %{REQUEST_URI} !^\/resources/styles/(.*).css
RewriteCond %{REQUEST_URI} !^\/resources/images/(.*).png
RewriteCond %{REQUEST_URI} !^\/resources/images/(.*).jpg
RewriteCond %{REQUEST_URI} !^\/resources/lib/jquery/(.*).js

Смотря на приведенные строки, можно сразу заметить, что в них происходит сравнение REQUEST_URI со строкой, описанной perl-совместимым регулярным выражением, и в случае совпадения, подмены URL не происходит.
Примечание: Нужно не забывать о том, что все директивы RewriteCond должны быть описаны до использования RewriteRule.

Описанный выше способ — лишь один из возможных, рассмотрим как реализована концепция в Zend Framework.В предыдущем примере предполагалось, что точка входа находится в корневом каталоге веб-приложения, предлагаемая по умолчанию структура проекта на базе Zend Framework отличается тем, что каталог для общего доступа вынесен на уровень ниже, по умолчанию его имя public и доступ к веб-приложению настраивается таким образом, чтобы каталог public был корнем приложения, т.е. для получения доступа к ресурсам, находящимся вне директории public, в скрипте необходимо использовать условную адресацию для возврата на уровень выше либо же абсолютную адресацию, что вовсе не удобно, можно поступить например следующим образом:
    define( 'DIR_SEPARATOR ', '/' );
    define( 'ROOT', '..' . DIR_SEPARATOR );
  
Таким образом мы получили константу, содержащую относительный путь к логическому корню нашего приложения.
Осталось написать содержимое файла public/.htaccess:

RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

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

Документация к модулю mod_rewrite
Поделиться публикацией

Комментарии 20

    +7
    Всё проще:
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteBase /
    
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule ^(.*)$ index.php?/$1 [L]
    </IfModule>
    

    Не файл и не директория (там может быть свой index.php|html|htm e.t.c.) — тогда вам на index.php.

    А по сути — Вы только что процитировали практически любой MVC-фреймворк: ZF, CodeIgniter, Kohana, CakePHP, Yui, Symfony и так далее
      0
      Вы абсолютно правы, концепция единой точки входа является основой практически всех распространенных фреймворков. На это и нацелена первая половина статьи — объяснить как это происходит, основу этого архитектурного решения, так как, как это ни парадоксально, некоторые разработчики попросту не знают, как называется этот механизм и не до конца понимают всех целей, которые им преследуются. В частности программисты возрастом менее двух профессиональных лет, а то и старше затрудняются ответить на вопрос, что такое «избыточность кода» и к чему она приводит.

      Я дописал статью до конца, надеюсь во второй половине и вы найдете интересные моменты.
        0
        Охохо, сейчас что-то будет :)
        Использование статиков не всегда оправдано — это раз.
        Определение аяксовости запросов надо делать через $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'? а не разделять по точкам входа. При этом, у Вас точка входа всё-таки одна и Вы теперь придумываете простейший роутинг (либо dispatcher, кому как нравится). При этом, ajax, json, xml и http точки входов отличаются только способом отображения (т.е. — подгружаемыми шаблонами) и их разделение — задача как раз FrontController на основе каких-либо параметров, а не Dispatcher.
          0
          Вы правы абсолютно на 100%, но моя ошибка заключается в выборе предметной части примеров, а цель неизменна и до сих пор — описать способ, так сказать Дизайн Единой Точки входа с маршрутизацией не на уровне сервера, а на уровне программного кода.
          FrontController должен быть описан в методе execute финального потомка entryPoint, это неявно подразумевается в коде.
          Использование статиков не всегда оправдано — это раз.

          Опять же все зависит от ситуации, например есть в проекте файл utilities.php в котором содержится 5 тысяч строк программного кода и в каждом отдельном запросе используется лишь часть его — разве классы со статиками не выход, тем более что подключаться они будут не всем скопом, а через autoload? а как Вы прокомментируете существование .net фреймворка?
          При этом, ajax, json, xml и http точки входов отличаются только способом отображения (т.е. — подгружаемыми шаблонами) и их разделение — задача как раз FrontController на основе каких-либо параметров, а не Dispatcher.

          Это наверное уже зависит от желания автора и конкретной задачи, понятно, что если перечисленные точки входа отличаются лишь способом отображения, то фронт-контроллера вполне хватит, но как я писал в статье, могут возникнуть концептульно несхожие задачи, и тогда выход = диспетчер.
          Например есть у дилингового цента сайт и через него поставляются котировки в режиме реального времени в клиентские терминалы, причем раздача котировок достаточно активная и отдаваемые данные непосредственно связанны с данными фигурирующими на сайте, т.е. нужно принципиально новое приложение, которое имеет много связей с существующим. Я уверен, вы предложите много вариантов решения данной проблемы, вплоть до выделения этого приложения в отдельное на собственном сервере, но к примеру нагрузка еще не настолько велика и какой-нибудь большой рефакторинг просто финансово не может быть обоснован, что же делать?

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

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

            Т.е., в идеале, логика одна (либо незначительно отличающаяся) для различных вариантов отображения (будь то REST API, ajax, либо простой http). Т.е. меняется только набор вью + некоторые части (авторизация, например, и т.д.). Таким образом — Ваш EntryPoint — всего лишь начало Dispatcher-a.

            Почему использование статических классов не всегда оправдано? Тут всё проще — содержимое статического класса держится в памяти до смерти скрипта, а вот переменная, содержащая в себе инстанциированный класс, будет уничтожена как только пропадёт последняя ссылка на неё (Garbage Collector постарается — т.е. я сделаю unset переменной в которой хранился инстанс класса, в котором, в свою очередь, было поле с объектом другого класса — зачистятся оба).

            П.С. — перенесите в какой-нибудь профильный блог (PHP, например — так больше народу статью увидит).
              0
              извените, ошибся местом ответа, Вам ответил ниже.
      –1
      содержимое статического класса держится в памяти до смерти скрипта

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

      И наверное в контексте общей темы топика я приведу еще один пример, который и был инициатором всей этой затеи, но потом ушел из зоны внимания — скрипт крон-менеджера. Его архитектура не базируется на паттерне MVC, так как ни контроллера ни тем более представления в нем нет и быть не может, есть, грубо говоря, только модель, в одном единственном экземпляре. Но он в то же время неотъемлемая часть системы и работает со всеми теми же данными. Т.е. это приложение абсолютно иного назначения, обязанное выполнятся в контексте сайта. А применив к нему описанную технологию, мы в нем урезаем избыточность, плюс к этому появляются новые возможности для отладки и логирования на одной общей базе.
        0
        Очень много задач можно решить базируясь на MVC и «скрипт крон-менеджера» этому не исключение. В нем также могут быть свои контроллеры, модели и отображения. Например, так сделано в Symfony1.4 для создания task-ов.
          0
          В скрипте крона есть как минимум модель и контроллер. Как иначе Вы собираетесь запрашивать действия модели? Плюс можно дампить некие вещи в консоль, вот и view появляется, а многие хостинги к тому же поддерживают отправку этого вывода на почту.
            0
            пошел учить мат. часть, как оказалось, мое представление о MVC-паттерне сходно с представлением большинства программистов с небольшим опытом, т.е. мой контроллер — это ТТУК — «Толстые тупые уродливые контроллеры» (http://ru.wikipedia.org/wiki/Model-View-Controller)
          +1
          Как уже выше отметили, отделять ajax удобнее $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'

          Добавлю лишь, что, мне удобнее да и нагляднее получать xml, json и тд так:
          domain.com/module/action/index.xml
          domain.com/module/action/index.json
          domain.com/module/action/index.blabla

          в то время как
          domain.com/module/action/index.html — отдает нужный контент в html
              –1
              мне кажется, что перед тем, как рассуждать над MVC нужно научиться использовать switch вместое elseif
                0
                Приведи пожалуйста хоть один довод тому, что if...elseif чем то уступает switch...case?
                Я сознательно не использую конструкцию switch...case, в свое время один британец с 20-ти летним опытом, работающий в немаленькой компании Converteam, привел аргументированные доводы в пользу того, что конструкцию case...switch не стоит использовать в принципе, и все благодаря существованию такого оператора, как break.
                А в остальном, это дело личного предпочтения.
                  0
                  по функционалу — ничем
                  выглядит намного понятней. чисто субъективно
                  чем плох break?
                    0
                    Его доводы были основаны на том, что возможность выхода из блока кода в любом месте — есть плохо. Т.е. кроме break, к плохим операторам так же были отнесены и continue и return.
                    Да, когда ты пишешь сам, ты знаешь все свои привычки и способен в случае появления, определить баг достаточно быстро, но когда работаешь в команде, проще и правильнее задать некоторые правила, благодаря которым исключительная ситуация просто не возникнет, в случае таких банальных вещей, как использование break;
                    А так может получится, что пишет человек код, внутри блока case каким то образом появляется условие с break внутри, а за этим условием еще добрая половина блока case, которая должна обязательно исполнится, и тут случается, так, что условие не в полной мере охватывает все варианты, все, у тебя по непонятной причине отваливается кусок логики, и повезет, если его писали недавно, то сориентироваться можно быстро, а если проект n-годовой давности и разрабатывал его не ты, хотел бы такого жучка встретить?

                    Тоже по теме преждевременного выхода из блоков кода — если немножко погуглить по фразе «Правила хорошего тона программирования» или «чистота кода», в нескольких источниках можно обнаружить рекомендации того, что оператор return в методе или функции должен встречаться единожды — в последней строке блока метода, доводы к этим рекомендациям примерно те же, что и в случае с оператором break;
                      0
                      Не использовать switch только потому, что при этом надо использовать break? о_О
                      До абсурда-то зачем доходить? Правила созданы для программиста, а не программист для правил ;)
                        0
                        Абсурд — это когда человек бросает громкие фразы, ничем их не подтверждая. Если вы имеете противоположную моей точку зрения, приложите хоть немного усилий, чтобы обосновать ее, иначе любое утверждение можно превратить фарс.
                  +1
                  мне кажется, что перед тем, как рассуждать над MVC нужно научиться использовать switch вместое elseif

                  Этот вопрос на самом деле совершенно не имеет смысла. Оно будет вынесено в отдельный метод и будет читабельно, что if'ами, что switch'ами.
                  А раз уж решили выпендриться, то следовало вспомнить Фаулера и сказать фразу по-умнее, например «Стоит заменить условный оператор полиморфизмом». И это будет более в тему, потому что switch относиться к MVC ровно настолько же, насколько объявление константы.

                  0
                  Статья переработана.

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

                  Самое читаемое