Is there a way to force function parameter to be "unchangeable"?

Viewed 61
<?php
// Enter your code here, enjoy!
class Foo {
    private $a;
    public function getA() {
        return $this->a;
    }
    public function setA($a) {
        $this->a = $a;
    }   
}

class Bar {
    public function test(Foo $f) {
        $f->setA(2);
    }
}

$foo = new Foo();
$foo->setA(1);
var_dump($foo);

$bar = new Bar();
$bar->test($foo);
var_dump($foo);

//output
//object(Foo)#1 (1) {
//  ["a":"Foo":private]=>
//  int(1)
//}
//object(Foo)#1 (1) {
//  ["a":"Foo":private]=>
//  int(2)
//}

The above code shows PHP object parameters are passed by reference. This results in a dilemma that, by looking at a function's signature, one does not know whether the parameters are changed in the function. This means one have to read the whole function just to find out whether the parameter is changed, which makes the whole code less readable and more bug-prone.

Is there a way to "systematically assure the code reader" that an object parameter cannot be changed inside a function? I have though of adding more comments but comments can sometimes differ from what the code actually does and when that happens it becomes even harder to debug.

Workarounds are acceptable, but I would like something that will only affect some specific parameters of some specific functions and not others, in a human-readable fashion.

Essentially the following effect is what I want:

class Foo {
    private $a;
    public function getA() {
        return $this->a;
    }
    public function setA($a) {
        $this->a = $a;
    }   
}

class Bar {
    public function test1(Foo $f1, Foo $f2) {
        $f1->setA(2);
        $f2->setA(2);
    }
    public function test2(Foo $f1, "immutable" Foo $f2) {
        $f1->setA($f2->getA());
    }
    public function test3(Foo $f1, "immutable" Foo $f2) {
        $f1->setA(3);
        $f2->setA(3);
    }
}

$foo1 = new Foo();
$foo2 = new Foo();

$bar = new Bar();
// This will work
$bar->test1($foo1,$foo2);
// This will also work
$bar->test2($foo1,$foo2);
// This will throw Exception / Or the $foo2 is unchanged outside the function without Exception is also fine
$bar->test3($foo1,$foo2);
1 Answers

Although there isn't any builtin way to do this in PHP, you can make use of a workaround.

You can use debug_backtrace to get the call trace when the method setA() is being called. If the result includes any class name being called in other than the host class, you can throw an exception for immutability.

Snippet:

<?php

class Foo {
    private $a;
    public function getA() {
        return $this->a;
    }
    public function setA($a) {
        $current_class = get_class($this);
        foreach(debug_backtrace() as $stack_trace){
            if($stack_trace['class'] !== $current_class){
                throw new Exception("$current_class object is immutable inside another class method!");
            }
        }
        $this->a = $a;
    }   
}

class Bar {
    public $f;
    public function test(Foo $f) {
        $f->setA(100);
    }
}

$foo = new Foo();
$foo->setA(1);

$bar = new Bar();
$bar->test($foo);

Online Demo

Note: This only applies to Foo objects willing to be immutable inside another class methods and not otherwise.

Related