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

exemptFiles

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.

index.php, bootstrap.php, functions.php, helpers.php, autoload.php, config.php, routes.php, app.php, cli-config.php

© 2026 Xygeni. All rights reserved