Memory leaks due to circular referencing in PHP

In PHP, the destructor is called as soon as all the references to the current object are removed. This logic of reference counting can fail when circular referencing occurs. Circular referencing is when different objects circularly refer to one another, or the same object refers to itself by using the keyword $this.

Ways to resolve circular referencing

  • Restructure your code
  • Dereference objects explicitly
  • Use weak reference (PHP 7 >=>= 7.4.0, PHP 8)

Restructuring code

The most reliable way to resolve circular referencing is to restructure your code. For example, if your classes depend on one another of some part of the code, then you can create a trait from this shared code and use it in the classes independently.

Restructuring code to resolve circular referencing

Dereferencing objects explicitly

Memory leak by circular dependency can also be resolved by explicitly dereferencing objects after they have performed their duties. Let’s look at this in code below:

<?php
class ClassA
{
public $dependencyLink;
public function destroyLink()
{
echo "Destroying dependencyLink A\n";
unset($this->dependencyLink);
}
public function __destruct(){
echo "Destructor of A\n";
}
}
class ClassB
{
public $dependencyLink;
public function helloWorld()
{
echo "HelloWorld\n";
}
public function __destruct(){
echo "Destructor of B\n";
}
}
$obj1 = new ClassA;
$obj2 = new ClassB;
$obj1->dependencyLink = $obj2;
$obj2->dependencyLink = $obj1;
// Showing circular referencing
$obj2->dependencyLink->dependencyLink->helloWorld();
// $obj1->destroyLink();
unset($obj1);
unset($obj2);
echo "I am here at end of script\n";
?>

On executing the code, you will see the following output:

HelloWorld
I am here at end of script
Destructor of A
Destructor of B

Explanation

  • Lines 2–15: We define a ClassA with a public variable $dependencyLink and two public methods destroyLink and __destruct.

  • Similarly we define a ClassB on lines 17–29.

  • New objects of ClassA and ClassB are created on lines 31 and 32.

  • We create a circular referencing on lines 34 and 35, which caused this behavior.

  • Line 38: The object $obj2 calls its helloWorld method via circular referencing.

  • The output shows that the objects were not unset by lines 43 and 44, and their destructor methods were executed after completing the script.

Now, uncomment the line 40 and execute the code again. This time the output will be in order.

HelloWorld
Destroying dependencyLink A
Destructor of B
Destructor of A
I am here at end of script
  • By calling the destroyLink method at line 40, we unset the dependencyLink, resolving the problem of circular referencing.

Weak referencing

Another way to resolve circular referencing is by using the WeakReference class of PHP. This allows us to create a reference that does not disrupt the destruction process.

Note: WeakReference class only works in PHP 7 >=>= 7.4.0, PHP 8.

Syntax

WeakReference::create($obj);

Arguments

The create method accepts an object and creates a weak reference against it.

<?php
class ClassA
{
public $name = "A";
public $circular_ref;
public function __construct()
{
$this->circular_ref = WeakReference::create($this);
}
public function __destruct(){
echo "Destructor of A\n";
}
}
echo 'PHP version: ' . phpversion() . "\n";
$obj1 = new ClassA;
// Showing circular referencing
echo $obj1->circular_ref->get()->name . "\n";
unset($obj1);
echo "I am here at end of script\n";
?>

Explanation

  • Lines 2–15: We define a ClassA to demonstrate the weak circular reference using its variable $circular_ref.
  • Line 9 shows how we can create a weak reference to an object itself by using $this.
  • Line 16 shows the version of PHP that we are using.
  • Line 20 shows how we can get properties of a WeakReferenced object
  • Notice the version of PHP we are using is 7.4.1. Otherwise, this would not have worked.

Note: WeakReference objects cannot be serialized.

Copyright ©2024 Educative, Inc. All rights reserved