Before we start with the new stuff for today - we should fix what we have found to be wrong in the first 2 parts
In our bootstrap.php file just before sending the response to the client we set a header:
$response->setHeader('Content-Type', 'text/html; charset=UTF-8', true);
Thanks to Luke Richards who pointed that if we later want to return different content - JSON, XML or something we are stuck with this header. In fact having
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
in the layout template is enough to make sure our content is displayed correctly in the client browser, so we just drop the
$response->setHeader('Content-Type', 'text/html; charset=UTF-8', true);
line from our bootstrap.php.
Now to the new stuff - as planned earlier now I want to implement session handler, which works with MySQL database and not with files. Two are the main reasons we want this - security and scalability. The security part - when in files, anyone with access to the webserver filesystem can steal or even manipulate session data. And while this is not an issue when we manage our own web server - the other reason - scalability applies strong here. If we ever need to have two or more webservers serving our application because of the great amount of visitors we have - we will need to have a solution for the sessions. Sticky sessions are one solution to this, but not guarantee to us that the servers will be equally loaded. Zend are offering session management in their application server - this is another solution. Storing the sessions in common cache cluster - Memcached for example - is also an option. We will take different approach and just put our session data in the database.
session_set_save_handler function makes this possible.
We will need to construct a table for the session info - here is the SQL statement for it:
CREATE TABLE `session` ( `session_id` VARCHAR(32) NOT NULL, `session_data` TEXT NOT NULL, `t_created` DATETIME NOT NULL, `t_updated` DATETIME NOT NULL, PRIMARY KEY (`session_id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8;
In t_created field we will save the timestamp when the session is first created and in t_updated we will save the timestamp when the session is last updated (last accessed).
EDIT: (My_Session_Manager used to be with static functions and we used direct session_set_save_handler function.)
To comply with Zend Framework convnetions we will write a class My_Session_Manager which implements Zend_Session_SaveHandler_Interface - defining functions for open, close, read, write, destroy and gc handlers. If we want our class to be loadable from Zend_Loader we should follow Zend naming convention, so we create directory ‘library/My/Session‘ and put in there file called Manager.php, so:
‘library/My/Session/Manager.php‘
< ?php require_once 'Zend/Session/SaveHandler/Interface.php'; class My_Session_Manager implements Zend_Session_SaveHandler_Interface { /** * This is instance of My_Session_data, which extends Zend_Db_Table and manages the database connection * * @var My_Session_Data */ private static $sessionData; private static $thisIsOldSession = false; private static $originalSessionId = ''; public function open($save_path, $name) { self::$sessionData = new My_Session_Data(); return true; } public function close() { return true; } public function read($id) { $rows = self::$sessionData->find($id); $row = $rows->current(); if ($row) { self::$thisIsOldSession = true; self::$originalSessionId = $id; return $row->session_data; } else { return ''; } } public function write($id, $sessionData) { $data = array ( 'session_data' => $sessionData, 't_updated' => new Zend_Db_Expr('NOW()'), ); if (self::$thisIsOldSession && self::$originalSessionId != $id) { // session ID is regenerated, so set $thisIsOldSession to false, so we insert new row self::$thisIsOldSession = false; } if (self::$thisIsOldSession) { self::$sessionData->update ( $data, self::$sessionData->getAdapter()->quoteInto('session_id = ?', $id) ); } else { //no such session, create new one $data['session_id'] = $id; $data['t_created'] = new Zend_Db_Expr('NOW()'); self::$sessionData->insert($data); } return true; } public function destroy($id) { self::$sessionData->delete(self::$sessionData->getAdapter()->quoteInto('session_id = ?', $id)); return true; } public function gc($maxLifetime) { $maxLifetime = intval($maxLifetime); self::$sessionData->delete("DATE_ADD(t_updated, INTERVAL $maxLifetime SECOND) < NOW()"); return true; } }
and may be you have noticed we here use class My_Sessoin_Data - this is very simple class indeed - look at its declaration:
‘library/My/Session/Data.php‘:
< ?php class My_Session_Data extends Zend_Db_Table_Abstract { protected $_name = 'session'; }
so, what’s happening here? My_Session_Data extends Zend_Db_Table_Abstract and we use it to access our sessions table in our database. My_Session_Manager defines several static methods - our session handlers. They use the functionality provided by Zend_Db_Table to perform the SQL commands to the database, I won’t explain in details, because in Zend_DB chapter in the Reference Guide this is explained better than I could do it ![]()
open() just creates new object from My_Session_Data and saves it to private member variable of the class. close() removes the reference to the My_Session_Data object just returns true. read() is also very simple - we search in the database table for row with the id of the current session. If we find such - we return the data written there. Else - we return empty string. write() is the most complex method here, although not big deal either - we construct $data array, holding the data we want to update in the database table and then we call the update() method of My_Session_Data.
self::$sessionData->getAdapter()->quoteInto('session_id = ?', $id)
is quite clumsy way to state that we want to update the row where session_id is equal to $id, but I couldn’t think of shorter and better
- If someone have some better idea I’ll be happy to hear it.
If there is no updated row, then we do not have such session - then we just create it. We add to the $data array info about the primary key - session_id and when the session is created - NOW(). BUGFIX here - in the old code we checked for the number of updated rows from the update query, which we ran every time - and if 0 rows were updated we inserted the new session. But if the session is in fact old and saved again in the same second, so ‘t_updated’ is still the same - then 0 rows are updated in spite of being old session. So we have a fix here - we introduce static variable $thisIsOldSession which is set to true in read() if session is found - then in write() we use this to determine if we should do update or insert.
destroy() should delete a session, so we delete the row in the database about it. And finally - the garbage collector gc() - here we delete all rows that are not updated more than $maxLifetime seconds. And that’s all. So now how we make that php uses our class for session handling?
We go back in our bootstrap.php file, and right after we have initialized the database connection (we need it for the session management) we add some code, telling the Zend_Session to use our just built session manager class:
// Construct the database adapter class, connect to the database and store the db object in the registry $db = Zend_Db::factory($configuration->db); $db->query("SET NAMES 'utf8'"); Zend_Registry::set('db', $db); // set this adapter as default for use with Zend_Db_Table Zend_Db_Table_Abstract::setDefaultAdapter($db); // Now set session save handler to our custom class which saves the data in MySQL database $sessionManager = new My_Session_Manager(); Zend_Session::setOptions(array( 'gc_probability' => 1, 'gc_divisor' => 5000 )); Zend_Session::setSaveHandler($sessionManager);
We can fine tune the garbage collector, but I think 1 in 5000 page hits is good enough. The value of 1440 seconds for max lifetime of sessions is the default one - we can change this too if needed.
Now it _should_ work. Let’s test it. Create new action controller:
‘application/modules/default/controllers/TestController.php‘:
< ?php class TestController extends Zend_Controller_Action { public function indexAction() { $defaultNamespace = new Zend_Session_Namespace('Default'); if (isset($defaultNamespace->numberOfPageRequests)) { $defaultNamespace->numberOfPageRequests++; // this will increment for each page load. } else { $defaultNamespace->numberOfPageRequests = 1; // first time } $this->view->xxx = $defaultNamespace->numberOfPageRequests; } }
and template script for the index action:
‘application/modules/default/views/scripts/test/index.phtml‘:
< ?php echo "Page requests this session: ", $this->xxx; ?>
Now when we reload several times ‘http://localhost/test’ we see that our session handler works.
See you again in Part 4. Here is the code so far: part3.rar













zn^ | 12-Jun-08 at 11:56 am | Permalink
Hi,
How can we get a session back from the database ? Could you give an example ?
Thanks !
admin | 12-Jun-08 at 4:25 pm | Permalink
Hi zn^,
We will use the standart Zend Framework way to work with our sessions. What we did here is to change the session handler from the standart php-way - saving the sessions in /tmp dir to saving them to the database. When we set our My_Session_Manager object, which implements Zend_Session_SaveHandler_Interface interface as session handler - everything is done and fetching a session back from the database is the same as it is when we do not set this custom session manager.
In fact the last example in this post does exactly this:
with
$defaultNamespace = new Zend_Session_Namespace(‘Default’);
We have in $defaultNamespace accessor to the default namespace in our session - it implements the magic __get, __set, __isset and __unset methods, so we can access session data as in:
zn^ | 13-Jun-08 at 5:39 pm | Permalink
Thanks for your answer. But if the user reboots his web browser, his session is lost ?
In your example, if the user refresh 5 times the page, the defaultNamespace->var1 is 5, then, if he reboots his web browser end come back on the page, the defaultNamespace->var1 is 1.
How can I do to get this “5″ back ?
Thanks a lot for your help !
zn^
admin | 14-Jun-08 at 12:51 pm | Permalink
If you want your session to persist between restarts of the user browser you should use
Zend_Session::rememberMe(integer $seconds)
function.
Note that this function should be called only once for the session, because it regenerates the ID of the session and sends new cookie, effectively starting new session.
Nishmi | 23-Jul-08 at 6:28 pm | Permalink
Hi,
Thanks for the artcile,Im getting the following error.
Fatal error: Uncaught exception ‘Zend_Session_Exception’ with message ‘Session must be started before any output has been sent to the browser; output started in ………..
any ideas?
ur help is very much appreciated.
goose | 23-Jul-08 at 7:22 pm | Permalink
I’d just like to say, great tutorial.
However, after setting up this code, I can’t seem to get sessions to work at all. I also receive this annoying error: Fatal error: Exception thrown without a stack frame in Unknown on line 0
Any ideas?
Thanks
Nishmi | 23-Jul-08 at 7:23 pm | Permalink
hello bro,
that is sorted nw.now I have anew problem .
Warning: Zend_Db_Profiler::require_once(Zend/Db/Profiler/Query.php) [zend-db-profiler.require-once]: failed to open stream: No such file or directory in ……………
may be u can help me.
admin | 24-Jul-08 at 4:24 pm | Permalink
@Nishmi:
May be you use older version of Zend Framework, where Zend_Db_Profiler is not present, i don’t know.
In fact this Zend_Db_Profiler chunk of code is forgotten code from a debugging
so you can safely remove these lines:
They are not needed.
admin | 24-Jul-08 at 4:28 pm | Permalink
@goose:
about this:
“I also receive this annoying error: Fatal error: Exception thrown without a stack frame in Unknown on line 0″
This happened before the fix for the sessions, if you have downloaded the rar file from this part - part3.rar, the bugfix I think is not implemented there. Since then I have fixed a few issues in the session handler, here is the fixed variant:
goose | 24-Jul-08 at 4:55 pm | Permalink
@admin:
Thank you for the quick reply. I Still get the same error.
Some background information…I have completed Maugrim’s ZF Blog tutorial (http://blog.astrumfutura.com/archives/351-An-Example-Zend-Framework-Blog-Application-Part-1-Introductory-Planning.html)
and I am trying to merge your work with his. I think that the Zend_auth class is interfering with your session stuff. Does this make sense??
Thanks
admin | 24-Jul-08 at 11:21 pm | Permalink
It is strange that you still get the error. When you get it?
In fact in the new release of Zend Framework (1.6, which is released under RC1 version) we have Zend_Session_SaveHandler_DbTable , which does the job of my class above, and even more. As many things in Zend Framework it is somehow bloated with not so important code to support wide range of behaviour, so for basic usage a simple version of the session save handler like my implementation is still an option.
As for if Zend_Auth class is intefering with the session - yes it is, because after authentication some info is put in the session, but this is the desired behaviour, so it is not the problem. If the code still throws this exception the problem is somewhere in the session handler itself. If you can reproduce the exception with some code please send to me to viperx@andreinikolov.com, so I can investigate it.
goose | 25-Jul-08 at 6:43 pm | Permalink
@admin:
I just sent you an email
mike | 09-Sep-08 at 4:03 pm | Permalink
I am getting the error below on testing the sessioncontroller. How do i resolve this?
Fatal error:Uncaught exception ‘Zend_Session_Exception’ with message ‘A session namespace object already exists for this namespace(;Default’), and so additional accessors(session namespace objects) for this namespace are permitted’ in C:..\library\Zend\Session\Namespace.php:106#0 etc.
viperx | 10-Sep-08 at 9:45 am | Permalink
I don’t know, you should try some debugging and see what is wrong. I cannot guess what is your code
sumbul | 01-Nov-08 at 1:56 pm | Permalink
Nice post,
It seemed fine till the part 2 but in this part testController is redirecting me to index action instead of its own. Thus, instead of the session values I am getting the index/index.phtml content. It is actually not entering the class “TestController extends Zend_Controller_Action”
Plz help
viperx | 12-Nov-08 at 8:12 pm | Permalink
Hi sumbul,
I can’t think of a reason why you got redirected to index.. when you open /test or /test/index you should see the test page
Ahmed El Talkhawy | 19-Nov-08 at 12:43 pm | Permalink
Very good tutorial, i liked it very much.
I have noticed that when i point the browser to
“test” it works as expected and when i point it to “test/” with the trailing slash it removes the CSS styles, why is that ?
viperx | 19-Nov-08 at 5:35 pm | Permalink
make the path to the css files absolute and it will be ok, so: instead of