Search code examples
phpoopvariablesnullvariable-types

How to distinguish if a PHP property is undefined or set to NULL


So I am facing this problem. I have a class representing a record in my database (User in this example). The class has as many properties as the database table has columns. For simplicity, I have just three in my example:

  1. $id - ID of the user (must be set to a positive integer for registered user, might be set to 0 for user objects that aren't saved in the database yet)
  2. $name - Name of user (must be set for every user, but before loading it from the database might be undefined)
  3. $email - E-mail address of the user (might be NULL in case the user didn't submit an e-mail address)

My (simplified) class looks like this:

<?php
class User
{
  private $id;
  private $name;
  private $email;
  
  public function __construct(int $id = 0)
  {
      if (!empty($id)){ $this->id = $id; }
      //If $id === 0, it means that the record represented by this instance isn't saved in the database yet and the property will be filled after calling the save() method
  }
  
  public function initialize(string $name = '', $email = '')
  {
      //If any of the parameters isn't specified, prevent overwriting curent values
      if ($name === ''){ $name = $this->name; }
      if ($email === ''){ $email = $this->email; }
      
      $this->name = $name;
      $this->email = $email;
  }
  
  public function load()
  {
      if (!empty($this->id))
      {
          //Load name and e-mail from the database and save them into properties
      }
  }

  public function save()
  {
      if (!empty($this->id))
      {
          //Update existing user record in the database 
      }
      else
      {
          //Insert a new record into the table and set $this->id to the ID of the last inserted row
      }
  }
  
  public function isFullyLoaded()
  {
      $properties = get_object_vars($this);
      foreach ($properties as $property)
      {
          if (!isset($property)){ return false; }   //TODO - REPLACE isset() WITH SOMETHING ELSE
      }
      return true;
  }
  
  //Getters like getName() and getId() would come here
}

Now finally to my problem. As you can see, the instance of this class can be created without all properties set. That's a problem in case I want to e. g. call getName() while the name isn't known yet (it wasn't set via the initialize() method and load() wasn't called). For that, I wrote method isFullyLoaded() which checks if all properties are known and if not, load() should be called (from the method calling isFullyLoaded(). And the core of the problem is, that some variables might be empty strings (''), zero values (0) or even null (like the $email property). So I want to distinguish variables that have any value set (including null) and those who have never been assigned any value.

Specific example: I want to achieve this code:

$user1 = new User(1);
$user1->initialize('Vic', '[email protected]');
var_dump($user1->isFullyLoaded());

$user2 = new User(2);
$user2->initialize('Cassidy', null); //No e-mail was specified during the registration
var_dump($user2->isFullyLoaded());

$user3 = new User(3);
$user3->initialize('Myron'); //E-mail isn't known yet, but might be saved in the database
var_dump($user3->isFullyLoaded());

to output this:

bool(true)
bool(true)
bool(false)

TL:DR How do distinguish undefined variable and variable which has been assigned NULL in PHP?


Solution

  • PHP has not the value undefined like javascript. But it is not strict typed so if you do not find a better solution here is one with an custom type UNDEFINED

    <?php
    class UNDEFINED { }
    
    class Test {
    var $a;
    
        function __construct( $a='' ) {
                $this->a = new UNDEFINED();
                if( $a !== '' ) {
                        $this->a = $a;
                }
        }
    
    
        function isDefined() {
                $result =true;
                if(gettype($this->a) === 'object'){
                 if(get_class($this->a) === 'UNDEFINED') {
                   $result=false;
                 }
                }
    
                echo gettype($this->a) . get_class($this->a);
                return $result;
        }
    
    }
    
    $test= new Test();
    
    $test->isDefined();
    

    Here is a may be litte better version which used instanceof instead of get_call and getType

    <?php
    class UNDEFINED { }
    
    class Test {
      var $id;
      var $a;
      var $b;
    
      function __construct( $id) {
        $this->id = $id;
        $this->a = new UNDEFINED();
        $this->b = new UNDEFINED();
      }
    
      function init( $a = '' , $b = '') {
        $this->a = $this->setValue($a,$this->a);
        $this->b = $this->setValue($b,$this->b);
      }
    
      function setValue($a,$default) {
        return $a === '' ? $default : $a;
      }
    
      function isUndefined($a) {
        return $a instanceof UNDEFINED;
      }
     
      public function isFullyLoaded()
      {
        $result = true;
        $properties = get_object_vars($this);
        print_r($properties);
        foreach ($properties as $property){
          $result = $result && !$this->isUndefined($property);
          if ( !$result) break;
        }
        return $result;
      }
    
      function printStatus() {
        if($this->isFullyLoaded() ) {
          echo 'Loaded!';
        } else {
          echo 'Not loaded';
        }
      }
    }
    
    $test= new Test(1); 
    $test->printStatus();
    $test->init('hello');
    $test->printStatus();
    $test->init('', null);
    $test->printStatus();