PHP Destructor

Yo Yogesh.M Updated 17 Oct 2026
6 min read ·Lesson 63 of 109
In this post

What Is a Destructor? In simple terms: A destructor is a special method that runs automatically when an object is no longer needed and is about to be removed from memory. It is the object's chance to

What Is a Destructor?

In simple terms: A destructor is a special method that runs automatically when an object is no longer needed and is about to be removed from memory. It is the object's chance to "clean up after itself" — for example, closing a file, releasing a lock, or deleting a temporary file.

Technically: A destructor is a method named __destruct(). It takes no parameters. PHP calls it when the last reference to an object disappears — when the variable goes out of scope, is unset, or is overwritten — or, for objects still alive, during script shutdown. Objects involved in circular references are destroyed later by the garbage collector.

Constructor vs Destructor

ConstructorDestructor
Name__construct()__destruct()
Called whenThe object is created with newThe object is destroyed
ParametersAnyNone
Typical jobAcquire data and resourcesRelease resources

A Destructor in Action

<?php
class TempFile
{
    private string $path;

    public function __construct(string $name)
    {
        $this->path = sys_get_temp_dir() . '/' . $name . '.tmp';
        file_put_contents($this->path, '');
        echo "Created " . basename($this->path) . "\n";
    }

    public function write(string $data): void
    {
        file_put_contents($this->path, $data, FILE_APPEND);
    }

    public function __destruct()
    {
        if (is_file($this->path)) {
            unlink($this->path);
        }
        echo "Deleted " . basename($this->path) . "\n";
    }
}

function buildReport(): void
{
    $temp = new TempFile('report');
    $temp->write("Daily sales report\n");
    echo "Processing report\n";
}   // $temp goes out of scope here

buildReport();
echo "Back in the main script\n";

Output

Created report.tmp
Processing report
Deleted report.tmp
Back in the main script

How It Works

  1. new TempFile('report') runs the constructor, which creates the file.
  2. The object is stored only in the local variable $temp.
  3. When buildReport() returns, $temp disappears. The object has no remaining references, so PHP calls __destruct() immediately — before the next line of the main script runs.
  4. The destructor deletes the temporary file, so cleanup cannot be forgotten, even if the function returns early.

PHP uses reference counting: each object knows how many variables refer to it. When the count drops to zero, the object is destroyed right away. This makes destruction timing predictable in most situations.


When Exactly Is the Destructor Called?

<?php
class Tracker
{
    public function __construct(private string $name)
    {
        echo "Construct {$this->name}\n";
    }

    public function __destruct()
    {
        echo "Destruct {$this->name}\n";
    }
}

$a = new Tracker('A');
$copy = $a;          // second reference to the same object

unset($a);
echo "After unset(\$a)\n";

$copy = null;
echo "After \$copy = null\n";

$b = new Tracker('B');
$b = new Tracker('C');   // B loses its only reference
echo "End of script\n";

Output

Construct A
After unset($a)
Destruct A
After $copy = null
Construct B
Construct C
Destruct B
End of script
Destruct C

Step-by-Step Explanation

  1. unset($a) removes one reference, but $copy still refers to object A, so A is not destroyed yet.
  2. $copy = null removes the last reference; A is destroyed immediately.
  3. $b = new Tracker('C') first creates C, then overwrites $b; B loses its only reference and is destroyed.
  4. C is still alive when the script ends, so its destructor runs during shutdown.
EventDestructor Runs
Last variable referring to the object goes out of scopeImmediately
unset($var) or $var = null on the last referenceImmediately
Script ends normally, or exit() is calledDuring shutdown
Object is part of a circular referenceWhen the garbage collector runs
Fatal error, or the process is killedNot guaranteed

Circular References and Garbage Collection

If two objects refer to each other, their reference counts never reach zero on their own, even when no variable refers to them anymore:

<?php
class Node
{
    public ?Node $partner = null;

    public function __construct(private string $name) {}

    public function __destruct()
    {
        echo "Destroying {$this->name}\n";
    }
}

$a = new Node('A');
$b = new Node('B');
$a->partner = $b;
$b->partner = $a;

unset($a, $b);
echo "Variables unset\n";

