Pull to refresh

Как не надо разрабатывать на Zend Framework

Reading time8 min
Views8.1K

Здравствуйте! На днях подвернулась работа — дописать сайт на zend framework. Программист, который начал разрабатывать этот проект не успевал в сроки, не выполнял требования заказчика и в итоге его заменили. Открыв первый раз исходный код я ужаснулся от того количества ошибок, которые допускал разработчик и ведь он утверждал заказчику что является опытным специалистом. Далее я попытаюсь рассказать про некоторые ошибки, которые были им допущены. Данный материал будет полезен начинающим ZF-программистам, в качестве инструкции того как делать нельзя. Также я расскажу про некоторые моменты непосредственно не связанные с фреймворком, но также являющиеся яркими примерами невежества разработчиков.

Использование встроенных методов модели, вместо написания своих


Первое, что сразу бросилось в глаза — выполнение запросов через объект модели прямо в коде контроллера, что напрочь перечеркивает все преимущества MVC. Чтобы было понятно о чем речь приведу пример исходного кода:
$model = new Model();
$some_data = $model->fetchAll(array('field1 = ?' => 1'field2 = ?' => 0));
$all_data = $model->fetchAll();
Colored with dumpz.org
Такое смешивание логики было в каждом файле контроллера, что очень мешало чтению кода и исправлению ошибок, постоянно возникали какие-то не очевидные зависимости, перезаписывались важные данные. Соответственно так делать не в коем случае нельзя, даже если вы разрабатываете небольшой проект.

Обращение напрямую к глобальным массивам вместо обращения к объекту Request


У ZF есть очень удобная оболочка над глобальными переменными в виде объекта класса Zend_Controller_Request_Http. Данный класс предоставляет нам массу возможностей по доступу к данным и используется фреймворком в процессе диспатчинга. Поэтому не стоит пренебрегать использованием объекта запроса.

Отсутствие context-switching когда это необходимо


Для реализации обработки ajax-запросов можно воспользоваться, т.н. context-switch что представляет собой удобный способ изменения формата ответа. Если вы используете json то в методе init контроллера задать что-то вроде следующего:
$this->_helper->AjaxContext()->addActionContext('ajax-handler''json')->initContext('json');
Colored with dumpz.org
Теперь все данные переданные во view в экшене ajax-handler будут перекодированы в формат json. Этот способ является более предпочтительным по сравнению с отключением view и ручным конвертированием данных в json.

Пренебрежение использованием Zend_Form и Zend_Validate


Не стоит использовать функции php для валидации данных, т.к. в ZF есть очень удобные валидаторы, которые можно объединять в группы и ставить на обработку определенных полей формы. Используя ZF-валидаторы вы уменьшаете шанс, того вы что-нибудь упустите и тем самым делаете ваши приложения более устойчивыми.

Отсутствие проверки корректности данных


Вы должны проверять все данные полученные вами от пользователя, причем вы должны проверять их в контексте прав доступа данного пользователя, например, если вы пишите добавление/удаление материалов из списка избранного и у вас есть js-функции вроде приведенных ниже, то вам стоит очень хорошо задуматься:
function addObject(object_id, user_id) {  
    if (user_id > 0) {       
        $.get('/realestate/favorite/oid/'+object_id+'/uid/'+user_id, function () {            
            $("#addfavorite"+object_id).hide();
        });
    }
}

function removeObject(object_id, user_id) {
    if (user_id > 0) {
        $.get('/profile/removefavorite/oid/'+object_id+'/uid/'+user_id, function () {
            $("#removefavorite"+object_id).hide();
        });
    }
}
Colored with dumpz.org
Как выяснилось потом на server-side user_id не проверялся на равенство идентификатору текущего пользователя, что является серьезной уязвимостью

Использование вместо ACL иерархии классов контроллеров


