В последнее время я все сильнее и сильнее убеждаюсь в универсальности Zend Framework, как платформы для создания web-приложений. Сегодня я расскажу о процессе создания каркаса сайта на Zend Framework, который предоставит необходимую основу для реализации сайтов средней сложности:
  • Часть первая
    • Аутентификация — вход пользователей в систему
    • ACL — распределение прав доступа

  • Часть вторая
    • Маршрутизация — настройка url для различных компонентов системы
    • Registry — быстрый доступ к системным константам

Аутентификация


Аутентификация — неотъемлемая часть практически любого сайта. Как вам известно для этих целей в Zend Framework используется специальный компонент Zend_Auth. Данный компонент позволяет производить процесс входа в систему путем проверки совпадения пары логин-пароль. Обычно точкой входа является специальный action аутентификации, а также возможно экшн регистрации (или подтверждения регистрации). Рассмотрим простой пример аутентификации:
public function authAction(){
        $form = new Application_Form_Enter();
        if ($form->isValid($this->getRequest()->getPost())){
            $bootstrap = $this->getInvokeArg('bootstrap');
            $auth = Zend_Auth::getInstance();
            $adapter = $bootstrap->getPluginResource('db')->getDbAdapter();
            $authAdapter = new Zend_Auth_Adapter_DbTable(
                                                       $adapter, 'user', 'login', 
                                                       'password', 'MD5(?)'
                                                  );
            $authAdapter->setIdentity($form->login->getValue());
            $authAdapter->setCredential($form->password->getValue());
            $result = $auth->authenticate($authAdapter);
            // Если валидация прошла успешно сохраняем в storage инфу о пользователе
            if ($result->isValid()){
                $storage = $auth->getStorage();
                $storage_data = $authAdapter->getResultRowObject(
                                         null, 
                                         array('activate', 'password', 'enabled'));
                $user_model = new Application_Model_DbTable_User();
                $language_model = new Application_Model_DbTable_Language();
                $storage_data->status = 'user';
                $storage->write($storage_data);
            }
    }
}

Это типичный код аутентификации, который вы можете использовать. Для полноценной работы вам понадобится соответствующая форма, которая пересылает логин и пароль на данный action.
После успешной аутентификации пользовательские данные сохраняются в хранилище (storage) Zend_Auth. Далее, когда вам потребуется узнать какую-нибудь информацию о текущем пользователе, вы можете обратится к Zend_Auth (он доступен везде, т.к. является реализацией Singleton) следующим образом:
$auth = Zend_Auth::getInstance();
// Если пользователь аутентифицирован
if ($auth->hasIdentity()){
    // Считываем данные о пользователе
    $user_data = $auth->getStorage()->read();
}

Также важным действием, которое нужно выполнить, является начальная инициализация Zend_Auth при первом заходе пользователя на сайт. Для этого добавим в bootstrap следующий метод:
public function _initAuth(){
    $auth = Zend_Auth::getInstance();
    $data = $auth->getStorage()->read();
    if (!isset($data->status)){
        $storage_data = new stdClass();
        $storage_data->status = 'guest';
        $auth->getStorage()->write($storage_data);
    }
}

Acl


В большинстве web-приложений есть несколько статусов доступа, каждый из которых имеет определённые привилегии. Для большинства сайтов привилегии и их распределение являются относительно постоянными, поэтому мы реализуем Acl в виде правил записанных непосредственно в коде программы. Если же вы разрабатываете систему, которая имеет часто меняющуюся структуру статусов и прав (например CMS), то вам необходимо строить более гибкую реализацию Acl, права в которой будут хранится, например, в базе данных.
Основными задачами, которые должна выполнять система контроля доступа, является задание привилегий и собственно контроль за доступом. Для реализации этих задач нам потребуется два компонента:
  • Acl — списки прав доступа
  • Плагин выполняющий проверку корректности доступа текущего пользователя к запрашиваемому ресурсу

