The beginning of this week was a little bit more action packed than planned. After a couple of weeks of coding and intensive testing (seriously!), @rauchbar and me were of the opinion that the feature is production ready and could be deployed. But well, shit happens when you least expect it.
PHP Stacktrace
As developers, our daily work consists of coding, debugging and trying to analyse logs. Whenever an exception (or additionally in PHP: errors) occure, we open the log file and look for the whole stacktrace (preassumed we work OO-like). This enables a lot of advantages: We can follow the whole path of the method call that lead to the exception. We can comprehend which arguments were used in which files and on which lines. Furthermore, we can also see the classes that were used on runtime when we used an abstract implementation (Abstract Classes, Interfaces).
In short: the Stacktrace helps us a lot – normally!
Stacktrace and Opcache
Disclaimer: The following paragraph is more a interpretation of mine and not a result of any observation. Unfortunately, I did not found any resource about how the Stacktrace relates to Opcache or a fancy tool to analyse the Opcache. Therefore, enjoy with caution…
The following source code violates my personal code style. $foo is passed to the method and should not be overriden with something that is not related to the origin value. Unfortunately, that happened here. And it somehow passed the code review and went to production.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php declare(strict_types=1); class Foo { public function bar(int $foo) { return static::baz($foo); } public static function baz($foo = null) { $foo = true; throw new Exception('blub'); } } $foo = new Foo; $foo->bar((int)'12345'); |
The corresponding Stacktrace is:
We had a lot of debugging time to check why the Stacktrace shows true as the passed value to baz and not 12345. The source code above does nothing else then passing the integer value from bar to baz.
The answer is that $foo is overriden before an exception is thrown. And because PHP’s Opcache tries to make things performant, the internal pointer/variable/whatever points to true and not to 12345. That seems to be the reason why the Stacktrace is as it is.
Disclaimer – Again: This is only an assumption, please use it on your own risk! If you find any sources about that please let me know 😊
PS: The source code above is highly simplified and not the real code what triggered the exception. The real code is much more complicated and not published here since it is not public.