Tuesday, January 30, 2007

Example Development Environment

This is an outline of a development architecture I put together for a client.

A example software development environment utilizing, Linux, Apache, PHP, and Subversion.

No testing environment is shown here but it is easily added by cloning the dev env.

Outline of the Sandbox Environment

The Development Environment

The Production Environment

Friday, January 26, 2007

PHP Data Objects

DataObject is a design pattern that incapsualtes the database with a simple to use interface. This alows the Controller to interact with the database layer in an SQL'less way. To acomplish this you build a base DataObject abstract class that implements the base functionality and sets up the interface for a Data Object. Then for each of your 'Business Objects' (such as customer), you extend the DataObject class and implement the various methods provided by DataObject. (see attached code for details).

You'll in the posted PHP files below that the code gets more 'complex' as we move down the stack (Controller->BusinessObject->DataObject). This is the beauty of OOP since we have implemented the DataObject only once in our application (it may be used by many Business Objects). As long as we maintain its interface, we are free to refactore its implementation details and add functionallity. And there is only one file to change.

Retrival of object from the database

In the simple case of the controller needing to retrieve a customer. Traditionally this might involve building a query of something like...




This process involes the Controller having far too much knowelge of the Database layer. This tight coupling should be avoided.

The same process using a database object would go something like this...



Search the database for an Object

This is quite elegant with DO's. Just simply create a new DO, set what you do know about what you are seaching for then execute the object find method.



Saving and Updating Objects

Again this is very simple also. For a populated customer DO object, just issue its insert method and to update, use the update method.



Here is a Proof of Concept Controller PHP script



Here is the Customer Data Object that extends DataObject



And here is the Base Data Object Class


Very Simple Captcha

Very Simple Captcha or "Poor Man's Captcha"

This is a very simple way to implement a Captcha concept. It is less effective than say a generated on the fly image (using something like GD), but it is far better than using nothing at all.

Summary of Usage

In the PHP scripts that controls a page that requires a Captcha, simpley call the static function Captcha::generateCaptchaCode().
Then in the template of the page use Captcha::getHTMLCaptcha() to retrieve the HTML markups for the images representing the code.
Then on submision of the form with the captch, check the submitted value against the code in the session ($_SESSION['captcha_code'] by default)

Example Captcha Image

Simple Usage Example



Code for the Static Captcha class

Tuesday, January 23, 2007

Drupal Caching

Drupal 5.0 With caching off

ab -c100 -n10 http://example.com/index.php
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.141 > apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/

Benchmarking example.com (be patient).....done

Server Software: Apache/2.0.59
Server Hostname: example.com
Server Port: 80

Document Path: /index.php
Document Length: 10541 bytes