gc_collect_cycles();
echo "Garbage collected\n";

Output (the order of the two "Destroying" lines may vary)

Variables unset
Destroying A
Destroying B
Garbage collected

After unset(), each object still holds a reference to the other, so neither destructor runs. PHP's cycle collector finds such unreachable groups periodically (or when gc_collect_cycles() is called) and destroys them. This is rarely a problem in short web requests, but it matters in long-running scripts and workers (see Garbage Collection).


Practical Example: Releasing a Lock Automatically

Destructors are well suited for releasing resources that were acquired in the constructor — a pattern known as RAII ("resource acquisition is initialization"):

<?php
final class FileLock
{
    private $handle;

    public function __construct(private string $path)
    {
        $this->handle = fopen($path, 'c');

        if ($this->handle === false || !flock($this->handle, LOCK_EX | LOCK_NB)) {
            throw new RuntimeException('Another process is already running.');
        }

        echo "Lock acquired\n";
    }

    public function __destruct()
    {
        if (is_resource($this->handle)) {
            flock($this->handle, LOCK_UN);
            fclose($this->handle);
            echo "Lock released\n";
        }
    }
}

function runNightlyImport(string $lockFile): void
{
    $lock = new FileLock($lockFile);

    echo "Importing products...\n";
    throw new RuntimeException('Supplier feed unavailable');
}

try {
    runNightlyImport(sys_get_temp_dir() . '/import.lock');
} catch (RuntimeException $e) {
    echo "Import failed: " . $e->getMessage() . "\n";
}

Output

Lock acquired
Importing products...
Lock released
Import failed: Supplier feed unavailable

Why This Works

  1. The constructor acquires an exclusive, non-blocking lock. If another copy of the import is already running, construction fails and the import never starts.
  2. When the exception leaves runNightlyImport(), the local $lock variable goes out of scope, and the destructor releases the lock before the catch block runs.
  3. The lock is released whether the import succeeds, returns early, or fails — the cleanup is tied to the object's lifetime.

(Inside a catch block, the exception object can keep a reference to the function's variables through its stack trace in some situations; for critical resources, combining destructors with explicit try/finally cleanup is the safest approach.)


Rules and Limitations

  • A destructor takes no parameters and its return value is ignored.
  • If a child class defines a destructor, the parent's destructor is not called automatically; call parent::__destruct() explicitly if needed (see PHP Inheritance).
  • Throwing an exception from a destructor during script shutdown causes a fatal error. Destructors should catch their own exceptions.
  • Do not rely on destructors for essential work such as saving data or sending emails: after a fatal error or a killed process, they may never run.
  • The order in which remaining objects are destroyed at shutdown should not be relied upon.

When You Need Explicit Cleanup

For resources that must be released at a precise moment, provide an explicit method (close(), release()) and call it in a finally block; keep the destructor as a safety net:

public function close(): void
{
    if (is_resource($this->handle)) {
        fclose($this->handle);
    }
}

public function __destruct()
{
    $this->close();   // safety net if close() was not called
}

Common Mistakes

Expecting Destruction While References Remain

Effect: unset($obj) does not trigger the destructor because another variable (or an array, or another object) still refers to the object.

Fix: Remember that only the last reference matters.

Doing Essential Work in Destructors

Effect: Data is lost when the script ends abnormally.

Fix: Save data explicitly; use destructors only for cleanup.

Throwing Exceptions From Destructors

Effect: Fatal errors during shutdown.

Fix: Catch and log exceptions inside the destructor.

Forgetting parent::__destruct()

Effect: Resources managed by the parent class are not released.

Fix: Call the parent destructor from overriding destructors.


Best Practices

  • Use destructors to release resources acquired in the constructor (files, locks, temporary files).
  • Keep destructors short, safe, and free of exceptions.
  • Provide explicit cleanup methods for precise control, with the destructor as a fallback.
  • Avoid circular references in long-running processes, or break them explicitly.
  • Never depend on destructors for business-critical operations.

Official reference: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.destructor

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators
Ask about this post
AI Ask about this post

Ask questions about PHP Destructor and get answers drawn from it.

Signed-in readers only.