Skip to main content

PHP Assertions

I stumbled upon assertions in PHP today, though why I didn’t know they exist after working in the language for so long and what I was originally looking for when I came across them are both mysteries. And with the increasing focus on software quality in the PHP community, I wondered why I hadn’t seen them used by others. I decided to ask around.

I asked a few friends if they knew about assertions. They did. I asked if they used them. They didn’t.

Remi Woler: I think nobody has found a good use case. It weaves tests into code. How are you going to recover from a failed assertion?

Davey Shafik: They kinda suck. For example: assert('mysql_query("")') It's a string of code that gets eval’d.

So, PHP assert didn’t get stellar endorsements from people whose opinions I respect.

My main experience with assertions comes from C where they are defined as macros. Its argument must evaluate true, otherwise the program terminates with an error. These checks can be stripped at compile time with -DNDEBUG if desired, although there is some disagreement on the wisdom of doing so.

PHP asserts are implemented differently. First, they’re configurable in php.ini or by using assert_options(). A failure doesn’t necessarily have to abort the script—you can bail if you want to, or disable them, or convert them to run-time warnings, or even invoke a callback to handle them. This makes them very flexible and much less black-and-white than in C.

The actual assert() function accepts either a string or a boolean for its condition. So, you can write either:

assert(is_string($foo));

or:

assert('is_string($foo)');

In the first example, the statement is evaluated and the resulting boolean is passed to assert(). While perhaps a little more traditional, it’s not efficient as you will see momentarily.

In the second example, the string is passed to assert() directly which eval’s it to determine the truthiness. This is a better approach for two reasons:

  1. assert() immediately returns true when assertions are disabled. The code string is not evaluated and any performance hit from executing unnecessary statements is minimized.
  2. If the assertion fails, the code string is passed to a callback (if one is used) and can be included in any output or logging.

I’m not convinced Davey’s eval concerns are entirely well-founded in this instance because of the above reasons and the fact that it’s static code to be evaluated by PHP. It’s a controlled environment, not eval($randomUserSuppliedCode).

PHP 5.4 also added a second parameter to assert()—a string description to annotate the test. If present, the string is also passed to the callback.

The PHP manual offers some guidance on using assertions:

Assertions should be used as a debugging feature only. You may use them for sanity-checks that test for conditions that should always be true and that indicate some programming errors if not or to check for the presence of certain features like extension functions or certain system limits and features.

Assertions should not be used for normal runtime operations like input parameter checks. As a rule of thumb your code should always be able to work correctly if assertion checking is not activated.

Both are good advice, but contradictory; your code may not work if assertion checking is disabled and you are using them to test system limitations.

Wikipedia explains the difference between assertions and error handling:

Assertions should be used to document logically impossible situations and discover programming errors — if the impossible occurs, then something fundamental is clearly wrong. This is distinct from error handling: most error conditions are possible, although some may be extremely unlikely to occur in practice. Using assertions as a general-purpose error handling mechanism is unwise: assertions do not allow for recovery from errors; an assertion failure will normally halt the program’s execution abruptly. Assertions also do not display a user-friendly error message.

So at this point I disregarded the manual’s and Wikipedia’s advice and tinkered with them. PHP assertions don’t behave like their C brethren, so perhaps the traditional C way of thinking (asserts are debugging only) might be restrictive? What I found was that PHP assertions, with a bit of creativity, could be used to write readable, quality code.

Consider a naive Active Record implementation. You might have code that resembles:

<?php
class User
{
    protected $id;
    ...

    public function setId($id) {
        if (!is_null($this->id)) {
            throw new BadMethodCallException('ID already set for user.');
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException('ID for user is invalid.');
        }
        $this->id = $id;
    }

    ...
}

It is possible to use assert() to test the $id argument (disregarding the manual’s advice) and a callback to throw the exceptions (ignoring Wikipedia).

<?php
assert_options(ASSERT_CALLBACK, function ($file, $line, $code, $desc) {
    list($exClass, $msg) = explode(':', $desc, 2);
    throw new $exClass($msg);
});

class User
{
    protected $id;
    ...

    public function setId($id) {
        assert('is_null($this->id)',
            'BadMethodCallException:ID already set for user.');
        assert('is_int($id) && $id > 1',
            'InvalidArgumentException:ID for user is invalid.');

        $this->id = $id;
    }

    ...
}

This isn’t how assertions are intended to be used, but it does address Remi’s concern about recovery. One doesn’t typically recover from an assertion but now the condition has been converted into an exception so recovery is possible to the same extent that recovery from the exception would be.

If assertions have been turned off then the code won’t work, so if you needed to rely on this then you have to add assert_options(ASSERT_ACTIVE, true) to your bootstrap file.