Рассмотрим самый простой случай, когда ресурсами являются части сайта, т.е. в терминах MVC — действия. Каждый пользователь наследует права от некого абстрактного статуса (гость, пользователь, администратор), привилегии каждого статуса описываются в Acl. Для реализации Acl расширим Zend_Acl:
class Acl extends Zend_Acl {
    public function  __construct() {
        //Добавляем роли
        $this->addRole('guest');
        $this->addRole('user', 'guest');
        $this->addRole('admin', '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('user_allow'));
        $this->add(new Zend_Acl_Resource('user/index'), 'user_allow');
        // ...

        // РЕСУРСЫ АДМИНА !
        $this->add(new Zend_Acl_Resource('admin_allow'));
        $this->add(new Zend_Acl_Resource('admin/index'), 'admin_allow');
        //...        

        //Выставляем права, по-умолчанию всё запрещено
        $this->deny(null, null, null);
        $this->allow('guest', 'guest_allow', 'show');
        $this->allow('user', 'user_allow', 'show');
        $this->allow('admin','admin_allow', 'show');
    }

    public function can($privilege='show'){
        //Инициируем ресурс
        $request = Zend_Controller_Front::getInstance()->getRequest();
        $resource = $request->getControllerName() . '/' . $request->getActionName();
        //Если ресурс не найден закрываем доступ
        if (!$this->has($resource))
            return true;
        //Инициируем роль
        $storage_data = Zend_Auth::getInstance()->getStorage()->read();
        $role = array_key_exists('status', $storage_data)?$storage_data->status : 'guest';
        return $this->isAllowed($role, $resource, $privilege);
    }
}

Разместите этот код в файле application/classes/Acl.php.
Описываем списки прав в стандартной для ZF форме. Также здесь создается метод, который проверяет права доступа текущего пользователя к текущему действию. В качестве идентификаторов ресурсов используется формат controller/action. Если вы проектируете систему таким образом что у вас права действий внутри контроллера не меняются, то вы можете использовать вместо идентификаторов ресурсов только имена контроллеров (не забудьте изменить метод can).
Для большей гибкость мы добавляем понятие «привилег��я», которая позволит контролировать определенные действия внутри экшена. Привилегия для просмотра имеет название «show».
Теперь когда у нас есть список прав доступа и мы умеем определять имеет ли пользователь доступ к текущему действию, нужно внедрить проверку в цикл обработки запросов Zend Framework. Для этого лучше всего подходит создание front controller plugin. Плагины позволяют выполнять заданные действия на различных этапах процесса диспатчинга:
class CheckAccess extends Zend_Controller_Plugin_Abstract {
    /**
     * Метод preDispatch выполняет проверку прав доступа на
     * данный controller/action в случае ошибки вызывает метод
     * generateAccessError
     * 
     * @param Zend_Controller_Request_Abstract $request
     */
    public function  preDispatch(Zend_Controller_Request_Abstract $request) {
        $acl = Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('Acl');
        if (!$acl->can()){
            $this->generateAccessError();
        }
    }

    /**
     * Метод генерации сообщения о ошибке прав доступа.
     * Выполняет перенаправление на контроллер error и выводит в нём
     * сообщение о ошибке передаваемое в метод.
     * 
     * @param string $msg
     */
    public function generateAccessError($msg='Доступ запрещён!'){
        $request = $this->getRequest();
        $request->setControllerName ('error');
        $request->setActionName('error');
        $request->setParam('message', $msg);
    }
}

Разместите этот код в файле application/plugins/CheckAccess.php.
Этот плагин будет выполнять проверку доступа пользователя при каждом, запросе поступающем на сайт. Для проверки доступа используется класс Acl, рассмотренный выше. В случае ошибки запрос будет целенаправлен на error/error. Для того, чтобы корректно выводилось сообщение об ошибке, вам нужно добавить соответствующий код в ErrorController.php.
Теперь нужно подключить плагин и создать ресурс Acl в bootstrap:
public function _initAcl(){
    Zend_Loader::loadClass('Acl');
    Zend_Loader::loadClass('CheckAccess');
    Zend_Controller_Front::getInstance()->registerPlugin(new CheckAccess());
    return new Acl();
} 

Для того чтобы Zend_Loader «знал» где искать наши файлы добавим в application.ini:
includePaths.plugins = APPLICATION_PATH "/plugins"
includePaths.classes = APPLICATION_PATH "/classes"

P.S. в данном посте рассматривается лишь один из способов реализации контроля доступа, который не является универсальным, но удовлетворяет потребности большинства не больших сайтов