⌂ Home ▲ Previous: Constructors and destructors ▼ Next: Inheritance
[There is discussed the correspondence between object and class in this document. The objects in general are described here – KK]
In software development, an object is an entity semantic that has state, behavior, and identity. An object can model some part of reality or can be an invention of the design process whose collaborations with other such objects serve as the mechanisms that provide some higher-level behavior. Put another way, an object represents an individual, identifiable item, unit, or entity, either real or abstract, with a well-defined role in the problem domain.
Example: Class object
<?php
class SomeClass
{
public function __construct(
public $publicProperty,
protected $protectedProperty = 15.5,
private $privateProperty = 'hello',
) {
}
}
$someObject = new SomeClass(1, 2.5);
print("Information:\n");
var_dump($someObject);
print('Type: ' . gettype($someObject) . PHP_EOL . PHP_EOL);
print("Public property: {$someObject->publicProperty}\n");
$someObject->publicProperty = 3;
print("Public property: {$someObject->publicProperty}\n");
var_dump($someObject);
print(PHP_EOL);
Result (PHP 8.4):
Information:
object(SomeClass)#1 (3) {
["publicProperty"]=>
int(1)
["protectedProperty":protected]=>
float(2.5)
["privateProperty":"SomeClass":private]=>
string(5) "hello"
}
Type: object
Public property: 1
Public property: 3
object(SomeClass)#1 (3) {
["publicProperty"]=>
int(3)
["protectedProperty":protected]=>
float(2.5)
["privateProperty":"SomeClass":private]=>
string(5) "hello"
}
Source code: Example
Example: Class object definition and initialisation
<?php
$objectFromStdClass = new stdClass();
print("Defined from stdClass class:\n\n");
print_r($objectFromStdClass);
print(PHP_EOL);
class SomeClass
{
public $publicProperty;
protected $protectedProperty;
private $privateProperty;
}
$uninitialisedObjectFromClass = new SomeClass();
print("Not initialised, defined from class:\n\n");
print_r($uninitialisedObjectFromClass);
print(PHP_EOL);
class OtherClass
{
function __construct(
public $publicProperty,
protected $protectedProperty,
private $privateProperty
) {
}
}
$initialisedObjectFromClass = new OtherClass(16, 14.2, 'welcome');
print("Initialised, defined from class:\n\n");
print_r($uninitialisedObjectFromClass);
print(PHP_EOL);
Result (PHP 8.4):
Defined from stdClass class:
stdClass Object
(
)
Not initialised, defined from class:
SomeClass Object
(
[publicProperty] =>
[protectedProperty:protected] =>
[privateProperty:SomeClass:private] =>
)
Initialised, defined from class:
SomeClass Object
(
[publicProperty] =>
[protectedProperty:protected] =>
[privateProperty:SomeClass:private] =>
)
Source code: Example
Example: Class object modifying
<?php
class SomeClass
{
function __construct(
public mixed $publicProperty,
protected string $protectedProperty,
private string $privateProperty = 'nothing',
) {
}
public function setProtectedProperty(string $protectedProperty)
{
$this->protectedProperty = 'base ' . $protectedProperty;
}
public function setPrivateProperty(string $privateProperty)
{
$this->privateProperty = 'base ' . $privateProperty;
}
}
class OtherClass extends SomeClass
{
public function setProtectedProperty(int|string $protectedProperty)
{
$this->protectedProperty = 'derived ' . $protectedProperty;
}
}
$someObject = new SomeClass('some value', 15.5);
print("Some object:\n\n");
print_r($someObject);
print(PHP_EOL);
$someObject->publicProperty = 'orange';
$someObject->setProtectedProperty('tangerine');
$someObject->setPrivateProperty(1024);
print_r($someObject);
print(PHP_EOL);
$someObject->someDynamicProperty = '16';
$someObject->otherDynamicProperty = 'coffee';
print_r($someObject);
print(PHP_EOL);
$otherObject = new OtherClass('other value', 10);
print("Other object:\n\n");
print_r($otherObject);
print(PHP_EOL);
$otherObject->publicProperty = 100;
$otherObject->setProtectedProperty(200);
print_r($otherObject);
print(PHP_EOL);
Result (PHP 8.4):
Some object:
SomeClass Object
(
[publicProperty] => some value
[protectedProperty:protected] => 15.5
[privateProperty:SomeClass:private] => nothing
)
SomeClass Object
(
[publicProperty] => orange
[protectedProperty:protected] => base tangerine
[privateProperty:SomeClass:private] => base 1024
)
SomeClass Object
(
[publicProperty] => orange
[protectedProperty:protected] => base tangerine
[privateProperty:SomeClass:private] => base 1024
[someDynamicProperty] => 16
[otherDynamicProperty] => coffee
)
Other object:
OtherClass Object
(
[publicProperty] => other value
[protectedProperty:protected] => 10
[privateProperty:SomeClass:private] => nothing
)
OtherClass Object
(
[publicProperty] => 100
[protectedProperty:protected] => derived 200
[privateProperty:SomeClass:private] => nothing
)
Source code: Example
PHP provides a way for objects to be defined so it is possible to iterate through a list of items, with, for example a foreach statement. By default, all visible properties will be used for the iteration.
Example: Simple object iteration
<?php
class MyClass
{
public $var1 = 'value 1';
public $var2 = 'value 2';
public $var3 = 'value 3';
protected $protected = 'protected var';
private $private = 'private var';
function iterateVisible() {
echo "MyClass::iterateVisible:\n";
foreach ($this as $key => $value) {
print "$key => $value\n";
}
}
}
$class = new MyClass();
foreach($class as $key => $value) {
print "$key => $value\n";
}
echo "\n";
$class->iterateVisible();
?>
The above example will output:
var1 => value 1
var2 => value 2
var3 => value 3
MyClass::iterateVisible:
var1 => value 1
var2 => value 2
var3 => value 3
protected => protected var
private => private var
As the output shows, the foreach iterated through all of the visible properties that could be accessed.
Example: Iterating over object and reading properties
<?php
class SomeClass
{
public string $somePublicProperty = 'apple';
public string $otherPublicProperty = 'orange';
public string $anotherPublicProperty = 'banana';
protected string $someProtectedProperty = 'pear';
private string $somePrivateProperty = 'peach';
public function iterateWithValues()
{
foreach ($this as $value) {
print("{$value}\n");
}
}
public function iterateWithKeysAndValues()
{
foreach ($this as $key => $value) {
print("{$key}: {$value}\n");
}
}
}
class OtherClass extends SomeClass
{
public function iterateWithValues()
{
foreach ($this as $value) {
print("{$value}\n");
}
}
public function iterateWithKeysAndValues()
{
foreach ($this as $key => $value) {
print("{$key}: {$value}\n");
}
}
}
$someObject = new SomeClass();
print(
"# SomeClass:\n\n"
. "from outside:\n\n"
);
foreach ($someObject as $value) {
print("{$value}\n");
}
print(PHP_EOL);
foreach ($someObject as $key => $value) {
print("{$key}: {$value}\n");
}
print(PHP_EOL);
print("from inside:\n\n");
$someObject->iterateWithValues();
print(PHP_EOL);
$someObject->iterateWithKeysAndValues();
print(PHP_EOL);
$otherObject = new OtherClass();
print(
"# OtherClass:\n\n"
. "from outside:\n\n"
);
foreach ($otherObject as $value) {
print("{$value}\n");
}
print(PHP_EOL);
foreach ($otherObject as $key => $value) {
print("{$key}: {$value}\n");
}
print(PHP_EOL);
print("from inside:\n\n");
$otherObject->iterateWithValues();
print(PHP_EOL);
$otherObject->iterateWithKeysAndValues();
print(PHP_EOL);
Result (PHP 8.4):
# SomeClass:
from outside:
apple
orange
banana
somePublicProperty: apple
otherPublicProperty: orange
anotherPublicProperty: banana
from inside:
apple
orange
banana
pear
peach
somePublicProperty: apple
otherPublicProperty: orange
anotherPublicProperty: banana
someProtectedProperty: pear
somePrivateProperty: peach
# OtherClass:
from outside:
apple
orange
banana
somePublicProperty: apple
otherPublicProperty: orange
anotherPublicProperty: banana
from inside:
apple
orange
banana
pear
somePublicProperty: apple
otherPublicProperty: orange
anotherPublicProperty: banana
someProtectedProperty: pear
Source code: Example
Example: Iterating over object and updating properties
<?php
class SomeClass
{
public string $somePublicProperty = 'apple';
public string $otherPublicProperty = 'orange';
public string $anotherPublicProperty = 'banana';
protected string $someProtectedProperty = 'pear';
private string $somePrivateProperty = 'peach';
public function iterateWithValues(string $postfix)
{
foreach ($this as &$value) {
$value .= $postfix;
}
}
public function iterateWithKeysAndValues(string $postfix)
{
foreach ($this as $key => &$value) {
$value .= $postfix;
}
}
}
class OtherClass extends SomeClass
{
public function iterateWithValues(string $postfix)
{
foreach ($this as &$value) {
$value .= $postfix;
}
}
public function iterateWithKeysAndValues(string $postfix)
{
foreach ($this as $key => &$value) {
$value .= $postfix;
}
}
}
$someObject = new SomeClass();
print(
"# SomeClass:\n\n"
. "from outside:\n\n"
);
foreach ($someObject as &$value) {
$value .= ' 1...';
}
print_r($someObject);
print(PHP_EOL);
foreach ($someObject as $key => &$value) {
$value .= ' 2...';
}
print_r($someObject);
print(PHP_EOL);
print("from inside:\n\n");
$someObject->iterateWithValues(' 3...');
print_r($someObject);
print(PHP_EOL);
$someObject->iterateWithKeysAndValues(' 4...');
print_r($someObject);
print(PHP_EOL);
$otherObject = new OtherClass();
print(
"# OtherClass:\n\n"
. "from outside:\n\n"
);
foreach ($otherObject as &$value) {
$value .= ' 5...';
}
print_r($otherObject);
print(PHP_EOL);
foreach ($otherObject as $key => &$value) {
$value .= ' 6...';
}
print_r($otherObject);
print(PHP_EOL);
print("from inside:\n\n");
$otherObject->iterateWithValues(' 7...');
print_r($otherObject);
print(PHP_EOL);
$otherObject->iterateWithKeysAndValues(' 8...');
print_r($otherObject);
print(PHP_EOL);
Result (PHP 8.4):
# SomeClass:
from outside:
SomeClass Object
(
[somePublicProperty] => apple 1...
[otherPublicProperty] => orange 1...
[anotherPublicProperty] => banana 1...
[someProtectedProperty:protected] => pear
[somePrivateProperty:SomeClass:private] => peach
)
SomeClass Object
(
[somePublicProperty] => apple 1... 2...
[otherPublicProperty] => orange 1... 2...
[anotherPublicProperty] => banana 1... 2...
[someProtectedProperty:protected] => pear
[somePrivateProperty:SomeClass:private] => peach
)
from inside:
SomeClass Object
(
[somePublicProperty] => apple 1... 2... 3...
[otherPublicProperty] => orange 1... 2... 3...
[anotherPublicProperty] => banana 1... 2... 3...
[someProtectedProperty:protected] => pear 3...
[somePrivateProperty:SomeClass:private] => peach 3...
)
SomeClass Object
(
[somePublicProperty] => apple 1... 2... 3... 4...
[otherPublicProperty] => orange 1... 2... 3... 4...
[anotherPublicProperty] => banana 1... 2... 3... 4...
[someProtectedProperty:protected] => pear 3... 4...
[somePrivateProperty:SomeClass:private] => peach 3... 4...
)
# OtherClass:
from outside:
OtherClass Object
(
[somePublicProperty] => apple 5...
[otherPublicProperty] => orange 5...
[anotherPublicProperty] => banana 5...
[someProtectedProperty:protected] => pear
[somePrivateProperty:SomeClass:private] => peach
)
OtherClass Object
(
[somePublicProperty] => apple 5... 6...
[otherPublicProperty] => orange 5... 6...
[anotherPublicProperty] => banana 5... 6...
[someProtectedProperty:protected] => pear
[somePrivateProperty:SomeClass:private] => peach
)
from inside:
OtherClass Object
(
[somePublicProperty] => apple 5... 6... 7...
[otherPublicProperty] => orange 5... 6... 7...
[anotherPublicProperty] => banana 5... 6... 7...
[someProtectedProperty:protected] => pear 7...
[somePrivateProperty:SomeClass:private] => peach
)
OtherClass Object
(
[somePublicProperty] => apple 5... 6... 7... 8...
[otherPublicProperty] => orange 5... 6... 7... 8...
[anotherPublicProperty] => banana 5... 6... 7... 8...
[someProtectedProperty:protected] => pear 7... 8...
[somePrivateProperty:SomeClass:private] => peach
)
Source code: Example
When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values (values are compared with ==), and are instances of the same class.
When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.
An example will clarify these rules.
Example: Example of object comparison
<?php
function bool2str($bool)
{
if ($bool === false) {
return 'FALSE';
} else {
return 'TRUE';
}
}
function compareObjects(&$o1, &$o2)
{
echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
}
class Flag
{
public $flag;
function __construct($flag = true) {
$this->flag = $flag;
}
}
class OtherFlag
{
public $flag;
function __construct($flag = true) {
$this->flag = $flag;
}
}
$o = new Flag();
$p = new Flag();
$q = $o;
$r = new OtherFlag();
echo "Two instances of the same class\n";
compareObjects($o, $p);
echo "\nTwo references to the same instance\n";
compareObjects($o, $q);
echo "\nInstances of two different classes\n";
compareObjects($o, $r);
?>
The above example will output:
Two instances of the same class
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : FALSE
o1 !== o2 : TRUE
Two references to the same instance
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE
Instances of two different classes
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
Note:
Extensions can define own rules for their objects comparison (==).
Example: Class object comparing
<?php
class SomeClass
{
public $someProperty = 1024;
}
class OtherClass
{
public $someProperty = 1024;
}
$someObject = new SomeClass();
$otherObject = new SomeClass();
$anotherObject = new OtherClass();
print("Objects of different classes:\n\n");
if ($someObject == $anotherObject) {
print("Are equal\n");
} else {
print("Are not equal\n");
}
if ($someObject === $anotherObject) {
print("Are identical\n");
} else {
print("Are not identical\n");
}
print(PHP_EOL);
print("Different objects of the same class:\n\n");
if ($someObject == $otherObject) {
print("Are equal\n");
} else {
print("Are not equal\n");
}
if ($someObject === $otherObject) {
print("Are identical\n");
} else {
print("Are not identical\n");
}
print(PHP_EOL);
print("The same object:\n\n");
if ($someObject == $someObject) {
print("Are equal\n");
} else {
print("Are not equal\n");
}
if ($someObject === $someObject) {
print("Are identical\n");
} else {
print("Are not identical\n");
}
print(PHP_EOL);
Result (PHP 8.4):
Objects of different classes:
Are not equal
Are not identical
Different objects of the same class:
Are equal
Are not identical
The same object:
Are equal
Are identical
Source code: Example
serialize() returns a string containing a byte-stream representation of any value that can be stored in PHP. unserialize() can use this string to recreate the original variable values. Using serialize to save an object will save all variables in an object. The methods in an object will not be saved, only the name of the class.
In order to be able to unserialize() an object, the class of that object needs to be defined. That is, if you have an object of class A and serialize this, you’ll get a string that refers to class A and contains all values of variables contained in it. If you want to be able to unserialize this in another file, an object of class A, the definition of class A must be present in that file first. This can be done for example by storing the class definition of class A in an include file and including this file or making use of the spl_autoload_register() function.
<?php
// A.php:
class A {
public $one = 1;
public function show_one() {
echo $this->one;
}
}
// page1.php:
include "A.php";
$a = new A;
$s = serialize($a);
// store $s somewhere where page2.php can find it.
file_put_contents('store', $s);
// page2.php:
// this is needed for the unserialize to work properly.
include "A.php";
$s = file_get_contents('store');
$a = unserialize($s);
// now use the function show_one() of the $a object.
$a->show_one();
?>
It is strongly recommended that if an application serializes objects, for use later in the application, that the application includes the class definition for that object throughout the application. Not doing so might result in an object being unserialized without a class definition, which will result in PHP giving the object a class of __PHP_Incomplete_Class_Name, which has no methods and would render the object useless.
So if in the example above $a became part of a session by adding a new key to the $_SESSION superglobal array, you should include the file A.php on all of your pages, not only page1.php and page2.php.
Beyond the above advice, note that you can also hook into the serialization and unserialization events on an object using the __sleep() and __wakeup() methods. Using __sleep() also allows you to only serialize a subset of the object’s properties.
Example: Class object serializing
<?php
class SomeClass
{
public function __construct(
public string $somePublicProperty = 'some public',
protected string $someProtectedProperty = 'some protected',
private string $somePrivateProperty = 'some private',
) {
}
}
$someObject = new SomeClass();
var_dump($someObject);
print(PHP_EOL);
$result = serialize($someObject);
print($result . PHP_EOL . PHP_EOL);
$coresult = unserialize($result);
var_dump($coresult);
print(PHP_EOL);
Result (PHP 8.4):
object(SomeClass)#1 (3) {
["somePublicProperty"]=>
string(11) "some public"
["someProtectedProperty":protected]=>
string(14) "some protected"
["somePrivateProperty":"SomeClass":private]=>
string(12) "some private"
}
O:9:"SomeClass":3:{s:18:"somePublicProperty";s:11:"some public";s:24:"*someProtectedProperty";s:14:"some protected";s:30:"SomeClasssomePrivateProperty";s:12:"some private";}
object(SomeClass)#2 (3) {
["somePublicProperty"]=>
string(11) "some public"
["someProtectedProperty":protected]=>
string(14) "some protected"
["somePrivateProperty":"SomeClass":private]=>
string(12) "some private"
}
Source code: Example
Creating a copy of an object with fully replicated properties is not always the wanted behavior. A good example of the need for copy constructors, is if you have an object which represents a GTK window and the object holds the resource of this GTK window, when you create a duplicate you might want to create a new window with the same properties and have the new object hold the resource of the new window. Another example is if your object holds a reference to another object which it uses and when you replicate the parent object you want to create a new instance of this other object so that the replica has its own separate copy.
An object copy is created by using the clone keyword (which calls the object’s __clone() method if possible).
$copy_of_object = clone $object;
When an object is cloned, PHP will perform a shallow copy of all of the object’s properties. Any properties that are references to other variables will remain references.
__clone(): void
Once the cloning is complete, if a __clone() method is defined, then the newly created object’s __clone() method will be called, to allow any necessary properties that need to be changed.
Example: Class object cloning
<?php
class SubObject
{
static $instances = 0;
public $instance;
public function __construct() {
$this->instance = ++self::$instances;
}
public function __clone() {
$this->instance = ++self::$instances;
}
}
class MyCloneable
{
public $object1;
public $object2;
function __clone()
{
// Force a copy of this->object, otherwise
// it will point to same object.
$this->object1 = clone $this->object1;
}
}
$obj = new MyCloneable();
$obj->object1 = new SubObject();
$obj->object2 = new SubObject();
$obj2 = clone $obj;
print "Original Object:\n";
print_r($obj);
print "Cloned Object:\n";
print_r($obj2);
?>
The above example will output:
Original Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)
[object2] => SubObject Object
(
[instance] => 2
)
)
Cloned Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)
[object2] => SubObject Object
(
[instance] => 2
)
)
It is possible to access a member of a freshly cloned object in a single expression:
Example: Access member of freshly cloned object
<?php
$dateTime = new DateTime();
echo (clone $dateTime)->format('Y');
?>
The above example will output something similar to:
2016
Example: Object cloning
<?php
class SomeClass
{
public string $someRegularProperty;
public OtherClass $someObjectProperty;
public function __construct()
{
$this->someRegularProperty = 'original';
$this->someObjectProperty = new OtherClass();
}
}
class OtherClass
{
public string $someProperty = 'original';
}
$someObject = new SomeClass();
var_dump($someObject);
print(PHP_EOL);
$otherObject = clone $someObject;
var_dump($otherObject);
print(PHP_EOL);
print(
'Equal: ' . ($someObject == $otherObject ? 'yes' : 'no') . PHP_EOL
. 'Identical: ' . ($someObject === $otherObject ? 'yes' : 'no') . PHP_EOL
. PHP_EOL
);
$someObject->someRegularProperty = 'modified';
$someObject->someObjectProperty->someProperty = 'modified';
var_dump($someObject);
print(PHP_EOL);
var_dump($otherObject);
print(PHP_EOL);
Result (PHP 8.4):
object(SomeClass)#1 (2) {
["someRegularProperty"]=>
string(8) "original"
["someObjectProperty"]=>
object(OtherClass)#2 (1) {
["someProperty"]=>
string(8) "original"
}
}
object(SomeClass)#3 (2) {
["someRegularProperty"]=>
string(8) "original"
["someObjectProperty"]=>
object(OtherClass)#2 (1) {
["someProperty"]=>
string(8) "original"
}
}
Equal: yes
Identical: no
object(SomeClass)#1 (2) {
["someRegularProperty"]=>
string(8) "modified"
["someObjectProperty"]=>
object(OtherClass)#2 (1) {
["someProperty"]=>
string(8) "modified"
}
}
object(SomeClass)#3 (2) {
["someRegularProperty"]=>
string(8) "original"
["someObjectProperty"]=>
object(OtherClass)#2 (1) {
["someProperty"]=>
string(8) "modified"
}
}
Source code: Example
One of the key-points of PHP OOP that is often mentioned is that “objects are passed by references by default”. This is not completely true. This section rectifies that general thought using some examples.
A PHP reference is an alias, which allows two different variables to write to the same value. In PHP, an object variable doesn’t contain the object itself as value. It only contains an object identifier which allows object accessors to find the actual object. When an object is sent by argument, returned or assigned to another variable, the different variables are not aliases: they hold a copy of the identifier, which points to the same object.
Example: References and objects
<?php
class A {
public $foo = 1;
}
$a = new A;
$b = $a; // $a and $b are copies of the same identifier
// ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n";
$c = new A;
$d = &$c; // $c and $d are references
// ($c,$d) = <id>
$d->foo = 2;
echo $c->foo."\n";
$e = new A;
function foo($obj) {
// ($obj) = ($e) = <id>
$obj->foo = 2;
}
foo($e);
echo $e->foo."\n";
?>
The above example will output:
2
2
2
Example: Class object and reference
<?php
class SomeClass
{
public string $someProperty = 'original';
}
$someObject = new SomeClass();
print_r($someObject);
print(PHP_EOL);
$otherObject = $someObject;
var_dump($otherObject);
print(PHP_EOL);
print(
'Equal: ' . ($someObject == $otherObject ? 'yes' : 'no') . PHP_EOL
. 'Identical: ' . ($someObject === $otherObject ? 'yes' : 'no') . PHP_EOL
. PHP_EOL
);
$anotherObject = &$someObject;
var_dump($anotherObject);
print(PHP_EOL);
print(
'Equal: ' . ($someObject == $anotherObject ? 'yes' : 'no') . PHP_EOL
. 'Identical: ' . ($someObject === $anotherObject ? 'yes' : 'no') . PHP_EOL
. PHP_EOL
);
$someObject->someProperty = 'modified';
var_dump($someObject);
print(PHP_EOL);
var_dump($otherObject);
print(PHP_EOL);
Result (PHP 8.4):
SomeClass Object
(
[someProperty] => original
)
object(SomeClass)#1 (1) {
["someProperty"]=>
string(8) "original"
}
Equal: yes
Identical: yes
object(SomeClass)#1 (1) {
["someProperty"]=>
string(8) "original"
}
Equal: yes
Identical: yes
object(SomeClass)#1 (1) {
["someProperty"]=>
string(8) "modified"
}
object(SomeClass)#1 (1) {
["someProperty"]=>
string(8) "modified"
}
Source code: Example
A lazy object is an object whose initialization is deferred until its state is observed or modified. Some use-case examples include dependency injection components that provide lazy services fully initialized only if needed, ORMs providing lazy entities that hydrate themselves from the database only when accessed, or a JSON parser that delays parsing until elements are accessed.
Two lazy object strategies are supported: ghost objects and virtual proxies, hereafter referred to as lazy ghosts and lazy proxies. In both strategies, the lazy object is attached to an initializer or factory that is called automatically when its state is observed or modified for the first time. From an abstraction point of view, lazy ghost objects are indistinguishable from non-lazy ones: they can be used without knowing they are lazy, allowing them to be passed to and used by code that is unaware of laziness. Lazy proxies are similarly transparent, but care must be taken when their identity is used, as the proxy and its real instance have different identities.
Note: Version Information
Lazy objects were introduced in PHP 8.4.
Example: Lazy ghost
<?php
class SomeClass
{
public string $someProperty;
public function __construct(string $someProperty)
{
print("Construction...\n");
$this->someProperty = $someProperty;
}
}
$someReflection = new ReflectionClass(SomeClass::class);
$someGhost = $someReflection->newLazyGhost(function (SomeClass $object) {
$object->__construct('initialised');
});
print(
'Ghost type: ' . gettype($someGhost) . PHP_EOL
. 'Ghost class: ' . get_class($someGhost) . PHP_EOL
. PHP_EOL
);
var_dump($someGhost);
print(PHP_EOL);
print("Property reading: {$someGhost->someProperty}\n\n");
var_dump($someGhost);
print(PHP_EOL);
Result (PHP 8.4):
Ghost type: object
Ghost class: SomeClass
lazy ghost object(SomeClass)#3 (0) {
["someProperty"]=>
uninitialized(string)
}
Construction...
Property reading: initialised
object(SomeClass)#3 (1) {
["someProperty"]=>
string(11) "initialised"
}
Source code: Example
Example: Lazy proxy
<?php
class SomeClass
{
public string $someProperty;
public function __construct(string $someProperty)
{
print("Construction...\n");
$this->someProperty = $someProperty;
}
}
$someReflection = new ReflectionClass(SomeClass::class);
$someProxy = $someReflection->newLazyProxy(function (SomeClass $object) {
return new SomeClass('initialised');
});
print(
'Proxy type: ' . gettype($someProxy) . PHP_EOL
. 'Proxy class: ' . get_class($someProxy) . PHP_EOL
. PHP_EOL
);
var_dump($someProxy);
print(PHP_EOL);
print("Property reading: {$someProxy->someProperty}\n\n");
var_dump($someProxy);
print(PHP_EOL);
Result (PHP 8.4):
Proxy type: object
Proxy class: SomeClass
lazy proxy object(SomeClass)#3 (0) {
["someProperty"]=>
uninitialized(string)
}
Construction...
Property reading: initialised
lazy proxy object(SomeClass)#3 (1) {
["instance"]=>
object(SomeClass)#4 (1) {
["someProperty"]=>
string(11) "initialised"
}
}
Source code: Example
It is possible to create a lazy instance of any user defined class or the stdClass class (other internal classes are not supported), or to reset an instance of these classes to make it lazy. The entry points for creating a lazy object are the ReflectionClass::newLazyGhost() and ReflectionClass::newLazyProxy() methods.
Both methods accept a function that is called when the object requires initialization. The function’s expected behavior varies depending on the strategy in use, as described in the reference documentation for each method.
Example: Creating a lazy ghost
<?php
class Example
{
public function __construct(public int $prop)
{
echo __METHOD__, "\n";
}
}
$reflector = new ReflectionClass(Example::class);
$lazyObject = $reflector->newLazyGhost(function (Example $object) {
// Initialize object in-place
$object->__construct(1);
});
var_dump($lazyObject);
var_dump(get_class($lazyObject));
// Triggers initialization
var_dump($lazyObject->prop);
?>
The above example will output:
lazy ghost object(Example)#3 (0) {
["prop"]=>
uninitialized(int)
}
string(7) "Example"
Example::__construct
int(1)
Example: Creating a lazy proxy
<?php
class Example
{
public function __construct(public int $prop)
{
echo __METHOD__, "\n";
}
}
$reflector = new ReflectionClass(Example::class);
$lazyObject = $reflector->newLazyProxy(function (Example $object) {
// Create and return the real instance
return new Example(1);
});
var_dump($lazyObject);
var_dump(get_class($lazyObject));
// Triggers initialization
var_dump($lazyObject->prop);
?>
The above example will output:
lazy proxy object(Example)#3 (0) {
["prop"]=>
uninitialized(int)
}
string(7) "Example"
Example::__construct
int(1)
Any access to properties of a lazy object triggers its initialization (including via ReflectionProperty). However, certain properties might be known in advance and should not trigger initialization when accessed:
Example: Initializing properties eagerly
<?php
class BlogPost
{
public function __construct(
public int $id,
public string $title,
public string $content,
) { }
}
$reflector = new ReflectionClass(BlogPost::class);
$post = $reflector->newLazyGhost(function ($post) {
$data = fetch_from_store($post->id);
$post->__construct($data['id'], $data['title'], $data['content']);
});
// Without this line, the following call to ReflectionProperty::setValue() would
// trigger initialization.
$reflector->getProperty('id')->skipLazyInitialization($post);
$reflector->getProperty('id')->setValue($post, 123);
// Alternatively, one can use this directly:
$reflector->getProperty('id')->setRawValueWithoutLazyInitialization($post, 123);
// The id property can be accessed without triggering initialization
var_dump($post->id);
?>
The ReflectionProperty::skipLazyInitialization() and ReflectionProperty::setRawValueWithoutLazyInitialization() methods offer ways to bypass lazy-initialization when accessing a property.
Lazy ghosts are objects that initialize in-place and, once initialized, are indistinguishable from an object that was never lazy. This strategy is suitable when we control both the instantiation and initialization of the object, making it unsuitable if either of these is managed by another party.
Lazy proxies, once initialized, act as proxies to a real instance: any operation on an initialized lazy proxy is forwarded to the real instance. The creation of the real instance can be delegated to another party, making this strategy useful in cases where lazy ghosts are unsuitable. Although lazy proxies are nearly as transparent as lazy ghosts, caution is needed when their identity is used, as the proxy and its real instance have distinct identities.
Objects can be made lazy at instantiation time using ReflectionClass::newLazyGhost() or ReflectionClass::newLazyProxy(), or after instantiation by using ReflectionClass::resetAsLazyGhost() or ReflectionClass::resetAsLazyProxy(). Following this, a lazy object can become initialized through one of the following operations:
ReflectionProperty::skipLazyInitialization() or ReflectionProperty::setRawValueWithoutLazyInitialization().ReflectionClass::initializeLazyObject() or ReflectionClass::markLazyObjectAsInitialized().As lazy objects become initialized when all their properties are marked non-lazy, the above methods will not mark an object as lazy if no properties could be marked as lazy.
Lazy objects are designed to be fully transparent to their consumers, so normal operations that observe or modify the object’s state will automatically trigger initialization before the operation is performed. This includes, but is not limited to, the following operations:
ReflectionProperty::getValue(), ReflectionProperty::getRawValue(), ReflectionProperty::setValue(), or ReflectionProperty::setRawValue().ReflectionObject::getProperties(), ReflectionObject::getProperty(), get_object_vars().Iterator or IteratorAggregate using foreach.serialize(), json_encode(), etc.Method calls that do not access the object state will not trigger initialization. Similarly, interactions with the object that invoke magic methods or hook functions will not trigger initialization if these methods or functions do not access the object’s state.
The following specific methods or low-level operations allow access or modification of lazy objects without triggering initialization:
ReflectionProperty::skipLazyInitialization() or ReflectionProperty::setRawValueWithoutLazyInitialization().get_mangled_object_vars() or by casting the object to an array.serialize() when ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE is set, unless __serialize() or __sleep() trigger initialization.ReflectionObject::__toString().var_dump() or debug_zval_dump(), unless __debugInfo() triggers initialization.This section outlines the sequence of operations performed when initialization is triggered, based on the strategy in use.
ReflectionProperty::skipLazyInitialization() or ReflectionProperty::setRawValueWithoutLazyInitialization() are set to their default values, if any. At this stage, the object resembles one created with ReflectionClass::newInstanceWithoutConstructor(), except for already initialized properties.null or no value. The object is no longer lazy at this point, so the function can access its properties directly.After initialization, the object is indistinguishable from an object that was never lazy.
unset() was called.After initialization, accessing any property on the proxy will yield the same result as accessing the corresponding property on the real instance; all property accesses on the proxy are forwarded to the real instance, including declared, dynamic, non-existing, or properties marked with ReflectionProperty::skipLazyInitialization() or ReflectionProperty::setRawValueWithoutLazyInitialization().
The proxy object itself is not replaced or substituted for the real instance.
While the factory receives the proxy as its first parameter, it is not expected to modify it (modifications are allowed but will be lost during the final initialization step). However, the proxy can be used for decisions based on the values of initialized properties, the class, the object itself, or its identity. For instance, the initializer might use an initialized property’s value when creating the real instance.
The scope and $this context of the initializer or factory function remains unchanged, and usual visibility constraints apply.
After successful initialization, the initializer or factory function is no longer referenced by the object and may be released if it has no other references.
If the initializer throws an exception, the object state is reverted to its pre-initialization state and the object is marked as lazy again. In other words, all effects on the object itself are reverted. Other side effects, such as effects on other objects, are not reverted. This prevents exposing a partially initialized instance in case of failure.
Cloning a lazy object triggers its initialization before the clone is created, resulting in an initialized object.
For proxy objects, both the proxy and its real instance are cloned, and the clone of the proxy is returned. The __clone method is called on the real instance, not on the proxy. The cloned proxy and real instance are linked as they are during initialization, so accesses to the proxy clone are forwarded to the real instance clone.
This behavior ensures that the clone and the original object maintain separate states. Changes to the original object or its initializer’s state after cloning do not affect the clone. Cloning both the proxy and its real instance, rather than returning a clone of the real instance alone, ensures that the clone operation consistently returns an object of the same class.
For lazy ghosts, the destructor is only called if the object has been initialized. For proxies, the destructor is only called on the real instance, if one exists.
The ReflectionClass::resetAsLazyGhost() and ReflectionClass::resetAsLazyProxy() methods may invoke the destructor of the object being reset.
▵ Up ⌂ Home ▲ Previous: Constructors and destructors ▼ Next: Inheritance