While the basic usage examples are a perfectly acceptable way to utilize
Zend Framework sessions, there are some best practices to consider.
Consider the
Zend_Auth
example
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.
Jeśli chcesz aby wszystkie żądania używały sesji i używały sesji Zend Framework, to rozpocznik sesję w pliku ładującym:
Przykład 25.6. Rozpoczynanie globalnej sesji
<?php ... require_once 'Zend/Session.php'; Zend_Session::start(); ... ?>
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 Zend_Session::start()
first. (More on
advanced features later.)
Są cztery sposoby rozpoczęcia sesji, gdy używamy Zend_Session. Dwa są złe.
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 php_value session.auto_start 0
to your
.htaccess file (usually in your HTML document root directory).
2. Wrong: Do not use PHP's
session_start()
function directly If you use session_start()
directly, and then start using
Zend_Session_Namespace, an exception will be thrown by Zend_Session::start()
("session
has already been started"). If you call session_start()
, after using
Zend_Session_Namespace or starting Zend_Session::start()
explicitly, an error of level
E_NOTICE will be generated, and the call will be ignored.
3. Correct: Use Zend_Session::start()
. 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:
Unconditionally, set the strict
option to true (see
Zend_Session::setOptions()
) in your userland bootstrap.
Call Zend_Session::start()
, only for requests that need to use sessions, before
the first call to new Zend_Session_Namespace()
.
Use new Zend_Session_Namespace()
normally, where needed, but make sure
Zend_Session::start()
has been called previously.
The strict
option prevents new Zend_Session_Namespace()
from automatically
starting the session using Zend_Session::start()
. 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 Zend_Session::start()
. 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
Zend_Session::setOptions()
on users of their library code, since these options have
global side-effects (as do the underlying options for ext/session).
4. Correct: Just use new Zend_Session_Namespace()
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 new
Zend_Session_Namespace()
happens before any output (i.e.
HTTP headers
) has been sent by PHP to the client, if you are using the default, cookie-based sessions (strongly
recommended). Using
output buffering
often is sufficient to prevent this issue and may help improve performance. For example, in
php.ini
, "output_buffering = 65535
" enables output buffering with a 64K
buffer.
Session namespaces can be locked, to prevent further alterations to the data in that namespace. Use
Zend_Session_Namespace's lock()
to make a specific namespace read-only, unLock()
to make a read-only namespace read-write, and isLocked()
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
PHP references
).
Przykład 25.7. Locking Session Namespaces
<?php // zakładając: $userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace'); // marking session as read only locked $userProfileNamespace->lock(); // unlocking read-only lock if ($userProfileNamespace->isLocked()) { $userProfileNamespace->unLock(); } ?>
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.
Przykład 25.8. Locking Sessions in Views
<?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; } } ?>
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.
Przykład 25.9. Namespaced Sessions for Controllers with Automatic Expiration
<?php require_once 'Zend/Session.php'; // question view controller $testSpace = new Zend_Session_Namespace('testSpace'); $testSpace->setExpirationSeconds(300, "accept_answer"); // expire only this variable $testSpace->accept_answer = true; -- // answer processing controller $testSpace = new Zend_Session_Namespace('testSpace'); if ($testSpace->accept_answer === true) { // within time } else { // not within time } ?>
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.
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 $aNamespace = new Zend_Session_Namespace('aNamespace');
, after the first intance has
been created, as follows in the example below:
Przykład 25.10. Limiting to Single Instances
<?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'); // passes doSomething($options, $authSpaceAccessor2); // pass the accessor to wherever it is needed . . . $aNamespaceObject = new Zend_Session_Namespace('Zend_Auth'); // this will throw an error ?>
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 new Zend_Session_Namespace('Zend_Auth')
will not be allowed after the code
above has been executed, the developer becomes responsible for storing the instance object
($authSpaceAccessor2
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.
Modifying an array inside a namespace does not work. The simplest solution is to store arrays after all desired values have been set. ZF-800 documents a known issue affecting many PHP applications using magic methods and arrays.
Przykład 25.11. Known problem with arrays
<?php $sessionNamespace = new Zend_Session_Namespace('Foo'); $sessionNamespace->array = array(); $sessionNamespace->array['testKey'] = 1; // does not work before PHP 5.2.1 ?>
If you need to modify the array after assigning it to a session namespace key, fetch the array, then
Przykład 25.12. Workaround: fetch, modify, save
<?php $sessionNamespace = new Zend_Session_Namespace('Foo'); $sessionNamespace->array = array('tree' => 'apple'); $tmp = $sessionNamespace->array; $tmp['fruit'] = 'peach'; $sessionNamespace->array = $tmp; ?>
Alternatively, store an array containing a reference to the desired array, and then access it indirectly.
If you allow Zend_Auth
to persist authentication tokens using Zend Framework sessions. In order
to access the authentication token on subsequent requests, you would need to:
Przykład 25.14. Workaround: accessing authentication tokens in sessions
<?php // chicken-and-egg... you must know which token class before looking :( require_once 'Zend/Auth/Digest/Token.php'; require_once 'Zend/Session.php'; Zend_Session::start(); require_once 'Zend/Auth/Digest/Adapter.php'; require_once 'Zend/Auth.php'; $auth = new Zend_Auth(new Zend_Auth_Digest_Adapter('someDigestFilename')); $token = $auth->getToken(); echo "Valid: ", (empty($token->isValid()) ? 'No' : 'Yes'), "\n"'; echo "Identity: ", (empty($token->getIdentity()) ? 'unknown' : $token->getIdentity()), "\n"'; echo "Messages: ", (empty($token->getMessages()) ? 'none' : $token->getMessages()), "\n"'; ?>
If you tell Zend_Auth
to not persist authentication tokens in sessions, and then manually store
the authorization id to the session, then just use well-known locations in a session namespace. Also, this
avoids persisting authentication error messages in the session. (Note: Some community members are working on
a "flash message" system for Zend sessions.) 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.
Przykład 25.15. Workaround: simpler access for authorization ids
<?php require 'Zend/Session.php'; Zend_Session::start(); $namespace = Zend_Session_Namespace('Zend_Auth'); echo "Valid: ", (empty($namespace->authorizationId) ? 'No' : 'Yes'), "\n"'; echo "Authorization / user Id: ", (empty($namespace->authorizationId) ? 'none' : $namespace->authorizationId), "\n"'; echo "Authentication attempts: ", (empty($namespace->attempts) ? '0' : $namespace->attempts), "\n"'; echo "Authenticated on: ", (empty($namespace->date) ? 'No' : date(DATE_ATOM, $namespace->date), "\n"'; ?>
The 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
"Zend_Session is currently marked as read-only" 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 (Zend_Session::writeClose()
), or
destroying a session (Zend_Session::destroy()
) 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 session_destroy()
and session_write_close()
, which has no "undo"
mechanism to facilitate setup/teardown with unit tests.
To work around this, see the unit test testSetExpirationSeconds()
in
tests/Zend/Session/SessionTest.php and SessionTestHelper.php
, which make use of PHP's
exec()
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 $_SESSION[]
made in the calling
process become available to the child process, provided the parent closed the session before using
exec()