joebeeson http://blog.joebeeson.com programming, knick-knacks, minutiae posterous.com Tue, 13 Sep 2011 19:33:00 -0700 Leaning on CakePHP http://blog.joebeeson.com/leaning-on-cakephp http://blog.joebeeson.com/leaning-on-cakephp

There is a ton of information that CakePHP sets up for each request. Looking at the Controller object API, you can see that a sizable chunk is devoted to maintaining the data about the current request (as it should be) but what, if anything, are you doing with that information? Here's some tidbits I've ran into while developing a plugin that you can use to start "leaning on CakePHP" in your application.

One of the first things you should do for any application is to create your basic CRUD and index actions in the AppController. This give us a reliable base to build upon, helps keep things DRY and make our lives easier down the road by easily transitioning into API verbs. Here's an example of the index method...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

/**
* Handles index requests.
*
* @return void
* @access public
*/
public function index() {

    // Only do this work if our concrete controller has not.
    if (!isset($this->viewVars[Inflector::pluralize($this->modelClass)])) {
        $this->set(array(
            Inflector::pluralize($this->modelClass) => $this->paginate(
                $this->modelClass,

                /**
* Here we toss in any conditions that were found as named
* parameters and which match up to a column in the model
* schema.
*/
                array_intersect_key(
                    $this->params['named'],
                    $this->{$this->modelClass}->schema()
                )
            )
        ));
    }
}

As this is intended to go into the AppController you can see that we're using $this->modelClass to determine the controller's default model. We also don't perform any "work" if we see that it has already been done by the controller. Finally, as a bit of nice to have code we are looking in the named parameters for anything that matches a column name and automatically applying that as a condition to our pagination. This lets us easily create an address such as /posts/index/author_id:1

One thing to point out here is that this can easily be overridden by the controller. If it needs to do more than is available we can easily create an index method in the "concrete" controller and only call parent::index() if it's still usable. Let's check out another method...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

/**
* Handles deletion requests.
*
* @param $id string
* @return void
* @access public
*/
public function delete($id) {
    if ($this->{$this->modelClass}->delete($id)) {
        $this->_flashSuccess('Record successfully deleted.');
        $this->redirect(array(
            'action' => 'index'
        ));
    } else {
        $this->_flashError('There was an error deleting the record.');
    }
}

[There are some custom methods defined in there that should be relatively self-explanatory] This one is pretty straight forward: The user asked us to delete something, we're going to attempt to delete it and based on the outcome give the user a textual response and possibly redirect them to the index action of the current controller.

I can already hear you saying "But... but... Joe, what if they don't have permissions to delete the record?" well, that's not the job of this method to determine. These are only methods to be built upon. Hence why they're in our AppController. Perform your authentication checks in the concrete controller and only call the parent method if everything checks out.

Alright one more now, my favorite.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

/**
* Handles update requests.
*
* @param $id string
* @return void
* @access public
*/
public function update($id) {
    if (!empty($this->data)) {

        /**
* Since the `create` method does most of the dirty work for us
* we simply set away the ID for the records we're attempting
* to update and fire off our request.
*/
        $this->data[$this->modelClass][$this->{$this->modelClass}->primaryKey] = $id;
        $this->create();
    } else {

        /**
* By using our `view` method we'll get all of our view variables
* setup for us.
*/
        $this->view($id);
    }
}

