<sect1 id="zend.session.advancedusage">

    <title>Продвинутое использование<!--Advanced Usage--></title>

    <para>
        Хотя базовое использование является совершенно допустимым вариантом
        использования сессий Zend Framework, стоит рассмотреть другие
        возможности их использования. См.
        <link linkend="zend.auth.introduction.using"> пример на
        <code>Zend_Auth</code></link>, который по умолчанию неявно
        использует Zend_Session_Namespace для сохранения меток аутентификации.
        Этот пример показывает один из способов быстрой и легкой интеграции
        Zend_Session_Namespace и Zend_Auth.
<!--
        While the basic usage examples are a perfectly acceptable way to utilize Zend Framework sessions, there are some
        best practices to consider. Consider the
        <link linkend="zend.auth.introduction.using"><code>Zend_Auth</code> example</link>
        that transparently uses Zend_Session_Namespace by default to persist authentication tokens. This example shows
        one approach to quickly and easily integrate Zend_Session_Namespace and Zend_Auth.
-->
    </para>

    <sect2 id="zend.session.startingasession">

        <title>Старт сессии<!--Starting a Session--></title>

        <para>
            Если вы хотите, чтобы все запросы имели сессии и использовали
            сессии Zend Framework, то стартуйте сессию в файле загрузки:
<!--
            If you want all requests to have a session and use Zend Framework sessions, then start the session in the
            bootstrap file:
-->
        </para>

        <example>
            <title>Старт общей сессии<!--Starting the Global Session--></title>
<programlisting language="php">
<![CDATA[<?php
...
require_once 'Zend/Session.php';
Zend_Session::start();
...
?>]]></programlisting>
        </example>

        <para>
            Стартуя сессию в файле загрузки, вы исключаете вероятность того, что
            старт сессии произойдет после того, как заголовки будут
            отправлены броузеру, что вызвовет исключение и, возможно,
            отображение испорченной страницы посетителю сайта. Некоторые
            расширенные возможности Zend_Session требуют вызова
            <code>Zend_Session_Core::start()</code> в начале (больше о
            расширенных возможностях будет написано позднее).
<!--
            By starting the session in the bootstrap file, you avoid the possibility that your session might be started
            after headers have been sent to the browser, which results in an exception, and possibly a broken page for
            website viewers. Various advanced features require <code>Zend_Session_Core::start()</code> first. (More on
            advanced features later).
-->
        </para>

        <para>
            Есть четыре способа стартовать сессию, используя Zend_Session. Два
            из них - неправильные.
