Recently I had a discussion with a colleague about the relative merits of using a popular, established PHP framework such as (but not limited to) CakePHP to accelerate application development and maintenance. As a follow-up to my previous post — specifically point #2, and to fully flesh out my thoughts on the matter, I’ll enumerate as thoroughly as possible the reasons I have chosen not to use such a framework in my projects to this date. I’m going to be using CakePHP in this example but most of them are similar in nature.

Before I explain what I don’t like about these MVC frameworks, though (aside from “code that reads like a sentence”), I should state how my personal framework differs from theirs.

  • I use an .htaccess file to inspect the requested URL, see if a “real” file exists at that location, and route the request to my bootstrap (along with the actual URL that was requested) if it doesn’t. WordPress also does this.
  • My bootstrap instantiates and uses a Controller, invoking an “action” method which performs the requested action, then redirects the user, specifies which view to display, or outputs data, depending on the nature of the request.
  • The controller actions query the database directly, using prepared statements in SQL, and load any results into arrays. With a trivial amount of effort, I could load them into logically nested arrays or generic objects if I felt that was necessary or useful.
  • Form validation, error handling, database connections, pagination, HTTP redirects, AJAX (I use JSON, so is it AJAJ?), and a handful of other common functions are handled by custom methods and functions that I wrote.
  • Views are simple PHP files that are loaded by a template that contains code specific to my framework.

