Revised model links

In yesterday's post I discussed creating a global app_model (usgin late static binding) function to create a url and with that rolling around in my head I realized that I myself hadn't taken it far enough. I present to you my revised addition to model (and app_model when 5.3 comes out,) the Model::link function.

One of the things I disliked about Felix's manner of creating the url was that when you were creating a slug you were requerying the database. That is a nominal hit when you only have a few hits a day (like my site currently does) but when you get to a larger volume of trafic, running that 50 time a page can grind things to a halt quickly. To make matters worse you already had pulled the data from the database, you were just neglecting to use it. This is my revised solution, passing the whole db result into the link function and setting options to override what you need to change.

Lets look at an example (pulled from this blog's code since it is where I origionally wrote this.) This is from /app/views/posts/view.ctp so assume that $post is the current post returned from a $this->Post->find('first', array('conditions' => array( 'id' => $id ) ) );

<?php
echo Post::link($post);
if ($session->read('Auth.user.type') == 'admin') echo Post::link($post, array('action' => 'edit', 'title' => '(edit)'));
?>
<?php
echo __('By', true).': ';
echo User::link($post).' '; //The model is smart enough to make this a user link
echo __('Posted', true).' ';
echo $time->timeAgoInWords($post['Post']['created']);
?>

Now look at that without static methods

<?php
echo $html->link($post['Post']['title'], array('controller' => 'posts', 'action' => 'view', $post['Post']['id']);
if ($session->read('Auth.user.type') == 'admin') echo $html->link('(edit)', array('controller' => 'posts', 'action' => 'edit', $post['Post']['id']);

?>
<?php
echo __('By', true).': ';
echo $html->link($post['User']['name'], array('controller' => 'users', 'action' => 'view', $post['User']['id'])).' ';
echo __('Posted', true).' ';
echo $time->timeAgoInWords($post['Post']['created']);
?>

I for one, prefer the first method. There are a few extra benifits from this that I didn't forsee, like inline keywords in content. For me I just added a regexp replacement that replaces in my posts [post::guid] with the evaluated form of [Post::guid] and [Post::url::guid], I will never again have my links go out of date and fail due to a changed slug. Put this in the model you're interested in allowing the static link function on.

/**
* Link function
*
* Create a link either from an object or a passed ID
*/
Public Static Function link ($object = null, $options = array()) {
$model = 'Post';
$nameField = 'title';

if (!is_array($object)) {
$object = array(
'id' => $object,
);
}

//This means we have been given this through a belongs to / hasOne (or cascade from a passed id)
if (!isSet($object[$model])) {
$object = array(
$model => $object
);
}

if (isSet($options['title'])) {
$title = $options['title'];
unset($options['title']);
}
if (isSet($options['class'])) {
$class = "class=\"{$options['class']}\"";
unset($options['class']);
} else {
$class = '';
}

$options = array_merge(array(
'controller' => inflector::underscore(inflector::pluralize($model)),
'action' => 'view',
$object[$model]['id']
), $options);

$url = Router::url($options);

if (!isSet($title)) {
//If it is post it will be come $post['Post']['title']
if (isSet($object[$model][$nameField])) {
$title = $object["$model"][$nameField];
} else {
$title = ClassRegistry::init($model)->field($nameField, array('Post.id' => $object[$model['id']));
}
}
return "$title";
}

Sincerely,~Andrew Allen