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

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

Fail-list

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

Первое, что сразу бросилось в глаза - выполнение запросов через объект модели прямо в коде контроллера, что напрочь перечеркивает все преимущества MVC. Чтобы было понятно о чем речь приведу пример исходного кода:

<?php
$model = new Model();
$some_data = $model->fetchAll(array('field1 = ?' => 1, 'field2 = ?' => 0));
$all_data = $model->fetchAll();

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

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

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

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

Для реализации обработки ajax-запросов можно воспользоваться, т.н. context-switch что представляет собой удобный способ изменения формата ответа. Если вы используете json то в методе init контроллера задать что-то вроде следующего:

<?php
$this->_helper->AjaxContext()->addActionContext('ajax-handler', 'json')->initContext('json');

Теперь все данные переданные во 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();
        });
    }
}

Как выяснилось потом на сервер-сайд user_id не проверялся на равенство идентификатору текущего пользователя и это печально...

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

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

<?php
// ...
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');
        }
    }
}

Чем плох такой подход? Во-первых, очень сложным является перераспределение прав доступа. Во-вторых, тяжело поддерживать несколько ролей. Я могу долго продолжать :)

Для данных целей в ZF, есть прекрасный инструмент формирования списков прав доступа или ACL. Лично я использую небольшой плагин, который проверяет права доступа данного пользователя на запрашиваемый экшн/контроллере в процессе диспатчинга. Данный способ позволяет формировать права доступа в простом, легко изменяемом списке, вроде этого:

<?php
// ...
//Добавляем роли
$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(null, null, null);
$this->allow('guest', 'guest_allow', 'show');
$this->allow('user','user_allow', 'show');
$this->allow('manager','manager_allow', 'show');

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

Никогда, никогда не фильтруйте данные которые поступают из поля ввода пароля! Это чревато долгими попытками найти причину, по которой пользователи не могут залогинится. В моем случае причиной было следующее:

<?php
//...
$f = new Zend_Filter_StripTags();
$pwd = $f->filter($this->_request->getPost('pwd'));

Учитывая что пароли должны хранится в зашифрованном виде и никогда не должны выводится, наличие в них тегов, или пробелов, никаким образом не может привести к уязвимостям, соответственно и фильтрация ни к чему

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

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

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

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

<?php
// ...
if ($a > $b)
    $this->view->result = 'ok'
else
    $this->view->result = 'fail';

Здесь никакого дополнительного пояснения не требуется, я думаю последняя строка очень красноречиво делает это за меня :D

Заключение

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