Held IT Rotating Header Image

Configuring Zend Framework with Dwoo

I spent some time trying to set up Zend Framework using the Quick Start Guide, combined with the Dwoo Template Engine.  Even though there is an actual guide written for the Zend Framework side, as well as a Dwoo adapter and corresponding wiki help page – there still seems to be a lot of confusion around this setup process.  The number of complaints in the comments in the quick start guide combined with the lack of help I was able to find through Google while attempting this myself has led me to write a post  in order to hopefully help people dodge some of the problems and confusion.  Let’s get started.

Start out with the Zend Framework quick start intro.  Download the framework, and follow the instructions to create a project.  If you have any problems getting zf.sh/zf.bat to work correctly – remember that there is always the option of editing the _setupToolRuntime function in zf.php and just hard coding the path to the Zend library location.

After you pass the checkpoint in the ZF instructions for pointing your browser to the application and seeing a welcome page successfully, it is time to get Dwoo set up.  I personally just downloaded the tar.gz and unpackaged it into a directory in the following structure:

Structure for how I have Dwoo set up in my Zend Framework project.

Structure for how I have Dwoo set up in my Zend Framework project.

At this point I added the following code to the top of my index.php file located at public/index.php:

define('BASE_DIR', __DIR__);
define('DWOO_DIRECTORY', BASE_DIR.'/../library/Dwoo/');

This allows me to add the following code to the application/Bootstrap.php file:

protected function _initDwoo() {
    $autoloader = Zend_Loader_Autoloader::getInstance();
    $autoloader->pushAutoloader(array('Bootstrap','dwooAutoload'), 'Dwoo');

    $viewInterface = new Dwoo_Adapters_ZendFramework_View(array(
        'engine' => array(
            'compileDir' => APPLICATION_PATH . '/views/compile_dir',
            'cacheDir' => APPLICATION_PATH . '/views/cache_dir'
        )
    ));
    $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($viewInterface);
    Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
}

function dwooAutoload($class)
{
	include DWOO_DIRECTORY . strtr($class, '_', DIRECTORY_SEPARATOR).'.php';
}

The code above will use the defined constants to create a secondary autoloader for Zend. In the event that the Zend autoloader is not able to find a file, it will resort to this autoloader (which will work correctly for Dwoo).  The code then sets a few options to specify the compile and cache directories.  Make sure these directories exist in your application and are writable (feel free to change the location of these directories).

When you have gotten everything working to this point, continue on with the ZF quick start guide.  The only things I changed after this point are the php code snippets inside the view scripts (.phtml files).  For example, in the layout section:

<!-- application/layouts/scripts/layout.phtml -->
<?php echo $this->doctype() ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Zend Framework Quickstart Application</title>
  <?php echo $this->headLink()->appendStylesheet('/css/global.css') ?>
</head>
<body>
<div id="header" style="background-color: #EEEEEE; height: 30px;">
    <div id="header-logo" style="float: left">
        <b>ZF Quickstart Application</b>
    </div>
    <div id="header-navigation" style="float: right">
        <a href="<?php echo $this->url(
            array('controller'=>'guestbook'),
            'default',
            true) ?>">Guestbook</a>
    </div>
</div>

<?php echo $this->layout()->content ?>

</body>
</html>

becomes modified slightly for Dwoo:

<!-- application/layouts/scripts/layout.phtml -->
{doctype()}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Zend Framework Quickstart Application</title>
  {headLink()->appendStylesheet('/css/global.css')}
</head>
<body>
<div id="header" style="background-color: #EEEEEE; height: 30px;">
    <div id="header-logo" style="float: left">
        <b>ZF Quickstart Application</b>
    </div>
    <div id="header-navigation" style="float: right">
        <a href="{url(array(controller='guestbook'), 'default', true)}">Guestbook</a>
    </div>
</div>

{layout()->content}

</body>
</html>

The guestbook/index.phtml file:

<!-- application/views/scripts/guestbook/index.phtml -->

<p><a href="<?php echo $this->url(
    array(
        'controller' => 'guestbook',
        'action'     => 'sign'
    ),
    'default',
    true) ?>">Sign Our Guestbook</a></p>

Guestbook Entries: <br />
<dl>
    <?php foreach ($this->entries as $entry): ?>
    <dt><?php echo $this->escape($entry->email) ?></dt>
    <dd><?php echo $this->escape($entry->comment) ?></dd>
    <?php endforeach ?>
</dl>

becomes:

<p><a href="{url(array(controller='guestbook',action='sign'), 'default', true)}">Sign Our Guestbook</a></p>

Guestbook Entries: <br />
<dl>
    {foreach $entries entry}
    <dt>{$entry->email}</dt>
    <dd>{$entry->comment}</dd>
    {/foreach}
</dl>

You get the idea. As an unrelated side hint, if you want to use mysql instead of the directions for sqlite in the ZF quick start guide – swap out the following configuration format:

resources.db.adapter = "PDO_MYSQL"
resources.db.params.host = "localhost"
resources.db.params.dbname = "database_name"
resources.db.params.username = "database_user"
resources.db.params.password = "database_password"

I hope this has helped. Feel free to ask any questions. :-)

Discovering and Getting Started with Google Webmaster Tools Search Queries

The Google Webmaster Tools site has been around for quite a while, offering a wide array of information to help website owners manage and track google specific information for all websites the owner may have published.  These services include crawl errors, popular google keywords, external and internal links, crawl rates, sitemap information, and more.  More recently, an exciting addition has come to Webmaster Tools called Search Queries.

Google Webmaster Tools Search Queries Graph

Google Webmaster Tools Search Queries Graph

Search Queries gives users the ability to track Google searches in which their site has been displayed in the results.  The tool will track the total number of these unique queries, the number of impressions in which the site has been displayed in the results, as well as the number of clicks on these impressions.  The graph shown above is the search queries results of an example period for this site, showing the ability to view this information for different date ranges and locations.

Google Webmaster Tools Search Query Statistics

Google Webmaster Tools Search Query Statistics

The image above shows another piece of information shown in the Search Queries area, which gives the user a detailed breakdown of the individual search queries for the site.  Useful information is given explaining the number of impressions, clicks, CTR, and average position for this query.  The number of impressions simply shows the number of times the site has been in the result list for that query in the date range selected, while the number of clicks is the number of times out of those impressions that the site was actually clicked on by the search user.  CTR is an acronym for Click Through Rate, which is just a calculation of the number of clicks divided by the number of impressions.  Finally, the average position refers to the actual position of the site among all the results given for the search query.  An average position less than ten generally means that on average the site showed up on the first page of results for that search query.

Google Webmaster Tools Search Query Breakdown

Google Webmaster Tools Search Query Breakdown

The last screenshot given above shows a breakdown of an individual query shown in the query list for the site.  The query is broken down into results given by different positions that the site has shown up in.  This specific query shown has the most impressions in the “6 to 10″ range, but also other impressions tracked in a few other positions.  The breakdown also shows all of the pages linked to for this search query.

As seen above, there is quite a lot of useful information given by the new Search Queries section of Google Webmaster Tools.  This information can be used by all levels of website publishers and administrators to track and improve how their website shows up in Google search results.  I suggest that any person interested in seeing behind the scenes of how users get to their site through Google set their site up for Webmaster Tools, and take a look at the new Search Queries section. And if it wasn’t clear before, it is absolutely free!  For those interested in reading more about Webmaster Tools and Search Queries, Google provides more information and help for how to use this data:

The Ext JS datachanged event.

During my recent performance testing with Ext JS I came across the Ext.data.Store datachanged event, which was causing some pretty large performance issues.  The datachanged event is documented as fired “when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a widget that is using this Store as a Record cache should refresh its view.”  This seems to be used often in the specific application I have been looking at, due to the fact that there is a lot of background calculation causing data records to be changed behind grids.  I have created a small example application in Ext JS to show the purpose of the datachanged event, and the possible issues developers may run into with it.

The example consists of a basic grid and store with some static data loaded into it.  There are four buttons on the bottom toolbar of the grid to show different functionality.  The first button:

xtype: 'button',
text: 'Add',
handler: function(button, event) {
  myStore.each(function(record) {
    record.data.first += "s";
    record.data.last += ".";
    record.data.fullname = record.data.first + " " + record.data.last;
  });
  myStore.fireEvent('datachanged', myStore);
}

simply modifies each record in the store slightly, and then fires the datachanged event afterward.  This is a quick and proper way to modify the records, and then show the changes in the grid.  The second button:

xtype: 'button',
text: 'Remove',
handler: function(button, event) {
  myStore.each(function(record) {
    if (record.data.last.substring(record.data.last.length-1) == ".")
    {
      record.data.first = record.data.first.substring(0, record.data.first.length-1);
      record.data.last = record.data.last.substring(0, record.data.last.length-1);
      record.data.fullname = record.data.first + " " + record.data.last;
    }
  });
  myStore.fireEvent('datachanged', myStore);
}

