Archive for January, 2010

Useful CakePHP Tutorial Roundup for January 29, 2010

Posted in CakePHP on January 29th, 2010 by Jamie – 1 Comment

Well, I’ve finally gathered enough good CakePHP links to warrant another tutorial roundup. It’s not that there haven’t been any good CakePHP posts out there – the blogs are full of ‘em, especially with 1.3 finally in beta – it’s just that I’ve been (and still am) pretty busy. But as always, I’m constantly coming across useful CakePHP tips, code, and tutorials that save me a lot of time. So I thought it might be useful to others to gather up some tutorials every once in a while and just get them out there. Some are new; some are old chestnuts.

Three quality community resources to showcase today:

  • MultiTree BehaviorThom (cyberthom) (Jan 27, 2010)
    Thom provides a robust, functional alternative to Cake’s core tree behavior for those who want multiple trees in one table. I wrote a little hack of the Tree behavior to accomplish this same task, but the MultiTree Behavior is a hell of a lot better. Nice job, Thom! The methods have mostly the same function names as the core Tree behavior, so you can basically drop this behavior in and then just change your $actsAs “Tree” to $actsAs “MultiTree”. I started using it this morning so I haven’t put it through all of its paces, but it seems pretty solid.
  • Providing common functionality with AppShell – Joe Beeson (Oct 26, 2009)
    This little tutorial/accompanying script implements an “AppShell” that you can use a parent for your Shell classes, in the spirit of AppModel and AppController. I’ll be honest, I haven’t actually used Joe’s code yet, but the idea is great. I’m a bit surprised that the core doesn’t already provide this functionality – instead of YourShell -> AppShell -> Shell (just like YourModel -> AppModel -> Model), the extension chain is just YourShell -> Shell. I guess that AppShell isn’t in the core simply because the Shell class is woefully underused by most developers. But if you find yourself writing a lot of shell scripts with common functionality, then AppShell looks like a good time saver.
  • Authsome Component – Felix Geisendörfer, Debuggable (Dec 25, 2009)
    This is another “haven’t used it yet, but definitely will” bit of code by Felix Geisendörfer, who’s released his share of nice code. Authsome is a less intrusive replacement for the core Auth component, and it looks pretty slick and simple. It handles logging in a lot better than the Auth component, and I like the static Authsome::get(’user_variable’) functionality.

Major Update / Make-Workage to Zend_Search_Lucene Datasource for CakePHP

Posted in CakePHP on January 22nd, 2010 by Jamie – Be the first to comment

I’ve majorly retooled my recently released Zend_Search_Lucene datasource for CakePHP. You can find the latest version on Github, and I’ve also updated the tutorial to reflect the changes.

htaccess trick – redirect to WWW domain without hardcoding the domain name

Posted in Server on January 22nd, 2010 by Jamie – Be the first to comment

I’m notoriously bad at writing good htaccess redirection rules, so when it came time to write a rule that would redirect any non-www URL to its www equivalent, I was a bit lost. I wanted a rule that I could apply to any website without needing to hard-code the domain. I found a few examples after some Google searching, but nothing worked for me. So, a colleague and I came up with this rule, which does the job:

RewriteCond %{HTTP_HOST} ^([a-z-]+)\.([a-z]{2,6})$ [NC]
RewriteRule ^(.*)$ http://www.%1\.%2/$1 [R=301,L]

Can it be improved? You tell me!

Zend_Search_Lucene Datasource for CakePHP

Posted in CakePHP, Zend Framework on January 13th, 2010 by Jamie – 14 Comments

Major update January 22/10: much of the content of this article has been updated to reflect the changes to the datasource, the latest version of which you can download on Github.

Just out of the oven – a Zend_Search_Lucene datasource for CakePHP (built with 1.2 but probably works just fine in 1.3) that I originally wrote for an in-house CMS site search plugin. I can’t release the plugin itself (and there’s so much CMS-specific code that it would need a lot of work to make it generic anyway), but I thought that someone might find the datasource itself useful. It’s pretty basic at this point and doesn’t implement some of the fancier Zend_Search_Lucene features such as sorting (it just returns sorted in score order, which is probably what you want anyway).

Zend_Search_Lucene is a text-based search index system for developers who don’t want to (or can’t) use a database for search indexing.

