Контроллеры, основанные на механизмах рефлексии.

    контроллер — это специализированная сущность для управления другими сущностями.
    Часто используют функциональные контроллеры, основанные на найменге (naming) примеры всем широко известны, например плагины смарти:
    smarty_type_name() как видите из документации, плагин становится доступным если объявлены соответствующие функции.

    в целом там простой контроллер который ищет функцию по простым правилам, основанных на именовании.
    часто такие вызываемые сущности хочется проинкапсулировать или иначе собрать и закрыть во что-либо, в результате появляются файлы с типично функциональным подходом:
    mylib.php:
    function mylib_afunc(){};
    function mylib_bfunc(){};
    function mylib_cfunc(){};


    конечно, это не гарантирует целостности, засоряет глобальное пространство имен и проч.
    Удобно такие вещи проинкапсулировать в класс:

    class MyLib{
        static function a(){};
        static function b(){};
        static function c(){};
    }


    далее не сложно понять как нужно сделать контролер, основанный на method_exists, которому первым аргументом вполне можно давать имя класса-контейнера. Как правило, на этом фантазия останавливается, но из объектной парадигмы можно извлечь еще много различных выгод:
    • Используя наследование можно иметь «расширения» наборов таких вызываемых сущностей
    • Используя полиморфизм + наследование можно переопределять необходимые функции для тех или иных сущностей
    • Ограничение видимости поможет скрыть внутреннюю реализацию, которая должна быть скрыта от контроллера.

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

    для начала покажу самый дурной вариант того как это делают:
    swich $_GET['action']{
        case: "a" ...
        case: "b" ...
        case: "c" ...
    }

    хуже только варианты с if-elseif-else, чтоб такого не было пишут контроллер, например, как в смарти, основанный на найменге:

    if (function_exists('myAction_'.$_GET['action']))
        call_user_func('myAction_'.$_GET['action']);


    но я опишу немножко другой подход, более формальный:
    тут код в цвете: dumpz.org/2513

    а тут просто код, чтоб не бегать:
    
    
    abstract class Module {
       public final function buildPage($action) {
       	do{
       		//нельзя вызвать функции, начинающиеся с подчеркивания
       		//(общепринятое соглашение считать их приватными)
    		if (substr($action, 0, 1) == '_' or empty($action)) 
    			break;
    		//нельзя вызвать функции описанные в абстрактном классе, они для работы системы
    		if (method_exists(__CLASS__, $action))
    			break;
    		if (!method_exists($this, $action))
    			break;
    		$method = new ReflectionMethod($this, $action);
    		//доступны только публичные методы
    		if (!$method->isPublic())
    			break;
    		return $this->$action();
       	}while(false);	
    	return $this->error404();
       }
       public function error404(){
       		echo 'такой страницы нет в системе';
       }
    }
    
    class ExampleModule extends Module{
    	function hello(){
    		echo 'hello World!';
    	}
    	
    	function test(){
    		echo 'Тест!!!';
    	}
    	protected function inner(){
    		echo 'это внутренняя функция не доступна контроллеру!';
    	}
    }
    
    $module=new ExampleModule();
    $module->buildPage(isset($_GET['action'])?$_GET['action']:'');
    


    контроллером является функция "buildPage"

    таким образом если вызвать скрипт
    example.com/?action=hello
    то попадете на работу функции hello,
    а вот так вас не контроллер не пропустит:
    example.com/?action=inner

    примеров использования таких контроллеров масса — все ограничено лишь вашей фантазией, они успешно заменяют swich и каскады if-else, обеспечивают понятный слой абстракции, достаточно быстры и легко расширяемы.

    дальнейшее развитие идеи таких контроллеров лежит в использовании наследования, например если вы узнали что ваш пользователь зарегистрирован, то ничего вам не мешает создать класс для зарегистрированных
    class UserExampleModule extends ExampleModule {
         function hello(){
             echo 'привет зарегистрированный пользователь!';
         }
    }
    

    и при создании объекта писать:
    if (проверка авторизации)
        $module=new UserExampleModule();
    else
        $module=new ExampleModule();
    

    тогда для авторизованных пользователей будет использован уже класс UserExampleModule для создания объекта module, в котором контроллер будет искать функцию.

    в целом это все о чем я хотел рассказать ^_^, дальше расскажу уж просто про идею этого метода.
    в первые я «изобрел» это колесо еще 2 года назад (для себя, хотя мир знал это давненько), тогда же оказалось, что такой подход используется некоторыми западными авторами-программистами, до сих пор я вижу тонны кода, в которых не используются даже простые контроллеры на найминге, вместо этого почему-то программистов прельщает писать ОГРОМНЫЕ swich, призываю вас так не поступать -), надеюсь вам этот топик поможет.

    всем спасибо, не судите строго!

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

      –1
      мне кажется, что вы в два года назад «изобрели» третью букву в слове MVC :)
        +3
        зато читать было интересно, спасибо
          0
          контролеры давно придуманы как таковые, а вот использование в PHP их я думаю начал с первыми. я же написал что изобрел колесо — эта методика была давно известна, просто у нас о ней мало пишут.
            0
            мне просто показалось, что приведенная проблема решилась бы если писать проект с использованием паттерна mvc, я не прав?
            тогда с интересом жду продолжения темы с развитием про наследование и полиморфизм
              0
              ну не знаю, я постарался придумать пример достаточно законченым с одним контроллером, без представления и модели, ее я оставил за рамками.
          0
          1. А инстанцирование нужного модуля вы на контроллер не возлагаете? Тогда вы не избавляетесь от swich, а просто уменьшаете его размер (нужно будет выбирать только модуль). Почему не вынести контроллер в отдельный класс и не проверять точно так же class_exists и проверять публичность меода через is_callable?

          2. А где исходник самого ReflectionMethod? Мне пока не совсем понятно зачем он вообще нужен.

          3.
          >> if (проверка авторизации)
          >> $module=new UserExampleModule();
          >> else
          >> $module=new ExampleModule();
          Совсем уж как-то дико выглядит. Намного проще/понятней закрывать доступ к отдельным функциям или модулям. Если же необходимо разное поведение для (не)зарегестрированного пользователя, то проверять авторизацию уже в самом модуле и из одного и того же public метода вызывать разные private/protected
            0
            по поводу пункта 2:
            ReflectionMethod — нативный класс PHP служит для анализа метода класса.

            по поводу пункта 1: я же тут не привожу реализацию MCV я показываю как контроллер можно построить это просто пример.

            по поводу пункта 3: acl давно существует, но это другой метод, мне этот более понятен, кода видно то, что может быть использовано, а не ситуация, когда есть ресурсы, которые закрыты.
              +1
              1. Просто если мы определяем какой метод вызывать, то теряется сам смысл рефлексии. Она ведь нужна только потому что из текущего класса все is_callable. А так это смотрится не как пример построения контроллера, а как слегка надуманный пример использования рефлексии, за что спасибо.

              2. Спасибо. Как-то упустил это расширение. Кстати, в вашем случае, его может заменить и своим классом )

              3. Так и я не про acl говорю. ACL, по сути, не навязывает где должна проходить проверка прав доступа. При использовании вашего метода, где имеено и на каком этапе вы будете определять какие нужно инстанцировать как User* а какие нет?

              При таком контроллере + огрничении доступа воображение рисует index.php вида:
              swich($module) {
                 case 'blog':
                    if (User::isAuthenticated()) 
                     $module = new UserBlogModule();
                  else
                     $module = new BlogModule();
                   }
              }
              if ($module) {
                 $module->buildPage('action');
              }
              

              Немного неудобно, не правда ли?
                +1
                да я понял вас, вы, конечно, правы, в идеале нужно написать фабрику в самом абстрактном модуле, которая будет выбирать тип создаваемого модуля (в зависимости от ролей)
                /**
                @return Module
                **/
                static function fabrica($moduleName)…

                реализация ее проста, но специфична для задачи в приведенном выше примере она будет наподобии:
                static function fabrica($moduleName){
                    if (User::isAuthenticated() and class_exists("User".$moduleName)  and is_a("User".$moduleName, "Module") ) {
                         $moduleName="User".$moduleName;
                    }elseif(!is_a($moduleName, "Module")){
                          return null;
                    }
                    return new $moduleName;
                }
                


                как видите теперь вообще можно писать:
                $module=Module:: fabrica($GET['module']);

                и ни о чем не думать, вообще я планирую однажды написать о том как я реализую полный контроллер для создания страниц на этой идеалогии, он решает многие задачки и сильно упрощает жизнь, но статья посвящена именно тому как классы и рефлексию использовать в контроллере, а не тому как писать контроллер или реализовать MCV, но всеже забежав немного в перед я, надеюсь, ответил на ваш вопрос ;-)
              +1
              >2. А где исходник самого ReflectionMethod? Мне пока не совсем понятно зачем он вообще нужен.
              ru.php.net/manual/ru/language.oop5.reflection.php
              –3
              Мне понравилось с function_exists… Но надо не забывать, что $_GET['action'] нужно отфильтровать на спецсимволы — безопасность всегда важна :)
                +1
                Конечно важна, но, скажите, вы можете привести пример строки, которая может вызвать сбой при передаче в function_exists? )
                  0
                  тут действительно не нужно париться с верх меры, ведь я рассказал о том как писать контроллер, привел такой пример, а вообще такие контроллеры удобно использовать в событийной модели разбора XML документов например, когда метод реагирует на отведенный ему XML тег, да масса примеров, SAOP так делал — удобно выходило, RPC тудаже, в целом статья о контролллере.
                0
                То есть, так можно вызывать любой публичный метод контроллера? Ой как нехорошо. Как минимум потому, что неплохо бы разделять экшны по типу запроса (GET/POST/POST+AJAX).

                А вот если добавить немного нейминга:
                function GET_a()
                  0
                  function POST_b()

                  и т.д. в том же духе, то по-моему будет ближе к идеалу
                    0
                    > То есть, так можно вызывать любой публичный метод контроллера? Ой как нехорошо.
                    А что в этом плохого? Ведь можно вызвать метод не любого класса, а только некоторых.

                    А разделение по get/post/xmlhttprequest это уже дело вкуса. Зачастую нужно переключать только view а, значит нет неоходимости в таком разделении.
                      0
                      на практике найминг очень полезная вещь, я хотел показать, что есть методы отличные от найминга =)
                      разделять экшны по типу запроса не советую просто потому что часто это не нужно, но в целом идею принял, мне понравилось, действительно можно сделать чтоб сначала искалась функция
                      function POST_b()
                      если ее нет, то
                      function b(), так, пожалуй, иногда может пригодиться, но редко… как правило функция делает одно и тоже а от типа запроса зависит тип ответа всего лишь, либо это JSON (POST+AJAX), либо кусок страницы (обновляемый тупо AJAX) или это вся страница. эта работа ложится на другой слой вообще говоря на слой Viewer в патрене MVC и к контроллеру имеет слабое отношение, тоесть метод (action) отработав дает результирующие данные, а они уж идут либо в один либо в другой шаблонизатор.

                      Если действительно есть интерес посмотреть как сделать систему для сайта, то могу написать именно про это, там много подводных камней.
                        0
                        Меня беспокоит только то, что можно дернуть любую функцию и любым методом. Ну неструктурно это.

                        А про систему — пишите, интересно. :)
                          0
                          ну тут двояко… вот есть у вас форма поиска, которая может получать доп параметры, а может не получать их и какая разница

                          ? do=search вы просто урлом передаете или хидден параметром формы поиска? в принципе без разницы. ну может не структурно, хотя я не понимаю. назовите причину по которым такие вещи нужно разделить и я, быть может, пойму о чем вы.
                      0
                      1. методы должны иметь префикс: $this->do_*(), act_*, do_step* для безопасности и выделения.

                      Фактически такой подход конечно, служит автоматизации. Не надо писать несколько строк. И можно расширять контроллер без изменения конструктора. Но по моему, методы $this->*() должны вызываться с определёнными параметрами (из GET/POST/*), или до вызова определённого метода должны устанавливаться переменные среды (т. е. $this->SET1, $this->set2['param1']).
                      И такой подход имеет право только при сходных страничках ($this->do_step_*()).
                        0
                        mrkto.com/controller_on_php/
                        0
                        Интересный способ do {} while(false);
                        Я все время пользовался try {} catch () {}
                          0
                          экспешены просто медленее (ну покрайней мере у меня такое убеждение осталось) + не так читаемо (нет гарантий перехвата).

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

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