CakePHP Reverse Routing

By: SethCardoza - Published: 2009-08-31 13:50:44 in Category: CakePHP

CakePHP provides a very strong and flexible routing engine, for both routing, and reverse routing. Creating a route for the home page is as simple as adding the following line to your routes.php configuration file.

Router::connect('/about_us', array('controller' => 'pages', 'action' => 'display', 'about_us'));

Now, anyone visiting http://example.com/about_us page, will see the view defined in your Pages controller, and the about_us view. This is a very simple example, but leads us to the topic of reverse routing.

We can easily create a link to the about_us page in our markup very easily with the following:

<a href="/about_us">About Us</a>

However, CakePHP provides us with a way to do this through reverse routing and the HTML helper.

<?php $html->link('About Us', array('controller' => 'pages', 'action' => 'display', 'about_us'); ?>

Will generate the following output.

<a href="/about_us">About Us</a>

And, if we later update the route defined in the beginning to a more detailed url, for SEO purposes:

Router::connect('/about-seth-cardoza', array('controller' => 'pages', 'action' => 'display', 'about_us'));

We will not have to update the $html->link(); because the reverse routing takes care of that for us.

This is just a brief introduction to the power and capabilities of CakePHP's routing and reverse routing. I recommend you read more about CakePHP's routing in their online manual.

If You Want to Charge for a Service Online, Make Sure You Engage Your Customers

By: SethCardoza - Published: 2009-08-25 13:08:57 in Category: General

I recently switched my invoicing system from Simply Invoices to Fresh Books. Simply Invoices was great while I used it, but the complete lack of customer service forced me to switch. Admittedly, I was using their services for free, but I repeatedly requested/suggested new features that would not only benefit me, but all users, with no feedback whatsoever. Maybe this was because I was not a paying customer, but I doubt that even if I was paying for the service, I would have received any feedback. I switched to Fresh Books because they had the features I was looking for. Upon switching, I received a phone call and an email from their customer service making sure that I was satisfied with their services thus far.

If you expect people to pay for your services online, you need to engage your customers. People want to know that there is someone behind the curtain. I don't mean to single out Simple Invoices, they provided a good service to me for free. The problem is that there is plenty of competition on the web. I can easily go elsewhere if I am not satisfied with your service. In this case, I switched to Fresh Books. They not only had the features I was looking for, but followed up with human contact.

Disabling Layouts and Views in CakePHP

By: SethCardoza - Published: 2009-07-28 23:33:44 in Category: CakePHP

It is easy to disable both the layout and view in CakePHP by putting the following line in your controller action:

$this->autoRender = false;

If you want to disable just the layout, use the following line in your controller action:

$this->layout = false;

And if you only want to disable the view for this action, use the following line in your controller:

$this->render(false);

Note that using $this->layout = false; and $this->render(false); together in your controller action will give you the same results as $this->autoRender = false;

CakePHP Image Helper for Front End Optimization

By: SethCardoza - Published: 2009-07-24 16:19:02 in Category: CakePHP

Going along with one of the many ways to optimize front end performance, I created an image helper for CakePHP that will get the image dimensions and include them as html attributes.

<?php
/**
 * This class builds an image tag. The main purpose of this is to get the image dimensions and
 * include the appropriate attributes if not specified. This will improve front end performance.
 * 
 * @author Seth Cardoza <seth.cardoza@gmail.com>
 * @category image
 * @package helper
 */
class ImageHelper extends Helper
{
    /**
     * Builds html img tag determining width and height if not specified in the
     * attributes parameter.
     *
     * @param string $src relative path to image including the 'img' directory
     * @param array $attributes array of html attributes to apply to the image
     *
     * @access public
     *
     * @return no return value, outputs the img tag
     */
    public function displayImage($src, $attributes = array()) {
        //get width/height via exif data
        //build image html
        if(file_exists(WWW_ROOT . $src)) {
            $image_size = getimagesize(WWW_ROOT . $src);
            if(!array_key_exists('width', $attributes) && array_key_exists('height', $attributes)) {
                $attributes['width'] = ($image_size[0] * $attributes['height']) / $image_size[1];
            } elseif(array_key_exists('width', $attributes) && !array_key_exists('height', $attributes)) {
                $attributes['height'] = ($image_size[1] * $attributes['width']) / $image_size[0];
            } else {
                $attributes['width'] = $image_size[0];
                $attributes['height'] = $image_size[1];
            }
        }

       
        $html = '<img src="' . $src . '"';
       
        foreach($attributes as $key => $value) {
            $html .= ' ' . $key . '="' . htmlentities($value, ENT_COMPAT, 'ISO-8859-1', false) . '"';
        }
       
        $html .= ' />';
        echo $html;
    }
}
?>

Download CakePHP Image Helper

Image Resizing Class

By: SethCardoza - Published: 2009-07-21 22:20:02 in Category: CakePHP

This is a simple script to resize images with PHP. It uses the GD Library functions, and is currently not compatible with ImageMagick. It will automatically determine the image type and use the appropriate functions to resize.

<?php
/**
 * This class handles image resizing. It will automatically determine the image
 * type and use the appropriate php resize functions. It uses GD libs and is not
 * currently compatibly with ImageMagick.
 * 
 * @author Seth Cardoza <seth.cardoza@gmail.com>
 * @category image
 * @package component
 */
class Image {
    public $name = 'Image';
    private $__errors = array();

    /**
     * Determines image type, calculates scaled image size, and returns resized image. If no width or height is
     * specified for the new image, the dimensions of the original image will be used, resulting in a copy
     * of the original image.
     *
     * @param string $original absolute path to original image file
     * @param string $new_filename absolute path to new image file to be created
     * @param integer $new_width (optional) width to scale new image (default 0)
     * @param integer $new_height (optional) height to scale image (default 0)
     * @param integer $quality quality of new image (default 100, resizePng will recalculate this value)
     *
     * @access public
     *
     * @return returns new image on success, false on failure. use ImageComponent::getErrors() to get an array
     * of errors on failure
     */
    public function resize($original, $new_filename, $new_width = 0, $new_height = 0, $quality = 100) {
        if(!($image_params = getimagesize($original))) {
            $this->__errors[] = 'Original file is not a valid image: ' . $orignal;
            return false;
        }
       
        $width = $image_params[0];
        $height = $image_params[1];
       
        if(0 != $new_width && 0 == $new_height) {
            $scaled_width = $new_width;
            $scaled_height = floor($new_width * $height / $width);
        } elseif(0 != $new_height && 0 == $new_width) {
            $scaled_height = $new_height;
            $scaled_width = floor($new_height * $width / $height);
        } elseif(0 == $new_width && 0 == $new_height) { //assume we want to create a new image the same exact size
            $scaled_width = $width;
            $scaled_height = $height;
        } else { //assume we want to create an image with these exact dimensions, most likely resulting in distortion
            $scaled_width = $new_width;
            $scaled_height = $new_height;
        }

        //create image       
        $ext = $image_params[2];
        switch($ext) {
            case IMAGETYPE_GIF:
                $return = $this->__resizeGif($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;
            case IMAGETYPE_JPEG:
                $return = $this->__resizeJpeg($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;
            case IMAGETYPE_PNG:
                $return = $this->__resizePng($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;   
            default:
                $return = $this->__resizeJpeg($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality);
                break;
        }
       
        return $return;
    }
   
    public function getErrors() {
        return $this->__errors;
    }
   
    private function __resizeGif($original, $new_filename, $scaled_width, $scaled_height, $width, $height) {
        $error = false;
       
        if(!($src = imagecreatefromgif($original))) {
            $this->__errors[] = 'There was an error creating your resized image (gif).';
            $error = true;
        }
       
        if(!($tmp = imagecreatetruecolor($scaled_width, $scaled_height))) {
            $this->__errors[] = 'There was an error creating your true color image (gif).';
            $error = true;
        }
       
        if(!imagecopyresampled($tmp, $src, 0, 0, 0, 0, $scaled_width, $scaled_height, $width, $height)) {
            $this->__errors[] = 'There was an error creating your true color image (gif).';
            $error = true;
        }

        if(!($new_image = imagegif($tmp, $new_filename))) {
            $this->__errors[] = 'There was an error writing your image to file (gif).';
            $error = true;
        }
       
        imagedestroy($tmp);

        if(false == $error) {
            return $new_image;
        }
       
        return false;
    }
   
    private function __resizeJpeg($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality) {
        $error = false;
       
        if(!($src = imagecreatefromjpeg($original))) {
            $this->__errors[] = 'There was an error creating your resized image (jpg).';
            $error = true;
        }

        if(!($tmp = imagecreatetruecolor($scaled_width, $scaled_height))) {
            $this->__errors[] = 'There was an error creating your true color image (jpg).';
            $error = true;
        }
       
        if(!imagecopyresampled($tmp, $src, 0, 0, 0, 0, $scaled_width, $scaled_height, $width, $height)) {
            $this->__errors[] = 'There was an error creating your true color image (jpg).';
            $error = true;
        }

        if(!($new_image = imagejpeg($tmp, $new_filename, $quality))) {
            $this->__errors[] = 'There was an error writing your image to file (jpg).';
            $error = true;
        }
       
        imagedestroy($tmp);
       
        if(false == $error) {
            return $new_image;
        }
       
        return false;
    }
   
    private function __resizePng($original, $new_filename, $scaled_width, $scaled_height, $width, $height, $quality) {
        $error = false;
        /**
         * we need to recalculate the quality for imagepng()
         * the quality parameter in imagepng() is actually the compression level,
         * so the higher the value (0-9), the lower the quality. this is pretty much
         * the opposite of how imagejpeg() works.
         */
        $quality = ceil($quality / 10); // 0 - 100 value
        if(0 == $quality) {
            $quality = 9;
        } else {
            $quality = ($quality - 1) % 9;
        }

       
        if(!($src = imagecreatefrompng($original))) {
            $this->__errors[] = 'There was an error creating your resized image (png).';
            $error = true;
        }
       
        if(!($tmp = imagecreatetruecolor($scaled_width, $scaled_height))) {
            $this->__errors[] = 'There was an error creating your true color image (png).';
            $error = true;
        }
       
        imagealphablending($tmp, false);
       
        if(!imagecopyresampled($tmp, $src, 0, 0, 0, 0, $scaled_width, $scaled_height, $width, $height)) {
            $this->__errors[] = 'There was an error creating your true color image (png).';
            $error = true;
        }
       
        imagesavealpha($tmp, true);
       
        if(!($new_image = imagepng($tmp, $new_filename, $quality))) {
            $this->__errors[] = 'There was an error writing your image to file (png).';
            $error = true;
        }
       
        imagedestroy($tmp);
       
        if(false == $error) {
            return $new_image;
        }
       
        return false;
    }
}
?>

I originally wrote this as a CakePHP component and added it to the Bakery. The script is general enough that I was able to remove the few bits of code that were CakePHP specific. If/when the article is published, I will link it here.

Download Image Resizing Class

Changing the Default Currency in DigiSHOP

By: SethCardoza - Published: 2009-06-20 21:30:51 in Category: DigiSHOP

DigiSHOP is not one to let you do things on your own. The code has little comments, and none of them are useful to developers wanting to customize the shopping cart. As always, any customizations you make will void your warranty, so customize at your own risk.


If you have already installed your shopping cart, but want a different default currency than the current one, SumEffect says you need to call them and have them do it, or void your warranty. It's actually very simple to accomplish though. All it takes is a few changes to the settings.

First, connect to the database, and bring up the ds_settings table. For this tutorial, I am going to assume that the current default currency is USD. You will need to change the following settings, from their values below.

locale = "en_US"
locale.admin.date = "en_US"
locale.showIntCurrSymbol = "N"
mCountry = "United States"
merchantCountry = "US"
taxCountry = "US"

For this tutorial, I am going to change the default currency to Canadian dollars. I will have to change these settings as follows:

locale = "en_CA"
locale.admin.date = "en_CA"
locale.showIntCurrSymbol = "Y"
mCountry = "Canada"
merchantCountry = "CA"
taxCountry = "CA"

Really, not all of these settings need to be changed, but this will ensure fewer issues later on. The locale.showIntCurrSymbol setting only needs to be changed from N to Y if you want the international currency symbol to be displayed, instead of the dollar (US) sign. Further, the mCountry, merchantCountry and taxCountry only need to be changed if the store's billing address is located outside of the current country.

Submitting a Form With jQuery

By: SethCardoza - Published: 2009-05-05 13:59:32 in Category: General

On a recent project I was working on, I had to have a form submit when the option was changed on a select box. This is a simple task with jQuery. I just had to add a the change() event listener to the appropriate select boxes, and then submit the form. It didn't work though. The event listener worked, but the form would not submit. I tried several solutions, all pretty much the same in the end. None of them worked, and then I realized that I named my submit button "submit". It's a force of habit, something I do without even thinking. It is what caused my solution to break though. "Submit" is a reserved word and it prevented jQuery from submitting my form.

Naming your submit buttons "submit' is not a good idea anyway. It is a bad habit I have, and will now be broken from this point forth. Remember, if you want to submit your form with jQuery, don't name your submit buttons "submit". Better yet, avoid using reserved words at all.

Kona on the Indo Board

By: SethCardoza - Published: 2009-04-17 20:59:55 in Category: General

My dog, Kona, used to be terrified of my Indo Board. He wouldn't even go near it when it was stationary. But, a few treats and he rides on it with me now.

SVN - Creating a New Repository

By: SethCardoza - Published: 2009-04-07 14:29:52 in Category: Version Control

Creating a new repository with Subversion is simple. Just run the following command on your svn server:

svnadmin create /path/to/svn/example.com

This will create a new, blank repository with the name "example.com". Now you will want to do one of two things, import an existing project, or setup the initial directory structure for your new repository. To import an existing project, simply run the following command:

svn import /local/path/to/existing/project http://svn.example.com/path/to/svn/example.com

Now, if you are not importing an existing project you will want to setup your initial directory structure, most likely in the following fashion.

/path/to/svn/example.com/branches

/path/to/svn/example.com/tags

/path/to/svn/example.com/trunk

All you have to do is, checkout the project, create the necessary directories, and commit them to the repository. A friend of mine created a shell script to automate creating a new svn repository, and setting up the initial directory structure and/or importing an existing project.

SVN Copy - Creating a Branch or Tag

By: SethCardoza - Published: 2009-04-02 18:21:26 in Category: Version Control

Tagging and branching with svn are as simple as using the copy command. For this tutorial, I will assume that your repository has the following structure:

/path/to/repository/branches

/path/to/repository/tags

/path/to/repository/trunk

To create a tag of the trunk, run the following command:

svn copy http://svn.example.com/path/to/repository/trunk http://svn.example.com/path/to/repository/tags/snapshot-of-trunk

To create a tag of your current working copy (assuming you are in that directory on your local machine):

svn copy . http://svn.example.com/path/to/repository/tags/working-copy-seth

To tag a branch, say before merging the branch back into the trunk, run the following command:

svn copy http://svn.example.com/path/to/repository/branches/branch-seth/ http://svn.example.com/path/to/repository/tags/snapshot-branch-seth

To create a branch of the trunk:

svn copy http://svn.example.com/path/to/repository/trunk http://svn.example.com/path/to/repository/branches/branch-seth

To create a branch of your current working copy:

svn copy . http://svn.example.com/path/to/repository/branches/branch-seth

Tags should only be used to create snapshots of the repository. No development should be done on a tag, meaning you should never commit code to a tag. Branches are used for development that you do not want to interfere with everyday activity. They can be used for experimental code, code that you may only want to have run on your local machine, but still would like to have the power of version control behind your code. There are many other situations in which you would want to use a tag or a branch. You can read more about subversion and tags and branches on Wikipedia.

Book Review: Beginning CakePHP From Novice to Professional

By: SethCardoza - Published: 2009-03-25 20:09:39 in Category: Book Reviews

I have been getting into CakePHP development more and more recently. With all the CakePHP books that have come out in the past year, I was hoping I could find one that would be a pretty comprehensive guide to CakePHP. I was looking for something that would basically tell me everything that Cake's online manual would. I know I will get the usual "why not just read the manual?". Well, if I have time to read a book, I'm not usually around a computer, and reading off the computer screen for an extended period of time isn't pleasant.

Beginning CakePHP From Novice to Professional does a decent job covering the basics of CakePHP. Having built even the simplest of applications with Cake before reading this book, I was able to breeze through the first half of the book. Much of it covers the very basics, including installation and setup, naming conventions, and etc. Most of the code examples in the book extend Cake's blog tutorial, which most Cake developers will be familiar with.

The book covers CakePHP 1.2, which is currently the latest major release. I was surprised it does not include documentation on how to upgrade a site from 1.1 to 1.2. Of course, it is different for every site, but some general instructions would be a good idea. The book has a paragraph on ACL with CakePHP, but no more than that. ACL is one of the more complex paradigms in Cake, and it would have been nice to have some documentation on this subject.

I learned a few tricks from this book, but nothing I couldn't have learned anywhere else. Beginning CakePHP From Novice to Professional would be great for those brand new to CakePHP, but it definitely does not cover more than the basics. If you are already familiar with Cake and have been working with it for a while, this book will be of little use to you. Hopefully one of the other CakePHP books out there will go into more depth than this one. As I read them, I will review them here, so be on the look out for them.

Why the Wii Fit Works (and Why It Doesn't)

By: SethCardoza - Published: 2009-03-24 12:38:39 in Category: General

In case you don't know, the Wii Fit is an exercise game for the Wii. There is much debate as to whether or not it is a viable source for exercise. Today, I will explain why both sides are correct.

Why It Works:

  • Allows you to set goals for BMI and body weight, and track your progress toward those goals
  • It provides a variety of Aerobic, Isotonic, and Isometric exercises.
  • It promotes competition, whether it be to beat your own high score, or beat those that also use your Wii Fit

Why It Doesn't Work:

  • It is easy to "cheat" at the games, meaning you can still do well but with minimal physical effort.
  • If you spend most of your time playing the Balance Games, you will not get much exercise.
  • You have to use the Wii Fit for it to work. Just like any other exercise, it only works if you do it.

The Wii Fit should not be seen as a replacement for a full exercise regiment. In my opinion it is best for times when you cannot make it to the gym, or the weather prevents you from running or biking. It is definitely better than no exercise at all though. Just remember to stay motivated and set achievable goals.

Animated Loading GIF Generator

By: SethCardoza - Published: 2009-03-16 20:27:10 in Category: General

I found this nifty little application that lets you generate a custom animated "loading" GIF. There are over 30 different images to choose from, with the ability to customize the color.

Animated Loading GIF Generator

SVN Merge - Merging a Branch Into the Trunk

By: SethCardoza - Published: 2009-03-06 21:04:02 in Category: Version Control

So you've created a branch for one reason or another, mainly to make sure that the trunk stays stable and doesn't create chaos for the other developers on your team. You've committed several changes to this branch and thoroughly tested them to make sure everything is in working order. Now how do you get them back into the trunk? This is how.

You should have a local copy of the branch since that is where you have done the development.

/local/path/to/repository/branch

Now, if you don't already have a copy of the trunk locally, get one. Navigate to your local copy of the trunk.

/local/path/to/repository/trunk

You will need the revision number of the revision when you created the branch. For the sake of this example, I will say that the branch was at revision 100. Now run the following command to merge the branch into your local copy of the trunk.

svn merge -r 100:HEAD https://svn.example.com/path/to/repository/branch/my-dev-branch

You should now see that the files modified in your branch have been merged in your local copy of the trunk. Some files may be in conflict, and will have a "C" next to them in the file list after the merge command was run. You can also run an svn status to see if any files are in conflict. Resolve all conflicts, manually if necessary. Now make sure everything is working on your local copy of the trunk. If so, check your changes into the trunk.

On a side note, I like to tag the trunk before I merge a branch back into it. It just puts my mind at ease knowing I have a snapshot of a known working copy of the trunk.

Book Review: Watchmen

By: SethCardoza - Published: 2009-02-27 23:18:27 in Category: Book Reviews

Watchmen is a graphic novel based in an alternate 1985, where Richard Nixon is still President. Costumed heroes have been outlawed, except for those sponsored by the government. The fear is that people with that much power can wreak havoc if unchecked. The story starts off with the death of The Comedian, a costumed hero that has done a great deal of work for the government. Rorschach, another costumed hero, one that is not government sponsored, finds his death very suspicious. He investigates his death, and the eventual deaths of other costumed heroes. The character profiles and personalities are not particularly original, especially to those big into comic books, but they are based on tried and true characters that work.

The time setting is obviously dated, but story itself could easily be adapted to the present day. There is plenty of detail, both in the writing and the artwork, that will make you want to read this over so that you don't miss anything. There are some excerpts from an biography written by one of the characters that do give some important details to the back story, but they can be a bit long and dry. It would have been nice if these details were worked in more seamlessly with some artwork, rather than the excerpts used.

Overall, the book is a great work of art. With the movie due out in less than a week, I recommend reading it before seeing the movie. I think the movie will be good, but will not do the book justice. It will be a tough story to adapt to film. The excerpts I mentioned earlier will have to be told instead of read. Something I neglected to mention, the comic within the comic, will hopefully make it into the film in some way or another. It is not key to the story, but it is one of those little touches of detail that makes the book great.