CakePHP Auth component, Flash and Internet Explorer… a deadly combination

OK, it’s not really deadly at all… other than that it will kill your CakePHP session and log you out.

My CakePHP-based CMS uses YUI Uploader, a Flash-based file uploader utility. It’s much better than the default HTML file uploader, because it supports a fully CSS-customizable progress bar and multiple file uploads.

It’s pretty slick, even though I did tear some hair out earlier in the year trying to get it integrated into the CMS. All went well for several months, until one particular client, using Windows Vista and Internet Explorer 8, discovered a showstopper of a problem: whenever you uploaded a file, all would seem well until you went to save your changes and you’d get kicked back to the login screen, without the changes being saved. Bad news!

I did some diagnostics and determined that, yes indeed, the CakePHP session was in fact being dropped as soon as the Flash process finished queuing the file uploads (an AJAX-based process), before you actually click the “Save” button… but since there’s nothing else happening dynamically on the page, it wasn’t obvious that the session had been killed in the background.

Anyway, some research led me to a perfect explanation of the problem, and an equally perfect solution: Flash is sending a different user agent string, which was resetting the CakePHP session. I’m still not sure why it was only affecting Internet Explorer, but at any rate, a simple change to the app/config/core.php file solved the problem in a snap. The critical line:

Configure::write('Session.checkAgent', false);

I suppose by removing this line, the application is ever-so-slightly less secure, but there should be enough other precautions in place that removing the user agent check as part of the process of validating a session should not pose a significant security risk.

CakePHP paginator sorting problem solved!

I’ll keep this brief, because I need to get back to writing code, but I wanted to share the solution I found to a CakePHP problem that has been nagging me for a while and for which I had never found a simple resolution.

I’m using the $paginator->sort() method to create links in the column headers for tables of paginated search results in my CMS. The method works great, except for one small problem: I could never get it to reverse the order. Intuitively, with links like these, you should be able to click them once to sort in ascending order, and then click again to reverse into descending order. But the reverse was never working for me.

Some research showed that you can pass in all sorts of options to the method, but I wanted to avoid having to make a change like that to about a dozen views; plus, it just didn’t seem right — the method is supposed to do the reverse by default.

At last I have discovered the source of my trouble: you need to explicitly name the model in the parameter that defines the sort field. I’ve been getting more diligent about always naming my models explicitly, but back when I set up these paginated tables (which was about the first thing I did in writing the application), I wasn’t doing it yet. Adding in the model, the reverse order on a second click works perfectly. Here’s a before-and-after example to illustrate the problem and its resolution.

Before:

<?php echo $paginator->sort('Title', 'title'); ?>

After:

<?php echo $paginator->sort('Title', 'Article.title'); ?>

CakePHP headaches

CakePHPI’m in the midst of my second big CakePHP-based project for a client, still loving CakePHP and the MVC concept overall, but I am definitely having some headaches with CakePHP this time around.

First off, I ran into some issues early on in the project that were attributable to CakePHP’s caching mechanism. Not sure why though, because caching was off by default (in fact, I was only even vaguely aware of its existence) on the first project I did; this one is building on that one; and I didn’t change any settings for caching in the core.php configuration file.

Caching is nice in a production environment, but it is a pain in the butt, to say the least, in a development environment. At least now I have it turned off. One less thing to worry about.

Today I’ve been struggling with some other frustrations that have nothing, really, to do with what I’m working on. What I’m working on would be frustrating enough, trying to wrap my brain around the intricacies of hasAndBelongsToMany relationships. But I can’t even get to that because of a pair of other issues.

First up, something I think I’ve finally got figured out. I’m writing my admin tool right now, so all of the pages I’m working on are using admin routing. Again, it should just be working; this is building upon stuff I already wrote for the first big project, which is working great and has been live for over a month now. And, for the most part, the admin routing has been working, but every once in a while I’ll click on a page that tries to load in the default template, and when it does, I get this:

Notice (8): Undefined variable: javascript [APP/views/themed/neutral/layouts/default.ctp, line 23]

OK, first off, I have the JavaScript helper defined in my controller. Second, why is it trying to load the default page layout instead of the admin layout? Well, that second question is probably irrelevant, because I viewed source on the page and found what the real error is, and what is apparently triggering CakePHP to load the default layout: there was a missing controller. And that was just because I had copied one of my other controllers as a starting point for this new one, and had not yet edited any of the code within it. In other words, I just shouldn’t click that link yet.

