Fixing a Models result array, when doing subqueries

This approach should no longer be used in the later versions of CakePHP. I highly suggest using the ContainableBehavior.

In some cases, you want to grab extra data in the find() method by calling an SQL statement like COUNT() AS, or SELECT(). When you do this, your extra data is not nested in the Model index of your resulted array. In the example below, we are doing a test find() and taking a look at the returned array.

// Find() query
$this->User->find('all', array(
	'fields' => array(
    	'User.*', 
        'COUNT(User.id) AS totalUsers'
   	)
)); 
/* Resulting array
[User] => Array (
    [id] => 1
    [username] => milesj
)
[0] => Array (
    [totalUsers] => 100
)*/

Now there are two ways to fix this problem, one is doing it in the afterFind() of your model, and the other is editing the core CakePHP DBO files. The first method can be found at the link below, and was written by a fellow baker, Teknoid. This technique would only apply to the model it was put in, the next technique applies it globally.

http://teknoid.wordpress.com/2008/09/29/dealing-with-calculated-fields-in-cakephps-find/

The second method is editing the resultSet() method of your datasource (does not apply to all datasources), which was brought to my attention by grigri. In my example, this technique will work for both MySQL and MySQLi, but I will be using MySQLi. Ee need to open the MySQLi datasource found at cake/libs/model/datasources/dbo/dbo_mysqli.php, copy the whole code and save our own version at app/model/datasources/dbo/dbo_mysqli.php. Once we have created our own file, we will navigate our way down to the method resultSet(). All we need to do is add another if statement in the while loop that looks for a result similar to Model__fieldName. Below you can see the before and after edits (only a part of the method):

// Old code block
while ($j < $numFields) {
    $column = mysqli_fetch_field_direct($results, $j);
    if (!empty($column->table)) {
        $this->map[$index++] = array($column->table, $column->name);
    } else {
        $this->map[$index++] = array(0, $column->name);
    }
    $j++;
}
// New code block
while ($j < $numFields) {
    $column = mysqli_fetch_field_direct($results,$j);
    if (!empty($column->table)) {
        $this->map[$index++] = array($column->table, $column->name);
    } else {
        if (strpos($column->name, '__')) {
            $parts = explode('__', $column->name);
            $this->map[$index++] = array($parts[0], $parts[1]);
        } else {
            $this->map[$index++] = array(0, $column->name);
        }
    }
    $j++;
}

This technique is probably the easiest to do, and will apply to all models. Once we have altered our datasource, we can change our find() method and our result should be working correctly now.

// Find() query
$this->User->find('all', array(
	'fields' => array(
    	'User.*', 
        'COUNT(User.id) AS User__totalUsers'
   	)
)); 
/* Resulting array
[User] => Array (
    [id] => 1
    [username] => milesj
    [totalUsers] => 100
)*/

Using and understanding setFlash()

When I first started using CakePHP, I never used the Session->setFlash() method to display success or error messages. I never used it because I was under the impression that flash messages were automatically redirected if debug is turned off, but this only applies to the controllers flash(). So I started to use and understand setFlash(), and here are my findings.

<?php // Controller action
public function add($id) {
	$pageMessage = '';
	if (!empty($this->data)) {
		if ($this->Post->save($this->data)) {
			$pageMessage = 'Your post has been added!';
			$this->data = '';
		}
	}
	$this->pageTitle = 'Add Post';
	$this->set('pageMessage', $pageMessage);
}
?>

Now there isn't anything entirely wrong with the code above, it simply adds a bit of unnecessary logic. To do it correctly using Cake, you would remove all my instances of $pageMessage and instead use $this->Session->setFlash() (SessionComponent must be active). This method saves the success message to the session, and within the view you would use $session->flash() to output the message.

By default the $session->flash() method will generate a layout for the flash message. You could use your own layout by creating a view in the app/views/layout/ folder. For example, if we created a layout called success.ctp, the code may look something like the following:

<div id="success">
Success!<br />
<?php echo $content_for_layout; ?>
</div>

The $content_for_layout variable will be replaced with the flash message. And now to use this layout you would pass a second parameter with the name of the layout, like so:

$this->Session->setFlash('Your post has been added!', 'success');

You could use this technique and create as many flash layouts as you please, but there are other alternatives. The setFlash() can take a third parameter being an array of options/variables that can be used in the flash view. One of the default options is the class. You could set the class option to "success" and in the auto-generated view it will have the class success; you could then just create a .success class in your CSS.

What I presented is only a small way that you could use setFlash(). You could find more information on the setFlash() by visiting the link below. And with my closing statement, I will also throw up a quick example for using this method.

http://book.cakephp.org/view/400/setFlash

<?php // controllers/posts_controller.php
public function add($id) {
	if (!empty($this->data)) {
		if ($this->Post->save($this->data)) {
			$this->Session->setFlash('Your post has been added!', 'success');
			$this->data = '';
		}
	}
	$this->pageTitle = 'Add Post';
}
// views/posts/add.ctp (somewhere in the code)
$session->flash();
// views/layouts/success.ctp
<div id="success">
	Success!<br />
	<?php echo $content_for_layout; ?>
</div>
?>

Numword officially released

