<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'><id>tag:blogger.com,1999:blog-4072966502572230680</id><updated>2008-03-19T20:49:19.878Z</updated><title type='text'>bglog</title><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml'/><author><name>Bertrand Gorge</name></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>8</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-3962080995962656401</id><published>2008-03-12T08:13:00.001Z</published><updated>2008-03-19T20:39:57.547Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Perfomance trics for your web application</title><content type='html'>Stéphane Thomas, from Simple Entrepreneur, has gathered a bunch of slideshares from people around town that have worked on scaling web applications:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.simpleentrepreneur.com/2008/03/11/comment-gerer-la-montee-en-charge-d-une-application-web/"&gt;http://www.simpleentrepreneur.com/2008/03/11/comment-gerer-la-montee-en-charge-d-une-application-web/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It's an interesting read, to complete with &lt;a href="http://highscalability.com/"&gt;http://highscalability.com&lt;/a&gt;</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2008/03/perfomance-trics-for-your-web.html' title='Perfomance trics for your web application'/><link rel='related' href='http://www.simpleentrepreneur.com/2008/03/11/comment-gerer-la-montee-en-charge-d-une-application-web/' title='Perfomance trics for your web application'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=3962080995962656401' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/3962080995962656401'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/3962080995962656401'/><author><name>Bertrand Gorge</name></author></entry><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-6304230844216952912</id><published>2008-03-10T09:46:00.007Z</published><updated>2008-03-11T08:56:50.083Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='bugzilla'/><category scheme='http://www.blogger.com/atom/ns#' term='mediawiki'/><title type='text'>Bug Milestone</title><content type='html'>I've created a mediawiki extension that mimics Trac's ability to follow the progression of a Bugzilla milestone. All you have to do is put some bugs in a milestone, then put a tag in your wiki page with the name of the milestone, and you get a nice clickable progress bar with the bugs that are open or not: &lt;a href="http://www.mediawiki.org/wiki/Extension:BugMilestone"&gt;http://www.mediawiki.org/wiki/Extension:BugMilestone&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://blogs.epistema.com/bg/uploaded_images/BugMilestone-726084.png"&gt;&lt;img src="http://blogs.epistema.com/bg/uploaded_images/BugMilestone-726075.png" alt="" border="0" /&gt;&lt;/a&gt;</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2008/03/bug-milestone.html' title='Bug Milestone'/><link rel='related' href='http://www.mediawiki.org/wiki/Extension:BugMilestone' title='Bug Milestone'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=6304230844216952912' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/6304230844216952912'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/6304230844216952912'/><author><name>Bertrand Gorge</name></author></entry><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-4946520818072573982</id><published>2007-09-17T19:18:00.000Z</published><updated>2007-10-09T20:30:21.163Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='mdb2'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='pear'/><category scheme='http://www.blogger.com/atom/ns#' term='ms sql'/><title type='text'>Moving from MySQL to MS SQL using PEAR MDB2</title><content type='html'>&lt;a href="http://pear.php.net/MDB2"&gt;MDB2&lt;/a&gt; is a PEAR package that performs some abstraction on the native PHP DB calls. In short, it allows to use $DB-&gt;query($sql) instead of mysql_query or mssql_query (plus a few more things).&lt;br /&gt;&lt;br /&gt;The following article is the list of pitfalls we had to fix in order to get a fully compatible application, that would run the same on MySQL or MSSQL (for now).&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Field types&lt;/h2&gt;Field types are not the same between the two engines of course, but all in all it is not too bad. &lt;strong&gt;ENUMs&lt;/strong&gt; don't exist in MS SQL so these will have to be moved to CHAR(1), MEDIUMTEXT should be TEXT.&lt;br /&gt;&lt;br /&gt;If you want to use MDB2 Schema, beware that it considers ENUM('Y', 'N') to be booleans if the field starts with an "is_" or "has_", but then builds a database with boolean being TINYINT (we'll have to get back on them about that).&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;SQL syntax&lt;/h2&gt;There's a few things that work in MySQL and breaks in MS SQL. Here's what we found:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;REPLACE INTO: &lt;/strong&gt;this one is a bugger, we used REPLACE extensively. Well that doesn't exist on MS SQL.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;ISNULL: &lt;/strong&gt;&lt;code&gt;ISNULL(somefield)&lt;/code&gt; should be &lt;code&gt;somefield IS NULL&lt;/code&gt;. That's the ANSI way and ISNULL won't work on MS SQL...&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;IFNULL: &lt;/strong&gt;&lt;a href="http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html"&gt;IFNULL&lt;/a&gt; is &lt;a href="http://msdn2.microsoft.com/en-us/library/ms184325.aspx"&gt;ISNULL&lt;/a&gt; on MS SQL, but you should really use the function &lt;a href="http://msdn2.microsoft.com/en-us/library/ms190349.aspx"&gt;COALESCE&lt;/a&gt;, which works the same (when used with two parameters) and is more ANSI.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;ORDER BY, GROUP BY: &lt;/strong&gt;on MS SQL, you should be carefull that all the fields that you want to ORDER BY on, should be in the SELECT clause. Same goes for GROUP BY it seems. See further for GROUP BY as it can be a real bugger...&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;COUNT: &lt;/strong&gt;on MS SQL, COUNT only works with one field somehow&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;DISTINCT: &lt;/strong&gt;DISTINCT is a bit touchy on MS SQL - you should be carefull not to have fields that have type TEXT for example.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;NOW: &lt;/strong&gt;Won't work on MS SQL. MDB2 provides some utility functions to build dates for SQL, but that really does a &lt;code&gt;date('Y-m-d H:i:s')&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;TO_DAYS: &lt;/strong&gt;Won't work on MS SQL either. We really missed that one.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;LIMIT: &lt;/strong&gt;LIMIT doesn't exist in MS SQL, but MDB2 prevides a &lt;a href="http://pear.php.net/package/MDB2/docs/latest/MDB2/MDB2_Driver_Common.html#methodsetLimit"&gt;setLimit()&lt;/a&gt; function that will do the trick on all DBMS. Good enough.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;AutoIncremented fields&lt;/h2&gt;That's a big one. It'd be a good thing that DBMS makers could standardize on something for autoincremented indexes.&lt;br /&gt;&lt;br /&gt;In MySQL, if your field id is an autoincremented field, you could do:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;INSERT INTO my_table (id, name) VALUES (NULL, 'first');&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That won't work on MS SQL (you can't leave a NULL value on them). The proper way to do it with MDB2 is as follows:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$values = array();&lt;br /&gt;&lt;br /&gt;$values['name'] = $GLOBALS['mdb2']-&gt;quote("first");&lt;br /&gt;&lt;br /&gt;$newId = $GLOBALS['mdb2']-&gt;extended-&gt;getBeforeID('my_table', 'id', true, true);&lt;br /&gt;if($newId != 'NULL')&lt;br /&gt; $values['id'] = $newId;&lt;br /&gt;&lt;br /&gt;$sql = "INSERT INTO my_table (".implode(',', array_keys($values)).") ".&lt;br /&gt;    " VALUES (" . implode(',', $values) . ")";&lt;br /&gt;&lt;br /&gt;$result = $GLOBALS['mdb2']-&gt;query($sql);&lt;br /&gt;if (EpiDBTools::IsMDB2Error($result))&lt;br /&gt; return false;&lt;br /&gt;&lt;br /&gt;$newId = $GLOBALS['mdb2']-&gt;extended-&gt;getAfterID($newId, 'my_table', 'id');&lt;br /&gt;if (PEAR::isError($newId))&lt;br /&gt;{&lt;br /&gt; $newId = false;&lt;br /&gt; return false;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It's a bit verbose but it works. The getBeforeID is required if you want your code to work some day on PostgreSQL, which uses sequences instead of AutoIncrement values.&lt;br /&gt;&lt;br /&gt;Now if you want to insert a row in table with an autoincremented field, and still want to explicit the value of the index, you'll need to enable the IDENTITY_INSERT first, like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;if ($GLOBALS['mdb2']-&gt;dbsyntax == 'mssql')&lt;br /&gt;{&lt;br /&gt; // On MS SQL, allow temporarilly to insert a row by specifying the id&lt;br /&gt; $result = $GLOBALS['mdb2']-&gt;query('SET IDENTITY_INSERT my_table ON');&lt;br /&gt; if (PEAR::isError($result))&lt;br /&gt;   return false;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;$sql = "INSERT INTO my_table (id, name) VALUES (4, 'first')";&lt;br /&gt;&lt;br /&gt;$result = $GLOBALS['mdb2']-&gt;query($sql);&lt;br /&gt;if (PEAR::isError($result))&lt;br /&gt; return false;&lt;br /&gt;&lt;br /&gt;if ($GLOBALS['mdb2']-&gt;dbsyntax == 'mssql')&lt;br /&gt;{&lt;br /&gt; $result = $GLOBALS['mdb2']-&gt;query('SET IDENTITY_INSERT my_table OFF');&lt;br /&gt; if (PEAR::isError($result))&lt;br /&gt;   return false;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Even more verbose, but it works.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;MS SQL settings&lt;/h2&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://msdn2.microsoft.com/en-us/library/aa259217%28SQL.80%29.aspx"&gt;ANSI_NULL_DFLT_ON&lt;/a&gt;: &lt;/strong&gt;you should set this parameter to ON, if you don't want all your fields to be NOT NULL...&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;&lt;a href="http://msdn2.microsoft.com/en-us/library/aa259220%28SQL.80%29.aspx"&gt;IMPLICIT_TRANSACTIONS&lt;/a&gt;: &lt;/strong&gt;beware that if you leave that ON, then you'll have to do COMMITs from time to time, otherwise your changes won't be taken in account in the database. If you come from the MyISAM world, set this one to off (which is ON by default if you set ANSI_DEFAULTS to on).&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;GROUP BY&lt;/h2&gt;In MySQL, I allow myself to do queries like the following to get all the clients that have at least one order:&lt;br /&gt;&lt;br /&gt;SELECT * FROM client&lt;br /&gt;INNER JOIN order ON order.client_id = client.id&lt;br /&gt;GROUP BY client.id&lt;br /&gt;&lt;br /&gt;That &lt;span style="font-weight: bold;"&gt;won't&lt;/span&gt; work with MS SQL. In the SELECT clause, you are allowed to put things that appear in the GROUP BY clause (ie. client.id), or GROUP BY functions (MAX, AVERAGE, ...). It appears that MySQL is much more flexible than MSSQL regarding to this.</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2007/09/moving-from-mysql-to-ms-sql-using-pear.html' title='Moving from MySQL to MS SQL using PEAR MDB2'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=4946520818072573982' title='2 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/4946520818072573982'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/4946520818072573982'/><author><name>Bertrand Gorge</name></author></entry><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-2746661827958892061</id><published>2007-05-08T18:39:00.000Z</published><updated>2007-05-08T20:54:19.250Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Dynamic inheritence for application extension in PHP</title><content type='html'>In any big enough PHP application, comes a time when some of the objects that are used in the application might be overriden by some extensions, to customize the functionnality of the application.&lt;br /&gt;&lt;br /&gt;Let's take a example: your application has a class Employee that deals with the logic for every employee in your client's organization. Now imagine you have a client that wants to buy your application but wants to synchronize the employee table with its LDAP directory - fair enough.&lt;br /&gt;&lt;br /&gt;What we want to do, is really to extend the Employee class, so that we can change the code of a few functions and add a few others to give your final client the behaviour he asks for.&lt;br /&gt;&lt;br /&gt;So we add an extension (a folder really) where we put a class that extends Employee. We call that class LDAP_Employee for the sake of clarity:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;class LDAP_Employee extends Employee&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;function LDAP_Employee($id)&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$this-&gt;Employee($id);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Some more code to deal with the LDAP directory ...&lt;br /&gt;&amp;nbsp;&amp;nbsp;// ...&lt;br /&gt;}&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now the problem we have, is that everywhere in our application, the instances are still created out of the original Employee class.&lt;br /&gt;&lt;br /&gt;The trick is to use function variables to create the instances.&lt;br /&gt;&lt;br /&gt;Let's say you have a global variable defined at the top of the application:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;$GLOBALS['ClassDefinitions']['Employee'] = 'Employee'; // Name of the original class&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now, to instanciate an employee we just need to do:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;$MyEmployee = new $GLOBALS['ClassDefinitions']['Employee']($MyId);&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If we want to use the LDAP_Employee class, we just need to change the global once, high enough in the application initialization routine - where the extensions are loaded, basically:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;$GLOBALS['ClassDefinitions']['Employee'] = 'LDAP_Employee'; // Now we'll use the overriden class everywhere&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The rest of the code is left unchanged, but the behaviour of the whole application is now changed.&lt;br /&gt;&lt;br /&gt;The only downside of this method, is that the overriden classes need to know exactly what class they extend (you can't have a variable after the extend keyword). So if you have a second extension that wanted to extends Employee, you're cooked. (you can, however, have a third extension that extends LDAP_Employee, of course)</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2007/05/dynamic-inheritence-for-application.html' title='Dynamic inheritence for application extension in PHP'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=2746661827958892061' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/2746661827958892061'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/2746661827958892061'/><author><name>Bertrand Gorge</name></author></entry><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-2586768909953628151</id><published>2007-04-06T08:30:00.001Z</published><updated>2008-03-19T20:44:57.716Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>file_exists - the performance killer....</title><content type='html'>I recently went into some profiling for one of our apps, because a page was taking more than two minutes to display...&lt;br /&gt;&lt;br /&gt;I found, to my surprise, that a call to file_exists (in a loop) was taking 95 percent of that time... Wtf ??? I just cached the result to the call to gain that much time... Good to know !&lt;br /&gt;&lt;br /&gt;As a side note, I did the profiling with the latest version of PHPEd (Nusphere), great tool.</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2007/04/fileexists-performance-killer.html' title='file_exists - the performance killer....'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=2586768909953628151' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/2586768909953628151'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/2586768909953628151'/><author><name>Bertrand Gorge</name></author></entry><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-336255440471927954</id><published>2007-03-09T10:35:00.001Z</published><updated>2008-03-19T20:49:19.907Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><title type='text'>Hierarchical structures in SQL</title><content type='html'>I wish I read this before... The "id, name, parent_id" way of storing trees in a SQL table is probably the most inneficient way to do it! There is a better way of doing things, and here it is:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://dev.mysql.com/tech-resources/articles/hierarchical-data.html"&gt;http://dev.mysql.com/tech-resources/articles/hierarchical-data.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://blogs.epistema.com/bg/uploaded_images/hierarchical-data-5-743056.png"&gt;&lt;img style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: pointer; TEXT-ALIGN: center" alt="" src="http://blogs.epistema.com/bg/uploaded_images/hierarchical-data-5-740773.png" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://blogs.epistema.com/bg/uploaded_images/hierarchical-data-5-754430.png"&gt;&lt;br /&gt;&lt;/a&gt;</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2007/03/hierarchical-structures-in-sql.html' title='Hierarchical structures in SQL'/><link rel='related' href='http://dev.mysql.com/tech-resources/articles/hierarchical-data.html' title='Hierarchical structures in SQL'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=336255440471927954' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/336255440471927954'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/336255440471927954'/><author><name>Bertrand Gorge</name></author></entry><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-7962356780394603708</id><published>2007-01-05T16:49:00.000Z</published><updated>2007-01-05T19:15:49.403Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='scorm'/><title type='text'>Fun with SCORM 2004</title><content type='html'>I'm doing some work making one of our app "Scorm 2004" compliant. I'm baffled to see how easy it is now that we have &lt;a href="http://prototype.conio.net/"&gt;prototype.js&lt;/a&gt;, as if the people who designed SCORM way back then had XHRs in mind... In any case, it helps to just do a ajax.request for the commit function...&lt;br /&gt;&lt;br /&gt;Now, they didn't always have such a good visionnary view on things, especially when they decided to switch the format of cmi.session_time. WTF is that ? &lt;span style="font-family:courier new;"&gt;PT3H5M3.5S&lt;/span&gt; for 3 hours 5 minutes and 3.5 seconds...? Not only is it complex to parse (for the PHP minded, I give away the code right below), but now look at this: &lt;span style="font-family:courier new;"&gt;P1Y3M2DT3H&lt;/span&gt; equals 1 year, 3 months, 2 days and 3 hours. Her.... What's a month, please ? Is that 30 days ? 31 ? Should I guess ? And why choose the same character for months and minutes ? bah.&lt;br /&gt;&lt;br /&gt;Ok. Now some code:&lt;br /&gt;&lt;pre&gt;function GetSecondsForScormPeriod&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$strScormPeriod&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;{&lt;/b&gt;&lt;/span&gt;&lt;br /&gt; $matches &lt;span style="color:BLUE;"&gt;=&lt;/span&gt; array&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;  if&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;!&lt;/span&gt;preg_match&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:PURPLE;"&gt;"/^P([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(.[0-9]+)?S)?)?$/"&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;,&lt;/b&gt;&lt;/span&gt; $strScormPeriod&lt;span style="color:BLUE;"&gt;&lt;b&gt;,&lt;/b&gt;&lt;/span&gt; $matches&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:RED;"&gt;&lt;b&gt;return&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BROWN;"&gt;0&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt; $seconds &lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:BROWN;"&gt;0&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt; $InTime &lt;span style="color:BLUE;"&gt;=&lt;/span&gt; false&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt; foreach &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$matches as $aMatch&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;  {&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;   if&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$aMatch&lt;span style="color:BLUE;"&gt;&lt;b&gt;{&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BROWN;"&gt;0&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;}&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;=&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:PURPLE;"&gt;'P'&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="color:RED;"&gt;&lt;b&gt;   continue&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;    &lt;span style="color:RED;"&gt;&lt;b&gt;if&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$aMatch&lt;span style="color:BLUE;"&gt;&lt;b&gt;{&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BROWN;"&gt;0&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;}&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;=&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:PURPLE;"&gt;'T'&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:BLUE;"&gt;&lt;b&gt;{&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;     $InTime &lt;span style="color:BLUE;"&gt;=&lt;/span&gt; true&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="color:RED;"&gt;&lt;b&gt;   continue&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;   }&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;   $unit &lt;span style="color:BLUE;"&gt;=&lt;/span&gt; substr&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$aMatch&lt;span style="color:BLUE;"&gt;&lt;b&gt;,&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;-&lt;/span&gt;&lt;span style="color:BROWN;"&gt;1&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;   $val &lt;span style="color:BLUE;"&gt;=&lt;/span&gt; substr&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$aMatch&lt;span style="color:BLUE;"&gt;&lt;b&gt;,&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BROWN;"&gt;0&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;,&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;-&lt;/span&gt;&lt;span style="color:BROWN;"&gt;1&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;   switch&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$unit&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;   {&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="color:RED;"&gt;&lt;b&gt;   case&lt;/b&gt;&lt;/span&gt; &lt;span style="color:PURPLE;"&gt;'Y'&lt;/span&gt;&lt;span style="color:BLUE;"&gt;:&lt;/span&gt; $seconds &lt;span style="color:BLUE;"&gt;+&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;int&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$val&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;365&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;24&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt; &lt;span style="color:RED;"&gt;&lt;b&gt;break&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;     &lt;span style="color:RED;"&gt;&lt;b&gt;case&lt;/b&gt;&lt;/span&gt; &lt;span style="color:PURPLE;"&gt;'M'&lt;/span&gt;&lt;span style="color:BLUE;"&gt;:&lt;/span&gt;&lt;br /&gt;       &lt;span style="color:RED;"&gt;&lt;b&gt;if&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;!&lt;/span&gt;$InTime&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;         $seconds &lt;span style="color:BLUE;"&gt;+&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;int&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$val&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;30&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;24&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;       &lt;span style="color:RED;"&gt;&lt;b&gt;else&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;         $seconds &lt;span style="color:BLUE;"&gt;+&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;int&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$val&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;       &lt;span style="color:RED;"&gt;&lt;b&gt;break&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;      &lt;span style="color:RED;"&gt;&lt;b&gt;case&lt;/b&gt;&lt;/span&gt; &lt;span style="color:PURPLE;"&gt;'D'&lt;/span&gt;&lt;span style="color:BLUE;"&gt;:&lt;/span&gt; $seconds &lt;span style="color:BLUE;"&gt;+&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;int&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$val&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;24&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt; &lt;span style="color:RED;"&gt;&lt;b&gt;break&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="color:RED;"&gt;&lt;b&gt;   case&lt;/b&gt;&lt;/span&gt; &lt;span style="color:PURPLE;"&gt;'H'&lt;/span&gt;&lt;span style="color:BLUE;"&gt;:&lt;/span&gt; $seconds &lt;span style="color:BLUE;"&gt;+&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;int&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$val&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt; &lt;span style="color:BLUE;"&gt;*&lt;/span&gt; &lt;span style="color:BROWN;"&gt;60&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt; &lt;span style="color:RED;"&gt;&lt;b&gt;break&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="color:RED;"&gt;&lt;b&gt;   case&lt;/b&gt;&lt;/span&gt; &lt;span style="color:PURPLE;"&gt;'S'&lt;/span&gt;&lt;span style="color:BLUE;"&gt;:&lt;/span&gt; $seconds &lt;span style="color:BLUE;"&gt;+&lt;/span&gt;&lt;span style="color:BLUE;"&gt;=&lt;/span&gt; &lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;float&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;(&lt;/b&gt;&lt;/span&gt;$val&lt;span style="color:BLUE;"&gt;&lt;b&gt;)&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt; &lt;span style="color:RED;"&gt;&lt;b&gt;break&lt;/b&gt;&lt;/span&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;   }&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;  }&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color:RED;"&gt;&lt;b&gt;  return&lt;/b&gt;&lt;/span&gt; $seconds&lt;span style="color:BLUE;"&gt;&lt;b&gt;;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color:BLUE;"&gt;&lt;b&gt;}&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2007/01/fun-with-scorm-2004.html' title='Fun with SCORM 2004'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=7962356780394603708' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/7962356780394603708'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/7962356780394603708'/><author><name>Bertrand Gorge</name></author></entry><entry><id>tag:blogger.com,1999:blog-4072966502572230680.post-5020150841044076911</id><published>2007-01-05T14:02:00.000Z</published><updated>2007-05-08T21:00:45.250Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='bugs'/><category scheme='http://www.blogger.com/atom/ns#' term='scorm'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>PHP Bug</title><content type='html'>Found what I'd call &lt;a href="http://bugs.php.net/bug.php?id=40000"&gt;a rather anoying bug in PHP&lt;/a&gt;. Because of the historic way of passing POST and GET values through global variables instead of the &lt;span style="font-family:courier new;"&gt;$_POST&lt;/span&gt; and &lt;span style="font-family:courier new;"&gt;$_GET&lt;/span&gt; autoglobals everyone uses nowadays, any character that is not valid in the name of a variable is replaced with an underscore...&lt;br /&gt;&lt;br /&gt;For example, if you have a form with &lt;&lt;span style="font-family:courier new;"&gt;input name="cmi.session_time" value="P1Y3M2DT3H" type="hidden"&lt;/span&gt;&gt; you'll get &lt;span style="font-family:courier new;"&gt;$_POST['cmi_session_time']&lt;/span&gt;. Not very usefull at all (spot the dot that became an underscore)...&lt;br /&gt;&lt;br /&gt;Let's just hope it gets fixed someday.&lt;br /&gt;&lt;br /&gt;Funilly enough, the bug reported on bugs.php.net got numbered 40000... lucky number !</content><link rel='alternate' type='text/html' href='http://blogs.epistema.com/bg/2007/01/php-bug.html' title='PHP Bug'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4072966502572230680&amp;postID=5020150841044076911' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://blogs.epistema.com/bg/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/5020150841044076911'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4072966502572230680/posts/default/5020150841044076911'/><author><name>Bertrand Gorge</name></author></entry></feed>