2
<?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);
cr001
  • 655
  • 4
  • 16
  • An alternative is to use immutable objects (sort of https://stackoverflow.com/questions/14427050/immutable-objects-in-php) which means that you cannot change the value of the object rather than can you change the value of this parameter. – Nigel Ren Jul 05 '22 at 06:38

1 Answers1

0

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.

nice_dev
  • 17,053
  • 2
  • 21
  • 35
  • I would like to do this at a function level, not at class level. i.e. certain functions in Bar might be able to change $foo->a but other functions cannot (and the function signature would indicate the difference). Is this also achieveable via some workaround? – cr001 Jul 05 '22 at 07:08
  • @cr001 Yes, you can. Can you let me know the function names or should I just add it for `test` in my example? Add some examples in your post. – nice_dev Jul 05 '22 at 07:10
  • let's just call them test and test2, where I want test parameters to be immutable and test2 parameters to be mutable. I don't want it to be dependent on function names though, but would rather want a "keyword-like" system, or a workaround that makes use of some keyword-level features. – cr001 Jul 05 '22 at 07:12
  • It would be ideal if the workaround somehow is bound to the parameter and only the parameter (not affecting other parameters and the function itself). (Like the "&" specifier but works the other way) – cr001 Jul 05 '22 at 07:14
  • 1
    Sure. I will add some more information of what I want. – cr001 Jul 05 '22 at 07:15
  • @cr001 Actually you want this on a parameter level and don't mind me, but your design seems to be broken. – nice_dev Jul 05 '22 at 07:23
  • Yes I want this on a parameter level. What do you mean by "design is broken"? – cr001 Jul 05 '22 at 07:25
  • @cr001 If you want one object of the same class to be immutable and the other one to be mutuable, you have inconsistency in your design. – nice_dev Jul 05 '22 at 07:27
  • Yeah if we talk on object level that's the case. But I have been talking about functions as the main point. In pass-by-value languages this is actually the default behavior (references would be passed as pointers and/or have specific keywords) but basically I wouldn't mind make a deep copy and achieving this as long as this is done automatically (possibly via some workarounds). – cr001 Jul 05 '22 at 07:30
  • @cr001 Although I wouldn't encourage this, but as a last workaround, you can set any property on the object(like a flag) indicating whether they are mutable or not. – nice_dev Jul 05 '22 at 07:39
  • If I can do that somehow so that I only need additional syntax on the function signature (specifically the parameter part), that would be fine too. What I don't want is one need to declare/set the flags manually, which is easy to forget and hard to check. (I want something that is very easily readable and can give guarantee to the reader that the parameter won't be changed outside the function) – cr001 Jul 05 '22 at 07:44
  • @cr001 You are anyways mentioning "immutable" when passing the parameter. You can instead set the flag for this during object instantiation itself. Something has to be remembered for something to be done. If you still don't wish to do it this way, I am out of clue for any more workarounds. – nice_dev Jul 05 '22 at 07:51