Concurrency Level: 100
Time taken for tests: 3.417284 seconds
Complete requests: 10
Failed requests: 6
(Connect: 0, Length: 6, Exceptions: 0)
Write errors: 0
Total transferred: 110154 bytes
HTML transferred: 104774 bytes
Requests per second: 2.93 [#/sec] (mean)
Time per request: 34172.840 [ms] (mean)
Time per request: 341.728 [ms] (mean, across all concurrent requests)
Transfer rate: 31.31 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 92 93 0.6 93 94
Processing: 1351 2433 652.3 2757 3322
Waiting: 1158 2222 668.5 2566 3129
Total: 1444 2526 652.6 2850 3416

Percentage of the requests served within a certain time (ms)
50% 2850
66% 3059
75% 3087
80% 3095
90% 3416
95% 3416
98% 3416
99% 3416
100% 3416 (longest request)

Drupal 5.0 With caching on (Normal)

[sam@vps ~]$ ab -c100 -n10 http://example.com/index.php
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.141 > apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/

Benchmarking example.com (be patient).....done

Server Software: Apache/2.0.59
Server Hostname: example.com
Server Port: 80

Document Path: /index.php
Document Length: 10435 bytes

Concurrency Level: 100
Time taken for tests: 0.506276 seconds
Complete requests: 10
Failed requests: 0
Write errors: 0
Total transferred: 148446 bytes
HTML transferred: 139122 bytes
Requests per second: 19.75 [#/sec] (mean)
Time per request: 5062.760 [ms] (mean)
Time per request: 50.628 [ms] (mean, across all concurrent requests)
Transfer rate: 284.43 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 93 93 0.7 93 94
Processing: 264 326 53.6 323 412
Waiting: 127 196 41.4 203 282
Total: 358 420 53.5 417 505

Percentage of the requests served within a certain time (ms)
50% 417
66% 430
75% 473
80% 492
90% 505
95% 505
98% 505
99% 505
100% 505 (longest request)

Drupal 5.0 With caching on (aggressive)

[sam@vps ~]$ ab -c100 -n10 http://example.com/index.php
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.141 INSERT:CONTENT:ENDgt; apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/Benchmarking example.com (be patient).....done

Server Software: Apache/2.0.59
Server Hostname: example.com
Server Port: 80

Document Path: /index.php
Document Length: 10541 bytes

Concurrency Level: 100
Time taken for tests: 0.469280 seconds
Complete requests: 10
Failed requests: 0
Write errors: 0
Total transferred: 144128 bytes
HTML transferred: 134804 bytes
Requests per second: 21.31 [#/sec] (mean)
Time per request: 4692.800 [ms] (mean)
Time per request: 46.928 [ms] (mean, across all concurrent requests)
Transfer rate: 298.33 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 92 92 0.9 93 93
Processing: 245 314 44.4 336 377
Waiting: 133 162 19.0 169 188
Total: 338 407 44.3 429 469
WARNING: The median and mean for the initial connection time are not within a normal deviation
These results are probably not that reliable.

Percentage of the requests served within a certain time (ms)
50% 429
66% 430
75% 440
80% 464
90% 469
95% 469
98% 469
99% 469
100% 469 (longest request)

Ponder Results

wow, certainly not a thorough test by any strech, but from ~3 req/sec (caching off) to ~20 req/sec with it on!
That is a quite impressive gain, 85%.

Saturday, January 20, 2007

Character Encoding 101

Although the discussion of character encoding for web developers is vast, This article by Tommy Olsson on Sitepoint is a great starting point. It does a supurb job of explaining the basics and defining the needed terms.
Another great article on encoding is over at Joel On Software

Get Lazy

After much deliberation and discussions with friends (thanks Mark), I have decided to embrace PHP5's built in __autoload function. I have chosen a method briefly documented on phpKitchen. For those of you who do not know what PHP5 __autoload is, no worry go here for a review

The short of it is for a class in the path {base class path}/persist/database/DbAdapter.php the class in the file dbAdapter.php would be named persist_database_DbAdapter. You then define an __autoload function similar to this...




So now what does this get us?? Instead of something like this...




can simply do...


$dbAdapter = persist_database_DbAdapter::getInstance();


Then when PHP tries to load the unknown class, persist_database_DbAdapter, the __autoload function will be called which will build the appropriate require_once statement to locate the class.

Now some may see this as casting the slight complexity of all those require_once statements only to add complexity of having strange class names like persist_database_DbAdapter. I may agree with this point of view but I would argue that there are much greater benefits to using __autoload() than just getting rid of those require_once statements and I will briefly summarize two of them below.

The first advantage (lesser of the two), is one of code readability. Whereas above I described the class name of persist_database_DbAdapter as ”strange”, I would suggest that the long class name actually helps to describe the true purpose of the class, making the code more readable by just the nature of the class name. Thus reducing the need to clutter the code with /* comments */.

The second advantage is much more substantial one and it involves what is often referred to as Lazy Initialization. What I often see happening in PHP is that required files for a Class are often added at the top of the file (outside of any methods). With this setup, whenever the Class is referenced all these requires are parsed and included regardless of if all the files they are including will actually be used on the particular Request cycle. Depending on the size of some of the files being parsed, this can be quite an expensive operation. For example, with the code below...




If getBar() is never called for this particular use of the class Bar, the process time of require_once(CLASS_PATH.'/foo/Bar.php') has been waisted. On the other hand if we use __autoload() the above code becomes simply...



So, in this case the require_once(CLASS_PATH.'/foo/Bar.php'); only occurs the instant before it is actually required (and never if it is not required). This is essentially the performance enhancement Pattern know as Lazy Initialization and when you are dealing with large and/or numerous class files, the processor time savings can be quite substantial.



Sunday, January 14, 2007

Back to school

For those of you who are into education for the knowlege (rather than the diploma), be sure to check out MIT Opencourseware. The couse selection is imense and the course materials are thourough and well organized. You will not get any ‘official’ credit for taking these courses but you cannot beat the price.