Now don't get me wrong, I'm not about to start doing this in my projects. But it’s fun to play and there’s still some questions worth pondering.

If you were to use assert() properly instead of something along the lines of my bastardized exception example, what type of things would be worth asserting?

Assertions are meant to identify program logic/design bugs, not as a run-time error handling mechanism. Isn’t this why we do unit testing? Playing devil’s advocate here, what’s wrong with pushing unit tests directly into your code if we have doc comments that are extracted for documentation?

Feel free to let me know your thoughts in the comments section below. Do you constrain yourself to the classical interpretation of assertions, or do you take advantage of the flexibility of PHP’s implementation? Where and when do you use them in your code?

Comments

  1. Doing a benchmark of creating the object and setting the id 10,000 times it appears the assertion is nearly ten times slower on my pc (even with neither triggering an assertion). It also requires PHP 5.4.8. I hadn't see the new description flag though. Thanks for pointing that out.

    ReplyDelete
  2. Spot on. Assertions are great for proofing parameter types for example. That's typically an error cause that ought to be found within the development stage. Using exceptions for delaying their discovery until deployment isn't sensible. (Unless of course method parameters are highly volatile due to unsettled code paths or raw user input that is).

    ReplyDelete
  3. For debugging I use assert during development as well as a rewritten error handler to allow for strict types in my code.

    for example:

    error handler reports a fatal error with full debug back trace if $bar is not a string

    function foo (string $bar)
    {
    print $bar;
    }

    // assert informs me that $foo is a zero length array

    function bar (array $foo)
    {
    assert (sizeof ($foo) > 0, '$foo has no length inside bar() ' . implode ("\n", debug_backtrace ());
    }

    During a release build all of these checks are removed before they are checked into the release branch in the version control.

    This has helped me develop really stable code that works as intended by providing logging of code errors that would normally bypass most debuggers and tests.

    This is how I believe these features should be used correctly and as with compiled applications, for optimization, these extra debugging tools should be stripped from your code.

    ReplyDelete

Post a Comment

Popular posts from this blog

Composing Music with PHP

I’m not an expert on probability theory, artificial intelligence, and machine learning. And even my Music 201 class from years ago has been long forgotten. But if you’ll indulge me for the next 10 minutes, I think you’ll find that even just a little knowledge can yield impressive results if creatively woven together. I’d like to share with you how to teach PHP to compose music. Here’s an example: You’re looking at a melody generated by PHP. It’s not the most memorable, but it’s not unpleasant either. And surprisingly, the code to generate such sequences is rather brief. So what’s going on? The script calculates a probability map of melodic intervals and applies a Markov process to generate a new sequence. In friendlier terms, musical data is analyzed by a script to learn which intervals make up pleasing melodies. It then creates a new composition by selecting pitches based on the possibilities it’s observed. . Standing on Shoulders Composition doesn’t happen in a vacuum. Bach wa

Learning Prolog

I'm not quite sure exactly I was searching for, but somehow I serendipitously stumbled upon the site learnprolognow.org a few months ago. It's the home for an introductory Prolog programming course. Logic programming offers an interesting way to think about your problems; I've been doing so much procedural and object-oriented programming in the past decade that it really took effort to think at a higher level! I found the most interesting features to be definite clause grammars (DCG), and unification. Difference lists are very powerful and Prolog's DCG syntax makes it easy to work with them. Specifying a grammar such as: s(s(NP,VP)) --> np(NP,X,Y,subject), vp(VP,X,Y). np(np(DET,NBAR,PP),X,Y,_) --> det(DET,X), nbar(NBAR,X,Y), pp(PP). np(np(DET,NBAR),X,Y,_) --> det(DET,X), nbar(NBAR,X,Y). np(np(PRO),X,Y,Z) --> pro(PRO,X,Y,Z). vp(vp(V),X,Y) --> v(V,X,Y). vp(vp(V,NP),X,Y) --> v(V,X,Y), np(NP,_,_,object). nbar(nbar(JP),X,3) --> jp(JP,X). pp(pp(PREP,N

What's Wrong with OOP

Proponents of Object Oriented Programming feel the paradigm yields code that is better organized, easier to understand and maintain, and reusable. They view procedural programming code as unwieldy spaghetti and embrace OO-centric design patterns as the "right way" to do things. They argue objects are easier to grasp because they model how we view the world. If the popularity of languages like Java and C# is any indication, they may be right. But after almost 20 years of OOP in the mainstream, there's still a large portion of programmers who resist it. If objects truly model the way people think of things in the real world, then why do people have a hard time understanding and working in OOP? I suspect the problem might be the focus on objects instead of actions. If I may quote from Steve Yegge's Execution in the Kingdom of Nouns : Verbs in Javaland are responsible for all the work, but as they are held in contempt by all, no Verb is ever permitted to wander about