Archive for May, 2010

Correct way to write a for-loop in PHP

I’ve been using PHP as one of my toolbox languages for a few years, and there are both benefits and drawbacks to it when compared to more formal OO languages like C# and Java. One of the distinct annoyances I have with PHP is the for loop, and it’s not the loop itself, it’s the arrays that the loop iterates over.

The arrays in PHP are great and highly versatile, but they also lack a length field. Many new PHP programmers simply seek out a PHP counting function (count or sizeof) and throw it into the loop.

The result is that this function is called on every iteration of the loop because the loop must re-evaluate the condition each time it runs (hopefully this is obvious). Fortunately, there is an often overlooked syntactical option to store the length in a variable for comparison that doesn’t require an extra line of code before the loop.

for($i = 0, $len = count($myArray); $i < $len; $i++) {
     // use $myArray[$i] as usual
}

And that’s it. Extremely simple, but it’s just one of those things that many people don’t even realize until they see it done.

CakePHP fixtures and database fields containing serialized data

I just came across and interesting issue while writing some CakePHP unit tests. I have a field that stores serialized data and the data is automatically unserialized via an afterFind() callback in the model.

When writing the test, I created a fixture and originally used run-time importing from the main database and all my tests worked fine. When I switched to prefabricated records written in the fixture file’s $records variable, I was getting errors with the unserialization.

Upon closer examination, I noticed that the bake script for fixtures was automatically escaping the double quotes in the serialized string, causing things to fail. So the point is, if creating fixture records via bake, be sure to remove any added slashes in fields containing serialized data.

CakePHP: Sanitizing, Validating, and Saving a list of information

Say you have a textarea field on an input form where the user inputs a list of items (like email addresses) and you intend to regularly use this list (for instance to send emails to each email address). Several things must be accomplished here:

  • Change the text into an array of items
  • Sanitize the items (unlike standard input, this list will be displayed and used much differently, so it is more convenient to store it in a ready-to-use state)
  • Validate the items (No point storing invalid email addresses)
  • Store the finished list in the database

If any step fails, the form should maintain the original input, so we also won’t touch the data on the controller side. We’ll utilize model hooks and and a custom validation method to do everything we need.

Let’s start by formatting and sanitizing the data so we can validate it. I define beforeValidate() as follows:

function beforeValidate() {
    // I allow this field to be empty
    if(empty($this->data['Survey']['recipient_list'])) {
        return true;
    }

    // try splitting by new lines first
    $list = preg_split("[\n|\r]", $this->data['Survey']['recipient_list'], -1, PREG_SPLIT_NO_EMPTY);
    $final_list = array();

    // check for comma splits
    $L = count($list);
    for($i=0; $i<$L; $i++) {
        $rows = explode(',', $list[$i]);

        if(count($rows) > 1) {
            foreach($rows as $row) {
                if(!empty($row)) {
                    $final_list[] = $row;
                }
            }
        }
        else {
            $final_list[] = $list[$i];
        }
    }

    // sanitize the final list
    App::import('Sanitize');

    foreach($final_list as &$addr) {
        $addr = Sanitize::paranoid($addr, array('@', '.', '-', '_'));
    }

    // Update our soon-to-be-validated data
    $this->data['Survey']['recipient_list'] = $final_list;

    // Save will stop if we don't return true
    return true;
}

This is obviously customized for my use, but it handles some pretty standard needs. It splits the input into items based on new lines and commas (any mix of the two). Then sanitizes each item, ensuring to preserve standard email address characters.

We then add our own validation method and define it as follows:

function validateRecipientList($check) {
    if(empty($check['recipient_list'])) {
        return true;
    }

    // Using cake's built in validation class
    $Validation = Validation::getInstance();

    foreach($check['recipient_list'] as $addr) {
        if(!$Validation->email($addr)) {
            return false;
        }
    }

    // Needed to continue
    return true;
}

I’m simply ensuring each item is an email address (note the use of Cake’s built in validation class).

Lastly, we utilize beforeSave() to serialize the list and prepare it for storage:

function beforeSave() {
    // serialize recipient list
    $this->data['Survey']['recipient_list'] = serialize($this->data['Survey']['recipient_list']);

    // Again, needed to continue
    return true;
}

All of this will now happen automagically whenever this model’s save() is called. We can even handle unserializing the list via afterFind(), but that’ll be left to the reader.

CakePHP Auth and User authorization – Which solution is right?

For the past few days, I’ve been working with the newest 1.3 release of CakePHP. It has been a while since I’ve used it, but it seems there are some great new features and some improved documentation.

I’ve been building a generic layer for a new app (and subsequent apps, hence the generic part), including user management, authentication, and user groups/roles. I’ve been trying to discern the most effective approach for implementing very customizable permissions. I am envisioning an interface similar to Drupal, where permissions are based on roles, but can be very specific. Additionally, I’d like to have support for individual user permissions when required as well.

I can see how I would accomplish this with ACL, and have also implemented a custom permissions component, but I keep wondering if there is anyway to achieve such detailed permissions without having to mix in the permissions as conditional statements and jumble up the logic.

I had more to the post, until I realized yet again that I could change directions. More to come when I refine this layer further…