Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

About error "Attempt to modify property of non-object"

Tags:

php

Can anyone tell me why the following code will have different results ?

unset object property

$s = new StdClass;
unset($s->a->b);    //it is working fine
unset($s->x->y->z); //it is got an error: Attempt to modify property of non-object

unset array index

$a = array();
unset($a[1][2]);    //it is working fine
unset($a[3][4][5]); //it is working fine
like image 844
Jasper Avatar asked May 03 '13 17:05

Jasper


1 Answers

StdClass vs Array

Array

Arrays in PHP allow for the implicit creation of multidimensional arrays from the base. ie:

$a[] = "something"; //new array created, indexed at 0
$a[] = "yep"; //adds element to end of array (array_push);

The var_dump of the above gives:

array(2) {
  [0]=>
  string(9) "something"
  [1]=>
  string(3) "yep"
}

In PHP 5.4.x no notices are given either. You could also do:

$a[] = "something";
$a[1][2][3] = "yep";

And all levels of the array are implicitly created giving the var_dump of:

array(2) {
  [0]=>
  string(9) "something"
  [1]=>
  array(1) {
    [2]=>
    array(1) {
      [3]=>
      string(3) "yep"
    }
  }
}

PHP was designed to handle this type of array-creation with ease. For more information on how arrays are handled, please read the Array Type documentation. Because of this, if you access a key within an array for value assignment, if it doesn't already exist, it is implicitly created. When used in conjunction with unset, no errors are thrown because it can check up to the final array and see that it key doesn't exist.

StdClass

Because this is an object, properties must be explicitly created past what was cast in to it. For example:

$s = new StdClass;
$s->a = new stdClass;
$s->a->b = new stdClass;
$s->a->b->c = "test";

Will yield:

object(stdClass)#1 (1) {
  ["a"]=>
  object(stdClass)#2 (1) {
    ["b"]=>
    object(stdClass)#3 (1) {
      ["c"]=>
      string(4) "test"
    }
  }
}

As we create the object's required to reach that point. If, however, you try and use:

$s = new StdClass;
$s->a->b->c = "test";

You get the error:

Warning: Creating default object from empty value in file.php on line x

However, the object is then created giving the var_dump of:

object(stdClass)#1 (1) {
  ["a"]=>
  object(stdClass)#2 (1) {
    ["b"]=>
    object(stdClass)#3 (1) {
      ["c"]=>
      string(4) "test"
    }
  }
}

Which is what you would 'expect' as it goes through the values a and creates a default object, then b and then assigns the object property b->c with the value of "test". When going to unset the values in an object, it won't go through casting all the properties to default objects for you. For example,

$s = new StdClass;
unset($s->a->b);

Won't give you an error because a can be a property of $s, and will do a default type cast to an object and then unset b. However, it won't go any further down the list.

unset($s->a->b->c);

Will go with the assumption that b is a created object within a, but it is not, so you are trying to access a property of a non-object by using b->c. A more explicit example:

$s = new stdclass;
$s->a->b->c = array();
unset($s->a->b->c->d);

the unset here won't throw any errors, because it will type-cast c to to an object and then unset the d. You do get the warning that it casts all the way up to c as stdClass though. What this shows, is that the last element/class will by type-cast to an object to access the property if it is not one already, but it won't type-cast all the elements to access the last property when using unset.

Conclusion

In all, the difference is between how PHP handles arrays and how it handles objects. Objects have a stricter standard and are not implicitly type-cast to multiple levels as they are accessed to be unset, whereas arrays are. With objects, you can implicitly create them to the levels needed (getting a warning as you do), but when being unset, the type-casting doesn't happen, and you are accessing properties that don't exist.

like image 147
Jon Avatar answered Nov 13 '22 00:11

Jon