PHP Constructors and Destructors

Establish valid objects at construction and keep important resource cleanup explicit rather than relying on destruction timing.

Lesson 49 of 86Advanced9 min read

A constructor establishes an object's initial state. PHP calls __construct() when you create an object with new. A destructor, __destruct(), can run when an object is no longer referenced or during shutdown. These hooks look like a matched pair, but their reliability and intended responsibilities differ.

Use constructors to require valid inputs and dependencies. Use explicit methods and try/finally for important cleanup whose timing matters. This guide explains constructor promotion, parent initialization and destructor limits through small deterministic examples, so you can avoid treating shutdown hooks as a transaction or delivery guarantee.

Constructors establish a valid state

A constructor is an ordinary specially named method with parameters. Those parameters can be typed and can have defaults. Use it to assign required properties and reject values that would leave the object unusable. After successful construction, callers should not need to remember a separate initialize step before every operation.

Keep construction reasonably focused. Reading a huge file, contacting several services and sending a message during new can make object creation unpredictable and difficult to test. A constructor can accept an already prepared dependency, while an explicit operation performs external work when the caller requests it.

If construction throws, the object is not successfully available to the caller. Validate early before acquiring resources where possible. When initialization must acquire several resources, handle partial failure deliberately so an exception does not leave earlier acquisitions unmanaged.

Constructor property promotion

PHP 8.0 introduced constructor promotion. Adding a visibility keyword to a constructor parameter declares and assigns the corresponding property, such as public function __construct(private string $name). This removes repetitive property and assignment lines while retaining a normal constructor body for validation.

Promotion is convenient for small value objects and explicit dependencies. Do not promote a property public merely to save a getter if callers should not mutate it. Visibility should follow ownership and invariants. PHP 8.1 readonly promotion can protect a binding after initialization when that fits the model.

Promoted parameters remain constructor parameters with normal argument rules. Their types and defaults should describe valid initialization. A convenient default that creates an invalid object weakens the constructor contract. If a required dependency is missing, making it optional may hide a configuration problem instead of solving it.

Parent constructors are not automatic in an override

If a child does not define its own constructor, it can inherit an accessible parent constructor. If the child defines one, PHP does not automatically call the parent constructor too. Use parent::__construct(...) explicitly when the parent needs initialization. Forgetting that call can leave inherited typed properties uninitialized.

The order of initialization matters when child work depends on parent state. Call the parent at a point that establishes its invariants before using them. Avoid invoking overridable methods during parent construction because the child may not have completed its own setup yet.

Constructor inheritance has special compatibility rules, and abstract or interface construction contracts can impose obligations. Rather than exploiting exceptions to ordinary method compatibility, keep a clear construction API. A named factory can describe several creation modes without a confusing list of nullable constructor parameters.

Destructors have uncertain practical timing

A destructor takes no parameters. It can run when references are released, but another reference can keep the object alive. Cycles, garbage collection and process shutdown complicate timing further. Destruction order during shutdown should not be used as a dependable dependency graph for important operations.

Do not rely on a destructor to commit a transaction, send a required email or save the only copy of valuable data. Process termination and fatal conditions can prevent expected application work. Destructors should also avoid throwing during shutdown, where an exception can produce an unrecoverable failure.

Use explicit close, flush or commit operations when timing and success must be observed. Wrap resource use in try/finally so cleanup runs when normal exceptions occur. A destructor may provide a defensive fallback, but the primary lifecycle should be visible to the caller. The constructor and destructor manual documents these hooks and inheritance behavior.

PHP Constructors and Destructors Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Require a positive quantity

The constructor does not merely assign data; it enforces a valid starting state. A negative integer satisfies the parameter type but violates the business rule. This is why type declarations and validation belong together rather than competing as alternative protections.

PHP
<?php
class BasketLine {
    private int $quantity;

    public function __construct(int $quantity) {
        if ($quantity < 1) {
            throw new InvalidArgumentException('Quantity must be positive');
        }
        $this->quantity = $quantity;
    }

    public function quantity(): int {
        return $this->quantity;
    }
}
echo (new BasketLine(3))->quantity() . PHP_EOL;
Output
3
Example 2

Initialize a parent and child deliberately

The child assigns its promoted stock property and explicitly initializes the inherited name. A production version should validate the stock range and the name. The example isolates inheritance mechanics rather than claiming every named item must use this hierarchy.

PHP
<?php
class NamedItem {
    public function __construct(protected string $name) {}
}
class StockItem extends NamedItem {
    public function __construct(string $name, private int $stock) {
        parent::__construct($name);
    }
    public function label(): string {
        return $this->name . ':' . $this->stock;
    }
}
echo (new StockItem('Pen', 8))->label() . PHP_EOL;
Output
Pen:8

Common PHP Constructors and Destructors Mistakes

Using old-style constructors or hidden output

Modern PHP uses __construct. Older code may contain a method with the same name as its class and assume that creates a constructor; that historical style is not a valid modern design. Rename and review old initialization code carefully during migration.

Avoid echo in real constructors and destructors. It can unexpectedly send output before HTTP headers, mix diagnostics into JSON or make tests depend on incidental output. Return data from operations and send diagnostics to a controlled logger. The printing example is a deliberate lifecycle demonstration only.

PHP Constructors and Destructors in Real Projects

Practical task: demonstrate explicit release

Create a tiny object whose destructor prints a message for this controlled command-line experiment. Construct it, print a ready message and unset the only reference. Then print a final message. The purpose is to observe one simple lifecycle, not to promise identical destruction timing for complex applications.

PHP
<?php
class DemoResource {
    public function __construct() {
        echo 'created' . PHP_EOL;
    }
    public function __destruct() {
        echo 'released' . PHP_EOL;
    }
}
$resource = new DemoResource();
echo 'ready' . PHP_EOL;
unset($resource);
echo 'done' . PHP_EOL;
Output
created
ready
released
done

There is only one reference and no cycle, so release occurs at unset in this example. Try assigning $other = $resource before unset and observe that the object remains referenced. In real resource code, prefer an explicit close method and verify its result rather than printing from destructors into an HTTP response.

PHP Constructors and Destructors FAQ

Must every class have a constructor?

No. Use one when required state or dependencies need initialization. A stateless class may not need it.

Does a child constructor call the parent automatically?

Not when the child defines its own constructor. Call parent::__construct explicitly when required.

Does unset always destroy an object?

No. It removes a reference. Other references can keep the object alive.

Should destructors save critical data?

No. Make critical persistence an explicit operation with observable success and error handling.

Is promotion less object-oriented?

No. It is shorter syntax for property declaration and assignment. Visibility, validation and ownership still need design decisions.

PHP Constructors and Destructors Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

A transaction commit must report success to the caller. Where should it happen?

PHP Constructors and Destructors Quiz

2 questions to check what you learned.

  1. Which method initializes a new object in modern PHP?

    • __construct()
    • __start()
    • A same-name class method automatically
  2. A child defines its own constructor. How does it initialize required parent state?

    • PHP always calls both automatically
    • Call parent::__construct(...) deliberately
    • Use unset($this)