Практически в каждом сайте есть несколько уровней доступа: гости, пользователи, администраторы и т.д. Для того чтобы контролировать права доступа статусов к определенным частям сайта были использованы иерархии контроллеров. Т.е. создавался класс-родитель для контроллеров админки, класс для остальных контроллеров и в этом родительском классе выполнялось что-то вроде этого:
    public function preDispatch() {
                // А вот пример глобальных переменных *fail*
        if (!empty($_REQUEST['session'])) { 
            session_id($_REQUEST['session']);
        } else {
            $auth = Zend_Auth::getInstance();
            if (!$auth->hasIdentity()) {
                $this->_redirect('backoffice/auth/login');
            }
        }
    }
Colored with dumpz.org
Чем плох такой подход? Во-первых, очень сложным является перераспределение прав доступа. Во-вторых, тяжело поддерживать несколько ролей. Можно долго продолжать
Для данных целей в ZF, есть прекрасный инструмент формирования списков прав доступа или ACL. Лично я использую небольшой плагин, который проверяет права доступа данного пользователя на запрашиваемый экшн/контроллере в процессе диспатчинга. Данный способ позволяет формировать права доступа в простом, легко изменяемом списке, вроде этого:
        //Добавляем роли
        $this->addRole('guest');
        $this->addRole('user''guest');
        $this->addRole('manager''user');

        //Добавляем ресурсы
        $this->add(new Zend_Acl_Resource('guest_allow'));
        $this->add(new Zend_Acl_Resource('index/index'),'guest_allow');
        $this->add(new Zend_Acl_Resource('index/registration'),'guest_allow');
        $this->add(new Zend_Acl_Resource('error/error'),'guest_allow');
        
        $this->add(new Zend_Acl_Resource('user_allow'));
        $this->add(new Zend_Acl_Resource('index/logout'),'user_allow');
        $this->add(new Zend_Acl_Resource('project/index'),'user_allow');
        $this->add(new Zend_Acl_Resource('task/index'),'user_allow');
        $this->add(new Zend_Acl_Resource('task/complete'),'user_allow');
        $this->add(new Zend_Acl_Resource('task/assigned'),'user_allow');
        
        $this->add(new Zend_Acl_Resource('manager_allow'));
        $this->add(new Zend_Acl_Resource('project/add'),'manager_allow');
        $this->add(new Zend_Acl_Resource('task/add'),'manager_allow');
        $this->add(new Zend_Acl_Resource('index/add-user'),'manager_allow');
        
        //Выставляем права, по-умолчанию всё запрещено
        $this->deny(nullnullnull);
        $this->allow('guest''guest_allow''show');
        $this->allow('user','user_allow''show');
        $this->allow('manager','manager_allow''show');
Colored with dumpz.org

Фильтрация данных в полях формы отвечающих за ввод пароля


Никогда, никогда не фильтруйте данные которые поступают из поля ввода пароля! Это чревато долгими попытками найти причину, по которой пользователи не могут залогинится. В моем случае причиной было следующее:
$f = new Zend_Filter_StripTags();
$pwd = $f->filter($this->_request->getPost('pwd'));
Colored with dumpz.org
Учитывая что пароли должны хранится в зашифрованном виде и никогда не должны выводится, наличие в них тегов, или пробелов, никаким образом не может привести к уязвимостям, соответственно и фильтрация ни к чему

Предусматривать локализацию нужно заранее


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

Отказ от использования ViewHelpers


Следует всегда использовать помощники видов url и baseUrl для формирования url и путей к статическим ресурсам. Это важно потому-что вы не можете быть уверены в том, каким образом будут расположено приложение у других разработчиков. В нашем случае пути формировались так как будто приложение лежит в корне хоста, что создало ряд проблем при развертывании на моей машине.

Использование текстовых констант вместо логических переменных


В завершении хочу рассказать про интересный способ замены логических переменных строками. В коде я нашел что-то вроде этого:
if ($a > $b)
    $this->view->result = 'ok'
else
    $this->view->result = 'fail';
Colored with dumpz.org
Здесь никакого дополнительного пояснения не требуется, я думаю последняя строка очень красноречиво делает это за меня.

Заключение


Это далеко не полный список ошибок, которые были найдены в процессе доработки проекта, но основные я описал. Надеюсь, что кто-то прочитав данный материал станет писать более качественный код. Спасибо за внимание!
Tags:
Hubs:
+54
Comments75

Articles