Refreshing the Auths session

If you have developed with the AuthComponent at all, you would know by now that the auth session does not refresh when ever a user updates its information (such a drawback). This is extremely useful in many situations, especially when a user updates his profile, and you need to echo the new content in the views. I have written the following method, which should be placed in your AppController. It can refresh the whole session or just a single key in the session.

/**
 * Refreshes the Auth session
 * @param string $field
 * @param string $value
 * @return void 
 */
public function _refreshAuth($field = '', $value = '') {
	if (!empty($field) && !empty($value)) { 
		$this->Session->write($this->Auth->sessionKey .'.'. $field, $value);
	} else {
		if (isset($this->User)) {
			$this->Auth->login($this->User->read(false, $this->Auth->user('id')));
		} else {
			$this->Auth->login(ClassRegistry::init('User')->findById($this->Auth->user('id')));
		}
	}
}

To refresh the whole session, you would call this method in an action while passing no arguments. If you would like to refresh a users email, you would pass email as the first argument, and the new email as the second. This method assumes you are using the SessionComponent and a User model.

// Refresh whole session
if ($this->User->save($this->data)) {
    $this->_refreshAuth();
    $this->Session->setFlash('Your information has been updated!');
}
// Refresh single key
$this->_refreshAuth('email', $this->data['User']['email']);

This should work fine for the time being, or at least until the Cake Team adds a refresh method to the AuthComponent. Cheers.

Proxy Search - Doing a search while using named parameters

While working on a project of mine, I wanted to be able to do a search, but have the post data be retained as named parameters (basically like a $_GET string). My first attempt was setting the form method to get, and trying to convert the $_GET by doing some mod_rewrite magic. This didn't work out at all, mainly because I couldn't get it to work for multiple named parameters. The only alternative I found was suggested by someone in the Google groups; they suggested posting a form to another action to deal with the logic, and finally redirect applying named parameters. This works perfectly, albeit adding an extra step in the process.

The process I created is something id like to call a Proxy Search. The following code should be placed in your app_controller.php file, so that it can be used as a global search by all controllers. You should not overwrite the proxy() action, unless you want specific logic in a certain controller.

/**
 * Sorts the named params for a posted page
 * @param string $model
 * @access public
 */
public function proxy($model) {
	$data = array_map('urlencode', $this->data[Inflector::camelize($model)]);
	$referer = explode('/', trim($this->referer(), '/'));
	$router = array_merge(array('controller' => $referer[0], 'action' => $referer[1]), $data);
	$this->redirect($router);
}	
/**
 * Applies the named params to the controller data
 * @return array
 */
public function _proxyGather() {
	return array_map('urldecode', $this->params['named']);
}

I will quickly try to explain how the form works. For example, if we are in the Users controller and want to do a search using the search() action, the forms url should point to /users/proxy/user instead of /users/search. Now shouldn't it be /users/proxy you ask? Well no, we need to know what model should be used in $data, so we need to pass an argument for the model that is used for the form (during $form->create()).

// UsersController::search()
echo $form->create('User', array('url' => array('controller' => 'users', 'action' => 'proxy', 'user')));
// If searching through books
echo $form->create('Book', array('url' => array('controller' => 'books', 'action' => 'proxy', 'book')));

Now back in the search() action, we need to grab the named params and pass them to the fields in the form. We do this by using the _proxyGather() method. No model name needs to be passed to _proxyGather(), simply because the data is pulled from the controller.

/**
 * Search players
 * @access public 
 */
public function search() {
    $this->data['User'] = $this->_proxyGather();
    $this->pageTitle = 'Search Users';
    $this->set('results', $this->paginate('User'));
}

You can also have this interact with pagination quite easily. All you need to do is add some conditional logic in the controller, and pass the data to paginator.

/**
 * Search players
 * @access public 
 */
public function search() {
    $this->data['User'] = $this->_proxyGather();
    if (!empty($this->data['User']['username'])) {
        $this->paginate['User']['conditions']['User.username LIKE'] = '%'. $this->data['User']['username'] .'%';
    }
    if (!empty($this->data['User']['firstName'])) {
        $this->paginate['User']['conditions']['User.firstName LIKE'] = '%'. $this->data['User']['firstName'] .'%';
    }
    $this->pageTitle = 'Search Users';
    $this->set('results', $this->paginate('User'));
}
// In the views using $paginator
$paginator->options(array('url' => $this->passedArgs));