Download the current version of the ZendSearchLuceneDatsource from my Github repository.

I won’t go into detail about how to add data into the Lucene database since the Zend Framework documention is so good (CakePHP should be jealous!). You’ll find all the info you need there. There are also a couple of older articles out there that show how you can integrate Zend_Search_Lucene into CakePHP:

Setup

First, copy zend_search_lucene.php to models/datasources.

Then, you’ll need to download the Zend_Search_Lucene library from the Zend Framework website and put some files into your /vendors directory:

  • Zend/Search (the directory and all of its contents)
  • Zend/Exception.php

You’ll also need to update your include path to include app/vendors, since the Zend Framework loads a lot of classes on its own. I also made a little autoload function to make the loading of Zend Framework classes easier. Put the following code somewhere common, such as app/bootstrap.php:

ini_set('include_path', ini_get('include_path') . ':' . CAKE_CORE_INCLUDE_PATH . DS . '/vendors');
function __autoload($path) {
if (substr($path, 0, 5) == 'Zend_') {
include str_replace('_', '/', $path) . '.php';
}
return $path;
}

You also need to put the DB config for the datasource in config/database.php (updated Jan 20/2010 for better DebugKit compatibility):

var $zendSearchLucene = array(
	'datasource' => 'ZendSearchLucene',
	'indexFile' => 'lucene', // stored in the cache dir.
	'driver' => '',
	'source' => 'search_indices'
);

Then, in the model that’ll act as your search index (say, for example, SearchIndex), specify the DB config:

<?php class SearchIndex extends AppModel {
var $useDbConfig = 'zendSearchLucene';
}
?>

Saving/Indexing

I’ve tried to keep the datasource functions as simple and familiar as possible. When saving an item to the index, the datasource expects a multidimensional array for each item. For compatibility with CakePHP’s datasource code, the ‘meat’ of the data is nested in the third level of the array. Each sub-array contains information about a field to be stored. For example:

$saveData = array('SearchIndex' => array(
  	'document' => array(
		array(
			'key' => 'name',
			'value' => $record[$Model->alias][$this->settings[$Model->alias]['name']],
			'type' => 'Text'
		),
		array(
			'key' => 'description',
			'value' => $record[$Model->alias][$this->settings[$Model->alias]['description']],
			'type' => 'Text'
		),
		array(
			'key' => 'url',
			'value' => $this->__constructUrl($Model, $record),
			'type' => 'Text'
		)
	)
 ));

Passing that data in a Model::save() call will in turn execute the following Zend code (more or less – this is a very simplified version of the actual ZendSearchLuceneSource saving code):

$index = Zend_Search_Lucene::open('/path/to/the/index/set/in/dbConfig');
$doc = new Zend_Search_Lucene_Document();
foreach ($data as $field) {
$doc->addField(Zend_Search_Lucene_Field::$field['type']($field['key'], $field['value']));
}
$index->addDocument($doc);

Obviously that’s a basic example; you’ll probably send a whole bunch of dynamic info to the indexer. But that’s the gist of it anyway.

Querying

You can search for records just like you would a regular datasource. Pass the search terms as a “query” condition. If you want the search terms to be highlighted in the returned results, pass ‘highlight’ => true in the array of options. Note that only indexed fields will be highlighted.

You can find all results:

function search($term) {
$results = $this->SearchIndex->find('all', array('highlight' => true, 'conditions' => array('query' => 'best cakephp tutorials')));
}

You can mimic Google’s I’m Feeling Lucky with find(’first’):

function search($term) {
$topResult = $this->SearchIndex->find('first', array('conditions' => array('query' => 'best cakephp tutorials')));
}

You can even paginate:

function search($term) {
$this->paginate = array(
'limit' => 10,
'conditions' => array('query' => 'best CakePHP tutorials'),
'highlight' => true
);

$results = $this->paginate();
}

Results are returned in the expected CakePHP way, as a multidimensional array – $results[0]['MyModelAlias'] for multiple records, $results['MyModelAlias'] for one (i.e. with find(’first’)).

There you go – enjoy! As always, comments and suggestions are welcomed.

I used the RSS Feed datasource by Loadsys as a guide to good datasource design. I may have borrowed a function or two. ;)

Neil Crookes’ Searchable plugin also helped.

