9

I am not sure what the title should be, but the code should explain it better:

class Group {
    private $number = 20;

    public function __toString() {
        return "$this->number";
    }
}

$number = new Group();
echo $number, PHP_EOL;
echo ++ $number, PHP_EOL;

echo PHP_EOL;

$number = "20";
echo $number, PHP_EOL;
echo ++ $number, PHP_EOL;

echo PHP_EOL;

$number = 20;
echo $number, PHP_EOL;
echo ++ $number, PHP_EOL;

Output:

20
20              <--- Expected 21

20
21

20
21

Any idea why I got 20 instead of 21? Even then the code below works:

$i = null ;
echo ++$i ; // output 1

I know Group is an object that implements __toString , i expected ++ to work with the string from __toString or at least throw an error

Baba
  • 89,415
  • 27
  • 158
  • 212
  • Does PHP offer a way to overload the increment operators? Since your class doesn't easily convert to a number type, you probably have to tell PHP what you want ++ to do. – dlp May 04 '13 at 14:34
  • 1
    if you declare $number not private in the Group class and then try echo ++ $number->number, PHP_EOL; works? – LuckyStarr May 04 '13 at 14:37
  • Nope .. but that was increment on string and even if otherwise .. i should get an error – Baba May 04 '13 at 14:37
  • Prefix ops like `++` work on variables. But `$number` is an expression in case 1, not a variable` – mario May 04 '13 at 14:38
  • @mario then it should be treated as a bug ... I should at least get a warning or notice just like when you try to use `int` with `__toString` – Baba May 04 '13 at 14:47

5 Answers5

12

The order in which the operations happen is important:

  1. The variable will be fetched as an object, it won't be casted to an integer (or something else).

  2. This ++ operator increments the lval (the long value) of the zval, but does normally nothing else. The object pointer remains the same. The internal (fast_)increment_function will be called with the zval which has a pointer to the object, which checks for the type first. If it's an object, it does nothing. So when your zval is an object, it is as useful as a no-operation. This won't output any warning.

  3. Only then the echo instruction performs a string cast on his arguments: The __toString method is called and returns 20.

  4. 20 will be output.

bwoebi
  • 22,955
  • 4
  • 54
  • 75
9

To answer you question with a little bit of code.

$number = new Group();
echo gettype($number);

$number = "20";
echo gettype($number);

$number = 20;
echo gettype($number);

Will result in

object
string
integer

The three cases:

  • You can't do any integer operation on a object, that why your code does not do what you expect. The __toString method will called very late, when the acutal output will computed, after you unsuccessfully tried to do an math operation with it.
  • You can to math with strings, because PHP internally converts them back to numbers
  • Obviously you can do math with integer

Bonus:

This will work:

$number = new Group();
echo 1 + "$number"; // 21

It converts you object into a string, which could be converted into a number for a math operation.

TheHippo
  • 54,987
  • 13
  • 72
  • 98
  • Adding explicitly one to the variable expression actually opens the way to changing the amount added very easy. Pre- or post-increment operations should be avoided. You'll never really know what will happen. [Douglas Crockford doesn't like them.](http://stackoverflow.com/questions/971312/why-avoid-increment-and-decrement-operators-in-javascript) - and in PHP it's way easier to make use of the short version `$i+=1^` instead of `$i++` - just one more character. And try to increment by 2... :) – Sven Jun 22 '13 at 20:22
2

I think it might be clearer with just changing the names of the variables like this :

class Group {
    private $number = 20;

    public function __toString() {
        return "$this->number";
    }
}

$group = new Group();
echo $group;//print 20 as per your __toString function

++ $group;

Now it seems obvious : what is supposed to do a '++' operator on a object of type group ??

1

Why don't you just:

class Group {
    private $number = 0;
    public function __construct($number = 0){
        $this->number = intval($number);
    }
    public function __toString() {
        return number_format(++$this->number); // pre-increment
    }
}
$g = new Group();
echo $g; // 1
echo $g; // 2

I use something like this to format offsets in tables.

CodeAngry
  • 11,799
  • 3
  • 46
  • 52
0

This is actually more feasible than you may think -- just need to do a little bit more type casting, as follows:

<?php

class Group {
private $number = 20;

    public function __toString() {
        return (string) $this->number; // replace "" w/string cast
    }
}

$number = (int)(string) new Group();
echo $number, PHP_EOL;
echo ++$number, PHP_EOL;

You don't have to use the string cast of course in the magic __toString() but I personally prefer to read the code that way rather than seeing the quotes -- but I think that's just a stylistic preference.

Casting the newly created object as a string causes the magic __toString method to automatically execute and it returns a numeric string which when cast to an int allows you to display the number, increment it and display it again.

Incidentally, the space between ++ and $number is okay; I closed it up b/c that is what I'm used to in other languages like C.

slevy1
  • 3,623
  • 2
  • 23
  • 30