new in Initializers (PHP 8.4)
debt(d7/e3/b3/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints.tools list is empty. The misconception — that the default object is shared like a static variable — produces no error or warning; behavior is silently correct (fresh instance per call) but the developer's mental model is wrong. The side-effect misuse (DB connections as defaults) is silent in production until users hit it. No standard linter rule is cited, and the version-mismatch case is caught by a parse error (d1), but the behavioral traps require code review or runtime observation.
Closest to 'simple parameterised fix' (e3). The quick_fix shows a straightforward one-component replacement: swap the nullable parameter + null-coalescing body pattern for the new initializer syntax. It touches the function signature and potentially removes a body line, but stays within one function/component. Not quite e1 because the refactor requires identifying all such patterns across a component rather than a single-line swap.
Closest to 'localised tax' (b3). This is an optional syntactic feature; once adopted in a signature it imposes a minor ongoing cost — maintainers must understand the fresh-instance semantics and the PHP 8.4 version requirement. It does not spread across the codebase structurally unless widely adopted. The applies_to scope is broad (web, cli) but the choice is per-function, not architectural.
Closest to 'notable trap' (t5). The misconception field explicitly states the canonical wrong belief: developers expect the default object to be shared across calls like a static variable (singleton-like), but each call gets a fresh instance. This is a documented gotcha that contradicts intuitions formed by other languages or by analogies to static defaults, but it is a single well-known edge case rather than a catastrophic systematic misunderstanding.
Also Known As
TL;DR
Explanation
PHP previously required that default parameter values, attribute arguments, and property initialisers be compile-time constants — scalar values, arrays, class constants, and null. Creating a default object value required a null default and manual assignment in the constructor body. PHP 8.4 allows 'new' expressions in these positions. 'function foo(Logger $log = new NullLogger())' is now valid. The object is created fresh for each call where the default is needed. This also works in property promotion: 'public function __construct(private Logger $log = new NullLogger()) {}' — clean null-object pattern without boilerplate.
Common Misconception
Why It Matters
Common Mistakes
- Using new in initializers in PHP < 8.4 — parse error; check version before adopting.
- Expecting the same instance to be reused across calls — each call that triggers the default creates a new object.
- Using new with classes that have constructor side effects (DB connections, file handles) as defaults — the side effect runs on every call that omits the argument.
- Forgetting this works in attribute arguments too — attribute classes that accept object dependencies can now have clean defaults.
Code Examples
<?php
// ❌ Pre-8.4 null default + manual instantiation boilerplate
class OrderService
{
private Logger $logger;
private Cache $cache;
public function __construct(
?Logger $logger = null,
?Cache $cache = null
) {
$this->logger = $logger ?? new NullLogger();
$this->cache = $cache ?? new ArrayCache();
}
}
<?php
// ✅ PHP 8.4 — new in initializers, clean constructor promotion
class OrderService
{
public function __construct(
private Logger $logger = new NullLogger(),
private Cache $cache = new ArrayCache(),
) {}
}
// Also valid in standalone functions
function sendNotification(
string $message,
Mailer $mailer = new SmtpMailer(),
): void {
$mailer->send($message);
}