Adding Better Scope Limiting to CakePHP 1.2’s Tree Behavior

Posted in CakePHP on January 6th, 2010 by Jamie – 6 Comments

OK, so I’ve been working a lot with Cake’s Tree Behavior for the past six or seven months, because a lot of the plugins I’m building at work require complex category structure (i.e. more complex than simple parent -> child). Tree Behavior’s implementation of MPTT is good, but – unless I’m using the behavior incorrectly or can’t find the proper syntax – the ’scope ‘ setting doesn’t really work in the way that I would expect.

Basically, Tree Behavior is great when all of the records in a table fall are part of the same tree. But what if you want different trees within the same table? For example, if you’re making a navigation system, you might have this relationship:

NavigationList hasMany NavigationItem
NavigationItem belongsTo NavigationList

Since navigation can get pretty complex, we might want to use the Tree Behavior to keep track of the ordering of the NavigationItem records. But, we want a separate tree for each NavigationList, so we don’t get our main menu, footer menu, member’s only menu, etc. mixed up. The “scope” setting already exists in Tree Behavior, but it’s basically undocumented, so I had to guess at its syntax (after looking at the Tree Behavior code of course). I tried this:

class NavigationItem extends AppModel {
    var $actsAs = array('Tree' => array('scope' => 'NavList'));
}

… hoping that the behavior would figure out that, when saving the NavigationItem, it should only make changes to the left/right values for NavigationItems with the same nav_list_id as the NavigationItem that’s being saved. But, no such luck; no limit is placed during the save operation, meaning that the entire table’s tree structure gets updated, rather than just those with nav_list_id = the nav_list_id of the NavigationItem we’re saving. So, I just decided to modify the TreeBehavior a bit. Some might whine about not modifying the core, but it’s a fine thing to do if you want more functionality. Just move tree.php out of /cake/lib/models/behaviors and into /app/models/behaviors.

I modified two methods, setup() and beforeSave(), and added my own _addScopeForeignKey() method. The full code is available on Github, but for the lazy the changes I made are:

TreeBehavior::setup() :

function setup(&amp;amp;$Model, $config = array()) {
	if (!is_array($config)) {
		$config = array('type' => $config);
	}
	$settings = array_merge($this->_defaults, $config);

	if (in_array($settings['scope'], $Model->getAssociated('belongsTo'))) {
		$data = $Model->getAssociated($settings['scope']);
		$parent =&amp;amp; $Model->{$settings['scope']};
		$settings['scope'] = $Model->alias . '.' . $data['foreignKey'] . ' = ' . $parent->alias . '.' . $parent->primaryKey;
		$settings['recursive'] = 0;
		$settings['scopeForeignKey'] = $data['foreignKey'];
	}
	$this->settings[$Model->alias] = $settings;
}

Add this line to the top of TreeBehavior::beforeSave() :

function beforeSave(&amp;$Model) {
	$this->_addScopeForeignKey(&amp;$Model);

        ....
}

Add this new function:

/**
 * add a foreign ID scope to the model settings.
 *
 * @param AppModel $Model Model instance
 * @return true on success, false on failure
 * @access protected
 */
	function _addScopeForeignKey(&amp;amp;$Model) {
		if (!isset($this->settings[$Model->alias]['scopeForeignKey'])) {
			return false;
		}

		if (!isset($Model->data[$Model->alias][$this->settings[$Model->alias]['scopeForeignKey']])) {
			return false;
		}

		$this->settings[$Model->alias]['scope'] = $Model->alias . '.' . $this->settings[$Model->alias]['scopeForeignKey'] . ' = ' . $Model->data[$Model->alias][$this->settings[$Model->alias]['scopeForeignKey']];

		return true;
	}

And that’s it. I haven’t tested it all that much but it’s doing the job right now. Comments, suggestions, improvements, polite criticism welcome. :)

Coming soon – CakePHP and the Zend Framework: together at last!

Posted in CakePHP, Zend Framework on January 4th, 2010 by Jamie – 1 Comment

Just a quick update – I’m currently working on integrating some libraries from the Zend Framework into CakePHP. The first one I’m doing is Zend_Validate, which is a heavy, robust alternative to CakePHP’s own Validation class. I’ll be sharing my results as a plugin when I’m done. Stay tuned.