Page 1 of 1

__toString() and exceptions

Posted: Sat Sep 05, 2009 1:20 pm
by m4rw3r
From PHP 5.2 and onwards it is no longer to throw Exceptions __toString() when using an object as a string (eg. in echo(), implode(), etc.).

The problem is that I have a SQL-builder which uses exceptions from __toString() if some data is missing (eg. a FROM part).
I use __toString() because it makes it a lot easier to concatenate strings with objects:

Code: Select all

$string = implode(', ', $list);
$string = ‘’;
?foreach($list as $element)?
{?
    if($element instanceof Some_class)?
    {?
        $string .= $element->getString();
?    }?
    else
?    {
?        $string .= $element;
?    }?
}
// Instead of
$string = implode('', $list);
It is both faster and easier to read (and maintain), but the problem is that I still need to throw exceptions from them.

So what I've figured so far is that I probably need to embed the error in the generated string, and then search for the error string in the generated SQL before I send it to the database:

Code: Select all

function getSQL()
{
    $sql = $this->__toString();
    self::detectError($sql);
 
    // db interaction...
}
The problem is that I also need a delimiter for this error message, any suggestions?

And if you know any other way of throwing exceptions from code which is run in __toString(), please tell.

Re: __toString() and exceptions

Posted: Sat Sep 05, 2009 6:24 pm
by Ollie Saunders

Code: Select all

function asStr($x) {
   if (method_exists($x, '__tostring')) { # false if non-object.
      return $x->__tostring();  # allows exceptions to be thrown normally.
   }
   return (string)$x;
}
 
implode(array_map($pieces, 'asStr')); # any exceptions will occur before implosion.

Re: __toString() and exceptions

Posted: Sun Sep 06, 2009 8:22 am
by m4rw3r
That seems like a good solution.

I've implemented the error sending through the generated string, and it works well.
The downside is that it won't interrupt the execution until it has generated the whole string to be able to parse it.

The downsides of your method are a decrease in performance and a lot of extra method calls (eg. string concatenation).

My benchmark:

Code: Select all

function asStr($x)
{
    return method_exists($x, '__tostring') ? $x->__toString() : $x;
}
 
class Foobar
{
    function __toString()
    {
        return 'Foobar';
    }
}
 
for($i = 0; $i < 100; $i++)
{
    $a[] = 'Bar';
    $a[] = new Foobar();
}
 
 
$t = microtime(true);
for($i = 0; $i < 1000; $i++)
{
    $s = implode('', array_map('asStr', $a));
}
$t2 = microtime(true);
 
echo 'asStr: '.($t2-$t)."\n";
 
 
$t = microtime(true);
for($i = 0; $i < 1000; $i++)
{
    $s = implode('', $a);
    $p = strpos($s, 'aaaa');  // this equals the check for an error message without finding one
}
$t2 = microtime(true);
 
echo '__toString: '.($t2-$t)."\n";
Results:

Code: Select all

asStr: 7.90123105049
__toString: 1.53427004814
What do you think can got wrong if the execution is allowed to continue when an error (eg. missing WHERE part in the query, no data in UPDATE query) is encountered?
It is "only" a string builder after all, and the only probable result I can see is that it continues to parse the more complex complex queries even if the result will be discarded.

Maybe I can move the exception needing parts of the string conversion from the __toString() method, and use __toString() directly, before I concatenate it? Going to check.
Apparently calling __toString() directly before concatenating its result with the string is faster than concatenating the object with the string and letting PHP call __toString() automatically.

Re: __toString() and exceptions

Posted: Sun Sep 06, 2009 9:31 am
by Ollie Saunders
Your embedded error idea is the kind of clever code that will cause maintainers headaches. Write code that makes your intentions obvious and is conceptually simplest.

Also "premature optimization is the root of all evil." - This quote is a cliché nowadays but no less true than when it was first used.

Re: __toString() and exceptions

Posted: Sat Sep 12, 2009 5:48 pm
by josh
In Zend Framework they catch the exception and implode() it's stack trace, returning it in the final returned string. You could also trigger a warning if you wanted to halt the flow. I think the key is they dont want you to do a lot of work in that method, in 5.3 it doesnt take arguments anymore which affects some legacy code too