Alright, I think that gives you an idea exactly what I deal with and like. Much of it seems similar to what CakePHP and others like it are doing. Why don’t I like those?

  1. It forces you to learn a new way of coding.

    This, ultimately, is the main reason I haven’t taken the plunge into any of these things. There’s a considerable learning curve. The documentation may not always lead you in the direction you want to go. When you do get where you’re going, you might not be satisfied with the results.

    The framework may also impose additional restrictions or present problems you wouldn’t otherwise have. When I was writing my own framework a couple years ago, I had written my controller class similar to the way Cake does, so that http://www.site.com/user/list would call a method called list() in the UserController. There’s a problem with that, though: “list” is a PHP language construct. Take a look at the complete list and you’ll see a bunch of words that you might want to use as methods. There’s a workaround on that page too, but it makes your code harder to understand — you won’t have a list() method, and anyone looking for it will have to dig for a while before they figure out what you did. If you control your own framework, it’s easy to just decide that you will prefix controller methods with a keyword such as “action_” as I did. If you’re using CakePHP, you have to write your own handler for this using __call.

  2. It may introduce bugs and expose security risks.

    When you use one of these frameworks, you are relying on other people to write a significant portion of your application for you. If they do a good job, this could save you lots of time. But if they don’t, it could slow you down substantially. We’re already relying on Zend and (usually) Apache and MySQL to provide a relatively bug-free platform, now you want to rely on someone else too? Now when you run into a problem, is it your fault? Is it CakePHP’s? Or is it somewhere else? It might take you a while to figure out. And if you determine it’s a problem with CakePHP, after you’ve filed a bug report, what do you do? Your application deadline isn’t pushed back just because you ran into a bug. You either have to find a workaround, fix it yourself (and hope that either the framework authors accept your fix, or they fix it too before you next update the framework), or live with a broken application until it can be fixed.

    If this bug is a security bug, it’s far worse; most likely you won’t detect the bug in development. In fact probably nobody will know about it until somebody publishes an exploit for it. Then it will most likely be fixed quickly in the framework’s VCS trunk. But how will you find out that your application is built on a vulnerable version of your framework? Even if you do, will the forced update break something in your application?

  3. It introduces additional overhead.

    A minor point, but worth noting. If overhead was the only issue here, I wouldn’t be making this post. Still, it cannot be denied that any advantage gained in coding speed by using a pre-built application framework comes at the expense of a small amount of performance. Custom frameworks will always have an advantage here.

    Also, although these frameworks do enable you to do complex joins and (I assume) grouping, you have little control over how the actual SQL is constructed, which can result in performance problems that are difficult to diagnose and possibly impossible to fix without defeating the purpose of using the framework in the first place.

  4. It can be harder to maintain.

    This one is important, because ease of maintenance is one of the virtues extolled by the users of these frameworks and the primary argument that my colleague presented to me. His claim is that it’s easier to maintain a multidimensional array of arguments than a list of raw SQL queries.

    Does he write fewer characters of code? Probably most of the time, yes. Is it really easier to maintain?

    In my experience in web development, in the “maintenance” stage of the software life cycle, existing database queries rarely need to be changed. The initial requirements of the client are typically something to the effect of, “I want X page to display X data”. An overwhelming majority of the time, X page will always display X data. Changes are usually of the nature, “I want to add Y data to X page”, or “I want X page to also link to Z page”. These types of changes are trivial to implement under either paradigm.

    If your client requests major changes, or you discover some other way that a major change is needed, then A) either you didn’t scope your project very well or this can be classified as a new project, and B) you have a lot of work to do, in any case. Does it really take longer to change 50 raw SQL queries than it takes to change 50 multidimensional arrays in find() methods? Most of your work is going to be done in the views, and you’re going to have to do that either way. I don’t truthfully know that it’s all that big of a difference in total.

    Consider these two code fragments:

    $user = $this->db->query("SELECT * FROM users WHERE user_id=$id")->fetch();
    $user = $this->User->find('first', array('conditions' => array('user_id' => $id)));

    They are about the same length. Which is clearer about what is happening?

    • The first clearly says that we are querying the database. The second looks at a glance like we have a “user” object which is responsible for finding something. Is it called first or are we only finding the first thing? Finding the first of what, from where? And why does the user object have such a generic find method anyway? Oh, it’s the users that we’re finding. (That’s backwards.) And it’s from the database, even though it doesn’t say so.

      These questions all have answers, of course. The problem is that you have to learn what those answers are. Knowing PHP isn’t enough. If someday you forget how the current version of CakePHP works, or if you hire someone to do your maintenance, Cake’s code will be more difficult to maintain.

    • The first makes sense as you read it from left to right. Get the database property from this object. Run the query() method. Fetch a result. The second reads erratically; reading it from left to right is awkward.
    • The first doesn’t have any arrays-of-arrays or any other words you have to ignore. The repeated use of the array keyword distracts from the purpose of the line of code. When I use prepared statements, I use one array. CakePHP always uses at least two, and commonly uses more.
    • Complicating the query does not complicate the call. Some people may prefer to manage a multidimensional array than a concatenated string. I don’t.

    Of course CakePHP can do more than just joins with this syntax. It can also fetch associated records from other tables in what I can only assume are separate queries, within the same call to find(). You might find this convenient. I find it confusing. Here’s a real example (not at all simplified) of what my code would typically look like (slaveDb is an instance of my extension of PDO):

    if($this->data['userInfo'] = $this->slaveDb->prepare_and_run('SELECT * FROM users WHERE user_id=:user_id', array(
    ':user_id' => $_GET['user_id']
    ))->fetch()) {
    $this->data['commentsArray'] = $this->slaveDb->prepare_and_run('SELECT * FROM comments WHERE user_id=:user_id', array(
    ':user_id' => $_GET['user_id']
    ))->fetchAll();
    }

    …and here’s a similar example, as I understand how CakePHP would do it (once you’ve configured the relationships, and attached the ‘Containable’ behavior to the object — which only experienced CakePHP developers will even know exists):

    $this->User->find('first', array(
    'conditions' => array(
    'user_id' => $_GET['user_id'],
    ),
    'contain' => 'Comment'
    ));

    Note that:

    • It took me half an hour to figure this out.
    • I’m still not sure I’ve done it properly. (I probably haven’t). If I’ve done it wrong, I can assure you that the real solution is more complex.
    • My code also prepares the resulting data for being displayed in a view. After browsing their documentation for a few more minutes, I can’t figure out how to do that in CakePHP.

    Obviously, CakePHP’s code looks significantly cleaner. But its brevity is its only advantage. Without direct knowledge of CakePHP, someone reading the code simply will not know what it’s doing, and possibly, they may not even know where to look to find out. Here are the things you know after reading my code that you don’t know after reading Cake’s:

    • The name of the database table.
    • That we are retrieving data from a slave database.
    • We are using prepared statements.
    • The exact SQL used to retrieve the data. (this can be extremely important in the optimization phase!)

    Will you always use the framework you’re using today? Suppose everybody decides that Yii or Symfony is better than CakePHP. Or suppose CakePHP changes hands and you don’t like its new direction, so you switch to one of these other frameworks that has some fundamental differences from CakePHP. Regardless of whether you like these changes or not, you accept them and work for a few years on this new framework. Now your old CakePHP client calls you and asks you to make some changes to your old application. Are you sure you can do it faster than you could if you were working with your own custom framework? If not, consider that maintenance might not be as much easier as it appears.

Categories: Opinion

Leave a Reply