<!--            There are four ways to start a session, when using Zend_Session. Two are wrong.
-->
        </para>

        <itemizedlist mark='opencircle'>
            <listitem>
                <para>
                    1. Неправильно: Устанавливать опцию session.auto_start в
                    php.ini или .htaccess (http://www.php.net/manual/en/ref.session.php#ini.session.auto-start).
                    Если вы не имеете возможность отключить эту опцию в php.ini,
                    то, если используется mod_php (или его эквивалент) и в
                    php.ini уже установлена эта опция, добавьте строку
                    <code>php_value session.auto_start 0</code> в ваш файл
                    .htaccess (обычно находится в корневой директории для
                    HTML-документов).
<!--
                    1. Wrong: Do not set PHP's session.auto_start ini setting in either php.ini or .htaccess
                    (http://www.php.net/manual/en/ref.session.php#ini.session.auto-start). If you do not have the
                    ability to disable this setting in php.ini, you are using mod_php (or equivalent), and the setting
                    is already enabled in php.ini, then add <code>php_value session.auto_start 0</code> to your
                    .htaccess file (usually in your HTML document root directory).
-->
                </para>
            </listitem>
            <listitem>
                <para>
                    2. Неправильно: Непосредственно использовать функцию
                    <ulink url="http://www.php.net/session_start"><code>session_start()</code></ulink>.
                    Если вы вызываете <code>session_start()</code> напрямую и
                    начинаете использование Zend_Session_Namespace, то при
                    вызове метода <code>Zend_Session::start()</code> будет
                    сгенерировано исключение ("session has already been
                    started"). Если вы вызываете <code>session_start()</code>
                    после использования Zend_Session_Namespace или явного вызова
                    <code>Zend_Session::start()</code>, то будет сгенерирована
                    ошибка уровня E_NOTICE и проигнорирован вызов функции.
<!--
                    2. Wrong: Do not use PHP's <code>
                    <ulink url="http://www.php.net/session_start">session_start()</ulink>
                    </code> function directly. If you use <code>session_start()</code> directly, and then start using
                    Zend_Session_Namespace, an exception will be thrown by <code>Zend_Session::start()</code> ("session
                    has already been started"). If you call <code>session_start()</code>, after using
                    Zend_Session_Namespace or starting <code>Zend_Session::start()</code> explicitly, an error of level
                    E_NOTICE will be generated, and the call will be ignored.
-->
                </para>
            </listitem>
            <listitem>
                <para>
                    3. Правильно:
                    Используйте <code>Zend_Session::start()</code>. Если
                    необходимо, чтобы все запросы имели и использовали сессии,
                    то поместите вызов этой функции в коде загрузки близко к
                    точке входа и без условной логики. При этом присутствуют
                    некоторые издержки за счет сессий. Если для одних запросов
                    нужны сессии, а для других - нет, то:
<!--
                    3. Correct: Use <code>Zend_Session::start()</code>. If you want all requests to have and use
                    sessions, then place this function call early and unconditionally in your ZF bootstrap code.
                    Sessions have some overhead. If some requests need sessions, but other requests will not need to use
                    sessions, then:
-->
                </para>
                <itemizedlist mark='opencircle'>
                    <listitem>
                        <para>
                            Установите опцию <code>strict</code> в true (см.
                            <link linkend="zend.session.startingasession"><code>Zend_Session::setOptions()</code></link>) в коде загрузки.
<!--
                            Unconditionally, set the <code>strict</code> option to true (see
                            <link
                        linkend="zend.session.startingasession"><code>Zend_Session::setOptions()</code>
                            </link>
                            ) in your userland bootstrap.
-->
                        </para>
                    </listitem>
                    <listitem>
                        <para>
                            Вызывайте <code>Zend_Session::start()</code>
                            только при тех запросах, для которых нужны сессии,
                            и до того, как будет произведен первый вызов
                            <code>new Zend_Session_Namespace()</code>.
<!--
                            Call <code>Zend_Session::start()</code>, only for requests that need to use sessions, before
                            the first call to <code>new Zend_Session_Namespace()</code>.
-->
                        </para>
                    </listitem>
                    <listitem>
                        <para>
                            Используйте
                            <code>new Zend_Session_Namespace()</code> как
                            обычно и там, где это нужно, но при этом необходимо
                            убедиться, что
                            <code>Zend_Session::start()</code> был вызван ранее.
<!--
                            Use <code>new Zend_Session_Namespace()</code> normally, where needed, but make sure
                            <code>Zend_Session::start()</code> has been called previously.
-->
                        </para>
                    </listitem>
                </itemizedlist>
                <para>
                    Опция <code>strict</code> предотвращает автоматический
                    старт сессии с использованием
                    <code>Zend_Session::start()</code> при вызове
                    <code>new Zend_Session_Namespace()</code>. Эта опция
                    помогает разработчикам пользовательских областей приложений
                    ZF следовать принятому при проектировании решению не
                    использовать сессии для определенных запросов, т.к. при
                    установке этой опции и последующем инстанцировании
                    Zend_Session_Namespace до явного вызова
                    <code>Zend_Session::start()</code> будет сгенерировано
                    исключение. Не используйте эту опцию в коде библиотек ZF,
                    поскольку проектные решения должны принимать только
                    разработчики пользовательской области. Аналогичным образом,
                    все разработчики "библиотек" должны осторожно подходить
                    к использованию <code>Zend_Session::setOptions()</code>
                    в коде их библиотек, поскольку эти опции имеют глобальную
                    область действия (как и лежащие в основе опции расширения
                    ext/session).
<!--
                    The <code>strict</code> option prevents <code>new Zend_Session_Namespace()</code> from automatically
                    starting the session using <code>Zend_Session::start()</code>. Thus, this option helps developers of
                    userland ZF applications enforce a design decision to avoid using sessions for certain requests,
                    since an error will be thrown when using this option and instantiating Zend_Session_Namespace,
                    before an explicit call to <code>Zend_Session::start()</code>. Do not use this option in ZF core
                    library code, because only userland developers should make this design choice. Similarly, all
                    "library" developers should carefully consider the impact of using
                    <code>Zend_Session::setOptions()</code> on users of their library code, since these options have
                    global side-effects (as do the underlying options for ext/session).
-->
                </para>
            </listitem>
            <listitem>
                <para>
                    4. Правильно: Просто используйте
                    <code>new Zend_Session_Namespace()</code> где необходимо, и
                    сессия будет автоматически запущена в Zend_Session. Это
                    наиболее простой вариант использования, подходящий для
                    большинства случаев. Но необходимо будет следить за тем,
                    чтобы первый вызов
                    <code>new Zend_Session_Namespace()()</code> всегда
                    происходил <emphasis>до того</emphasis>, как
                    выходные данные будут отправлены клиенту (т.е. до того, как
                    агенту пользователя будут отправлены HTTP-заголовки),
                    если используются основанные на куках сессии
                    (очень рекомендуется). Использование
                    <ulink url="http://php.net/outcontrol">буферизации
                    вывода</ulink> может быть удачным решением, при этом может
                    быть улучшена производительность. Например, в
                    <code>php.ini</code>
                    "<code>output_buffering = 65535</code>" включает буферизацию
                    вывода с размером буфера 64K.
<!--
                    4. Correct: Just use <code>new Zend_Session_Namespace()</code> whenever needed, and the session will
                    be automatically started within Zend_Session. This offers extremely simple usage that works well in
                    most situations. However, you then become responsible for ensuring that the first <code>new
                    Zend_Session_Namespace()</code> happens <emphasis>before</emphasis> any output (i.e.
                    <ulink url="http://www.php.net/headers_sent">HTTP headers</ulink>
                    ) has been sent by PHP to the client, if you are using the default, cookie-based sessions (strongly
                    recommended). Using
                    <ulink url="http://php.net/outcontrol">output buffering</ulink>
                    often is sufficient to prevent this issue and may help improve performance. For example, in
                    <code>php.ini</code>, "<code>output_buffering = 65535</code>" enables output buffering with a 64K
                    buffer.
-->
                </para>
            </listitem>
        </itemizedlist>

    </sect2>

    <sect2 id="zend.session.locking">

        <title>Блокировка пространств имен<!--Locking Session Namespaces--></title>

        <para>
            Можно применять блокировку к пространствам имен для предотвращения
            изменения данных в этом пространстве имен. Используйте
            метод <code>Zend_Session_Namespace::lock()</code> для того, чтобы
            сделать определенное пространство имен доступным только для чтения,
            <code>unLock()</code> - чтобы сделать пространство имен доступным
            для чтения и изменений, а <code>isLocked()</code> для проверки того,
            не было ли пространство имен заблокировано ранее. Блокировка не
            сохраняется от одного запроса к другому. Блокировка пространства
            имен не действует на методы установки (setter methods) в объектах,
            сохраненных в пространстве имен, но предотвращает использование
            методов установки пространства имен сессии для удаления или замены
            объектов, сохраненных непосредственно в пространстве имен.
            Также блокирование пространств имен Zend_Session_Namespace не
            препятствует использованию ссылок на те же данные
            (см. <ulink url="http://www.php.net/references">PHP references</ulink>).
<!--
            Session namespaces can be locked, to prevent further alterations to the data in that namespace. Use
            <code>Zend_Session_Namespace's lock()</code> to make a specific namespace read-only, <code>unLock()</code>
            to make a read-only namespace read-write, and <code>isLocked()</code> to test if a namespace has been
            previously locked. Locks are transient and do not persist from one request to the next. Locking the
            namespace has no effect on setter methods of objects stored in the namespace, but does prevent the use of
            the namespace's setter method to remove or replace objects stored directly in the namespace. Similarly,
            locking Zend_Session_Namespace namespaces does not prevent the use of symbol table aliases to the same data
            (see
            <ulink url="http://www.php.net/references">PHP references</ulink>
            ).
-->
        </para>

        <example>
            <title>Блокировка пространств имен<!--Locking Session Namespaces--></title>
<programlisting language="php">
<![CDATA[<?php
    // assuming:
    $userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace');

    // marking session as read only locked
    $userProfileNamespace->lock();

    // unlocking read-only lock
    if ($userProfileNamespace->isLocked()) {
        $userProfileNamespace->unLock();
    }
?>]]></programlisting>
        </example>

        <para>
            Есть некоторые идеи по поводу того, как организовывать модели в
            парадигме MVC для Веб, включая создание моделей представления для
            использования видами (views). Иногда имеющиеся данные, являются ли
            они частью вашей доменной модели или нет, являются подходящими для
            этой задачи. Для того, чтобы предотвратить изменение таких данных,
            используйте блокировку пространств имен сессий до того, как
            предоставить видам доступ к этим подмножествам вашей модели
            представления.
<!--
            There are numerous ideas for how to manage models in MVC paradigms for the Web, including creating
            presentation models for use by views. Sometimes existing data, whether part of your domain model or not, is
            adequate for the task. To discourage views from applying any processing logic to alter such data, consider
            locking session namespaces before permitting views to access this subset of your "presentation" model.
-->
        </para>

        <example>
            <title>Блокировка сессий в видах<!--Locking Sessions in Views--></title>
<programlisting language="php">
<![CDATA[<?php
class FooModule_View extends Zend_View
{
    public function show($name)
    {
        if (!isset($this->mySessionNamespace)) {
            $this->mySessionNamespace = Zend::registry('FooModule');
        }

        if ($this->mySessionNamespace->isLocked()) {
            return parent::render($name);
        }

        $this->mySessionNamespace->lock();
        $return = parent::render($name);
        $this->mySessionNamespace->unLock();

        return $return;
    }
}
?>]]></programlisting>
        </example>

    </sect2>

    <sect2 id="zend.session.expiration">

        <title>Время жизни пространства имен<!--Namespace Expiration--></title>

        <para>
            Время жизни может быть ограничено как у пространства имен в целом,
            так и у отдельных ключей. Общие случаи использования
            включают в себя передачу временной информации между запросами
            и повышение защищенности от определенных угроз безопасности
            посредством устранения доступа к потенциально чувствительной
            информации по прошествии некоторого времени после
            аутентификации. Истечение времени жизни может быть основано на
            количестве секунд или на концепции "прыжков" (hops), в которой
            "прыжком" считается каждый успешный запрос, в котором активируется
            пространство имен через, как минимум, один
            <varname>$space = new Zend_Session_Namespace('myspace');</varname>.
<!--
            Limits can be placed on the longevity of both namespaces and
            individual keys in namespaces. Common use cases
            include passing temporary information between requests, and
            reducing exposure to certain security risks by
            removing access to potentially sensitive information some time
            after authentication occurred. Expiration can
            be based on elapsed seconds, or based on the concept of "hops",
            where a hop occurs for each successive
            request that activates the namespace via at least one
            <varname>$space = new Zend_Session_Namespace('myspace');</varname>.
-->
        </para>

        <example>
            <title>Примеры установки времени жизни<!--Expiration Examples--></title>
<programlisting language="php">
<![CDATA[<?php
$s = new Zend_Session_Namespace('expireAll');
$s->a = 'apple';
$s->p = 'pear';
$s->o = 'orange';

// Время жизни установлено только для ключа "a" (5 секунд)
$s->setExpirationSeconds(5, 'a');

// Время жизни всего пространства имен - 5 "прыжков"
$s->setExpirationHops(5);

$s->setExpirationSeconds(60);
// Пространство имен "expireAll" будет помечено как с истекшим временем жизни
// при первом запросе, произведенном после того, как прошло 60 секунд,
// или после 5 "прыжков" - в зависимости от того, что произошло раньше
?>]]></programlisting>
        </example>

        <para>
            При работе с данными, время жизни которых истекает в текущем запросе,
            будьте внимательны при их извлечении. Несмотря на то, что данные
            возвращаются по ссылке, изменение этих данных не приведет к их
            сохранению после текущего запроса. Для "сброса" времени истечения
            извлеките данные во временные переменные, уничтожьте эти данные в
            пространстве имен и затем установите соответствующий ключ снова.
<!--
            When working with data expiring from the session in the current
            request, care should be used when retrieving
            it. Although the data is returned by reference, modifying the data
            will not make expiring data persist past
            the current request. In order to "reset" the expiration time, fetch
            the data into temporary variables, use
            the namespace to unset it, and then set the appropriate keys again.
-->
        </para>

    </sect2>

    <sect2 id="zend.session.controllers">

        <title>Инкапсуляция сессий и контроллеры<!--Session Encapsulation and Controllers--></title>

        <para>
            Пространства имен могут также использоваться для разделения доступа
            контроллеров к сессиям, чтобы защитить переменные от повреждения.
            Например, контроллер 'Zend_Auth' может хранить свои постоянные
            данные сессии отдельно от всех остальных контроллеров.
<!--
            Namespaces can also be used to separate session access by controllers to protect variables from
            contamination. For example, the 'Zend_Auth' controller might keep its session state data separate from all
            other controllers.
-->
        </para>

        <example>
            <title>Сессии с пространствами имен для контроллеров с автоматическим истечением времени<!--Namespaced Sessions for Controllers with Automatic Expiration--></title>
<programlisting language="php">
<![CDATA[<?php
require_once 'Zend/Session.php';
// контроллер для вывода вопроса
$testSpace = new Zend_Session_Namespace('testSpace');
// установка времени жизни только для этой переменной
$testSpace->setExpirationSeconds(300, "accept_answer");
$testSpace->accept_answer = true;

--

// контроллер для обработки ответа на вопрос
$testSpace = new Zend_Session_Namespace('testSpace');

if ($testSpace->accept_answer === true) {
    // время не истекло
}
else {
    // время истекло
}
?>]]></programlisting>
        </example>

    </sect2>

    <sect2 id="zend.session.limitinginstances">

        <title>Ограничение количества экземпляров Zend_Session_Namespace до одного на каждое пространство имен<!--Limiting Instances of Zend_Session to One Per Namespace--></title>

        <para>
            Мы рекомендуем использовать блокировку сессии (см. выше) вместо этой
            функциональной возможности, которая накладывает дополнительное бремя
            на разработчика, состоящее в передаче экземпляров
            Zend_Session_Namespace во все функции и объекты, нуждающихся в
            использовании этих пространств имен.
<!--
            We recommend using session locking (see above) instead of the feature below, which places extra management
            burden on the developer to pass any Zend_Session_Namespace instances into whatever functions and objects
            need access to each namespace.
-->
        </para>

        <para>
            Когда создается первый экземпляр Zend_Session_Namespace, связанный с
            определенным пространством имен, вы можете дать команду
            Zend_Session_Namespace больше не создавать объекты для этого
            пространства имен. Таким образом, в дальнейшем попытка создать
            экземпляр Zend_Session_Namespace для
            того же пространства имен вызовет генерацию исключения. Это
            поведение является опциональным и не принято по умолчанию, но
            остается доступным для тех, кто предпочитает передавать по коду
            единственный объект для каждого пространства имен. Это повышает
            защиту пространства имен от изменений компонентами, которые не
            должны делать этого, поскольку тогда они не будут иметь свободного
            доступа к пространствам имен. Тем не менее, ограничение пространства
            имен до одного экземпляра модет привести к большему объему кода или
            к его усложнению, поскольку он отменяет возможность использования
            директив вида
            <varname>$aNamespace = new Zend_Session_Namespace('aNamespace');</varname>
            после того, как был создан первый экземпляр. Это продемонстрировано
            в примере ниже:
<!--
            When constructing the first instance of Zend_Session_Namespace attached to a specific namespace, you can
            also instruct Zend_Session_Namespace to not make any more instances for that namespace. Thus, any future
            attempts to construct a Zend_Session_Namespace instance having the same namespace will throw an error. Such
            behavior is optional, and not the default behavior, but remains available to those who prefer to pass around
            a single instance object for each namespace. This increases protection from changes by components that
            should not modify a particular session namespace, because they won't have easy access. However, limiting a
            namespace to a single instance may lead to more code or more complex code, as it removes access to the
            convient <varname>$aNamespace = new Zend_Session_Namespace('aNamespace');</varname>, after the first intance has
            been created, as follows in the example below:
-->
        </para>

        <example>
            <title>Ограничение до единичных экземпляров<!--Limiting to Single Instances--></title>
<programlisting language="php">
<![CDATA[<?php
    require_once 'Zend/Session.php';
    $authSpaceAccessor1 = new Zend_Session_Namespace('Zend_Auth');
    $authSpaceAccessor2 = new Zend_Session_Namespace('Zend_Auth', Zend_Session_Namespace::SINGLE_INSTANCE);
    $authSpaceAccessor1->foo = 'bar';
    assert($authSpaceAccessor2->foo, 'bar');
    doSomething($options, $authSpaceAccessor2);
    .
    .
    .
    $aNamespaceObject = new Zend_Session_Namespace('Zend_Auth'); // это вызовет ошибку
?>]]></programlisting>
        </example>

        <para>
            Второй параметр в конструкторе выше говорит Zend_Session, что
            в будущем создание любых других экземпляров Zend_Session_Namespace с
            пространством имен 'Zend_Auth' не допустимо. Поскольку
            директиву <code>new Zend_Session_Namespace('Zend_Auth')</code>
            нельзя использовать после того, как будет выполнен приведенный выше
            код, то разработчику нужно будет где-либо сохранять объект
            (<varname>$authSpaceAccessor2</varname> в
            примере выше), если в дальнейшем при обработке того же запроса
            необходим доступ к этому пространству имен сессии.
            Например, вы можете сохранять экземпляр в статической переменной или
            передавать его другим методам, которым нужен доступ к данному
            пространству имен.
<!--
            The second parameter in the constructor above will tell Zend_Session_Namespace that any future
            Zend_Session's that are instantiated with the 'Zend_Auth' namespace are not allowed, and will thus cause an
            exception. Since <code>new Zend_Session_Namespace('Zend_Auth')</code> will not be allowed after the code
            above has been executed, the developer becomes responsible for storing the instance object
            (<varname>$authSpaceAccessor2</varname> in the example above) somewhere, if access to this session namespace is
            needed at a later time during the same request. For example, a developer may store the instance in a static
            variable, or pass it to other methods that might need access to this session namespace. Session locking (see
            above) provides a more convenient, and less burdensome approach to limiting access to namespaces.
-->
        </para>

    </sect2>

    <sect2 id="zend.session.modifyingarray">

        <title>Работа с массивами в пространствах имен<!--Working with Arrays in Namespaces--></title>

        <para>
            Изменение массива внутри пространства имен невозможно. Простейшим
            решением является сохранение массивов после того, как все желаемые
            значения были установлены. <ulink url="http://framework.zend.com/issues/browse/ZF-800">ZF-800</ulink>
            подтверждает известный баг, затрагивающий многие PHP-приложения,
            использующие "магические" методы и массивы.

<!--
            Modifying an array inside a namespace does not work. The simplest solution is to store arrays after all
            desired values have been set.
            <ulink url="http://framework.zend.com/issues/browse/ZF-800">ZF-800</ulink>
            documents a known issue affecting many PHP applications using magic methods and arrays.
-->
        </para>

        <example>
            <title>Известные проблемы с массивами<!--Known problem with arrays--></title>
<programlisting language="php">
<![CDATA[<?php
    $sessionNamespace = new Zend_Session_Namespace('Foo');
    $sessionNamespace->array = array();
    $sessionNamespace->array['testKey'] = 1; // Не работает в версиях ниже PHP 5.2.1
?>]]></programlisting>
        </example>

        <para>
            Если вам нужно изменить массив после того, как добавили его в
            пространство имен, извлеките массив, произведите необходимые
            изменения и сохраните его под тем же ключом в пространстве имен.
<!--
            If you need to modify the array after assigning it to a session namespace key, fetch the array, then
            modify it and save the array back to the session namespace.
-->
        </para>

        <example>
            <title>Обходной путь: извлечение, изменение и сохранение<!--Workaround: fetch, modify, save--></title>
<programlisting language="php">
<![CDATA[<?php
    $sessionNamespace = new Zend_Session_Namespace('Foo');
    $sessionNamespace->array = array('tree' => 'apple');
    $tmp = $sessionNamespace->array;
    $tmp['fruit'] = 'peach';
    $sessionNamespace->array = $tmp;
?>]]></programlisting>
        </example>

        <para>
            Можно также сохранить массив, содержащий ссылку на желаемый массив
            и косвенно работать с ним.
<!--
            Alternatively, store an array containing a reference to the desired array, and then access it indirectly.
-->
        </para>

        <example>
            <title>Обходной путь: сохранение массива, содержащего ссылку<!--Workaround: store array containing reference--></title>
<programlisting language="php">
<![CDATA[<?php
    $myNamespace = new Zend_Session_Namespace('mySpace');

    // работает даже с версиями PHP, содержащими баг
    $a = array(1,2,3);
    $myNamespace->someArray = array( & $a ) ;
    $a['foo'] = 'bar';
?>]]></programlisting>
        </example>

    </sect2>

    <sect2 id="zend.session.auth">

        <title>Использование сессий вместе с аутентификацией<!--Using Sessions with Authentication--></title>

        <para>
            Если ваш адаптер аутентификации для <code>Zend_Auth</code>
            возвращает результат, в котором идетификатором авторизации является
            объект (не рекомендуется) вместо массива, то выполняйте проверку
            класса идентификатора авторизации до того, как стартовать сессию.
            Вместо этого мы рекомендуем хранить идентификаторы авторизации,
            вычисленные в адаптере авторизации, под хорошо известным ключом в
            пространстве имен сессии. Например, по умолчанию
            <code>Zend_Auth</code> размещает идентификаторы под ключом 'storage'
            пространства имен 'Zend_Auth'.
<!--
            If your authentication adapter for <code>Zend_Auth</code> returns a result where the authorization identity
            is an object (not recommended), instead of an array, then make sure to require your authorization identity
            class definition before starting the session. Instead, we recommend storing the authorization ids computed
            within an authentication adapter inside a well-known key in a session namespace. For example, the default
            behavior of <code>Zend_Auth</code> places this in the 'storage' key of the 'Zend_Auth' namespace.
-->
        </para>

        <para>
            Если вы приказали <code>Zend_Auth</code> не сохранять метку сессии в
            сессиях, то можете вручную сохранять ID авторизации под хорошо
            известным ключом в любом пространстве имен сессии.
            Часто приложения имеют свои
            требования к тому, где хранить "мандат" (учетная запись с
            праметрами доступа пользователя) и идентификатор авторизации.
            Приложения часто устанавливают соответствие идентификаторов
            аутентификации (например, имена пользователей) и идентификаторов
            авторизации (например, присвоенное уникальное целое число) во время
            аутентификации, которая должна производится внутри метода
            <code>authenticate()</code> адаптера аутентификации Zend_Auth.
<!--
            If you tell <code>Zend_Auth</code> to not persist authentication tokens in sessions, then you can manually
            store the authorization id in the session namespace, in a well-known location in a session namespace of your
            choice. Often, applications have specific needs about where to store credentials used (if any) and
            "authorization" identity. Applications often map authentication identities (e.g. usernames) to authorization
            identities (e.g. a uniquely assigned integer) during authentication, which would occur in the Zend_Auth
            authentication adapter's <code>authenticate()</code> method.
-->
        </para>

        <example>
            <title>Пример: Простой доступ к ID авторизации<!--Example: Simplified access of authorization ids--></title>
<programlisting language="php">
<![CDATA[<?php
    // pre-authentication request
    require_once 'Zend/Auth/Adapter/Digest.php';
    $adapter = new Zend_Auth_Adapter_Digest($filename, $realm, $username, $password);
    $result = $adapter->authenticate();
    require_once 'Zend/Session/Namespace.php';
    $namespace = new Zend_Session_Namespace('Zend_Auth');
    if ($result->isValid()) {
        $namespace->authorizationId = $result->getIdentity();
        $namespace->date = time();
    } else {
        $namespace->attempts++;
    }

    // subsequent requests
    require_once 'Zend/Session.php';
    Zend_Session::start();
    $namespace = new Zend_Session_Namespace('Zend_Auth');

    echo "Valid: ", (empty($namespace->authorizationId) ? 'No' : 'Yes'), "\n"';
    echo "Authorization / user Id: ", (empty($namespace->authorizationId)
        ? 'none' : print_r($namespace->authorizationId, true)), "\n"';
    echo "Authentication attempts: ", (empty($namespace->attempts)
        ? '0' : $namespace->attempts), "\n"';
    echo "Authenticated on: ",
        (empty($namespace->date) ? 'No' : date(DATE_ATOM, $namespace->date), "\n"';
?>]]></programlisting>
        </example>

        <para>
            Идентификаторы авторизации, хранящиеся на клиентской стороне, могут
            использоваться в атаках на поднятие привилегий, если им доверяет
            серверная сторона и если они, например, не дублируются на серверной
            стороне (например, в данных сессии) и затем сверяются с
            идентификатором авторизации, предоставленным клентом для
            действующией сессии. Мы различаем понятия "идентификаторов
            аутентификации" (например, имена пользователей) и "идентификаторов
            авторизации" (например, ID пользователя #101 в таблице БД для
            пользователей).
<!--
            Authorization ids stored client-side are subject to privilege escalation vulnerabilities, if these ids are
            used and trusted by the server, unless, for example, the id is duplicated on the server-side (e.g. in the
            session) and then cross-checked with the authorization id claimed by the client for the in-effect session.
            We are differentiating between "authentication ids" (e.g. usernames) and "authorization ids" (e.g. user id
            #101 in the users DB table).
-->
        </para>

        <para>
            Последнее часто используется для повышения производительности -
            например, для выборки из пула серверов, кеширующих данные сессии,
            чтобы решить проблему "курицы и яйца". Часто
            возникают дебаты о том, использовать ли настоящий ID авторизации в
            куках или некую замену, которая помогает установить соответствие
            с настоящим ID авторизации (или сессии сервера(ов), хранящего
            сессию/профиль пользователя и т.д.), в то время как некоторые
            архитекторы системной безопасности предпочитают избегать
            публикования истинных значений первичных ключей, пытаясь достичь
            некоторого дополнительного уровня защиты в случае наличия
            уязвимостей к SQL-инъекциям.
<!--
            The latter is not uncommon for performance reasons, such as helping select from a pool of servers caching
            session information to help solve chicken-and-egg problems. Often debates ensue about whether to use the
            real authorization id in the cookie, or some substitute that aids in mapping to the real authorization id
            (or session or server(s) holding the user's session/profile, etc.), as some system security architects wish
            to prevent true "DB primary keys" from escaping into the wild. These architects try and obtain some level of
            protection by obfuscation in the event of a SQL injection vulnerability in their system. Not everyone uses
            auto-increment strategies for authorization ids.
-->
        </para>

    </sect2>

    <sect2 id="zend.session.testing">

        <title>Использование сессий с юнит-тестами<!--Using Sessions with Unit Tests--></title>

        <para>
            Zend Framework использует PHPUnit для своего тестирования. Многие
            разработчики расширяют существующие наборы
            юнит-тестов для покрытия кода в своих приложениях.
            Если при выполнении юнит-тестирований после завершения сессии были
            использованы любые связанные с записью методы, то генерируется
            исключение "<emphasis>Zend_Session is currently marked
            as read-only</emphasis>" ("Zend_Session помечен как доступный только
            для чтения"). Тем не менее, юнит-тесты, использующие Zend_Session,
            требуют особого внимания в разработке, поскольку закрытие
            (<code>Zend_Session::writeClose()</code>) или уничтожение сессии
            (<code>Zend_Session::destroy()</code>) не дает впоследствии
            устанавливать или сбрасывать ключи в любом объекте
            Zend_Session_Namespace. Это поведение является прямым следствием
            использования лежащего в основе расширения ext/session,
            функций <code>session_destroy()</code> и
            <code>session_write_close()</code>, которые не имеют механизма
            "отмены" для облегчения установки/демонтажа в юнит-тестировании.
<!--
            Zend Framework relies on PHPUnit to facilitate testing of itself. Many developers extend the existing
            suite of unit tests to cover the code in their applications. The exception
            "<emphasis>Zend_Session is currently marked as read-only</emphasis>" is thrown while
            performing unit tests, if any write-related methods are used after ending the session. However, unit tests
            using Zend_Session require extra attention, because closing (<code>Zend_Session::writeClose()</code>), or
            destroying a session (<code>Zend_Session::destroy()</code>) prevents any further setting or unsetting of
            keys in any Zend_Session_Namespace. This behavior is a direct result of the underlying ext/session mechanism
            and PHP's <code>session_destroy()</code> and <code>session_write_close()</code>, which has no "undo"
            mechanism to facilitate setup/teardown with unit tests.
-->
        </para>

        <para>
            Чтобы обойти это, см. юнит-тест
            <code>testSetExpirationSeconds()</code> в
            <code>tests/Zend/Session/SessionTest.php</code> и
            <code>SessionTestHelper.php</code>, которые используют
            <code>exec()</code> для запуска отдельного процесса. Новый процесс
            более точно имитирует второй, последующий, запрос из броузера.
            Отдельный процесс начинается с "чистой" сессии, так же, как при
            выполнении любого PHP-скрипта для веб-запроса. Кроме этого,
            любые изменения в <varname>$_SESSION[]</varname>, произведенные при вызове
            процесса, становятся доступными и в дочернем процессе, что дает
            родительскому процессу возможность закрыть сессию до использования
            <code>exec()</code>.
<!--
            To work around this, see the unit test <code>testSetExpirationSeconds()</code> in
            <code>tests/Zend/Session/SessionTest.php and SessionTestHelper.php</code>, which make use of PHP's
            <code>exec()</code> to launch a separate process. The new process more accurately simulates a second,
            successive request from a browser. The separate process begins with a "clean" session, just like any PHP
            script execution for a web request. Also, any changes to <varname>$_SESSION[]</varname> made in the calling
            process become available to the child process, provided the parent closed the session before using
            <code>exec()</code>
-->
        </para>

        <example>
            <title>Использование PHPUnit для тестирования кода, написанного с использованием Zend_Session*<!--Using PHPUnit to test code written using Zend_Session*--></title>
<programlisting language="php">
<![CDATA[<?php
        // testing setExpirationSeconds()
        require 'tests/Zend/Session/SessionTestHelper.php'; // also see SessionTest.php in trunk/
        $script = 'SessionTestHelper.php';
        $s = new Zend_Session_Namespace('space');
        $s->a = 'apple';
        $s->o = 'orange';
        $s->setExpirationSeconds(5);

        Zend_Session::regenerateId();
        $id = Zend_Session::getId();
        session_write_close(); // release session so process below can use it
        sleep(4); // not long enough for things to expire
        exec($script . "expireAll $id expireAll", $result);
        $result = $this->sortResult($result);
        $expect = ';a === apple;o === orange;p === pear';
        $this->assertTrue($result === $expect,
            "iteration over default Zend_Session namespace failed; expecting result === '$expect', but got '$result'");

        sleep(2); // long enough for things to expire (total of 6 seconds waiting, but expires in 5)
        exec($script . "expireAll $id expireAll", $result);
        $result = array_pop($result);
        $this->assertTrue($result === '',
            "iteration over default Zend_Session namespace failed; expecting result === '', but got '$result')");
        session_start(); // resume artificially suspended session

        // We could split this into a separate test, but actually, if anything leftover from above
        // contaminates the tests below, that is also a bug that we want to know about.
        $s = new Zend_Session_Namespace('expireGuava');
        $s->setExpirationSeconds(5, 'g'); // now try to expire only 1 of the keys in the namespace
        $s->g = 'guava';
        $s->p = 'peach';
        $s->p = 'plum';

        session_write_close(); // release session so process below can use it
        sleep(6); // not long enough for things to expire
        exec($script . "expireAll $id expireGuava", $result);
        $result = $this->sortResult($result);
        session_start(); // resume artificially suspended session
        $this->assertTrue($result === ';p === plum',
            "iteration over named Zend_Session namespace failed (result=$result)");
?>]]></programlisting>
        </example>

    </sect2>

</sect1>
