0

A class constant always seems to be interpreted as a string although it is defined as an integer. Why does PHP do this kind of type juggling and how do I prevent it?

See the following code:

class BitSet {
  const NONE = 0;
  const FOO = 1;
  const BAR = 2;
  const ALL = 3;

  public function __construct( $flags = self::NONE ) {
    if( $flags & self::ALL !== $flags )
      throw new \OutOfRangeException( '$flags = '.$flags.' is out of range' );
    $this->value = $flags;
  }

  protected $value = self::NONE;
}

$bs = new BitSet( BitSet::FOO );

The last line (the invocation of the constructor) throws the OutOfRangeException:

PHP Fatal error:  Uncaught exception 'OutOfRangeException' with message '$flags = 1 is out of range' in test-case.php:12
Stack trace:
#0 /srv/www/matthiasn/class-constant-debug.php(19): BitSet->__construct('1')
#1 {main}
thrown in /srv/www/matthiasn/class-constant-debug.php on line 12

As you can clearly see from backtrace entry #0 the constant BitSet::FOO is passed as a character not as an integer. Hence, the bit mask operation $flags & self::ALL !== $flags is not performed on integers but on the bitwise ASCII representation and therefore fails.

What the hell?! Is there any better way to get this right than to do an explicit (int)-cast everywhere?

user2690527
  • 1,729
  • 1
  • 22
  • 38

2 Answers2

3

I am not exactly sure what you expect, but note that !== has a higher precedence than & so you are doing a bitwise AND between 1 and true.

Do you perhaps mean:

if( ($flags & self::ALL) !== $flags )
jeroen
  • 91,079
  • 21
  • 114
  • 132
  • 1
    Yes, this it was. See my own solution. I found that out, half a minute after a posted this question. PHP is ... – user2690527 May 16 '15 at 13:55
  • @user2690527 Yes, I figured that that was the problem so I replaced my comment with an answer. – jeroen May 16 '15 at 13:57
  • The intention of the code seems pretty clear to me. I guess if you know the operator precedence by heart, the code seems obviously wrong, but it's an easy mistake to make. – IMSoP May 16 '15 at 14:00
1

Sorry, it was my fault and I went into the wrong direction. The solution is

if( ( $flags & self::ALL ) !== $flags )

to add parentheses. The !== operator seems to have a higher priority than &.

Without the parantheses, first the snippet self::ALL !== $flags is evaluated to FALSE and then $flags & FALSE is evaluated.

PHP is ... :-(

user2690527
  • 1,729
  • 1
  • 22
  • 38
  • 2
    PHP is ... doing exactly the same thing as other languages here. – IMSoP May 16 '15 at 13:56
  • How sweet, it's the fault of the language and not the guy in the chair. Because that's apparently how IT works. – N.B. May 16 '15 at 14:04