Codeigniter — облегчаем себе жизнь (расширяем базовый контроллер)

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


    Для начала подготовим наш приложение:
    расширяем роуты:
    Открываем routes.php(/system/application/config/routes.php)
    добавляем следущие строчки:
    $route['(ru|en)'] = $route['default_controller'];
    $route['(ru|en)/(.+)'] = "$2";
    * This source code was highlighted with Source Code Highlighter.

    таким образом теперь мы к любому методу, любого контроллера можем обращятся 3 способами:
    www.yourapp/controller/action
    www.yourapp/ru/controller/action
    www.yourapp/en/controller/action

    Создаём также 2 языковых файла interface_lang.php (/system/application/language/english/interface_lang.php и /system/application/language/russian/interface_lang.php)
    Приготовления по языку закончены, теперь подготовим наш контроль доступа.

    Для контроля доступа я использую Zend_Acl. Очень удобная вещь.
    Скачиваем ZendFramework. Создаём папку extensions(system/extensions) и переносим туда папку library/Zend
    дальше создаем 2 хелпера:
    zend_framework_loader_helper.php (/system/application/helpers/zend_framework_loader_helper.php)
    <? php
       if (!defined('BASEPATH')) exit('No direct script access allowed');
       //
       ini_set('include_path', ini_get('include_path').PATH_SEPARATOR.BASEPATH.'extensions/');
       require_once('Zend/Loader.php');

       Zend_Loader::loadClass('Zend_Acl');
       Zend_Loader::loadClass('Zend_Acl_Role');
       Zend_Loader::loadClass('Zend_Acl_Resource');
    * This source code was highlighted with Source Code Highlighter.

    и zend_framework_acl_helper.php (/system/application/helpers/zend_framework_acl_helper.php)
    <? php
       function init_roles() {
          $acl = new Zend_Acl();
          
          //определяем ресурсы/контроллеры
          $acl->add(new Zend_Acl_Resource('login'));
          $acl->add(new Zend_Acl_Resource('welcome'));
          $acl->add(new Zend_Acl_Resource('logout'));

          // определяем роли
          $acl->addRole(new Zend_Acl_Role('guest'));
          $acl->addRole(new Zend_Acl_Role('member'));
          
          //определяем доступ
          $acl->allow('guest','login');
          $acl->deny('guest','welcome');
          $acl->deny('guest','logout');

          $acl->deny('member','login');
          $acl->allow('member','welcome');
          $acl->allow('member','logout');

          return $acl;
    }
    * This source code was highlighted with Source Code Highlighter.

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

    Дальше открываем autoload.php(/system/application/config/autoload.php)
    и добавляем наши хелперы
    $autoload['helper'] = array('zend_framework_loader','zend_framework_acl');
    * This source code was highlighted with Source Code Highlighter.

    Приготовления закончены теперь надо бы начать всё это использовать,
    то есть реализовать проверку этого всего в наших контроллерах

    Есть несколько способов как расширить функциональность контроллера:
    1. Прописывать небоходимые действия в конструкторе.
    2. Делать тоже самое в методе _remap().
    3. Расширить класс контроллер и наследовать свои контроллеры уде от него.

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

    Я буду предпологать, что данные о пользователе(такие как его id, имя, принадлежность к группе) в храните в сессии используя стандартный механихм сессий для codeigniter

    Итак начнём:

    Создадим файл My_Controller.php (/system/application/libraries/My_Controller.php)
    <? php
    class MY_Controller extends Controller {
       private $resource = null
       private $priviledge = null;
       // роль пользователя по умолчанию 
       private $default_role = 'guest';
       private $ci;
       private $acl;
       private $language;

       public function MY_Controller(){
          parent::Controller();
          
          // определяем язык
          $lang = $this->uri->segment(1);

          if($lang == 'en'){
             $this->language = $lang;
          }
          else {
             $this->language = 'ru';
          }

          // подгружаем нужный язык
          switch($lang):

             case 'en':
             $this->lang->load('interface', 'english');
             $this->config->set_item('language', 'english');
             break;

             case 'ru':
             $this->lang->load('interface', 'russian');
             $this->config->set_item('language', 'russian');
             break;

             default:
             $this->lang->load('interface', 'russian');
             $this->config->set_item('language', 'russian');
             break;

          endswitch;

          // вызываем init_roles из zend_framework_alc_helper.php
          $this->acl = init_roles();

          // обращаемся к нашему роутеру для определения контроллера и метода
          $router =& load_class('Router');

          // устанавливаем конртоллер в класс и метод в привелегию
          $this->resource  = $router->fetch_class(); 
          $this->priviledge = $router->fetch_method();   
          // просто для удобства что бы не инициализировать позже в приложении
          $this->ci =& get_instance();

          $acl = $this->getACL();

             // если пользователь существует то работаем с его данными
             if($this->session->userdata('userInfo')){
                $identity = $this->session->userdata('userInfo');
                $this->setDefaultRole($identity['user_role']);
             }

          // если пользователь не авторизирован(guest) пытается запросить что-то, то редиректим его на страницу авторизации 
          if(!$acl->isAllowed($this->getDefaultRole(), $this->getResource(), $this->getPriviledge()) && $this->getDefaultRole() == 'guest'){
             redirect('login','refresh');
          }
          // если пользователь авторизирован и пытается запросить что-то куда нет доступа, то редиректим его на страницу авторизации 
          elseif(!$acl->isAllowed($this->getDefaultRole(), $this->getResource(), $this->getPriviledge()) && $this->getDefaultRole() != 'guest'){
             redirect('welcome','refresh');
          }
       }

       // сеттеры и геттеры
       
       public function getInstance(){
          return $this->ci;
       }

       public function getACL(){
          return $this->acl;
       }

       public function getDefaultRole(){
          return $this->default_role;
       }

       public function getPriviledge(){
          return $this->priviledge;
       }
       public function getResource(){
          return $this->resource;
       }

       public function setDefaultRole($role){
          $this->default_role = $role;
       }

       public function getLang(){
          return $this->language;
       }
    }
    * This source code was highlighted with Source Code Highlighter.

    Вот собственно и всё дальше при создании нового контроллера просто заменяем
    class Welcome extends Controller{

       public function Welcome(){
          parent:: Controller();
       }
    }
    * This source code was highlighted with Source Code Highlighter.

    на
    class Welcome extends MY_Controller{

       public function Welcome(){
          parent:: MY_Controller();
       }
    }
    * This source code was highlighted with Source Code Highlighter.

    и можно работать ))
    Поделиться публикацией

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

      0
      Как раз мне это скоро пригодится :) Спасибо
        0
        на здоровье ))
        0
        спасибо большое - очень полезно! пока не нужно,но думаю пригодится!
          0
          пожалуйста ))
          +1
          По моему тут ошибка:
          $acl->deny('member','login');
          $acl->allow('guest','welcome');
          $acl->allow('guest','logout');


          Должно быть:
          $acl->deny('member','login');
          $acl->allow('member','welcome');
          $acl->allow('member','logout');
            0
            Спасибо. Исправил ))
              0
              А по-моему, строки с deny вообще лишнии: в зенд-ацл то, что не разрешено, запрещено.
                0
                посмотрю, а вобще в проектах побольше, пользователей желательно наследовать друг от друга, благо Zend_Acl это позволяет
              0
              Здорово. Спасибо. Только вот Zend смущает ;)
                0
                не за что. а чем смущает то?
                  0
                  Ну тем, что он вообще используется ;) Есть, например, phpGACL.
                    0
                    гм, во-первых для функционала не заметил особо разницы, во-вторых не понял зачем мне тянуть в приложение ADODB, поскольку как я понял у них там зависимости, в третих, не вижу проблем подогнать код под необходимую библиотеку.
                0
                По авторизации есть альтернатива - Erkana auth от Michael Wales.
                Есть также замечательный "starter's pack" от того-же Michael Wales, где авторизация уже включена, и от CI_Controller наследуются три разных контроллера соответствующие разным уровням доступа: Public_Controller, Member_Controller и Admin_Controller.
                очень удобная штука для быстрого старта.
                  0
                  Библиотек есть много, хороших и разных, но далеко не все они достаточно гибки,
                  как вы например сами сказали в Еркана три контроллера, для 3 груп пользователей, а что делать если и 8 или 12 ?

                  А вообще идентификацию созательно не затрагиваю, потому как в этом деле у каждого свои предпочтения, да и далеко не всегда пишешь систему с нуля и бывает что она уже реализованна. От себя могу порекомендовать Zend_Auth ))
                    0
                    Немного неправильно. Три контроллера - это в стартер паке, т.е. в аналоге того что предложили вы. И главное достоинство ерканы - это то что она лишь костяк авторизации, т.е. набор методов которые помогут вам быстро реализовать именно ваши предпочтения. Это и удобно. Поэтому я в своё время не решился даже смотреть FreakAuth - она черезвычайно перегружена.
                    Вообще я чувствую надо будет собрать собственный стартер пак, в котором и языки реализованы, и авторизация, и редиректы правильные после авторизации, основанные на сессиях. Должно быть полезно.
                      0
                      Хмм в таком случае интересно, надо будет посмотреть, хотя меня и zend-овские классы очень радуют.

                      насчёт FreakAuth, да помнится тоже краем глаза посмотрел и сразу понял, что она мне не подойдёт.
                  0
                  Подсветите сорцы, пожалуйста.
                    0
                    приду домой попробую может хайлайтер какой посоветуете?
                  0
                  О боже. Я себе мозг сломал, наблюдая как CodeIgniter создаёт объект, инклудит модули и передаёт параметры. Может, конечно, так и надо делать, но у меня другие представления. Хотя, конечно, может стоящая вещь.
                    0
                    Поясните пожалуста, что вы имели в виду? Я согласен архитектура у CI не идеальная, но в принципе достаточно понятная, и мозг там ломать особенно не получится(ИМХО)
                    Супер объект - Один большой синглетон а остальное всё подключается, на мой взгляд подход себя оправдывает.
                      +1
                      Мнение, которое может не совпадать с общепринятым.

                      Например. Как может нравиться конструкци вида:
                      $this->validation->set_message('_check_login','Bad username or password.');
                      Да, всё понятно.
                      Однако, моя Zend Studio for Eclipse не может мне сказать какого типа поле validation. Т.е. IDE не знает что это за класс такой и какие у этого класса свойства/методы. Значит я должен помнить все(!) свойства/методы этого класса. Не очень облегчает жизнь программисту, не так ли?

                      А вот ещё.
                      $this->load->library('validation');
                      Тоже всё понятно. И опять же здесь всиле предыдущее замечание. Да плюс ко всему, знаете где он будет искать библиотеку 'validation'? Во многих местах.

                      А как вам урлы вида
                      http://my_site.my/subjects/some_class/messages:add
                      Здорово не правда ли? А потом этот запрос начинает мощно парситься. Из него начинают выделяться "сегменты", части до ":" и после него.

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

                        1.Итак зенд студио и авто-дополнение, проблема не столько в архитектуре, а скорее в отсутствии phpdoc

                        2. ну опять первый пункт, а насчёт где будет искать посомотрите метод _ci_load_class класса СI_Loader так-ли уж много мест?

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

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

                        Ну и на последок плюсы:
                        1. низкий порог вхождения - обучить нового человека можно за 2-3 дня.
                        2. отличная документация - почти всё по разложенно по полочкам с примерами.
                        3. сравнительно высокая быстродейтелность - я не говорю что лучшее, но у многих выигрывает.
                        4. легкая интеграция со сторрониими приложениями

                        Вот как-то так ))
                          0
                          Нифига подобного! Нельзя phpDoc-ом сделать так, чтобы после
                          $this->load->model('megamodel');
                          $this->megamodel->[Ctrl+Space]
                          выпадал автокомплит методов класса megamodel
                            0
                            А вот и можно.

                            @property CI_Uri uri

                            И NetBeans 6.5 прекрасно поёмет, что это такое.
                            Если знаешь английский, то можешь почитать по подробнее как более меннее удобно организовать автокомплит в NetBeans
                            www.mybelovedphp.com/2009/01/27/netbeans-revisited-code-completion-for-code-igniter-ii/
                            Сказать точно, сработает ли это в Eclipse, я не могу, так как им не пользуюсь.

                            А если ты не веришь, что такой тег существует тогда прошу сюда
                            manual.phpdoc.org/HTMLSmartyConverter/PHP/phpDocumentor/tutorial_tags.property.pkg.html

                            Никогда не утверждай то, чего не знаешь. Как говорил мне мой преподаватель, если ты не нашел, это не значит, что этого нет. Совершенствуйся, учись, а не демонстаритво утверждай, что «Нельзя», а что «Можно». Надеюсь ты адекватно воспримешь эту критику.
                    0
                    Ну если на гоночную машину(CI) посадить слона(ZF)
                    она перестанет быть гоночной машиной.

                    Базовый контроллер расширяется самописным БЫСТРЫМ кодом.. а не слонами.. :-)
                      0
                      гм, а вы не в курсе что, ZF тем и хорош что, себя всего не тянет, а позволяет работать с отдельными классами?В примере видно, что в данном случае подгружаются толькоу те классы которые нужны для списков доступа. Можете замерить сколько времени занимает, загрузка с ним и без и будете приятно удивленны.

                      Что касается быстрого самописного кода, то я например почти всегда отдам предпочтение хорошо задокументированной, проверенной, библиотеке, а не странным функциям которые местный гуру сваял на коленке. Опять же напишите мне быстрый самописный код который будет без проблем расширяться и позволит реализовать списки доступа с той же гибкостью.
                      0
                      разве функ-я в хелпере init_roles() не должна что-то врернуть? потипу return $acl;
                        0
                        Вот и я думаю, косяк. init_roles() должна возвращать return $acl;
                          0
                          звиняете, исправли, именно $acl она и возвращает
                        0
                        Ошибка, надо не /system/application/libraries/My_Controller.php, а /system/application/libraries/MY_Controller.php
                        Под виндой это монопенисуально не зависит от регистра, а вот под *nix системами, такую вольность допускать нельзя.
                          0
                          собственно «My» в данном случае настраиваемый префикс, как хотите так и называете ))
                            0
                            Так как про настройку ни слова, то подразумевается настройка по-умолчанию.
                          0
                          Нормальный код, только как уже высказались Zend тянуть хоть и по отдельности тяжко, да и полно «родных» ACL было на сайте, сам пользуюсь свой правда.

                          И еще меня коробило одно слово в коде, но встречается часто Priviledge как-то не по английски, словно испанское что-ли, имхо вариант privilege или rights было бы приятнее.

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

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