BlueM / Tree

PHP library for handling tree structures based on parent IDs, e.g. a self-joined database table
237 stars 46 forks source link

"getNodeById" returns non-existent nodes #41

Open sebastianstucke87 opened 2 years ago

sebastianstucke87 commented 2 years ago

\BlueM\Tree::getNodeById() returns non-existent nodes:

$data = [];
$sut = new BlueM\Tree($data);

// #1: returns a "\BlueM\Tree\Node" (incorrect)
var_dump($sut->getNodeById(0));

// #2: throws InvalidArgumentException (correct)
var_dump($sut->getNodeById(1));

Expected behavior: when an id does not exist, an \InvalidArgumentException should be thrown.


Details

There seems to be a problem with 0 (int), '0' (string) and false (bool):

// arrange
$data = [];
$sut = new BlueM\Tree($data);

$cases = [-1, 0, 1, '-1', '0', '1', PHP_INT_MIN, PHP_INT_MAX, true, false, null];
foreach ($cases as $case) {
    try {
        // act
        $result = $sut->getNodeById($case)->toArray();

        // assert
        assert(!is_array($result), 'fail: FIXME');

    } catch (InvalidArgumentException $e) {
        var_dump(['case' => $case, 'result' => 'pass']);

    } catch (Throwable $e) {
        var_dump(['case' => $case, 'result' => $e->getMessage()]);
    }
}

Result:


array (size=2)
  'case' => int -1
  'result' => string 'pass' (length=4)

array (size=2)
  'case' => int 0
  'result' => string 'fail: FIXME' (length=11)

array (size=2)
  'case' => int 1
  'result' => string 'pass' (length=4)

array (size=2)
  'case' => string '-1' (length=2)
  'result' => string 'pass' (length=4)

array (size=2)
  'case' => string '0' (length=1)
  'result' => string 'fail: FIXME' (length=11)

array (size=2)
  'case' => string '1' (length=1)
  'result' => string 'pass' (length=4)

array (size=2)
  'case' => int -9223372036854775808
  'result' => string 'pass' (length=4)

array (size=2)
  'case' => int 9223372036854775807
  'result' => string 'pass' (length=4)

array (size=2)
  'case' => boolean true
  'result' => string 'pass' (length=4)

array (size=2)
  'case' => boolean false
  'result' => string 'fail: FIXME' (length=11)

array (size=2)
  'case' => null
  'result' => string 'pass' (length=4)
BlueM commented 3 months ago

0 is the default root node ID (see https://github.com/BlueM/Tree/blob/master/src/Tree.php#L29). So $sut->getNodeById(0) returns the root node, and this is the expected behaviour. Getting the root node for '0' and false can be explained thru implicit type coercion – which could of course be prevented using strict types, but these are edge cases, and I guess in the vast majority of cases, this would be rather counterproductive.

On the other hand, the library is inconsistent – there is a $rootId, but the node with that ID is none of the nodes returned by getRootNodes(), which is a surprising behavior. Basically, the mere existence of a root ID is an implementation detail, and probably it would be better if there was none, as it doesn’t have any practical use. I’ll look into this.