is similar to the first, and simply removes the changes added by the first button. The third button is where we start to see the importance of the datachanged event:

xtype: 'button',
text: 'Add without datachanged',
handler: function(button, event) {
  myStore.each(function(record) {
    record.data.first += "s";
    record.data.last += ".";
    record.data.fullname = record.data.first + " " + record.data.last;
  });
}

This button has the same functionality as the first, except the datachanged event is not fired.  This means that all of the records behind the grid have changed – but the user will not be able to see these changes.  To see the full effect of this, try adding a few times without the datachanged, and then clicking remove once – at which point there should actually be an increase in characters.

The fourth and fifth buttons:

{
  xtype: 'button',
  text: 'Add with many datachanged',
  handler: function(button, event) {
    myStore.each(function(record) {
      record.data.first += "s";
      record.data.last += ".";
      record.data.fullname = record.data.first + " " + record.data.last;
      myStore.fireEvent('datachanged', myStore);
    });
  }
},
{
  xtype: 'tbseparator'
},
{
  xtype: 'button',
  text: 'Add more records',
  handler: function(button, event) {
    myStore.loadData(myData, true);
  }
}]

The purpose of these two buttons is to show performance issues with the datachanged event.  The difference between this add button and the previous one is that the datachanged event is fired on each iteration of the loop modifying the records, instead of after the loop completes and all of the records have been modifying.  The second button will add more records to the store – for the purpose of exaggerating the effect.  Try it out a few times. The ‘Add with many datachanged’ button should get very slow with 15-20 records in the grid, and most likely unusable after 30-50 records depending on processing power of your browser and computer.

I hope this example application and small code examples have helped.  You can access the full javascript for the application here.  Please feel free to leave a comment if you have any feedback or questions.

Clever Spam

I have been amazed recently by the amount of spam comments this blog has been receiving.  I have discovered that there are a couple different levels of spam:

1.  The ugly spam.  This spam has no bounds.  It usually starts with a sentence that has either been poorly translated or created by someone that obviously does not speak English too well.  After this comes a decent sized listing of links to sites mainly consisting of either ring tones, gambling, or porn.  Example:

Picture 6

Ugly Spam!

2. The slightly clever spam.  At least you can tell this person put a little effort into their bot.  It is still pretty obviously spam, but it is definitely a couple levels up from the ugly spam.  The sentence is pretty ugly, but readable.  I have no idea where that link goes, and I don’t want to know.  Nice try “AddinidonarNe”.

Slightly clever spam.

Slightly clever spam.

3. The clever spam. The example below might not be the best, but hell – I just like it.  Large Plastic Storage Boxes?  I’m not even ashamed.  We have all seen examples of clever spam.  The comments that make you really have to think to figure out if it is a commenter with poor grammar, or spam.  There are no direct links to other sites in the comment, but most likely a link in the name to a site full of advertisements.

Clever Spam!

Clever Spam!

So what am I getting at here?  I’d like to see more of the clever spam, and less of the others.  What will follow is a long list of tags and keywords to attract spam bots, and this time all clever spam will make it through my filter.  Yay.

healthcare reform new macbook pro comcast boxee  free ipad obama glen beck sarah palin google china lolcat.  free macbook pro ring tone roulette iphone justin bieber twilight masters toyota. ups tiki barber facebook myspace taxes nikon apple ford jimmy fallon kate gosselin reddit

Limited Wooden Skins and Covers for Macbook and iPad

KARVT Wooden Skin for Macbook Pro

KARVT Wooden Skin for Macbook Pro

A friend of mine from high school passed this one on to me, along with the hint that KARVT is a new company that he is starting.  Looks like the site is selling authentic wooden skins for all sizes of recent macbooks and macbook pros, with many choices from an original line – as well as an artist series that offers custom designs.  It even seems that other artists have the ability to submit custom designs to be reviewed and possibly added to the line.

Original designs go for $35, and artist designs for $50.  Not sure about you folks, but I know I will be ordering mine soon.

ExtJS Introduction

I have spent a lot of time lately working with the Ext JS javascript library, and decided it would be worth it to share my tips, tricks, hints, and overall experience.  I originally started using the library a couple years ago during my senior projects class in college.  There still seems to be a web page up describing the project…  Overall, the project didn’t end too well – but it did get me a good start in web development.

