Здравствуйте! На днях подвернулась работа - дописать сайт на 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
Заключение
Это далеко не полный список ошибок, которые были найдены в процессе доработки проекта, но основные я описал. Надеюсь, кто-то прочитав данный материал станет писать лучше. Спасибо за внимание!