Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP - is there any way to differentiate between unset and null?

Tags:

php

Consider the following code:

class Test {
    public $definedButNotSet;
}

$Test = new Test();

var_dump($Test->definedButNotSet=== null); // true
var_dump(isset($Test->definedButNotSet)); // false

$Test->definedButNotSet = null;
var_dump(isset($Test->definedButNotSet)); // false

It seems to me, that PHP implicitly sets a defined variable to null. Is there any way to circumvent this, and differentiate between a variable that was explicitly set to null and a variable that was only defined, but not set to any value?

UPDATE:

What I basically want to see if during the runtime the definedButNotSet variable was updated or not. So my expected results for the following code are:

$Test = new Test();
var_dump(isset($Test->definedButNotSet)); // false

$Test->definedButNotSet = null;
var_dump(isset($Test->definedButNotSet)); // true expected here but php returns false

An actual use case where the difference indeed matters, and basically this would be my use case also: when updating rows in a database, I would like to update the rows of a table, that the user changed only when an update method is called. For this, I have to know, if the user implicitly modified any variable in the class representing the row in the table or not.

I am running a custom ORM, which at the moment, fails, if I insert a row in a database with a column which has a default_timestamp method set as the default value, and in the same runtime, I try to update the same row again, as the database set value is not reflected in my class instance, thus at the update PHP sends to him that his value is null, which is not allowed.

like image 462
Adam Baranyai Avatar asked Nov 06 '22 04:11

Adam Baranyai


1 Answers

To challenge the framing of the problem, slightly, what you want to do is distinguish two types of write to the same property: those triggered automatically by your ORM, and those triggered manually by the user. Values which will be provided a default on insert are one subset of that, but values retrieved from the database may also need to be handled differently from those provided by the user (e.g. to track if an update is needed at all).

The way to do that is to make the property private, and provide getters and setters, or simulate property accessors using the magic __get and __set methods (and @property annotations for use by IDEs). Then you can store a map of which properties have been written to outside of your initialisation code.

A simple and probably buggy implementation to show the general idea:

/**
 * This doc-block will be read by IDEs and provide auto-complete
 * @property int|null $id
 * @property string|null $name
 */
class Test {
    private ?int $id = null;
    private ?string $name = null;

    private array $updatedFields = [];

    /**
     * @internal Only to be called by the ORM
     */
    public function construct(?int $id, ?string $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function __get(string $property) {
        if ( property_exists($property, $this) ) {
             return $this->$property;
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }

    public function __set(string $property, $newValue) {
        if ( property_exists($property, $this) ) {
             $this->$property = $newValue;
             $this->updatedFields[ $property ] = true;
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }

    /**
     * Standard meaning of isset() for external code 
     */
    public function __isset(string $property) {
        if ( property_exists($property, $this) ) {
             return isset($this->$property);
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }

    /**
     * Special function for ORM code to determine which fields have changed
     */
    public function hasBeenWritten(string $property): bool {
        if ( property_exists($property, $this) ) {
             return $this->updatedFields[ $property ] ?? false;
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }
}
like image 140
IMSoP Avatar answered Nov 14 '22 22:11

IMSoP