Function Or Variable Declared At File Scope
ID |
php.functions_variables_defined_outside_classes |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
Php |
Tags |
best-practice, code-style |
Description
Reports a function or a variable declared at file scope in a file that also declares a class, an interface, a trait or an enum.
A file-scope function is global: its name belongs to no owner, is reachable from every file once
the script is loaded, and cannot be overridden, mocked or autoloaded. A file-scope variable is
worse — it is process-wide mutable state that any later include can read or overwrite, with no
declaration site a reader can find. When the same file already declares a type, that type is the
owner the function or the value is missing.
Rationale
<?php
namespace App\Billing;
$taxRate = 0.21;
function formatAmount(int $cents): string
{
return number_format($cents / 100, 2);
}
class Invoice
{
private int $cents = 0;
public function total(): string
{
return formatAmount($this->cents);
}
}
formatAmount only exists once Invoice.php has been loaded, so any caller depends on a load
order no autoloader expresses; and because free functions cannot be autoloaded, the file has to be
pulled in by hand or listed in Composer’s files section. $taxRate is worse still: it is set
when the file is first included, is visible to every later file in the request, and there is
nothing to stop a second include from resetting it.
Both problems disappear the moment the value moves inside the type that is already in the file.
Remediation
Move the function in as a method or a static member, and the variable in as a property, a
static member or a class constant.
<?php
namespace App\Billing;
class Invoice
{
private const TAX_RATE = 0.21;
private int $cents = 0;
public function total(): string
{
return self::formatAmount($this->cents);
}
private static function formatAmount(int $cents): string
{
return number_format($cents / 100, 2);
}
}
If the helper is genuinely shared, move it to a dedicated final class of static methods, or to a
trait — either is autoloadable, testable and namespaced, which a free function is not.
Configuration
Three exemptions keep the rule quiet on code that is correct as written.
Purely procedural files are never reported. PHP’s top level is executable, and a great deal of correct PHP is deliberately procedural: helper libraries, template partials, route and configuration files, plugin entry points. Those have no type to move anything into, so the rule says nothing unless the file declares one.
A file-scope const is never reported. It is scoped to the namespace, it is immutable, and it
is the idiom the language offers.
Only a plain $name = … binding counts as a variable declaration. An echo, a require, a
function call or an element write such as $config['debug'] = true is a side effect, not a
declaration; that concern belongs to a different rule.
| Property | Meaning | Default |
|---|---|---|
|
File names whose file-scope declarations are expected. Entry points and convention files legitimately mix a class with the wiring that instantiates it, so they are skipped whole. |
|