CakePHP

On The Fly Model Chains With CakePHP

Posted by Matt on Fri, Apr 17 2009

This code has been completely re-worked. The latest is at http://github.com/mcurry/lazy_loader/tree/master. This post has more information about it.

Built For Speed

In yesterdays post I talked about how CakePHP builds a model chain of all the related models. The smart readers (which is really all of you guys. you're the best) probably realized that there is a lot of overhead in bulding the chain, especially for apps with lots of interlinked models. Wouldn't it be awesome if Cake only created the pieces of the chain as needed? Or what if some really awesome member of the community took the time to build such functionality and then shared it with everyone. You know, something like:

httpc://github.com/mcurry/lazy_loader/raw/5ea7dc8d0dd70ec945b33abaf8f67db940ee9764/app_model.php

What The Hell Is Going On Here?

Actually not that much. First we need to catch the constructor and remove all the associations. Wait. What? You crazy! Why would you do that? By removing all the associations before the parent constructor is called Cake thinks there are no related models and won't waste a bunch of time building the chain, when chances are you'll never use half of them. Don't worry, all the associations are first backed up in the __definedAssociations attribute.

What's the deal with the __loadAssociations attribute?

Some of the Cake core models don't play so well with this trick, so this attribute lists models that should have their chains loaded the old fashion way.

Great, So Now I Have A Model With No Chain. Thanks, Moron.

But wait. There's more. See the __get and __isset methods? Those are part of PHP5's overloading ability (Did I mention this was PHP 5.1 or better?). Whenever your code tries to reference a model in the chain that doesn't exist the __get method gets called, which in turn calls __connect to check if it's a valid association. If it is Cake's bind method is called and the model is added to the chain.

Your Code Sucks. It Doesn't Work With $recursive Set

Actually I didn't even check this. I just assumed it won't work. Really there isn't much reason to use $recursive in Cake 1.2. You know the saying: Once you go Containable you never go back. Did I mention this code works awesomely with Containable?

I Tried Your Code And It Broke My Shit

Yea, I expect that to happen. There are probably some incompatibilities with this. I think there is an issue with ACL and probably with other behaviors. It would be cool if you all could try it out and report back.

How Much Faster Is This?

Ahhh, no we get to The Question. Here we go:

Basic controller, no models:

Time per request - 145.636 ms
Calls to ClassRegistry::init - 0

Basic controller, one model, no chain:

Time per request - 142.817 ms
Calls to ClassRegistry::init - 1

Basic controller, one model, nine chained models:

Time per request - 244.533 ms
Calls to ClassRegistry::init - 19

Basic controller, one model, nine dislinked chained models:

Time per request - 162.905 ms
Calls to ClassRegistry::init - 1

Going from one unchained model to one model with a long chain added about 100ms (71% increase). Adding this code brought the number back pretty close to what it was before with just the one unchained model.

How Do I Use It

Pretty simple. Just drop this code into your /app/app_model.php.

One More Thing

I'm slipping this in at the end, because I figure anyone whose stuck around this long would be interested. I'm looking for CakePHP people, who like to read, and want to help me with a project. The bold text should give a pretty good hint as to what the project is. Send me an email at matt -at- pseudocoder.com if you're interested.

Posted in CakePHP | 22 Comments

One More Tip For Speeding Up CakePHP Apps

Posted by Matt on Thu, Apr 16 2009

I somehow completely failed to mention the whole $uses/ClassRegistry/loadModel/chained models thing in my 8 Ways to Speed Up CakePHP Apps post. That's like doing a post on the top 5 adult film stars who hit rock bottom, had it video taped and posted on YouTube, and failing to mention the epic Chasey Lain video (part 2). If you think that link is even remotely SFW, go re-read the description again.

Don't Use $uses Unless You Really, Absolutely Have To

$uses is a controller attribute that allows you to access additional models to the default one. Say you have a blog application and one of the controllers is posts. By default you have access to the Post model. If you wanted to also have access to the Comment model you could do (but shouldn't):

<?php
class PostsController extends AppController {
  var $name = 'Posts';
  var $uses = array('Post', 'Comment');
}
?>

Model Chains

Since Comment is associated to Post through a HasMany relationship you can access the Comment model through Post. Like this:

$comments = $this->Post->Comment->findAllByPostId($id);

The relation chain extends infinitely, including all models down the line.

Controller::loadModel and ClassRegistry::init

Great, but sometimes you do legitimately need access to a model that isn't anywhere in the relation chain. If you are going to use the model throughout the controller go ahead and include it in $uses, but if you only need it in one action there are better ways. They are Controller::loadModel() and ClassRegistry::init().

//the loadModel way
$this->loadModel('Comment');
$comments = $this->Comment->findAllByPostId($id);

//the ClassRegistry way
$Comment = ClassRegistry::init('Comment');
$comments = $Comment->findAllByPostId($id);

Controller::loadModel, not to be mistaken for the deprecated loadModel function, creates an instance of the model and assigns it to the controller. You can then access it the same way as you would as if it was loaded through $uses. ClassRegistry returns an instance of the model. Gwoo prefers the loadModel approach over ClassRegistry. Good enough for me.

Approximate Increase

The dreaded: "it depends." Having one or two extra models in your controller's $uses probably isn't going to kill your app. I added one extra model to my test app and there was about a 4% increase in how long the page took. Then I added seven extra models and there was approximately a 40% increase. So roughly 4-6% for every additional model.

Posted in CakePHP | 8 Comments

Review: Refactoring Legacy Applications Using CakePHP

Posted by Matt on Wed, Apr 08 2009

The Setup

Shortly after Chris Hartjes released his new book Refactoring Legacy Applications Using CakePHP he contacted me and asked if I'd be willing to review it. I jumped at the chance and Chris emailed me the DRM free PDF. After posting it to The PirateBay, I settled in and gave it a read.

The Details

The book is 61 pages and costs $10 for the PDF or slightly less than $20 for the print version. This isn't a book for CakePHP beginners. The ideal reader has good working knowledge of PHP and a basic understanding of CakePHP. Chris gives a quick overview of associations, behaviors, and routing, but it's more of a refresher then a in depth lesson. If you're reading and hit a concept you don't understand take a break from the book and visit The Cookbook.

The Review

In the last CakePHP digest I gave a mini review where I said:

I think by calling it "Refactoring Legacy Applications Using CakePHP" Chris limits his audience, as the book is really about adapting "spaghetti" style PHP code to CakePHP.

What I mean is don't get hung up on "oh, I don't have an application I want to refactor, so this book probably isn't for me." Really the book is about refactoring your brain (which could be considered a legacy application) away from a mish-mosh coding style to CakePHP's MVC system. Think of it like one of those makeover specials they do on Oprah (uh...the ones I've heard about from other people). You don't have to actually be planning to give yourself a makeover to enjoy the show and learn something.

The Before Code

Chris uses a baseball game admin tool for the code that he is refactoring. The before code is pretty much what you'd expect - an orgy of PHP, HTML and SQL all intertwined in a giant sticky mess. Some of the example blocks are pretty long, spanning several pages. It's up to you how much time you want to devote to understanding the old code. Chris does a good job of describing what the code does, but you'd probably get more value by taking the time to carefully read and try to understand what's going in.

The After Code

Each of the sections generally follows the same pattern. Some cracked out PHP code is shown, then Chris explains how to break it up to fit into CakePHP. You'd think that would get redundant, but each section tosses in new elements, building on what you've already learned. There is an emphasis on the fat model/skinny controller approach that all the popular kids are using. If you're not already on the FMSC bandwagon now is the time. In like 6 months everyone will be all SMFC and you'll still be blogging about FMSC and we'll all make fun of you.

I really like this style of learning over the "tutorial" approach seen in other CakePHP books. By looking at the legacy PHP code and seeing how Chris turns it into CakePHP your not just following some cookie cutter blog tutorial. You're forced to think about what's happening and can apply that reasoning to your own code. You do more learning and less memorizing.

The Part Where I Say Negative Things and Wax Nostalgically

The refactoring in the book went smoothly...a little too smoothly. I would have liked to see more crazy, dailyWTF style shit. For instance Chris admits that the database is shared with another CakePHP app, so it already follows Cake's conventions pretty closely. Cake has a reputation for being too rigid, but really you can make the models fit any database structure. I would have liked to seen a section on either porting data or adapting models to fit odd database schemas.

When I was rebuilding XplodSony.com (with Cake 1.1 no less) it was going to share the product database with SonyMETC.com. SonyMETC is written in WebObjects, which is a framework for those who like to program on a keyboard made of broken class, covered with tetanus. Needless to say, that database fit the Cake conventions about as well as The Glove fit OJ, but thanks to some model craziness it was still still able to work. I know both of these sites kind of look like shit now, but don't blame me. I broke up with them two years ago and they've been on a downward spiral ever since. I have that affect.

The other section I would have liked to see is something on partially refactoring an app. Sometimes it's just not possible to rebuild the whole thing at once, so you want the legacy app and the Cake app to run side-by-side. We're doing this at my current job. Most of our app is a V2 of an old PHP app that is now running on Cake. However we knew that there was no way we'd have time to build the reporting system before launch, so we took the legacy reporting system and wedged it into the Cake app. This allowed us to launch on schedule and take our time building the new reporting system right. And damn straight we took our time. A year later and we're almost half way done!

The End

So is the book worth your $10? Really $10 isn't that much, especially for what you're getting. I know the economy sucks right now and you're probably trying to be smart about your money. Here's the way I see it. In a year either the economy will be recovering/ed and everyone will be hiring CakePHP devs at which point that $10 will look like nothing next to the $250/hr we Cake devs pull down. OR society will have collapsed into a post-apocalyptic lawless cluster frak (with zombies) and money won't have any meaning. Either way you win, right?

PS

Obviously, the Pirate Bay thing was a joke. I would never do that. Chris worked very hard on this project and it shows. Don't copy that floppy!

Posted in CakePHP | 3 Comments

Programming CakePHP Apps with Komodo IDE

Posted by Matt on Wed, Feb 25 2009

I suck at screencasts; I know this. If I keep doing them, I'm bound to get better, right? Is it possible to get worse at something the more you do it? Anyway, I made a quick screencast showing off some CakePHP features with Komodo IDE. Leave a comment with your favorite CakePHP related IDE trick.

Screencast

Links and Snippets

The CakePHP Extension

http://community.activestate.com/node/2794

The bake script

Make sure to put %p in the "Start in" field. This tells Komodo to cd to the directory of the active project first. For this to work your project file needs to be in /app.
bake

The bake script

In addition to adding %p like above, you also need to add %b to the command. This will take the current file name, less the extension, and pass it as a parameter.
cake

Posted in CakePHP | 18 Comments

HttpSocket Instead of cURL in CakePHP Apps

Posted by Matt on Mon, Feb 16 2009

There are a few new Bakery articles lately that use cURL for remote calls (see here and here), rather then Cake's HttpSocket lib. This is probably because HttpSocket doesn't have a mention in the CookBook (yet :)).

Here's a quick primer on using HttpSocket as an alternative. I'll re-write the code from the TwitterMail article as an example.

First, their original code:

$curl_twitter = curl_init();
curl_setopt($curl_twitter, CURLOPT_URL, 'http://twitter.com/account/verify_credentials.json');
curl_setopt($curl_twitter, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($curl_twitter, CURLOPT_HEADER, false);
curl_setopt($curl_twitter, CURLOPT_USERPWD, $this->data[$this->name]['twitter_username']
                                            .":".$this->data[$this->name]['twitter_password']);
curl_setopt($curl_twitter, CURLOPT_POST, true);
curl_setopt($curl_twitter, CURLOPT_RETURNTRANSFER, true);
$curl_result = curl_exec($curl_twitter);
curl_close($curl_twitter);

Now using HttpSocket instead:

App::import('Core', 'HttpSocket');
$HttpSocket = new HttpSocket();
$request = array('auth' => array('user' => $this->data[$this->name]['twitter_username'],
                                 'pass' => $this->data[$this->name]['twitter_password'],
                                 'method' => 'Basic'),
                 'uri' => 'http://twitter.com/account/verify_credentials.json');
$result = $HttpSocket->request($request);

I explicitly set the auth method to "Basic" here. This is because the default auth method is "basic" - notice the lowercase "b". Twitter doesn't like this. This is fixed in the trunk version of Cake, but if you're using the latest release (8004), you'll need to set it manually.

There you go. Nine cURL lines becomes four lines of Cake code. In addition the dependency on the cURL lib is removed as HttpSocket uses PHP's fsockopen. Cake's HttpSocket doesn't have all the features of cURL, but it's certainly handles most web services with no problem.

Posted in CakePHP | 15 Comments
1 | 2 | 3 | 4 | 5