I hope this has been helpful to someone who was looking to do a search system using named params, I know I had fun building it! I was thinking of turning this into a component, but have not figured out a way to do so yet, enjoy anyways!

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>
?>

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();

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

My open-source scripts officially released!

So it has taken me nearly a year to get to this point. Over the past year I have been building some very advanced PHP classes and scripts that I wish to release as open-source. The types of scripts range from database handlers, template engines, form validation and cleaning, session management, CakePHP components and behaviors, so on and so forth. For the time being im releasing 3 scripts that I deemed "stable" and "complete". I hope you love them and get as much use out of them as I have. The following 3 scripts are now officially released to the public!

Databasic - A basic wrapper class for the MySQL database engine. Contains methods for connecting to a database, fetching and returning results, building insert and update queries, optimization and more.

Gears - A template engine that loads template files, binds variables to a single file or globally without the need for custom placeholders/variables, allows for parent-child hierarchy and parses the template structure.

Commentia - Commentia is a CakePHP Behavior that automatically runs after a comment is made. Each comment is tested upon a point system to determine and classify it. If a comment has more then 1 point it is automatically approved, if it has 0 points it continues pending, and if it is in the negative point range it is either marked as spam or deleted entirely.

Loading Models specific to certain actions

Recently I was working on a project, where a certain controller had over 15ish+ different models. The problem is that each action needed a different model, and I didn't want to load all those models at once. By using my common sense, loading that many models would put more of a strain on load time by having to initialize each model and bind them to the Controller. I needed a quick way to load a model(s) specific only to a certain action. At first I tried putting $this->user[] = array('Model'); at the top of each action, but that didn't work and it didn't initialize "before" the action like it is supposed to. So what I came up with is a little statement trick that you can place in your beforeFilter(), which will load models specific to certain actions. Enjoy!

/**
 * Executed before each action
 */
public function beforeFilter() {
	parent::beforeFilter();
	// Load action specific models
	switch ($this->params['action']) {
		case 'friends':		$models = array('Friend'); break;
		case 'messages':	$models = array('Message'); break;
		case 'blog':		$models = array('Entry', 'Comment'); break;
	}
	if (!empty($models)) {
		foreach ($models as $model) {
			$this->loadModel($model);
		}
	}
}

Also it would be best to disable models be default in controller. This way it doesn't load a certain model twice, or overwrite the defaults.

class TestController extends AppController {
	public $uses = null;
}

Method for grabbing named parameters

So I never worked much with named parameters before in CakePHP, but recently I needed a way to filter data. It seemed passing many arguments to the action wasn't the best or correct approach. My initial thought was to find a method that can easily grab the param, but after much searching and looking at the API and Cookbook, no such method exists. The next alternative was doing a long ugly shorthand if statement that looked something like this (assuming our param and URL is /search/filter:username/):

$filter = (isset($this->params['named']['filter'])) ? $this->params['named']['filter'] : '';

Now seeing that CakePHP is in 1.2 Stable, you would assume they would have a simple method to fix this. But no worries, I have created a method that is very easy to use and add to your application. To add it to your application, you would place it in your AppController.

/**
 * Used to get the value of a named param
 * @param mixed $var
 * @param mixed $default
 * @return mixed
 */
public function getNamedParam($var, $default = '') {
	return (isset($this->params['named'][$var])) ? $this->params['named'][$var] : $default;
}

The first argument $var would be the name of the param (e.g., "filter" in our example above) and the second argument $default is the value you can return if the param does not exist. To use the method, simply call it from anywhere in your controller like so:

$filter = $this->getNamedParam('filter');

I have also created a second method for grabbing the value of query string param (Example URL: /search/?filter=username). I hope this has been helpful to someone, it has certainly been helpful and easily readable to me.

/**
 * Used to get the value of a get query
 * @param mixed $var
 * @param mixed $default
 * @return mixed
 */
public function getQueryParam($var, $default = '') {
	return (isset($this->params['url'][$var])) ? $this->params['url'][$var] : $default;
}