Zend_Session-AdvancedUsage.xml 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. <sect1 id="zend.session.advancedusage">
  2. <title>Расширенное использование<!--Advanced Usage--></title>
  3. <para>
  4. Хотя базовое использование является совершенно допустимым вариантом
  5. использования сессий Zend Framework, стоит рассмотреть другие
  6. возможности их использования. См.
  7. <link linkend="zend.auth.introduction.using"> пример на
  8. <code>Zend_Auth</code></link>, который по умолчанию неявно
  9. использует Zend_Session_Namespace для сохранения меток аутентификации.
  10. Этот пример показывает один из способов быстрой и легкой интеграции
  11. Zend_Session_Namespace и Zend_Auth.
  12. <!--
  13. While the basic usage examples are a perfectly acceptable way to utilize Zend Framework sessions, there are some
  14. best practices to consider. Consider the
  15. <link linkend="zend.auth.introduction.using"><code>Zend_Auth</code> example</link>
  16. that transparently uses Zend_Session_Namespace by default to persist authentication tokens. This example shows
  17. one approach to quickly and easily integrate Zend_Session_Namespace and Zend_Auth.
  18. -->
  19. </para>
  20. <sect2 id="zend.session.startingasession">
  21. <title>Старт сессии<!--Starting a Session--></title>
  22. <para>
  23. Если вы хотите, чтобы все запросы имели сессии и использовали
  24. сессии Zend Framework, то стартуйте сессию в файле загрузки:
  25. <!--
  26. If you want all requests to have a session and use Zend Framework sessions, then start the session in the
  27. bootstrap file:
  28. -->
  29. </para>
  30. <example>
  31. <title>Старт общей сессии<!--Starting the Global Session--></title>
  32. <programlisting language="php">
  33. <![CDATA[<?php
  34. ...
  35. require_once 'Zend/Session.php';
  36. Zend_Session::start();
  37. ...
  38. ?>]]></programlisting>
  39. </example>
  40. <para>
  41. Стартуя сессию в файле загрузки, вы исключаете вероятность того, что
  42. старт сессии произойдет после того, как заголовки будут
  43. отправлены броузеру, что вызвовет исключение и, возможно,
  44. отображение испорченной страницы посетителю сайта. Некоторые
  45. расширенные возможности Zend_Session требуют вызова
  46. <code>Zend_Session_Core::start()</code> в начале (больше о
  47. расширенных возможностях будет написано позднее).
  48. <!--
  49. By starting the session in the bootstrap file, you avoid the possibility that your session might be started
  50. after headers have been sent to the browser, which results in an exception, and possibly a broken page for
  51. website viewers. Various advanced features require <code>Zend_Session_Core::start()</code> first. (More on
  52. advanced features later).
  53. -->
  54. </para>
  55. <para>
  56. Есть четыре способа стартовать сессию, используя Zend_Session. Два
  57. из них - неправильные.
  58. <!-- There are four ways to start a session, when using Zend_Session. Two are wrong.
  59. -->
  60. </para>
  61. <itemizedlist mark='opencircle'>
  62. <listitem>
  63. <para>
  64. 1. Неправильно: Устанавливать опцию session.auto_start в
  65. php.ini или .htaccess (http://www.php.net/manual/en/ref.session.php#ini.session.auto-start).
  66. Если вы не имеете возможность отключить эту опцию в php.ini,
  67. то, если используется mod_php (или его эквивалент) и в
  68. php.ini уже установлена эта опция, добавьте строку
  69. <code>php_value session.auto_start 0</code> в ваш файл
  70. .htaccess (обычно находится в корневой директории для
  71. HTML-документов).
  72. <!--
  73. 1. Wrong: Do not set PHP's session.auto_start ini setting in either php.ini or .htaccess
  74. (http://www.php.net/manual/en/ref.session.php#ini.session.auto-start). If you do not have the
  75. ability to disable this setting in php.ini, you are using mod_php (or equivalent), and the setting
  76. is already enabled in php.ini, then add <code>php_value session.auto_start 0</code> to your
  77. .htaccess file (usually in your HTML document root directory).
  78. -->
  79. </para>
  80. </listitem>
  81. <listitem>
  82. <para>
  83. 2. Неправильно: Непосредственно использовать функцию
  84. <ulink url="http://www.php.net/session_start"><code>session_start()</code></ulink>.
  85. Если вы вызываете <code>session_start()</code> напрямую и
  86. начинаете использование Zend_Session_Namespace, то при
  87. вызове метода <code>Zend_Session::start()</code> будет
  88. сгенерировано исключение ("session has already been
  89. started"). Если вы вызываете <code>session_start()</code>
  90. после использования Zend_Session_Namespace или явного вызова
  91. <code>Zend_Session::start()</code>, то будет сгенерирована
  92. ошибка уровня E_NOTICE и проигнорирован вызов функции.
  93. <!--
  94. 2. Wrong: Do not use PHP's <code>
  95. <ulink url="http://www.php.net/session_start">session_start()</ulink>
  96. </code> function directly. If you use <code>session_start()</code> directly, and then start using
  97. Zend_Session_Namespace, an exception will be thrown by <code>Zend_Session::start()</code> ("session
  98. has already been started"). If you call <code>session_start()</code>, after using
  99. Zend_Session_Namespace or starting <code>Zend_Session::start()</code> explicitly, an error of level
  100. E_NOTICE will be generated, and the call will be ignored.
  101. -->
  102. </para>
  103. </listitem>
  104. <listitem>
  105. <para>
  106. 3. Правильно:
  107. Используйте <code>Zend_Session::start()</code>. Если
  108. необходимо, чтобы все запросы имели и использовали сессии,
  109. то поместите вызов этой функции в коде загрузки близко к
  110. точке входа и без условной логики. При этом присутствуют
  111. некоторые издержки за счет сессий. Если для одних запросов
  112. нужны сессии, а для других - нет, то:
  113. <!--
  114. 3. Correct: Use <code>Zend_Session::start()</code>. If you want all requests to have and use
  115. sessions, then place this function call early and unconditionally in your ZF bootstrap code.
  116. Sessions have some overhead. If some requests need sessions, but other requests will not need to use
  117. sessions, then:
  118. -->
  119. </para>
  120. <itemizedlist mark='opencircle'>
  121. <listitem>
  122. <para>
  123. Установите опцию <code>strict</code> в true (см.
  124. <link linkend="zend.session.startingasession"><code>Zend_Session::setOptions()</code></link>) в коде загрузки.
  125. <!--
  126. Unconditionally, set the <code>strict</code> option to true (see
  127. <link
  128. linkend="zend.session.startingasession"><code>Zend_Session::setOptions()</code>
  129. </link>
  130. ) in your userland bootstrap.
  131. -->
  132. </para>
  133. </listitem>
  134. <listitem>
  135. <para>
  136. Вызывайте <code>Zend_Session::start()</code>
  137. только при тех запросах, для которых нужны сессии,
  138. и до того, как будет произведен первый вызов
  139. <code>new Zend_Session_Namespace()</code>.
  140. <!--
  141. Call <code>Zend_Session::start()</code>, only for requests that need to use sessions, before
  142. the first call to <code>new Zend_Session_Namespace()</code>.
  143. -->
  144. </para>
  145. </listitem>
  146. <listitem>
  147. <para>
  148. Используйте
  149. <code>new Zend_Session_Namespace()</code> как
  150. обычно и там, где это нужно, но при этом необходимо
  151. убедиться, что
  152. <code>Zend_Session::start()</code> был вызван ранее.
  153. <!--
  154. Use <code>new Zend_Session_Namespace()</code> normally, where needed, but make sure
  155. <code>Zend_Session::start()</code> has been called previously.
  156. -->
  157. </para>
  158. </listitem>
  159. </itemizedlist>
  160. <para>
  161. Опция <code>strict</code> предотвращает автоматический
  162. старт сессии с использованием
  163. <code>Zend_Session::start()</code> при вызове
  164. <code>new Zend_Session_Namespace()</code>. Эта опция
  165. помогает разработчикам пользовательских областей приложений
  166. ZF следовать принятому при проектировании решению не
  167. использовать сессии для определенных запросов, т.к. при
  168. установке этой опции и последующем инстанцировании
  169. Zend_Session_Namespace до явного вызова
  170. <code>Zend_Session::start()</code> будет сгенерировано
  171. исключение. Не используйте эту опцию в коде библиотек ZF,
  172. поскольку проектные решения должны принимать только
  173. разработчики пользовательской области. Аналогичным образом,
  174. все разработчики "библиотек" должны осторожно подходить
  175. к использованию <code>Zend_Session::setOptions()</code>
  176. в коде их библиотек, поскольку эти опции имеют глобальную
  177. область действия (как и лежащие в основе опции расширения
  178. ext/session).
  179. <!--
  180. The <code>strict</code> option prevents <code>new Zend_Session_Namespace()</code> from automatically
  181. starting the session using <code>Zend_Session::start()</code>. Thus, this option helps developers of
  182. userland ZF applications enforce a design decision to avoid using sessions for certain requests,
  183. since an error will be thrown when using this option and instantiating Zend_Session_Namespace,
  184. before an explicit call to <code>Zend_Session::start()</code>. Do not use this option in ZF core
  185. library code, because only userland developers should make this design choice. Similarly, all
  186. "library" developers should carefully consider the impact of using
  187. <code>Zend_Session::setOptions()</code> on users of their library code, since these options have
  188. global side-effects (as do the underlying options for ext/session).
  189. -->
  190. </para>
  191. </listitem>
  192. <listitem>
  193. <para>
  194. 4. Правильно: Просто используйте
  195. <code>new Zend_Session_Namespace()</code> где необходимо, и
  196. сессия будет автоматически запущена в Zend_Session. Это
  197. наиболее простой вариант использования, подходящий для
  198. большинства случаев. Но необходимо будет следить за тем,
  199. чтобы первый вызов
  200. <code>new Zend_Session_Namespace()()</code> всегда
  201. происходил <emphasis>до того</emphasis>, как
  202. выходные данные будут отправлены клиенту (т.е. до того, как
  203. агенту пользователя будут отправлены HTTP-заголовки),
  204. если используются основанные на куках сессии
  205. (очень рекомендуется). Использование
  206. <ulink url="http://php.net/outcontrol">буферизации
  207. вывода</ulink> может быть удачным решением, при этом может
  208. быть улучшена производительность. Например, в
  209. <code>php.ini</code>
  210. "<code>output_buffering = 65535</code>" включает буферизацию
  211. вывода с размером буфера 64K.
  212. <!--
  213. 4. Correct: Just use <code>new Zend_Session_Namespace()</code> whenever needed, and the session will
  214. be automatically started within Zend_Session. This offers extremely simple usage that works well in
  215. most situations. However, you then become responsible for ensuring that the first <code>new
  216. Zend_Session_Namespace()</code> happens <emphasis>before</emphasis> any output (i.e.
  217. <ulink url="http://www.php.net/headers_sent">HTTP headers</ulink>
  218. ) has been sent by PHP to the client, if you are using the default, cookie-based sessions (strongly
  219. recommended). Using
  220. <ulink url="http://php.net/outcontrol">output buffering</ulink>
  221. often is sufficient to prevent this issue and may help improve performance. For example, in
  222. <code>php.ini</code>, "<code>output_buffering = 65535</code>" enables output buffering with a 64K
  223. buffer.
  224. -->
  225. </para>
  226. </listitem>
  227. </itemizedlist>
  228. </sect2>
  229. <sect2 id="zend.session.locking">
  230. <title>Блокировка пространств имен<!--Locking Session Namespaces--></title>
  231. <para>
  232. Можно применять блокировку к пространствам имен для предотвращения
  233. изменения данных в этом пространстве имен. Используйте
  234. метод <code>Zend_Session_Namespace::lock()</code> для того, чтобы
  235. сделать определенное пространство имен доступным только для чтения,
  236. <code>unLock()</code> - чтобы сделать пространство имен доступным
  237. для чтения и изменений, а <code>isLocked()</code> для проверки того,
  238. не было ли пространство имен заблокировано ранее. Блокировка не
  239. сохраняется от одного запроса к другому. Блокировка пространства
  240. имен не действует на методы установки (setter methods) в объектах,
  241. сохраненных в пространстве имен, но предотвращает использование
  242. методов установки пространства имен сессии для удаления или замены
  243. объектов, сохраненных непосредственно в пространстве имен.
  244. Также блокирование пространств имен Zend_Session_Namespace не
  245. препятствует использованию ссылок на те же данные
  246. (см. <ulink url="http://www.php.net/references">PHP references</ulink>).
  247. <!--
  248. Session namespaces can be locked, to prevent further alterations to the data in that namespace. Use
  249. <code>Zend_Session_Namespace's lock()</code> to make a specific namespace read-only, <code>unLock()</code>
  250. to make a read-only namespace read-write, and <code>isLocked()</code> to test if a namespace has been
  251. previously locked. Locks are transient and do not persist from one request to the next. Locking the
  252. namespace has no effect on setter methods of objects stored in the namespace, but does prevent the use of
  253. the namespace's setter method to remove or replace objects stored directly in the namespace. Similarly,
  254. locking Zend_Session_Namespace namespaces does not prevent the use of symbol table aliases to the same data
  255. (see
  256. <ulink url="http://www.php.net/references">PHP references</ulink>
  257. ).
  258. -->
  259. </para>
  260. <example>
  261. <title>Блокировка пространств имен<!--Locking Session Namespaces--></title>
  262. <programlisting language="php">
  263. <![CDATA[<?php
  264. // assuming:
  265. $userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace');
  266. // marking session as read only locked
  267. $userProfileNamespace->lock();
  268. // unlocking read-only lock
  269. if ($userProfileNamespace->isLocked()) {
  270. $userProfileNamespace->unLock();
  271. }
  272. ?>]]></programlisting>
  273. </example>
  274. <para>
  275. Есть некоторые идеи по поводу того, как организовывать модели в
  276. парадигме MVC для Веб, включая создание моделей представления для
  277. использования видами (views). Иногда имеющиеся данные, являются ли
  278. они частью вашей доменной модели или нет, являются подходящими для
  279. этой задачи. Для того, чтобы предотвратить изменение таких данных,
  280. используйте блокировку пространств имен сессий до того, как
  281. предоставить видам доступ к этим подмножествам вашей модели
  282. представления.
  283. <!--
  284. There are numerous ideas for how to manage models in MVC paradigms for the Web, including creating
  285. presentation models for use by views. Sometimes existing data, whether part of your domain model or not, is
  286. adequate for the task. To discourage views from applying any processing logic to alter such data, consider
  287. locking session namespaces before permitting views to access this subset of your "presentation" model.
  288. -->
  289. </para>
  290. <example>
  291. <title>Блокировка сессий в видах<!--Locking Sessions in Views--></title>
  292. <programlisting language="php">
  293. <![CDATA[<?php
  294. class FooModule_View extends Zend_View
  295. {
  296. public function show($name)
  297. {
  298. if (!isset($this->mySessionNamespace)) {
  299. $this->mySessionNamespace = Zend::registry('FooModule');
  300. }
  301. if ($this->mySessionNamespace->isLocked()) {
  302. return parent::render($name);
  303. }
  304. $this->mySessionNamespace->lock();
  305. $return = parent::render($name);
  306. $this->mySessionNamespace->unLock();
  307. return $return;
  308. }
  309. }
  310. ?>]]></programlisting>
  311. </example>
  312. </sect2>
  313. <sect2 id="zend.session.expiration">
  314. <title>Время жизни пространства имен<!--Namespace Expiration--></title>
  315. <para>
  316. Время жизни может быть ограничено как у пространства имен в целом,
  317. так и у отдельных ключей. Общие случаи использования
  318. включают в себя передачу временной информации между запросами
  319. и повышение защищенности от определенных угроз безопасности
  320. посредством устранения доступа к потенциально чувствительной
  321. информации по прошествии некоторого времени после
  322. аутентификации. Истечение времени жизни может быть основано на
  323. количестве секунд или на концепции "прыжков" (hops), в которой
  324. "прыжком" считается каждый успешный запрос, в котором активируется
  325. пространство имен через, как минимум, один
  326. <varname>$space = new Zend_Session_Namespace('myspace');</varname>.
  327. <!--
  328. Limits can be placed on the longevity of both namespaces and
  329. individual keys in namespaces. Common use cases
  330. include passing temporary information between requests, and
  331. reducing exposure to certain security risks by
  332. removing access to potentially sensitive information some time
  333. after authentication occurred. Expiration can
  334. be based on elapsed seconds, or based on the concept of "hops",
  335. where a hop occurs for each successive
  336. request that activates the namespace via at least one
  337. <varname>$space = new Zend_Session_Namespace('myspace');</varname>.
  338. -->
  339. </para>
  340. <example>
  341. <title>Примеры установки времени жизни<!--Expiration Examples--></title>
  342. <programlisting language="php">
  343. <![CDATA[<?php
  344. $s = new Zend_Session_Namespace('expireAll');
  345. $s->a = 'apple';
  346. $s->p = 'pear';
  347. $s->o = 'orange';
  348. // Время жизни установлено только для ключа "a" (5 секунд)
  349. $s->setExpirationSeconds(5, 'a');
  350. // Время жизни всего пространства имен - 5 "прыжков"
  351. $s->setExpirationHops(5);
  352. $s->setExpirationSeconds(60);
  353. // Пространство имен "expireAll" будет помечено как с истекшим временем жизни
  354. // при первом запросе, произведенном после того, как прошло 60 секунд,
  355. // или после 5 "прыжков" - в зависимости от того, что произошло раньше
  356. ?>]]></programlisting>
  357. </example>
  358. <para>
  359. При работе с данными, время жизни которых истекает в текущем запросе,
  360. будьте внимательны при их извлечении. Несмотря на то, что данные
  361. возвращаются по ссылке, изменение этих данных не приведет к их
  362. сохранению после текущего запроса. Для "сброса" времени истечения
  363. извлеките данные во временные переменные, уничтожьте эти данные в
  364. пространстве имен и затем установите соответствующий ключ снова.
  365. <!--
  366. When working with data expiring from the session in the current
  367. request, care should be used when retrieving
  368. it. Although the data is returned by reference, modifying the data
  369. will not make expiring data persist past
  370. the current request. In order to "reset" the expiration time, fetch
  371. the data into temporary variables, use
  372. the namespace to unset it, and then set the appropriate keys again.
  373. -->
  374. </para>
  375. </sect2>
  376. <sect2 id="zend.session.controllers">
  377. <title>Инкапсуляция сессий и контроллеры<!--Session Encapsulation and Controllers--></title>
  378. <para>
  379. Пространства имен могут также использоваться для разделения доступа
  380. контроллеров к сессиям, чтобы защитить переменные от повреждения.
  381. Например, контроллер 'Zend_Auth' может хранить свои постоянные
  382. данные сессии отдельно от всех остальных контроллеров.
  383. <!--
  384. Namespaces can also be used to separate session access by controllers to protect variables from
  385. contamination. For example, the 'Zend_Auth' controller might keep its session state data separate from all
  386. other controllers.
  387. -->
  388. </para>
  389. <example>
  390. <title>Сессии с пространствами имен для контроллеров с автоматическим истечением времени<!--Namespaced Sessions for Controllers with Automatic Expiration--></title>
  391. <programlisting language="php">
  392. <![CDATA[<?php
  393. require_once 'Zend/Session.php';
  394. // контроллер для вывода вопроса
  395. $testSpace = new Zend_Session_Namespace('testSpace');
  396. // установка времени жизни только для этой переменной
  397. $testSpace->setExpirationSeconds(300, "accept_answer");
  398. $testSpace->accept_answer = true;
  399. --
  400. // контроллер для обработки ответа на вопрос
  401. $testSpace = new Zend_Session_Namespace('testSpace');
  402. if ($testSpace->accept_answer === true) {
  403. // время не истекло
  404. }
  405. else {
  406. // время истекло
  407. }
  408. ?>]]></programlisting>
  409. </example>
  410. </sect2>
  411. <sect2 id="zend.session.limitinginstances">
  412. <title>Ограничение количества экземпляров Zend_Session_Namespace до одного на каждое пространство имен<!--Limiting Instances of Zend_Session to One Per Namespace--></title>
  413. <para>
  414. Мы рекомендуем использовать блокировку сессии (см. выше) вместо этой
  415. функциональной возможности, которая накладывает дополнительное бремя
  416. на разработчика, состоящее в передаче экземпляров
  417. Zend_Session_Namespace во все функции и объекты, нуждающихся в
  418. использовании этих пространств имен.
  419. <!--
  420. We recommend using session locking (see above) instead of the feature below, which places extra management
  421. burden on the developer to pass any Zend_Session_Namespace instances into whatever functions and objects
  422. need access to each namespace.
  423. -->
  424. </para>
  425. <para>
  426. Когда создается первый экземпляр Zend_Session_Namespace, связанный с
  427. определенным пространством имен, вы можете дать команду
  428. Zend_Session_Namespace больше не создавать объекты для этого
  429. пространства имен. Таким образом, в дальнейшем попытка создать
  430. экземпляр Zend_Session_Namespace для
  431. того же пространства имен вызовет генерацию исключения. Это
  432. поведение является опциональным и не принято по умолчанию, но
  433. остается доступным для тех, кто предпочитает передавать по коду
  434. единственный объект для каждого пространства имен. Это повышает
  435. защиту пространства имен от изменений компонентами, которые не
  436. должны делать этого, поскольку тогда они не будут иметь свободного
  437. доступа к пространствам имен. Тем не менее, ограничение пространства
  438. имен до одного экземпляра модет привести к большему объему кода или
  439. к его усложнению, поскольку он отменяет возможность использования
  440. директив вида
  441. <varname>$aNamespace = new Zend_Session_Namespace('aNamespace');</varname>
  442. после того, как был создан первый экземпляр. Это продемонстрировано
  443. в примере ниже:
  444. <!--
  445. When constructing the first instance of Zend_Session_Namespace attached to a specific namespace, you can
  446. also instruct Zend_Session_Namespace to not make any more instances for that namespace. Thus, any future
  447. attempts to construct a Zend_Session_Namespace instance having the same namespace will throw an error. Such
  448. behavior is optional, and not the default behavior, but remains available to those who prefer to pass around
  449. a single instance object for each namespace. This increases protection from changes by components that
  450. should not modify a particular session namespace, because they won't have easy access. However, limiting a
  451. namespace to a single instance may lead to more code or more complex code, as it removes access to the
  452. convient <varname>$aNamespace = new Zend_Session_Namespace('aNamespace');</varname>, after the first intance has
  453. been created, as follows in the example below:
  454. -->
  455. </para>
  456. <example>
  457. <title>Ограничение до единичных экземпляров<!--Limiting to Single Instances--></title>
  458. <programlisting language="php">
  459. <![CDATA[<?php
  460. require_once 'Zend/Session.php';
  461. $authSpaceAccessor1 = new Zend_Session_Namespace('Zend_Auth');
  462. $authSpaceAccessor2 = new Zend_Session_Namespace('Zend_Auth', Zend_Session_Namespace::SINGLE_INSTANCE);
  463. $authSpaceAccessor1->foo = 'bar';
  464. assert($authSpaceAccessor2->foo, 'bar');
  465. doSomething($options, $authSpaceAccessor2);
  466. .
  467. .
  468. .
  469. $aNamespaceObject = new Zend_Session_Namespace('Zend_Auth'); // это вызовет ошибку
  470. ?>]]></programlisting>
  471. </example>
  472. <para>
  473. Второй параметр в конструкторе выше говорит Zend_Session, что
  474. в будущем создание любых других экземпляров Zend_Session_Namespace с
  475. пространством имен 'Zend_Auth' не допустимо. Поскольку
  476. директиву <code>new Zend_Session_Namespace('Zend_Auth')</code>
  477. нельзя использовать после того, как будет выполнен приведенный выше
  478. код, то разработчику нужно будет где-либо сохранять объект
  479. (<varname>$authSpaceAccessor2</varname> в
  480. примере выше), если в дальнейшем при обработке того же запроса
  481. необходим доступ к этому пространству имен сессии.
  482. Например, вы можете сохранять экземпляр в статической переменной или
  483. передавать его другим методам, которым нужен доступ к данному
  484. пространству имен.
  485. <!--
  486. The second parameter in the constructor above will tell Zend_Session_Namespace that any future
  487. Zend_Session's that are instantiated with the 'Zend_Auth' namespace are not allowed, and will thus cause an
  488. exception. Since <code>new Zend_Session_Namespace('Zend_Auth')</code> will not be allowed after the code
  489. above has been executed, the developer becomes responsible for storing the instance object
  490. (<varname>$authSpaceAccessor2</varname> in the example above) somewhere, if access to this session namespace is
  491. needed at a later time during the same request. For example, a developer may store the instance in a static
  492. variable, or pass it to other methods that might need access to this session namespace. Session locking (see
  493. above) provides a more convenient, and less burdensome approach to limiting access to namespaces.
  494. -->
  495. </para>
  496. </sect2>
  497. <sect2 id="zend.session.modifyingarray">
  498. <title>Работа с массивами в пространствах имен<!--Working with Arrays in Namespaces--></title>
  499. <para>
  500. Изменение массива внутри пространства имен невозможно. Простейшим
  501. решением является сохранение массивов после того, как все желаемые
  502. значения были установлены. <ulink url="http://framework.zend.com/issues/browse/ZF-800">ZF-800</ulink>
  503. подтверждает известный баг, затрагивающий многие PHP-приложения,
  504. использующие "магические" методы и массивы.
  505. <!--
  506. Modifying an array inside a namespace does not work. The simplest solution is to store arrays after all
  507. desired values have been set.
  508. <ulink url="http://framework.zend.com/issues/browse/ZF-800">ZF-800</ulink>
  509. documents a known issue affecting many PHP applications using magic methods and arrays.
  510. -->
  511. </para>
  512. <example>
  513. <title>Известные проблемы с массивами<!--Known problem with arrays--></title>
  514. <programlisting language="php">
  515. <![CDATA[<?php
  516. $sessionNamespace = new Zend_Session_Namespace('Foo');
  517. $sessionNamespace->array = array();
  518. $sessionNamespace->array['testKey'] = 1; // Не работает в версиях ниже PHP 5.2.1
  519. ?>]]></programlisting>
  520. </example>
  521. <para>
  522. Если вам нужно изменить массив после того, как добавили его в
  523. пространство имен, извлеките массив, произведите необходимые
  524. изменения и сохраните его под тем же ключом в пространстве имен.
  525. <!--
  526. If you need to modify the array after assigning it to a session namespace key, fetch the array, then
  527. modify it and save the array back to the session namespace.
  528. -->
  529. </para>
  530. <example>
  531. <title>Обходной путь: извлечение, изменение и сохранение<!--Workaround: fetch, modify, save--></title>
  532. <programlisting language="php">
  533. <![CDATA[<?php
  534. $sessionNamespace = new Zend_Session_Namespace('Foo');
  535. $sessionNamespace->array = array('tree' => 'apple');
  536. $tmp = $sessionNamespace->array;
  537. $tmp['fruit'] = 'peach';
  538. $sessionNamespace->array = $tmp;
  539. ?>]]></programlisting>
  540. </example>
  541. <para>
  542. Можно также сохранить массив, содержащий ссылку на желаемый массив
  543. и косвенно работать с ним.
  544. <!--
  545. Alternatively, store an array containing a reference to the desired array, and then access it indirectly.
  546. -->
  547. </para>
  548. <example>
  549. <title>Обходной путь: сохранение массива, содержащего ссылку<!--Workaround: store array containing reference--></title>
  550. <programlisting language="php">
  551. <![CDATA[<?php
  552. $myNamespace = new Zend_Session_Namespace('mySpace');
  553. // работает даже с версиями PHP, содержащими баг
  554. $a = array(1,2,3);
  555. $myNamespace->someArray = array( & $a ) ;
  556. $a['foo'] = 'bar';
  557. ?>]]></programlisting>
  558. </example>
  559. </sect2>
  560. <sect2 id="zend.session.auth">
  561. <title>Использование сессий вместе с аутентификацией<!--Using Sessions with Authentication--></title>
  562. <para>
  563. Если ваш адаптер аутентификации для <code>Zend_Auth</code>
  564. возвращает результат, в котором идетификатором авторизации является
  565. объект (не рекомендуется) вместо массива, то выполняйте проверку
  566. класса идентификатора авторизации до того, как стартовать сессию.
  567. Вместо этого мы рекомендуем хранить идентификаторы авторизации,
  568. вычисленные в адаптере авторизации, под хорошо известным ключом в
  569. пространстве имен сессии. Например, по умолчанию
  570. <code>Zend_Auth</code> размещает идентификаторы под ключом 'storage'
  571. пространства имен 'Zend_Auth'.
  572. <!--
  573. If your authentication adapter for <code>Zend_Auth</code> returns a result where the authorization identity
  574. is an object (not recommended), instead of an array, then make sure to require your authorization identity
  575. class definition before starting the session. Instead, we recommend storing the authorization ids computed
  576. within an authentication adapter inside a well-known key in a session namespace. For example, the default
  577. behavior of <code>Zend_Auth</code> places this in the 'storage' key of the 'Zend_Auth' namespace.
  578. -->
  579. </para>
  580. <para>
  581. Если вы приказали <code>Zend_Auth</code> не сохранять метку сессии в
  582. сессиях, то можете вручную сохранять ID авторизации под хорошо
  583. известным ключом в любом пространстве имен сессии.
  584. Часто приложения имеют свои
  585. требования к тому, где хранить "мандат" (учетная запись с
  586. праметрами доступа пользователя) и идентификатор авторизации.
  587. Приложения часто устанавливают соответствие идентификаторов
  588. аутентификации (например, имена пользователей) и идентификаторов
  589. авторизации (например, присвоенное уникальное целое число) во время
  590. аутентификации, которая должна производится внутри метода
  591. <code>authenticate()</code> адаптера аутентификации Zend_Auth.
  592. <!--
  593. If you tell <code>Zend_Auth</code> to not persist authentication tokens in sessions, then you can manually
  594. store the authorization id in the session namespace, in a well-known location in a session namespace of your
  595. choice. Often, applications have specific needs about where to store credentials used (if any) and
  596. "authorization" identity. Applications often map authentication identities (e.g. usernames) to authorization
  597. identities (e.g. a uniquely assigned integer) during authentication, which would occur in the Zend_Auth
  598. authentication adapter's <code>authenticate()</code> method.
  599. -->
  600. </para>
  601. <example>
  602. <title>Пример: Простой доступ к ID авторизации<!--Example: Simplified access of authorization ids--></title>
  603. <programlisting language="php">
  604. <![CDATA[<?php
  605. // pre-authentication request
  606. require_once 'Zend/Auth/Adapter/Digest.php';
  607. $adapter = new Zend_Auth_Adapter_Digest($filename, $realm, $username, $password);
  608. $result = $adapter->authenticate();
  609. require_once 'Zend/Session/Namespace.php';
  610. $namespace = new Zend_Session_Namespace('Zend_Auth');
  611. if ($result->isValid()) {
  612. $namespace->authorizationId = $result->getIdentity();
  613. $namespace->date = time();
  614. } else {
  615. $namespace->attempts++;
  616. }
  617. // subsequent requests
  618. require_once 'Zend/Session.php';
  619. Zend_Session::start();
  620. $namespace = new Zend_Session_Namespace('Zend_Auth');
  621. echo "Valid: ", (empty($namespace->authorizationId) ? 'No' : 'Yes'), "\n"';
  622. echo "Authorization / user Id: ", (empty($namespace->authorizationId)
  623. ? 'none' : print_r($namespace->authorizationId, true)), "\n"';
  624. echo "Authentication attempts: ", (empty($namespace->attempts)
  625. ? '0' : $namespace->attempts), "\n"';
  626. echo "Authenticated on: ",
  627. (empty($namespace->date) ? 'No' : date(DATE_ATOM, $namespace->date), "\n"';
  628. ?>]]></programlisting>
  629. </example>
  630. <para>
  631. Идентификаторы авторизации, хранящиеся на клиентской стороне, могут
  632. использоваться в атаках на поднятие привилегий, если им доверяет
  633. серверная сторона и если они, например, не дублируются на серверной
  634. стороне (например, в данных сессии) и затем сверяются с
  635. идентификатором авторизации, предоставленным клентом для
  636. действующией сессии. Мы различаем понятия "идентификаторов
  637. аутентификации" (например, имена пользователей) и "идентификаторов
  638. авторизации" (например, ID пользователя #101 в таблице БД для
  639. пользователей).
  640. <!--
  641. Authorization ids stored client-side are subject to privilege escalation vulnerabilities, if these ids are
  642. used and trusted by the server, unless, for example, the id is duplicated on the server-side (e.g. in the
  643. session) and then cross-checked with the authorization id claimed by the client for the in-effect session.
  644. We are differentiating between "authentication ids" (e.g. usernames) and "authorization ids" (e.g. user id
  645. #101 in the users DB table).
  646. -->
  647. </para>
  648. <para>
  649. Последнее часто используется для повышения производительности -
  650. например, для выборки из пула серверов, кеширующих данные сессии,
  651. чтобы решить проблему "курицы и яйца". Часто
  652. возникают дебаты о том, использовать ли настоящий ID авторизации в
  653. куках или некую замену, которая помогает установить соответствие
  654. с настоящим ID авторизации (или сессии сервера(ов), хранящего
  655. сессию/профиль пользователя и т.д.), в то время как некоторые
  656. архитекторы системной безопасности предпочитают избегать
  657. публикования истинных значений первичных ключей, пытаясь достичь
  658. некоторого дополнительного уровня защиты в случае наличия
  659. уязвимостей к SQL-инъекциям.
  660. <!--
  661. The latter is not uncommon for performance reasons, such as helping select from a pool of servers caching
  662. session information to help solve chicken-and-egg problems. Often debates ensue about whether to use the
  663. real authorization id in the cookie, or some substitute that aids in mapping to the real authorization id
  664. (or session or server(s) holding the user's session/profile, etc.), as some system security architects wish
  665. to prevent true "DB primary keys" from escaping into the wild. These architects try and obtain some level of
  666. protection by obfuscation in the event of a SQL injection vulnerability in their system. Not everyone uses
  667. auto-increment strategies for authorization ids.
  668. -->
  669. </para>
  670. </sect2>
  671. <sect2 id="zend.session.testing">
  672. <title>Использование сессий с юнит-тестами<!--Using Sessions with Unit Tests--></title>
  673. <para>
  674. Zend Framework использует PHPUnit для своего тестирования. Многие
  675. разработчики расширяют существующие наборы
  676. юнит-тестов для покрытия кода в своих приложениях.
  677. Если при выполнении юнит-тестирований после завершения сессии были
  678. использованы любые связанные с записью методы, то генерируется
  679. исключение "<emphasis>Zend_Session is currently marked
  680. as read-only</emphasis>" ("Zend_Session помечен как доступный только
  681. для чтения"). Тем не менее, юнит-тесты, использующие Zend_Session,
  682. требуют особого внимания в разработке, поскольку закрытие
  683. (<code>Zend_Session::writeClose()</code>) или уничтожение сессии
  684. (<code>Zend_Session::destroy()</code>) не дает впоследствии
  685. устанавливать или сбрасывать ключи в любом объекте
  686. Zend_Session_Namespace. Это поведение является прямым следствием
  687. использования лежащего в основе расширения ext/session,
  688. функций <code>session_destroy()</code> и
  689. <code>session_write_close()</code>, которые не имеют механизма
  690. "отмены" для облегчения установки/демонтажа в юнит-тестировании.
  691. <!--
  692. Zend Framework relies on PHPUnit to facilitate testing of itself. Many developers extend the existing
  693. suite of unit tests to cover the code in their applications. The exception
  694. "<emphasis>Zend_Session is currently marked as read-only</emphasis>" is thrown while
  695. performing unit tests, if any write-related methods are used after ending the session. However, unit tests
  696. using Zend_Session require extra attention, because closing (<code>Zend_Session::writeClose()</code>), or
  697. destroying a session (<code>Zend_Session::destroy()</code>) prevents any further setting or unsetting of
  698. keys in any Zend_Session_Namespace. This behavior is a direct result of the underlying ext/session mechanism
  699. and PHP's <code>session_destroy()</code> and <code>session_write_close()</code>, which has no "undo"
  700. mechanism to facilitate setup/teardown with unit tests.
  701. -->
  702. </para>
  703. <para>
  704. Чтобы обойти это, см. юнит-тест
  705. <code>testSetExpirationSeconds()</code> в
  706. <code>tests/Zend/Session/SessionTest.php</code> и
  707. <code>SessionTestHelper.php</code>, которые используют
  708. <code>exec()</code> для запуска отдельного процесса. Новый процесс
  709. более точно имитирует второй, последующий, запрос из броузера.
  710. Отдельный процесс начинается с "чистой" сессии, так же, как при
  711. выполнении любого PHP-скрипта для веб-запроса. Кроме этого,
  712. любые изменения в <varname>$_SESSION[]</varname>, произведенные при вызове
  713. процесса, становятся доступными и в дочернем процессе, что дает
  714. родительскому процессу возможность закрыть сессию до использования
  715. <code>exec()</code>.
  716. <!--
  717. To work around this, see the unit test <code>testSetExpirationSeconds()</code> in
  718. <code>tests/Zend/Session/SessionTest.php and SessionTestHelper.php</code>, which make use of PHP's
  719. <code>exec()</code> to launch a separate process. The new process more accurately simulates a second,
  720. successive request from a browser. The separate process begins with a "clean" session, just like any PHP
  721. script execution for a web request. Also, any changes to <varname>$_SESSION[]</varname> made in the calling
  722. process become available to the child process, provided the parent closed the session before using
  723. <code>exec()</code>
  724. -->
  725. </para>
  726. <example>
  727. <title>Использование PHPUnit для тестирования кода, написанного с использованием Zend_Session*<!--Using PHPUnit to test code written using Zend_Session*--></title>
  728. <programlisting language="php">
  729. <![CDATA[<?php
  730. // testing setExpirationSeconds()
  731. require 'tests/Zend/Session/SessionTestHelper.php'; // also see SessionTest.php in trunk/
  732. $script = 'SessionTestHelper.php';
  733. $s = new Zend_Session_Namespace('space');
  734. $s->a = 'apple';
  735. $s->o = 'orange';
  736. $s->setExpirationSeconds(5);
  737. Zend_Session::regenerateId();
  738. $id = Zend_Session::getId();
  739. session_write_close(); // release session so process below can use it
  740. sleep(4); // not long enough for things to expire
  741. exec($script . "expireAll $id expireAll", $result);
  742. $result = $this->sortResult($result);
  743. $expect = ';a === apple;o === orange;p === pear';
  744. $this->assertTrue($result === $expect,
  745. "iteration over default Zend_Session namespace failed; expecting result === '$expect', but got '$result'");
  746. sleep(2); // long enough for things to expire (total of 6 seconds waiting, but expires in 5)
  747. exec($script . "expireAll $id expireAll", $result);
  748. $result = array_pop($result);
  749. $this->assertTrue($result === '',
  750. "iteration over default Zend_Session namespace failed; expecting result === '', but got '$result')");
  751. session_start(); // resume artificially suspended session
  752. // We could split this into a separate test, but actually, if anything leftover from above
  753. // contaminates the tests below, that is also a bug that we want to know about.
  754. $s = new Zend_Session_Namespace('expireGuava');
  755. $s->setExpirationSeconds(5, 'g'); // now try to expire only 1 of the keys in the namespace
  756. $s->g = 'guava';
  757. $s->p = 'peach';
  758. $s->p = 'plum';
  759. session_write_close(); // release session so process below can use it
  760. sleep(6); // not long enough for things to expire
  761. exec($script . "expireAll $id expireGuava", $result);
  762. $result = $this->sortResult($result);
  763. session_start(); // resume artificially suspended session
  764. $this->assertTrue($result === ';p === plum',
  765. "iteration over named Zend_Session namespace failed (result=$result)");
  766. ?>]]></programlisting>
  767. </example>
  768. </sect2>
  769. </sect1>