Design by Contract in PHP with Assertions

Recently I wrote about meta programming in php because my recent project involves quite a bit of it. I will probably write about it at some other point but it’s still in sort of early development stage. However while these techniques produce very compact and flexible code, they can also be hard to debug. Since code gets modified at runtime you can often run into situations where something gets passed to the wrong method and you don’t really know why and where. Each time I called some method, I was making certain assumptions as to the state of the program. For example, whether or not I am accessing the right type of class, or whether or not the method is called properly and etc. Here is a sample comment block from a function I wrote recently:

/**
 *  Returns an array built by querying another table. The name of the table is based on the
 *  foreign key passed in as an argument(param $name) which is expected to be in the form
 *  tablename_id. The method will import tablenameModel and tablenameView  classes, initializes
 *  them and run listRecords() method on the tableModel instance.
 *
 *  Note that tablenameModel and tablenameView classes must exist and be defined in files
 *  models/tablenameModel.php and views/tablenameView.php. The classes should be derived
 *  from DefaultModel and DefaultView respectively.
 *
 * @param string $name The name of the foreign key form tablename_id
 * @return array
 */

I’m assuming tons of things here. I’m expecting classes to be defined in certain files, I expecting them to be descendants of certain other classes, and I need the argument to be passed to me in a specific format. During normal execution all of these conditions should be met automatically. The correct files should be in the right place, because of the way the whole system is set up. The classes in question should extend the correct parents because that is the convention I’m using. Similarly, I’m also enforcing a convention that all foreign keys are ought to be named in the form tablename_id. So if someone passes me a parameter that is in the wrong form, or that does not have correctly defined Model and View classes associated with it, this is a big issue. This should never, ever happen at runtime.

How do I enforce these things though? I opted to take my comments and turn them into assertion statements. At some point this almost turned into sort of a Design by Contract methodology. Ensure preconditions, guarantee postconditions and etc.. Here is the method body itself:

function getComboBoxArrayFromOtherTable($name)
{
   assert('is_string($name)');
   assert('Util::EndsWith($name, \'_id\');');
            
   $tablename = substr($name, 0, strrpos($name, '_'));
   $modelname = $tablename."Model";
   $viewname = $tablename."View";
            
   assert('file_exists(\'models/\'.$modelname.\'.php\')');
   assert('file_exists(\'views/\'.$viewname.\'.php\')');
            
   include "models/$modelname.php";    
   $othermodel = new $modelname();

   include "views/$viewname.php";      
   $otherview = new $viewname();
            
   assert('is_a($othermodel, \'DefaultModel\');');
   assert('is_a($otherview, \'DefaultView\');');
                    
   $othermodel->listRecords();
   return $othermodel->getComboArray();
}

I could of course throw exceptions, or make my script die() if these conditions were not meant. But as I said before, failure to meet the asserted conditions were was not really something that could be introduced by a user stumbling to a wrong URL or entering malformed data at runtime. Failing these assertions would be primarily caused by someone calling my methods improperly, or passing wrong arguments. In other words – programmer errors. And so, we spank the programmer rather than the user by using assertions which can be disabled in production code.

They are sort of sanity checks that make sure I’m using my own methods the way I originally intended them, and that I’m not really building system around some curious side effect happening deep inside my code that I never really intended for, but over a period of time grown to really on. Not to mention that they really help you write a self documenting code.

I often forget to spell out all my assumptions like in my code block above. This one is a nicely fleshed out because but not all of my comment blocks are this informative. In fact when I’m in a hurry I will sometimes write a single sentence or two just to have something show up in the doc file. Assertions however must be explicit, and really flesh out my pre-conditions and post conditions. So even if I didn’t write any documentation for a given method, someone could pick it up and work out at least partial (bare bones) documentation just by looking at some of these statements.

I haven’t really used assertions that much in my PHP code before. But with this project I’m totally sold on them.

[tags]php, assertions, design by contract, meta programming[/tags]

This entry was posted in Uncategorized. Bookmark the permalink.



9 Responses to Design by Contract in PHP with Assertions

  1. http://paste2.org/p/37171

    I was wondering if you could take a look at that for me? For some reason, it doesn’t display, it doesn’t give me an error… it just doesn’t display.

    In my game I am developing, we are having an issue with sometimes when someone attacks someone else… that (since its all based on classes) between lag and something else… sometimes there are weird times when nothing shows up.

    so i want to put this in the “view territory page” so when someone accesses their profile page, it will make sure that it displays the correct information, AND update it in the database. (the update part has been removed for security reasons, but it should still work even without that)

    Reply  |  Quote
  2. Luke Maciak UNITED STATES Mozilla Firefox Windows Terminalist says:

    I don’t know whether or not this is a typo, but check line 51:

    $infa = inumber_format($data['infantry']);

    I think it should be number_format rather than inumber_format:

    $infa = number_format($data['infantry']);

    Also see if you can print out the results as they come out of the database:

    print_r($data);

    This may help you debug this issue.

    Reply  |  Quote
  3. do i have to call the database information inside the function or will it work just inside the file?

    Reply  |  Quote
  4. :P and i sure hope that its not that damn i (well i kinda do, makes it a simple fix… and easly over looked.. .thats why i hate code, i wish it would read our minds)

    I don’t have access to the ftp tonight but I will try later.. .Thanks man

    Reply  |  Quote
  5. Luke Maciak UNITED STATES Mozilla Firefox Windows Terminalist says:

    [quote comment=”9287″]do i have to call the database information inside the function or will it work just inside the file?[/quote]

    No, not necessarily. If you do it outside of the function it should be fine.

    [quote comment=”9288″]I don’t have access to the ftp tonight but I will try later.. .Thanks man[/quote]

    Btw, why don’t you just set up a dev environment on your local machine. Check out XAAMP for example for a quick and painless setup for WAMP envirionment. It’s probably quicker way to code than the upload/test/change/upload routine.

    Reply  |  Quote
  6. Thats true… I should really set that up.. I just am always lazy.. uploading it just seems easier. :D its that habit thing we were talking about.

    Reply  |  Quote
  7. Thats true… I should really set that up.. I just am always lazy.. uploading it just seems easier. :D its that habit thing we were talking about.

    Reply  |  Quote
  8. … Thats crazy… i was setting up my useragent again… and somehow i double posted and it changed my version to “mozilla 1.8.1.14” crazy

    Reply  |  Quote
  9. Luke Maciak UNITED STATES Mozilla Firefox Windows Terminalist says:

    I think you may have a typo in your user agent string, and the script just falls back on the default (un-changable) one which I guess is Mozilla.

    Reply  |  Quote

Leave a Reply

Your email address will not be published. Required fields are marked *