[This code is extremely verbose because of the plugin's nature] Here we're leveraging on our own code. If the user hasn't sent any data we trigger our view method to get all of our view variables setup (and possibly perform authentication) before rendering. If the user has sent us data we simply toss in the ID of the record they're updating and then trigger our create method. How easy was that?

With this base of simple, straightforward CRUD methods you can save a large amount of time by reusing code and only writing modifications for specific cases.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Wed, 29 Jun 2011 16:00:00 -0700 Introducing the Filterable behavior http://blog.joebeeson.com/introducing-the-filterable-behavior http://blog.joebeeson.com/introducing-the-filterable-behavior

I'm sure you've been there before. Cleaning up data in every conceivable way that the user could muddle it up just so you can get the information saved or updated. Trimming whitespace, stripping HTML, parsing formatting languages, etc. The problem is that, while necessary, this sucks.

The idea is a simple one: do some stuff with the data just before we pass it off for validation. In the past people have done this in the controller action, perhaps in beforeValidate, hell you may even have some Javascript doing it for you client-side. Now, we've got a one-stop place for all the tweaking.

Anywho, enough chit-chat. Let's begin by showing off some of the fun tidbits it can do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

class Post extends AppModel {

public $behaviors = array('Filterable');

public $filters = array(
'title' => array(
'trim',
'ucwords'
),
'body' => array(
'htmlentities'
)
);

}

Here we're using a handful of PHP's functions to cleanup some of our fields. Nothing too special here, but wait, there's more!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

class Post extends AppModel {

public $behaviors = array('Filterable');

public $filters = array(
'title' => array(
'_spaceToAsterisks'
),
'body' => array(
'_rickRolled'
)
);

/**
* Takes a string and makes it fabulous.
*
* @param string $string
* @return mixed
* @access protected
*/
protected function _spaceToAsterisks($string) {
return str_replace(' ', '*', $string);
}

/**
* Automated Rick Rolling. The best kind there is.
*
* @param string $string
* @return mixed
* @access protected
*/
protected function _rickRolled($string) {
return str_replace(
'Rick',
'<a href="http://www.youtube.com/watch?v=dQw4w9WgXcQ">Rick</a>',
$string
);
}

}

Yup, that's right. Using our own methods to screw tidy things up. But we're not done yet, we've got one more trick up our sleeve...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

class Post extends AppModel {

public $behaviors = array('Filterable');

public $filters = array(
'title' => array(
array(
'SomeCustomObject',
'staticMethod'
),
array(
'SomeCustomObject',
'anotherStaticMethod'
),
)
);

}

And we're done. Hopefully the examples make the usage fairly straightforward. If you want you can still perform your own cleanup wherever you'd like, this won't interfere at all.

It's worth noting that none of this is to be a replacement to proper validation, think of it more as an attempt to get your data ready prior to validation. Without further delay, the code.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Wed, 29 Jun 2011 16:00:00 -0700 Relieve your database with this quick tip http://blog.joebeeson.com/59050067 http://blog.joebeeson.com/59050067

Here's a quick little tidbit which while may be obvious to more seasoned coders, will really help out your database and make your model code more robust.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

/**
* AppModel
*
* Base application model.
*
* @author Joe Beeson <jbeeson@gmail.com>
*/
class AppModel extends Model {

/**
* Number of associations to recurse through
* during find calls.
*
* @var int
* @access public
* @see http://book.cakephp.org/view/1063/recursive
*/
public $recursive = -1;

/**
* Behaviors.
*
* @var array
* @access public
* @see http://book.cakephp.org/view/1071/Behaviors
*/
public $actsAs = array(
'Containable'
);

}

Your standard AppModel more or less but with two entries: recursive is being set to -1 and we're adding ContainableBehavior.

Setting the recursive value to -1 tells CakePHP that we don't want it to bring in any associations when we run a query. Off the cuff this sounds like a bad idea, especially if you've never used ContainableBehavior before because you'd have to do some extra leg work to get associated records. That's the point. You should be explicitly asking the model to bring in the data you need and nothing more.

The ContainableBehavior is in there to help facilitate getting the associated data when we need to, instead of running two queries or manually writing our own ad-hoc joins.

While this all seems like extra work, your code will be cleaner and better off because of it -- being verbose with your database is a good thing.

 

 

 

 

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Fri, 24 Jun 2011 15:00:00 -0700 Logging all database queries http://blog.joebeeson.com/logging-all-queries http://blog.joebeeson.com/logging-all-queries

Ever find yourself in a situation where CakePHP executes a handful of database queries but the controller immediately redirects you, effectively throwing away the log of everything that happened during that request? What about trying to see "behind the scenes" of an AJAX request? Well, here's a little tidbit to help sneak a peak under the hood..

First off, yes, I am aware of things like FirePHP and how to use it within CakePHP and... well... I don't like Firefox. There. I said it. I'm quite at home with my Chrome browser, thank you very much. Now, hopefully before a full-on browser war starts, here's a solution to log all queries to the database into something a bit more universal, a log file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php

/**
* Called after the controller action is run, but before the view is rendered.
*
* @return void
* @access public
*/
public function beforeRender() {
if (Configure::read()) {
foreach (ConnectionManager::sourceList() as $datasource) {
$this->_logDataSourceQueries(
ConnectionManager::getDataSource($datasource)
);
}
}
parent::beforeRender();
}

/**
* Logs the queries from a `DataSource` object. The optional `$log`
* parameter denotes where to send the logs.
*
* @param DataSource $datasource
* @param string $log
* @return void
* @access protected
*/
protected function _logDataSourceQueries(DataSource $datasource, $log = 'sql') {
if ($datasource->isInterfaceSupported('getLog')) {
$queries = $datasource->getLog(false, false);
foreach ($queries['log'] as $query) {
CakeLog::write($log, sprintf(
'%3s %3s %3s %s',
$query['affected'],
$query['numRows'],
$query['took'],
$query['query']
));
if (!empty($query['error'])) {
CakeLog::write('sql', $query['error']);
}
}
}
}

You'll notice that inside the beforeRender we're checking to make sure we're in debug mode. By default none of the DataSource objects will log any queries if we're not debugging. From there it loops over all available DataSource objects and kicks them out to our _logDataSourceQueries method.

From there it's a pretty simple hop, skip and a jump to logging with CakeLog. We make sure the DataSource supports logging before grabbing out all of the queries and sprintfing them out to our log.

If you're feeling fancy you can mix-up the display of the queries, here's some ideas I'm too lazy to implement.

  • Adding warnings when a query exceeds a time threshold.
  • Organizing the queries by model.
  • Creating a summary of all queries. 
  • Logging the time took by controller, action for observation.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Thu, 23 Jun 2011 18:21:00 -0700 Forcing a maximum pagination limit http://blog.joebeeson.com/forcing-a-maximum-pagination-limit http://blog.joebeeson.com/forcing-a-maximum-pagination-limit

You know what's a really fun and easy way to bog down someone's server? Make a request against a controller for hundreds of thousands of records. Here's how to keep that from happening in your applications.

Your application has hundreds, thousands, however many, records sitting in a model and all paginated away in the controller. Awesome. Now go back to your application and add limit:9999999 into the URL, what happens? At best your server begrudgingly and slowly returns the gigantic resultset and at worst has shat the bed. Do this repeatedly and you have yourself a fairly easy denial of service attack.

Despite all the chaos such a thing can cause, it's actually really easy to fix, like, six line easy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

/**
* AppController
*
* Base application controller.
*
* @author Joe Beeson <jbeeson@gmail.com>
*/
class AppController extends Controller {

/**
* Pagination results limit.
*
* @var int
* @access protected
* @see AppController::beforeFilter
*/
protected $_maximumPaginationResults = 25;

/**
* Triggered prior to the action.
*
* @return void
* @access public
*/
public function beforeFilter() {
if (isset($this->passedArgs['limit'])) {
$this->passedArgs['limit'] = min(
$this->_maximumPaginationResults,
$this->passedArgs['limit']
);
}
}

}

By always asking for the lowest of the two values (one from the request, the other hardwired in the controller) we make sure that our pagination never goes off and commits database seppuku and, if you ever feel so inclined, you can just change the _maxPagination value in specific controllers to get different limits.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Thu, 23 Jun 2011 15:00:00 -0700 Recursive model associations http://blog.joebeeson.com/recursive-model-associations http://blog.joebeeson.com/recursive-model-associations

While usually pretty rare there are times when you want to define an association for a model that relates back to itself; parent record, children records, etc. Here's an example of how to get this up and running in your own model.

Here's a model called Person which holds records for, you guessed it, people. The schema consists of id, father_id, mother_id, first_name and last_name. Our goal here is to configure three extra associations for the model, PersonMother, PersonFather and PersonChildren so that CakePHP will automatically handle these associations for us.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

/**
* Person
*
* Application model for `Person` records.
*
* Schema: id, father_id, mother_id, first_name, last_name
*
* @author Joe Beeson <jbeeson@gmail.com>
*/
class Person extends AppModel {

/**
* "Belongs to" associations.
*
* @var array
* @access public
*/
public $belongsTo = array(
'PersonMother' => array(
'className' => 'Person',
'foreignKey' => 'mother_id'
),
'PersonFather' => array(
'className' => 'Person',
'foreignKey' => 'father_id'
)
);

/**
* "Has many" associations.
*
* @var array
* @access public
*/
public $hasMany = array(
'PersonChildren' => array(
'className' => 'Person',

/**
* Since we have [two] fields that could possibly associate us
* to our `PersonChild` records we have to override the query
* to make sure we're searching both.
*/
'finderQuery' => 'SELECT `PersonChildren`.* FROM `people` AS `PersonChildren` WHERE `PersonChildren`.`father_id` IN ({$__cakeID__$}) OR `PersonChildren`.`mother_id` IN ({$__cakeID__$})',

// Note we also need to say false to "foreignKey"
'foreignKey' => false
)
);

}

Our PersonFather and PersonMother records are a belongs to association, not a has one and the reason being that we want to be able to see the records from the other "direction" -- we get our parent records when we're queried, not visa-versa.

The secret sauce here is actually the className declaration in the association. This tells CakePHP that despite the association name, which usually tells it which model to load, that we want to use the Person model. This means that all of our other configurations, table, functions, behaviors, etc, will not need to be repeated since we are the model that will be associated.

The PersonChildren record is a bit trickier, reason being because there are two columns that may associate a parent with their children, father_id and mother_id. Luckily CakePHP gives us a configuration option, finderQuery, to help tell it how to get its associated records. If we didn't have such a unique situation we could get away without manually defining our own query.

One of the great things about defining all of your associations instead of relying upon your own custom code for finding records is that you can utilize tools like Containable or Linkable for selecting only the associations you need.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Mon, 20 Sep 2010 21:38:06 -0700 Lazy loading View helpers http://blog.joebeeson.com/lazy-loading-view-helpers http://blog.joebeeson.com/lazy-loading-view-helpers Along the same lines of the Dynamic model inclusion I posted awhile ago, this tidbit of code will let you lazy load helpers inside of your view. To use just create the file /app/views/auto_helper.php with the contents of the Github Gist below and then change your AppController's view variable to AutoHelper. public function beforeFilter() { $this->view = 'AutoHelper'; } If your application uses the ThemeView you can change the code to extend from that instead of View without a whole lot of trouble. Just be sure to import the ThemeView object by adding the following to the top of the file: App::import('View', 'Theme'); If you have any helpers that need to be available to catch the beforeRender callback or that require configuration, you should not let them be lazily loaded, keep them in the $helpers array in your controller. This hasn't been thoroughly tested so various quirks may arise, if so let me know. [gist id=588683]

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Tue, 11 May 2010 20:16:03 -0700 Embellish plugin for CakePHP http://blog.joebeeson.com/embellish-plugin-for-cakephp http://blog.joebeeson.com/embellish-plugin-for-cakephp The Embellish plugin is an easy way to bring in functionality for "lightweight markup languages" in your application such as Markdown, BBCode and Textile. If you know of any other markup languages that you'd like support for, let me know -- it's fairly easy to add them in. Check out the Github project for more information.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Fri, 07 May 2010 18:22:15 -0700 A Campfire component for CakePHP http://blog.joebeeson.com/a-campfire-component-for-cakephp http://blog.joebeeson.com/a-campfire-component-for-cakephp Here at work we use Campfire for collaboration between our teams and external clients. We find that it makes things easier when you can just email them a URL and don't have to setup up an IM client or worry about public and private servers, those sorts of things. To give our applications the ability to communicate to our development channel I wrote a component for CakePHP that utilizes Campfire's API for retrieving information and sending messages to an account. You setup the component in your AppController and provide it with account name (the part before campfirenow.com in the URL) and your authentication token, which can be found under My Info when logged in. var $components = array( 'Campfire' => array( 'auth' => 'TrLaTiehOeTiAhieP8iedoUPiaSO5SoExl9spius', 'account' => 'acmecorp' ) ); Note that whichever account you retrieve the authentication token from will be the account that the CampfireComponent will act as, this means it will be bound by the permissions for that account and messages sent will appear from this user.
When you're using the API, it's always through an existing user in Campfire. There's no special API user. So when you use the API as "david", you get to see and work with what "david" is allowed to. Authenticating is done with an authentication token, which you'll find on the "Edit my Campfire account" screen in Campfire (click the "Reveal authentication token for API" link).
You can find the source code as a Gist, here at Github.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Mon, 03 May 2010 21:02:38 -0700 Better CSS with Sassy http://blog.joebeeson.com/better-css-with-sassy http://blog.joebeeson.com/better-css-with-sassy I've come to the conclusion that I am outright fed up with CSS. Organizing it, upkeep of it, repeating the same thing over and over, all of it sucks. What makes matters worse is that everyone has their way of doing it "properly" -- there are 1,650,000 pages that popup for a search of "how to organize css" -- that's insane. Jeff Atwood puts it a bit more concisely...
In short, CSS violates the living crap out of the DRY principle. You are constantly and unavoidably repeating yourself.
Because of the sheer amount of suck this causes so many people there are a ton of fairly nifty tools out there for mitigating CSS' short comings. One of which is called Sass which boasts nested rules, variables, and mixins. I love me some mixins. Despite being a Ruby gem, some good folks have ported the logic to PHP and made PHamlP. Now, I'm way too lazy to just run their code every time I need to make a new CSS from Sass so, I whipped together Sassy, a CakePHP 1.3+ plugin to handle all that for me. It really does nothing more than parsing the Sass files if they're newer than their CSS counterpart. Really. That's it. You can still use any asset packer you like, you don't have to change the way you include CSS files -- you just have to add one line to your AppController. If you felt so inclined though, there are a couple configuration options you have available to you: // Look for files in /path/app/webroot/css/sass and save them to /path/app/webroot/css Configure::write('Sassy.Recompile.Folders', array( '/path/app/webroot/css/sass' => '/path/app/webroot/css', '/path/app/webroot/multiple/folders' => '/path/app/webroot/are/awesome' )); // Make Sassy check for updates on every other request (statistically) Configure::write('Sassy.Recompile.Percentage', 50); // If we get this named parameter in the URL, lets force a check for updates Configure::write('Sassy.Recompile.Parameter', 'recompile'); The plugin is, at the time of writing this, still fairly new so there's a pretty good chance you may run into an issue with it. As always, if you find a problem or want to make it better you should feel free to fork the project and help out.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Wed, 31 Mar 2010 22:34:32 -0700 Monitoring your application's health http://blog.joebeeson.com/monitoring-your-applications-health http://blog.joebeeson.com/monitoring-your-applications-health Awhile ago I ran across Jeff Atwood's post about Exception Driven Development and a line in it really struck home for me..
[...] I'm sorry to be the one to have to tell you this, but you kind of suck at your job -- which is to know more about your application's health than your users do
Basically if you are doing nothing to watch for and report errors that are occuring in your application then you should probably get a horse and live in the woods because everyone hates you.
Okay so that's a bit harsh, everyone has been guilty of this at one point in their career; it's pretty tedious to try and remember to add a log message for every possible error and even if somehow you managed to do so you would still have missed something -- that's why they're called errors, you messed up. So with the understanding in mind that you or I will never be able to catch or foresee every single possible error, I made the Referee plugin for CakePHP 1.3+ to slack off help us out. Its sole purpose is to catch every error, even fatal ones, and log them to the database. Also, because I like to be lazy the first to know about issues, I added some functionality to tie in automated notifications. You can see an example included the sourcecode. The installation takes about five minutes and is really fairly easy after which you should be up and running with all the error catching goodness doing its magic behind the scenes. It'll probably earn you a raise too. If you have any problems or ideas for enhancements feel free to drop me a line on the Github project. Referee Plugin

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Tue, 30 Mar 2010 22:38:38 -0700 Dynamic model inclusion (updated) http://blog.joebeeson.com/dynamic-model-inclusion-updated http://blog.joebeeson.com/dynamic-model-inclusion-updated Loading up models is one of the most resource intensive operations CakePHP does -- checking the schema, initializing behaviors, binding associations -- and it's only made worse by developers who insist that they "absolutely need these six models for every single request" or "these have to be in AppController, I use them everywhere!"
Some camps argue that you should explicitly load your models just before actually using them while others argue that the overhead is negligible and to not worry about it. I'm inclined to lean towards the former but I really hate seeing things like this... function index() { $this->loadModel('Post'); $posts = $this->Post->find('all'); } function view($id = '') { $this->loadModel('Post'); $post = $this->Post->read(null, $id); } Like I mentioned earlier, I appreciate what this is accomplishing, I just hate the repetition and the forced, manual nature of it. Doesn't jive with what I think CakePHP is all about. Luckily, there's a PHP magic method that will let us forget about all these issues, __get() -- here's the function from my AppController. /** * Catches requests for class members that are unset or not visible to * the callee. We check to see if they're asking for a known model and, * if they are, will load it up for them. * @param string $variable * @return mixed * @access public */ public function __get($variable) { if (!isset($this->models)) { // We don't want to get this array for every request, store it $this->models = Configure::listObjects('model'); } // Is it a model we're trying to access? if (in_array($variable, $this->models)) { // It is! Lets load it up and return the model... $this->loadModel($variable); return $this->$variable; } } The __get() method is called everytime you try to access a non-existant variable. In this method we're setting [cci lang="php"]Configure::listObject('model')[/cc] which returns an array of all models that CakePHP knows about, to our $this->models variable. We store it there so that we don't have to keep getting the same array. We then check if the $variable you're asking for happens to be a model we know about by using... [cci lang="php"]in_array($variable, $this->models)[/cc] -- if it is we do the usual song and dance of loading the model up with... [cci lang="php"]$this->loadModel($variable);[/cc] and last but not least, we return the newly loaded model for use. Now you can use any model in any controller without pre-loading them or cluttering your code up with initialization methods. Just write your code as though you had already loaded the model. It's important to note that future requests for the same model will skip the __get() method because we've already loaded it up for you. Enjoy!

Update...

Many thanks to ADmad and Ceeram for catching a snafu with the above code and some of the newer versions of CakePHP. It would seem that a pass by reference in one of the core files causes the whole thing to goto hell -- unfortunate really. As luck would have it though ADmad tossed together a patch to adjust the file in question and solve the issue. While this does break the cardinal rule of "Never, ever change the core" I'll leave the moral decisions up to you. [cc lang="diff] diff --git a/cake/libs/controller/component.php b/cake/libs/controller/component.php index 4df0b12..84d1a1b 100644 --- a/cake/libs/controller/component.php +++ b/cake/libs/controller/component.php @@ -244,12 +244,16 @@ class Component extends Object { } } else { if ($componentCn === 'SessionComponent') { - $object->{$component} =& new $componentCn($base); + $this->_loaded[$component] = new $componentCn($base); } else { - $object->{$component} =& new $componentCn(); + $this->_loaded[$component] = new $componentCn(); + } + if (PHP5) { + $object->{$component} = $this->_loaded[$component]; + } else { + $object->{$component} =& $this->_loaded[$component]; } $object->{$component}->enabled = true; - $this->_loaded[$component] =& $object->{$component}; if (!empty($config)) { $this->__settings[$component] = $config; } [/cc]

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Mon, 29 Mar 2010 23:43:05 -0700 Caching arbitrary actions http://blog.joebeeson.com/caching-arbitrary-actions http://blog.joebeeson.com/caching-arbitrary-actions While view caching is a great idea to help speed up any application, there are sometimes when you just can't fully cache a view and, furthermore, there are sometimes when you can't optimize a function any further or when aforementioned function has to wait on an external resource. For instances like these it's useful to cache the output of such actions.
To start off it's a good idea to setup a custom Cache configuration so you can fine-tune the various settings, the most important of which is probably the duration variable which determines when the cache is "stale" which means CakePHP won't use it anymore. You'll want to try and choose a duration that is high enough to be of as much use as possible while low enough as to not deliver old information. For our example we'll be adding caching for a function that fetches Twitter statuses since retrieving them every single time would probably bog down our server and, if we get enough traffic, is likely to get us some unwanted attention from them as we'd probably be hitting their servers too often for their liking. I've decided that a fifteen minute duration for my caching should be sufficient. I've also added a prefix of twitter_statuses_ so that should I need to delete all the cache files I can easily distinguish them from other cached information. // In /app/config/core.php Cache::config('TwitterStatuses', array( 'engine' => 'File', 'duration' => '15 minutes', 'prefix' => 'twitter_statuses_' )); Now we have to wire our caching into the method that performs the actual work we wish to cache. At this point it's important to determine what your unique identifier for a cache file should be. The indentifier is how we will find the cache file for use in the future and, if we choose to do so, delete it. For this example the Twitter username is a perfect cache identifier since it's unique and accurately represents what we're caching. function fetchTwitterRss($username) { $return = Cache::read($username, 'TwitterStatuses'); if ($return === false) { // We don't have the information cached, lets go get it... $url = 'http://twitter.com/statuses/user_timeline/' . $username . '.rss' $return = file_get_contents($url); // Cache the information for use later... Cache::write($username, $return, 'TwitterStatuses'); } return $return; } The very first thing we do is try to get the cached information by asking if there are any valid caches that have an identifier that matches $username and are in our TwitterStatuses configuration. The Cache class will return a boolean false if it's unable to get the information for us, which means we have to fetch the information the old-fashioned way. Now that we have the information we write it out, using the same identifier, $username and with the value, $return. Had we been able to get the cached information from the get-go we would have bypassed the whole lot since it would have returned something other than a boolean false value. There you have it, a quick and simple way of caching resource intensive operations.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Mon, 29 Mar 2010 22:48:21 -0700 Recursive rendering of hierarchical data http://blog.joebeeson.com/recursive-rendering-of-hierarchical-data http://blog.joebeeson.com/recursive-rendering-of-hierarchical-data Rendering an array of data that has a variable depth and an unknown number of nodes poses a bit of an issue when trying to faithfully recreate that structure for a visitor, how do you render a dynamic array of data that is variably deep? Recursive elements.
Recursion is "...the process a procedure goes through when one of the steps of the procedure involves rerunning the procedure" and should be second nature to most programmers. Here's an example of recursively rendering HTML using CakePHP elements. For the purposes of this example we're rendering a list of threaded comments. The comments are in a $comments variable and the children (if any) for each comment is stored in a key named "children" -- this is how the TreeBehavior organizes its data. /** * In our view file that is called by our controller */ // Loop over our comments and render them out foreach ($comments as $comment) { echo $this->element('comment', array('comment' => $comment)); } This loops over our $comments array and for each value inside of it will call our "comment.ctp" file for rendering. It will also pass along the array in a variable called $comment for use by the element. This is pretty standard fare for handling arrays in your view, the real special sauce is in the "comment.ctp" file and looks something like this...

element('comment', array('comment' => $child)); } } ?>
Excuse the lack of HTML styling, the syntax highlighter can only do one at a time it would appear
Here we're doing our normal element rendering with the HTML and our values from $comment, nothing new here. Prior to ending however we check if our "children" value isn't empty and, if it isn't, we loop over it and for each child that we have we call ourself for rendering. Notice how we're explicitly declaring that the $comment value should be filled with the value of $child -- this will make sure we're rendering the correct comment. Hopefully this should give you a good start on recursive rendering!

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson
Mon, 29 Mar 2010 17:25:20 -0700 Analogue Helper http://blog.joebeeson.com/analogue-helper http://blog.joebeeson.com/analogue-helper Ever extended a core helper and been frustrated at hunting down every single call to the previous helper and replacing it with a call to your new object? The AnalogueHelper will remap a helper to a new name when in a view, allowing you to replace calls to the core helper with your own, new object.
Setting the mappings is done by adding an array to the $helpers declaration for the AnalogueHelper in your controller... public $helpers = array( 'Analogue' => array( array( 'helper' => 'MyHtml', 'rename' => 'Html' ),array( 'helper' => 'MyForm', 'rename' => 'Form' ) ) ); This will take the helpers, MyHtmlHelper and MyFormHelper and remap them to the Html and Form variables when rendering begins.
[gist id=345566]

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1305637/42a9ad8d2315a7c6384163af80299a65.jpeg http://posterous.com/users/36p0W9fBRbsB Joe Beeson joebeeson Joe Beeson