My current project these days has me building ridiculously large Ext JS applications – forcing me to dig deep and learn the intricacies of Ext JS best practices for design, performance, extensibility, and new features.  Although there is nothing specifically helpful in this post as it is just the introduction to the series, I do already have posts in the works to talk about performance surrounding events and listeners as well as performance surrounding rendering.

If anyone would like to see a post on a specific Ext JS section – please drop me a comment.

Pandora 40 Hour Listening Limit

I have been using Pandora for quite a while, and have never felt the need to upgrade my account to the Pandora One service.  There has never been a big enough reason to get me to pay the $36/year to upgrade.  I don’t believe I am stingy… I just don’t believe in unnecessary spending.  If you check out the link above, there really aren’t that many advantages to using Pandora One.  The main benefits are higher quality streaming and the removal of ads.  I have never personally had an issue with the audio quality I currently get with the regular service, and I am okay with having to deal with ads if I am not going to pay.  Really, the only reason I have considered upgrading my account is because Pandora has been on the verge of collapse and I figured they could use all the help they could get.  However, more recent news that Pandora will be bringing in $40 million in revenue has suppressed my worries.

More recently,  I have started running into issues because of the new 40 hour listening limit set for regular Pandora users.  This blog post from Pandora explains that while the royalty crisis is now over, a new 40 hour limit for regular subscribers has been set in place.  Or, for the direct explanation given when getting close to the limit:

Pandora Listening Limit

Pandora Listening Limit

I am trying to understand exactly how this works.  Is Pandora really losing money with every single user that isn’t paying for Pandora One?  The highest figures I have seen for how much Pandora pays per song is $0.0019.  If an average song is about 3 minutes long, meaning I will listen to about 20 songs every hour – then I will have listened to a total of 800 songs in a month before my account becomes restricted.  This means that using the steepest figures, my cost to Pandora as a user every month would be $1.52.  I understand that this is purely the cost of royalties and doesn’t encompass the other costs associated with Pandora on a month to month basis, but it is still apparent to me that the royalty fees aren’t as much of an issue as Pandora may make them seem.

At this point it comes down to how much Pandora makes off of ad revenue – specifically the ads that are presented to me, to make things a little simpler.  I get an audio/video ad roughly every 5-10 songs while listening, as well as an ad when changing stations.  Based on the previous figure of 800 songs per month, this means that I will see roughly 120 ads during the month (and this doesn’t even cover the constantly change visual ads surrounding the player).  In order to cover the cost of the songs I get to listen to that month, Pandora would need to make roughly $0.01266 per ad view.  If Pandora is making less than this off of their ads… then yes, they are going to have problems – although there are at least a few other good revenue streams for Pandora from their non-subscription users.

So what is the point of all this talk?  I need an easy way to continue listening to music after that 40 hour limit hits – hopefully a way that allows me to keep my stations as well as one in which I don’t have to pay.  In my research I have come across ways around the 40 hour limit that involve clearing cookies, but based on reviews it seems that these methods are not always that promising.  My method may require a little more setup, but definitely works – just create another account!  For those readers with morals holding them back,  I was unable to find anything limiting users to more than one account while looking through the Terms of Use for Pandora.  Another thing that makes this method so great, is that Pandora already has built-in functionality to help you share your stations to your new account.  Let’s go over how to set this up:

  1. First, log out and create a second account.  After that account is set up, log back in to your original account.
  2. Second, go to the options area of each account and select “Share this Station With a Friend”.

    Select Share this Station With a Friend

    Select Share this Station With a Friend

  3. Next, enter in the email address tied to the new account and click send.

    Enter the email address of the new account.

    Enter the email address of the new account.

  4. Last, accept all the new stations in the new account.

At this point you are ready to go.  I normally listen to my first account for most of the month, and then switch over to the second account when I reach the limit.  Next month, I start with my original account again.  I am curious to see where Pandora goes with its “Pandora One” service, but for now… this seems to be the best method for me.  Let me know how it goes for you!

MySQL Float vs. Decimal Money Datatype

It is surprising how often I see confusion around the MySQL float and decimal data types – especially when it comes to deciding which one to use when storing financial figures. Let’s compare the float and decimal definitions, and then compare the two in real use.

From the MySQL documentation:

Float:

A small (single-precision) floating-point number. Allowable values are -3.402823466E+38 to
-1.175494351E-38,0, and 1.175494351E-38 to 3.402823466E+38. These are the
theoretical limits, based on the IEEE standard. The actual range might be slightly smaller
depending on your hardware or operating system.