So I'm releasing this fun little script I built a few months ago, it's called Numword. This script will take a number and convert it into its word equivalent. For example, 12 will become twelve, and 7 will become seven. Now why did I create this? Well one day a friend asked me if there was a PHP function that would do this, but there wasn't, so I set out and a few hours later it was done! I thought it would be a good idea to release this to the public, for anyone who might need something like this, as rare as it would be. Enjoy!

Download Numword v1.2
View the complete change log

Databasic v2.0 has arrived!

I am proud to present the new Databasic, version 2.0! Since I added quite a few new methods and functionality, rewrote and optimized most of the code, I felt it would be good to deem this version 2.0 instead of 1.11. The biggest new features in this version are support for multiple databases, using the create() method to create a database table, and the addition of many MySQL statement methods. Some of these new methods include drop(), truncate(), describe(), create() and optimize(). On top of all these additions, I have cleaned the code and fixed any bugs that were existent.

Note before upgrading! The new 2.0 version is not necessarily backwards compatible, the new system removes the constants that are used for the connection info, and instead uses a new method called store() that can store multiple connections. Finally the select() method has been changed, the arguments $limit and $offset have been removed and are now part of the $options array.

You may now subscribe to my newsletter, that will be send out emails when a script has a new version available.

Download Databasic v2.0
View the complete change log

Turning debug off automatically in production

So I recently got tired of having to manually set debug to 0 every time I updated an old site or published a new one. So I thought, why not have it automatically turn off when the site is live? For this to work, I'm assuming that you are working on a localhost with an IP of 127.0.0.1. If you are not, you might get this to work by changing the IP in the code below, or using HTTP_HOST instead of REMOTE_ADDR. Simply add this code at the top of your app/config/bootstrap.php file.

// Production
if (env('REMOTE_ADDR') != '127.0.0.1') {
	Configure::write('debug', 0);
}
// Or alternate technique
if (env('HTTP_HOST') != 'localhost') {
	Configure::write('debug', 0);
}

This also means you can leave debug set to 2 in your core config file at all times.

Pre-populating form values

When I first started using CakePHP, I couldn't find a way to pre-populate a form with data. Why would I need this you ask? Well it's simple, it would be used to edit something already existent. So what I need to do is grab the data from the database, and then populate the form. Took me a few hours to figure out how to do it, the guide really didn't explain how it should be done (or I just overlooked it). To have the data populate, all you need to do is to set $this->data to the database result. Heres a quick example action:

function edit($id) {
    $user = $this->User->findById($id);
    // If data is empty, populate it with user array
    if (empty($this->data)) {
    	$this->data = $user;
    }
    // Form is posted
    if ($this->RequestHandler->isPost()) {
   	 	// Do model validation and save
    }
    $this->pageTitle = 'Edit User';
}

Pre-populating will only be done if the post/data is empty; so once you post the form, the new data should fill the form. Now that we have given $this->data some value, we need to make sure the form is using the correct model ($this->data['User'] equals form User). In our case it would be the User model and its as easy as that.

// $this->data['User'] will populate here
echo $form->create('User');
echo $form->input('email');
echo $form->input('website');
echo $form->end();

Databasic v1.10.3 Released

It seems the most recent version of my Databasic class had a few problems in it. I left some code in there (echos and comments) that was used while I was testing, and I forgot to revert it. I want to thank Tanax for sending me an email and pointing it out. To everyone else, please download the latest version (1.10.3) and be sure to email me if you find any problems.

Download Databasic v1.10.3
View the complete change log

21 years young

So its my 21st birthday today, time flies by fast. I remember being only 16 and learning PHP, and now its 5 years later and I am pretty proud of where I am. But anyways, I will be celebrating at Dave & Busters, playing some video games and having a beer. Just want to say hi to everyone out there reading this, and a premeditated thank you to everyone wishing me a happy birthday!

Stay tuned for a few new tutorials I have planned for the coming week.

XAMPP in Windows: Enabling InnoDB

I recently convinced my friend to start learning CakePHP and to install XAMPP for windows. Everything was going smoothly up until he needed to have the database engine InnoDB enabled. Here's a very quick tutorial on how to enable InnoDB on Windows XAMPP (might also work with other local servers).

I'm assuming you have installed the XAMPP directory into the root or C:/ drive. The first thing to do is to open the my.cnf located at C:/xampp/mysql/bin/my.cnf (Notice Your my.cnf file may have an icon that looks like a computer/phone and has a type called SpeedDial. Do not worry, this is the correct file. To open the file, first start Notepad and then locate the my.cnf file and open it). Locate the text below and add # to the beginning of it (# acts as a comment and disables the command).

#skip-innodb

The next and final step is to remove # from any line the begins with innodb_. Your file should now look something like the following. Once you have done this, save the file and InnoDB should be useable in your local server.

#skip-innodb
innodb_data_home_dir = "C:/xampp/mysql/"
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = "C:/xampp/mysql/"
innodb_log_arch_dir = "C:/xampp/mysql/"
innodb_buffer_pool_size = 16M
innodb_additional_mem_pool_size = 2M
innodb_log_file_size = 5M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50

Commentia v1.2 Released!

I recently got a few emails from individuals about by Commentia script. They suggested that I add a settings system for the Behavior that loads on setup() (didn't know about this with the lack of documentation). Well to say the least I took their advice and redid the script so you don't have to manually edit the file, you just define the settings in the model. The documentation has been updated to reflect this change.

Download Commentia v1.2
View the complete change log