Fine, I can handle that. But when I was clicking on some pages that should be working, they just wouldn’t load, triggering the browser’s “server unavailable” error page, which I recognized as being the result of a segmentation fault error in Apache. So what within my PHP code, or in CakePHP, is crashing Apache? That was my real problem, and the reason for this blog post.

I googled “segmentation fault PHP” and got my answer in the form of the following:

Apache Segmentation Fault from CakePHP 1.2 Caused by Zend Optimizer

Thank you very much! This problem is happening for me in my local development environment, running MAMP. So I dug into MAMP’s php.ini file, and sure enough, Zend Optimizer was configured. I commented out all of the pertinent lines, bounced Apache, and we’re in business!

Well, sort of. The page still isn’t working, of course. It’s just now that its not working is not causing Zend Optimizer to crash Apache. Fortunately, I can see CakePHP’s error messages now, and it looks like I’ve got some problems in my model that are generating MySQL errors. Fair enough. At least I know what’s wrong now and can do something about it!

This is not a rant against CakePHP. Its error messaging is generally very useful, and its stack trace functionality rocks. Most of the time. Unfortunately it was a dangerous confluence of unrelated issues in my application today that caused the system to break down. But ultimately it was the result of issues with Zend Optimizer. I’m extremely thankful to “One Insightful M*******cker” for saving my sanity, and I just wanted to return the favor with a link back.

Why oh why won’t CakePHP store my tinyint(1) data?

CakePHPOK, I actually know the answer to the question posed in the subject line. Despite the palpable suspense, I am sure 99.9% of my audience can tune out now. That leaves the remaining 0.001 readers to dive into this problem with me.

I’m doing a lot of CakePHP development these days, and I’m loving it. (What? The McDonald’s legal team is on its way over here? Damn. Fine, McDonalds. Have it your way. What, Burger King too? I guess I better make a run for the border. OK, joke’s over.)

Of course, plunging into a sea of someone else’s code is always fraught with a little peril, and today I found some. Here’s the scenario:

MySQL is a pretty cool database. I’m very loyal to it. But there are some things it should do, but it just doesn’t. One of those things is support a boolean data type. So, we make do. A common way to make do, and the way preferred by CakePHP, is to use a tinyint(1) field and just store 0 or 1 in it. In fact, CakePHP loves this approach so much that whenever it sees a data field that’s a tinyint(1), it “automagically” refuses to accept any values for that field other than 0 or 1.

That’s super-dee-duper. If that’s what you want. But I have a data table, my users table, as it happens, and in that table I used to have a field called admin, a boolean value. Either the user’s an admin or not. Great. But I decided to upgrade this to allow more access levels than just 0 or 1 (and while I was at it, changing the field name, shockingly, to access_level). I wanted to be able to support up to ten levels. Well, great! A tinyint(1) will nicely store values from 0 through 9, so I’m set!

Except… it didn’t work. Every time I tried to save a value greater than 1, I’d find that it had saved 1 as the value. I could change it to 0 just fine, but anything else became 1.

I checked the documentation and found ample evidence for the “automagic” behavior, so I figured there were a couple of possible changes I could make that would fix the problem: I could change it to a tinyint(2), or I could change it to a char(1). Since I decided I’d rather (in theory) allow letters in the field than double-digit numbers, I went with char(1).

Only it still didn’t work.

I did some more research, found that I seemed to be on the right track, but I was still confounded.

Then it occurred to me. I knew I had seen a cache directory somewhere in the labyrinth of directories and subdirectories and sub-sub-sub-sub-subdirectories in the CakePHP package. And I also knew that a framework as developed as CakePHP probably wouldn’t hit up the database for schema information constantly, so maybe… just maybe… it was cached.

I burrowed down in my application to the app/tmp/cache/models directory, and sure enough… there’s a cache file for each data table, with the schema in a serialized form. Well, it’s a cache, right? Nothin’ to lose. Trash can, here we come! I refreshed my page, and voilà! Success!

So… word to the wise (or, not so wise, like me… otherwise you’d probably already know):

  1. CakePHP will only store 0 or 1 in a tinyint(1) field. Period.
  2. If you change the schema for any of your data tables, and CakePHP acts like you didn’t… dump that cache!

We now return you to our regular, slightly less geeky programming.