The Difference Between Unset and = Null

the difference between unset and = null

An important difference between both methods is that unset($a) also removes $a from the symbol table; for example:

$a = str_repeat('hello world ', 100);
unset($a);
var_dump($a);

Outputs:

Notice: Undefined variable: a in xxx
NULL

But when $a = null is used:

$a = str_repeat('hello world ', 100);
$a = null;
var_dump($a);

Outputs:

NULL

I ran the code through a benchmark as well and found that $a = null is roughly 6% faster than its unset() counterpart. It seems that updating a symbol table entry is faster than removing it.

Addendum

The other difference (as seen in this small script) seems to be how much memory is restored after each call:

echo memory_get_usage(), PHP_EOL;
$a = str_repeat('hello world ', 100);
echo memory_get_usage(), PHP_EOL;
// EITHER unset($a); OR $a = null;
echo memory_get_usage(), PHP_EOL;

When using unset() all but 64 bytes of memory are given back, whereas $a = null; frees all but 272 bytes of memory. I don't have enough knowledge to know why there's a 208 bytes difference between both methods, but it's a difference nonetheless.

What is difference between assigning NULL and unset?

As you can see below, both will behave pretty much the same for standard comparison operations.

Use unset() to free large objects / arrays that aren't used anymore but cannot be freed by the GC because of references still being held elsewhere.

-------------------------------------------------------------------------------
| Expression | gettype() | empty() | is_null() | isset() | boolean |
-------------------------------------------------------------------------------
| $x = ""; | string | TRUE | FALSE | TRUE | FALSE |
| $x = null | NULL | TRUE | TRUE | FALSE | FALSE |
| var $x; | NULL | TRUE | TRUE | FALSE | FALSE |
| $x is undefined | NULL | TRUE | TRUE | FALSE | FALSE |
-------------------------------------------------------------------------------

How to distinguish between unset and no-assignment for NULL values?

PHP has no way to differentiate between unassigned and null variables.
This makes it pretty unavoidable to keep track of which properties should be overwritten.

I see that you have two concerns:

  • Keeping Data immutable
  • Keeping the interface of Data clean (e.g. enforcing strict types)

One of the simplest datastructures that is able to track "defined" and "undefined" properties is an \stdClass object (but an array is perfectly fine too).
By moving the merge() method into the Data class you will be able to hide any implementation details - keeping the interface clean.

An implementation might look something like this:

final class Data {

/** @var \stdClass */
protected $props;

// Avoid direct instantiation, use ::create() instead
private function __construct()
{
$this->props = new \stdClass();
}

// Fluent interface
public static function create(): Data
{
return new self();
}

// Enforce immutability
public function __clone()
{
$this->props = clone $this->props;
}

public function withSomeNumber(?int $someNumber): Data
{
$d = clone $this;
$d->props->someNumber = $someNumber;
return $d;
}

public function withSomeString(?string $someString): Data
{
$d = clone $this;
$d->props->someString = $someString;
return $d;
}

public function getSomeNumber(): ?int
{
return $this->props->someNumber ?? null;
}

public function getSomeString(): ?string
{
return $this->props->someString ?? null;
}

public static function merge(...$dataObjects): Data
{
$final = new self();

foreach ($dataObjects as $data) {
$final->props = (object) array_merge((array) $final->props, (array) $data->props);
}

return $final;
}
}

$first = Data::create()
->withSomeNumber(42)
->withSomeString('foo');

// Overwrite both someNumber and someString by assigning null
$second = Data::create()
->withSomeNumber(null)
->withSomeString(null);

// Overwrite "someString" only
$third = Data::create()
->withSomeString('bar');

$merged = Data::merge($first, $second, $third); // Only "someString" property is set to "bar"
var_dump($merged->getSomeString()); // "bar"

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

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'");
}
}
}

difference between unset and empty variables in bash

if [ `set | grep '^VAR=$'` ]

This searches for the string "VAR=" in the list of variables set.

What's better at freeing memory with PHP: unset() or $var = null

It was mentioned in the unset manual's page in 2009:

unset() does just what its name says - unset a variable. It does not force immediate memory freeing. PHP's garbage collector will do it when it see fits - by intention as soon, as those CPU cycles aren't needed anyway, or as late as before the script would run out of memory, whatever occurs first.

If you are doing $whatever = null; then you are rewriting variable's data. You might get memory freed / shrunk faster, but it may steal CPU cycles from the code that truly needs them sooner, resulting in a longer overall execution time.

(Since 2013, that unset man page don't include that section anymore)

Note that until php5.3, if you have two objects in circular reference, such as in a parent-child relationship, calling unset() on the parent object will not free the memory used for the parent reference in the child object. (Nor will the memory be freed when the parent object is garbage-collected.) (bug 33595)


The question "difference between unset and = null" details some differences:


unset($a) also removes $a from the symbol table; for example:

$a = str_repeat('hello world ', 100);
unset($a);
var_dump($a);

Outputs:

Notice: Undefined variable: a in xxx
NULL

But when $a = null is used:

$a = str_repeat('hello world ', 100);
$a = null;
var_dump($a);

Outputs:

NULL

It seems that $a = null is a bit faster than its unset() counterpart: updating a symbol table entry appears to be faster than removing it.



  • when you try to use a non-existent (unset) variable, an error will be triggered and the value for the variable expression will be null. (Because, what else should PHP do? Every expression needs to result in some value.)
  • A variable with null assigned to it is still a perfectly normal variable though.

why UNSET and NULL are working in different ways

  1. unsetting a reference to a value removes the reference to that value, the value itself and other references pointing to it are untouched.
  2. Assigning something to a reference overwrites the value that reference is pointing to, and thereby the value of all other references that point to it.
  3. You do not need to micromanage memory in PHP. PHP will not copy the value in memory when passing values around. It uses a copy-on-write strategy which only allocates new memory when absolutely necessary. References in PHP are a logical operation to produce certain behavior, they are not memory management tools or C pointers.


Related Topics



Leave a reply



Submit