M is the total number of digits and D is the number of digits following the decimal point. If M
and D are omitted, values are stored to the limits allowed by the hardware. A single-precision
floating-point number is accurate to approximately 7 decimal places.

Decimal:

A packed “exact” fixed-point number. M is the total number of digits (the precision) and D is the
number of digits after the decimal point (the scale). The decimal point and (for negative numbers)
the “-” sign are not counted inM. If D is 0, values have no decimal point or fractional part.
The maximum number of digits (M) for DECIMAL is 65. The maximum number of supported decimals
(D) is 30. If D is omitted, the default is 0. If M is omitted, the default is 10.

What I understand from this is that floats should generally be used for large numbers where exact precision isn’t necessary, while decimals on the other hand do store the number to an exact precision.  This already a positive for the decimal data type, but let’s continue to an example:

mysql> create table floater_decimal(f float(10,2), d decimal(10,2));
Query OK, 0 rows affected (0.03 sec)

mysql> desc floater_decimal;
+-------+---------------+------+-----+---------+-------+
| Field | Type          | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| f     | float(10,2)   | YES  |     | NULL    |       |
| d     | decimal(10,2) | YES  |     | NULL    |       |
+-------+---------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

We have created table floater_decimal, with two fields – f float(10,2) and d decimal(10,2).

mysql> insert into floater_decimal values (5.33, 5.33);
Query OK, 1 row affected (0.02 sec)
mysql> select * from floater_decimal;
+------+------+
| f    | d    |
+------+------+
| 5.33 | 5.33 |
+------+------+
1 row in set (0.04 sec)

If we insert the same value (5.33) into both fields and do a normal select on both fields, it appears that we are pulling the same value back out of both fields.

mysql> select (1.0000000000*f) from floater_decimal;
+------------------+
| (1.0000000000*f) |
+------------------+
|     5.3299999237 |
+------------------+
1 row in set (0.00 sec)

However, if we select (1.0000000000*f) to force the query to show the more exact number actually being stored, it can be seen that 5.33 isn’t actually being stored to an exact precision.

mysql> select (1.0000000000*d) from floater_decimal;
+------------------+
| (1.0000000000*d) |
+------------------+
|   5.330000000000 |
+------------------+
1 row in set (0.00 sec)

If we apply the same logic to the decimal field, it can be seen that 5.33 really is being stored to an exact precision.

What does this mean?  It means that floats can be a very dangerous data type to use for storing financial figures.  It may seem okay to use from a first glance, but really the fact that the float isn’t storing an exact number can lead to mathematical issues when pulling the number back out.  Sure, the float is less than .000001 off from the intended number and should be rounded correctly when processed in the code – but imagine how far this .000001 difference can carry when adding/subtracting 1 million rows that are all slightly off.

If you intend to store financial figures to an exact precision, while avoiding errors that may seem impossible to find – use decimal instead of float!

WordPress Permalinks with Apache2

One of the most important things done during the process of setting up this blog was creating the permalinks structure.  Instead of the normal and sort of ugly:

http://www.heldit.com/?p=3

URL, this blog has been set up to use a different structure.  You might notice that the URL of the current page is:

http://www.heldit.com/2009/11/architecture/wordpress-permalinks-with-apache2

Which I consider to be the better and more useful URL.  Both links above will actually go to the same page, but the second link is the one that will be displayed and stored in search engines.  For general readability and SEO reasons, the second URL is much more appealing.

Setting this permalink structure up isn’t too difficult, and following a quick guide makes it easier.  I will provide instructions for setting up this structure using virtual hosts with Apache 2.  The first step is to edit the permalink settings via the WordPress admin console.  Navigate to Settings > Permalinks, click the custom option, and then type in:

/%year%/%monthnum%/%category%/%postname%/

This is the permalink structure used in the URL mentioned above, but there are also many more options to customize it further.  These options are described pretty well on the WordPress permalinks page: http://codex.wordpress.org/Using_Permalinks. After saving this new permalinks structure, the console will provide a couple lines to be inserted into the .htaccess file located in the root directory for the blog:

<IfModule>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

This may change based on each individual setup, so I would suggest grabbing it from the permalinks page instead of here.  If no .htaccess file exists in the blog root directory, create it and insert the lines.

The last step is to make sure that the mod_rewrite module is enabled.  This can be done by checking the virtual host file for the site to make sure it contains

RewriteEngine on

under the <VirtualHost> tag, as well as

AllowOverride all

under the <Directory..> tag.

After reloading the Apache 2 config, it is time to test by trying out both links to see if both result in the second URL.  Good luck!