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 itsunset()
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
unset
ting a reference to a value removes the reference to that value, the value itself and other references pointing to it are untouched.- 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.
- 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
MySQL No Connection Could Be Made Because the Target MAChine Actively Refused It
Hide Shipping Methods for Specific Shipping Class in Woocommerce
Preg_Match(); - Unknown Modifier '+'
Setting Value of a HTML Form Textarea
Read a File Backwards Line by Line Using Fseek
How to Sum N Number of Time (Hh:Mm Format)
What Is Pdo & Why Should I Use It
What Is the Most Efficient Way to Count All the Occurrences of a Specific Character in a PHP String
Parse Xml Namespaces with PHP Simplexml
How to Run Array_Filter Recursively in a PHP Array
Use Strings to Access (Potentially Large) Multidimensional Arrays
Using PHP to Populate a <Select></Select> Dropdown
How to Create Codeigniter Batch Insert Array
Example of How to Use Fastcgi_Finish_Request()
Check If String Contains a Value in Array
Warning: File_Get_Contents(): Https:// Wrapper Is Disabled in the Server Configuration by All