Tue 2 Dec 2008
The CakePHP bake utility can create unit tests and fixtures that can be used for testing your web app. Although these templates work fine, they are a bit difficult to maintain, especially as your application grows. I spent some time tweaking the test case and fixtures to make the process easier. All the sample templates are available at GitHub.
Fixture Changes
The first thing you’ll notice is that the schema is no longer explicitly defined in the fixture. Instead it will be pulled from the original table, which means you won’t have to worry about updating the fixture if you alter the table.
The second change is the addition of the $import line. This tells the fixture which table it should use as the base from the original database. Although it is possible to specify a model, which would then be used to figure out the table name, I found it was best to point directly to the table. If you original table isn’t in the default database, you can add a “connection” option to $import for which datasource should be used.
Test Case Changes
The model test case has one important change. To create an instance of the model being testing the ClassRegistry::init method is used. By doing it this way the model instance and any associated models will automatically be set to use the test database. This means you no longer have to extend the original model to override any particular option.
Notes
The setup described above assumes the test database is completely empty to start - no data and no tables. Also, because the original models are used, the fixture for any related model must also be included. This can be kind of a pain for larger databases. I don’t have a great alternative to this at the moment.
Popularity: 6% [?]
Wed 19 Nov 2008
SEO Component for CakePHP
http://github.com/mcurry/cakephp/tree/master/components/seo
I was getting annoyed having to set page titles and meta descriptions on all my Cake projects, so this is an attempt to handle that automatically. It’s at a very early state right now and probably only really works with my apps. I’m looking to expand it’s ability to better handle all situations/apps. Feedback and patches are appreciated.
In addition there is a second class, with a static function for generating pretty urls. This type of function would normally go in a helper, but I was trying to keep everything in one file, plus I’m super into static function calls at the moment.
Discogs Datasource for CakePHP
http://github.com/mcurry/cakephp/tree/master/datasources/discogs
I read about the Discogs API the other day and it looked cool, so I decided to do a project with it. The data provided by the API is very open: “Artist, Label, and Release data is made available to you via a Public Domain license. That means there are no restrictions on what you can do with the data.” This is great. Unfortunately, I wasn’t happy with the data being returned, which can be expected since it’s all user generated. I ended up switching the project to use the Amazon API, but figured I’d release the datasource anyway.
Popularity: 8% [?]
Wed 29 Oct 2008
I wrote a quick plugin for WordPress that pull code from GitHub for showing in a blog post. I figured that it would be a pain to go back to old posts and update code any time I committed a change, so this should do it automatically.
Usage
Simply paste link into the post and change “http” to “httpc”. Adding the “c” lets the plugin know you want the link replaced with the actual code. This way you can still post links that won’t get replaced. Code is cached in WordPress for 24 hours, so you may not see changes instantly.
Code
Here’s the plugin (and it’s also an example of the plugin in action):
<?php
/*
Plugin Name: GitHub Code Viewer
Version: 1.0
Plugin URI: http://www.pseudocoder.com/archives/2008/10/29/wordpress-plugin-for-showing-github-code/
Description: GitHub Code Viewer automatically pulls a file from GitHub and displays it in a blog post.
Author: Matt Curry
Author URI: http://www.pseudocoder.com
*/
class github {
var $db;
var $table = "github";
var $cache = array();
function github() {
global $wpdb;
$this->db = $wpdb;
$this->table = $this->db->prefix . "github";
}
function install() {
$result = $this->db->query("CREATE TABLE IF NOT EXISTS `{$this->table}` (
`id` int(10) unsigned NOT NULL auto_increment,
`url` text NOT NULL,
`code` text NOT NULL,
`updated` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
)");
}
function uninstall() {
$result = $this->db->query("DROP TABLE IF EXISTS `{$this->table}`");
}
function get_code($text='') {
$pattern = '/(httpc:\/\/[a-z0-9\/\_\-.]{1,})/i';
if (preg_match_all($pattern, $text, $matches)) {
$this->__loadCache($matches[0]);
foreach($matches[0] as $match) {
if (isset($this->cache[$match])) {
$code = $this->cache[$match];
} else {
$find = str_replace('httpc', 'http', $match);
$code = wp_remote_fopen($find . '?raw=true');
$code = str_replace('<', '<', $code);
$this->__setCache($match, $code);
}
$text = str_replace($match, $code, $text);
}
}
return $text;
}
function __loadCache($urls) {
$sql = sprintf('SELECT * FROM `%s`
WHERE url IN ("%s")',
$this->table,
implode('", "', $urls));
$results = $this->db->get_results($sql, ARRAY_A);
if ($results) {
$old = array();
foreach($results as $row) {
if($row['updated'] < date('Y-m-d H:i:s', strtotime('-1 day'))) {
$old[] = $row['id'];
} else {
$this->cache[$row['url']] = $row['code'];
}
}
if($old) {
$sql = sprintf('DELETE FROM `%s` WHERE id IN (%s)',
$this->table,
implode(',', $old));
$this->db->query($sql);
}
}
return true;
}
function __setCache($url, $code) {
$sql = sprintf('INSERT INTO `%s` (`url`, `code`, `updated`) VALUES ("%s", "%s", "%s")',
$this->table,
$url,
mysql_real_escape_string($code),
date('Y-m-d H:i:s'));
$result = $this->db->query($sql);
}
}
$github =& new github();
register_activation_hook(__FILE__, array(&$github, 'install'));
register_deactivation_hook(__FILE__, array(&$github, 'uninstall'));
add_filter('the_content', array(&$github, 'get_code'), 8);
?>
Popularity: 8% [?]
Fri 24 Oct 2008
A couple months ago Nate Abele wrote an article for C7Y which talked about the new find syntax in CakePHP 1.2.
CakePHP 1.2 beta introduced a new custom find syntax, where instead of calling methods like findAll(), you call methods like find(“all”). This allows for a great deal more flexibility and code reuse.
Nate further shows how you can make your own find types such as “find(’popular’).” Ever since I read the article I’ve been using this practice extensively. I would often times find myself copy and pasting the same find code in each model:
public function find($type, $options = array()) {
switch ($type) {
case "custom":
return $this->__findCustom($options);
default:
return parent::find($type, $options);
}
}
I was able to move this block to my AppModel and make it work for all my models:
<?php
/*
* App Model custom find types
* Copyright (c) 2008 Matt Curry
* www.PseudoCoder.com
* http://github.com/mcurry/cakephp/tree/master/snippets/app_model_find
* http://www.pseudocoder.com/archives/2008/10/24/cakephp-custom-find-queriescakephp-custom-find-queries/
*
* @author Matt Curry <matt@pseudocoder.com>
* @license MIT
*
*/
class AppModel extends Model {
function find($type, $options = array()) {
$method = null;
if(is_string($type)) {
$method = sprintf('__find%s', Inflector::camelize($type));
}
if($method && method_exists($this, $method)) {
return $this->{$method}($options);
} else {
return parent::find($type, $options);
}
}
}
?>
How It Works
Let’s say want a custom find type which returns the latest 10 comments in your blog. Normally you might do something like this in your controller:
$comments = $this->Comment->find('all', array('conditions' => array('spam' => false),
'order' => array('created' => 'desc'),
'limit' => 10));
If you make a custom find type, your controller code becomes:
$comments = $this->Comment->find('latest);
Then in your Comment Model:
function __findLatest($options) {
return $this->find('all', array('conditions' => array('spam' => false),
'order' => array('created' => 'desc'),
'limit' => 10));
}
The new find method in AppModel automatically looks for a method “__findLatest” in your model. If the method exists it is called and the results returned, otherwise the parent find is called, which handles all the core find types (all, first, count…).
As always this code is available in GitHub.
Popularity: 13% [?]
Wed 22 Oct 2008
It’s past my bedtime so this will be short. I wrote an Ajax chat plugin for CakePHP a few months ago. It wasn’t working so well with RC3, plus it used Prototype, so I decided to fix it up for RC3 and switch to jQuery.
Code is available in my GitHub repo
There is a demo at sandbox.pseudocoder.com
Popularity: 14% [?]
Tue 21 Oct 2008
Posted by Matt under
GitHub,
jQuery
No Comments
I wrote a post a few weeks back about how to make a entire element (usually a div) clickable based on one of the links contained within. Having never written a jQuery plugin before I figured this would be a pretty simple one to start with. The code is available in my GitHub repo. Instructions and an example are also included there.
Popularity: 9% [?]
Mon 20 Oct 2008
I put all the code I’ve previously posted on the CakePHP Bakery onto my GitHub account. A lot of the code has been updated so that it works on RC3.
Code
Asset Helper
HTML Cache
JavaScript Validation
Layout Switcher Component
Simplepie Component
Working example can be seen at sandbox2.pseudocoder.com
There is some other code, but I haven’t checked it yet to see if it’ll work with RC3. Use at your own risk.
Popularity: 10% [?]
Wed 3 Sep 2008
Cake’s core cache helper is great, but the files it outputs are PHP files, so it will never be as fast as straight HTML files. This HTML Cache Helper writes out pure HTML, meaning the web server doesn’t have to touch PHP when a request is made. Yea, I know there are some huge limitations with this. First of all you can’t have any user/session specific code on the page. Also there is no way to automatically check if the cache is expired and needs to be rebuilt.
Uses
I use this helper on RSStalker.com. It handles the custom RSS feeds (currently around 13k), which is perfect since there is nothing user specific in the XML. Each feed gets hit multiple times a day, by multiple aggregators. This really adds up to a ton of requests.
The Code
You can download it here. Or just copy and paste this into /app/views/helpers/html_cache.php:
<?php
/*
* HTML Cache CakePHP Helper
* Copyright (c) 2008 Matt Curry
* www.PseudoCoder.com
* http://github.com/mcurry/cakephp/tree/master/helpers/html_cache
* http://www.pseudocoder.com/archives/2008/09/03/cakephp-html-cache-helper/
*
* @author mattc <matt@pseudocoder.com>
* @license MIT
*
*/
class HtmlCacheHelper extends Helper {
function afterLayout() {
if(Configure::read('debug') > 0) {
return;
}
$view =& ClassRegistry::getObject('view');
$path = WWW_ROOT . 'cache' . DS
. implode(DS, array_filter(explode('/', $this->here)))
. DS . 'index.html';
$file = new File($path, true);
$file->write($view->output);
}
}
?>
There really isn’t much to it. Just add it to any controller that you want to cache the output of.
In addition you need to add two line to your webroot/.htaccess, so that the rewrite section looks like this:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/cache/$1/index.html -f
RewriteRule ^(.*)$ /cache/$1/index.html [L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>
Issues
To expire the cache I use a cron job which deletes old files from the directory.
find /full/path/to/app/webroot/cache -mmin +360 | xargs rm -f
The cached files are getting written right to your webroot. The default Cake .htaccess checks to see if a file actually exists, this is what allows images, js, css, and other files to be handled directly by the web server.
This won’t work with the root file of your controller. So for example www.rsstalker.com/feeds won’t work, but www.rsstalker.com/feeds/amazon does.
Popularity: 14% [?]