Bug report: false positive "function already exists" for class methods

Hello team.

I’d like to report a potential bug I recently found as below.

SnipVault's pre-save validation reports a function-name collision and disables the snippet when a class method happens to share its name with an existing global function. In my case a PHP class containing a get_categories() method is rejected with an error along the lines of function get_categories already exists, because WordPress core defines a global get_categories() in wp-includes/category.php.

The snippet does not declare any function at global scope. get_categories appears only as a method inside a class body, so there is no actual conflict — PHP resolves class methods in the class scope, entirely separate from the global function namespace.

The validation appears to match function declarations textually rather than by parsing the code, so it cannot distinguish a method declaration from a global function declaration.

Environment

Please fill in before sending:

  • SnipVault version: 1.3.2

  • WordPress version: 7.1

  • PHP version: 8.2

Steps to reproduce

  1. Create a new PHP snippet.

  2. Paste the following (6 lines, no global functions declared):

<?php
class SnipVault_Repro {
	public function get_categories() {
		return [ 'example' ];
	}
}
  1. Save the snippet.

Expected: the snippet saves and runs. SnipVault_Repro::get_categories() does not conflict with the global get_categories() and PHP loads it without error.

Actual: validation reports that get_categories already exists and the snippet is disabled / blocked from running.

Substituting any other method name that matches a core function reproduces the same result — get_option, get_posts, get_terms, get_users, get_comments are all extremely common method names in ordinary WordPress class design.

Why this matters beyond my snippet

This blocks a whole category of legitimate code rather than one unlucky name.

Elementor custom widgets cannot be written at all. Elementor's widget API requires subclasses of \Elementor\Widget_Base to implement get_categories() — it is how a widget declares which panel category it belongs to. Every custom Elementor widget therefore contains a method with that exact name, so every one of them trips this check. Given Elementor's install base, this is likely to affect a meaningful number of users, and the failure mode is confusing: the error names a function the user never wrote at global scope.

Ordinary class-based snippets are affected too. A settings class with a get_option() method, a query helper with get_posts(), a taxonomy helper with get_terms() — all are idiomatic PHP and all would be rejected.

Suggested fix

Replace textual matching with token-based analysis. PHP's built-in token_get_all() is sufficient and needs no dependencies. The rule is: a T_FUNCTION token declares a global function only when it appears at brace depth zero and is not inside a class / interface / trait / enum body.

Sketch:

function snipvault_find_global_functions( $code ) {
	$tokens = token_get_all( $code );
	$names  = [];
	$depth  = 0;          // current brace nesting depth
	$scopes = [];         // brace depth at which each class-like body started

	foreach ( $tokens as $i => $token ) {

		if ( is_array( $token ) ) {

			// Entering a class-like body: remember its depth
			if ( in_array( $token[0], [ T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM ], true ) ) {
				$scopes[] = $depth;
				continue;
			}

			if ( T_FUNCTION === $token[0] ) {
				// Skip if we are inside any class-like body
				if ( ! empty( $scopes ) ) {
					continue;
				}

				// Find the next meaningful token
				$name = null;
				for ( $j = $i + 1; $j < count( $tokens ); $j++ ) {
					if ( is_array( $tokens[ $j ] ) ) {
						if ( in_array( $tokens[ $j ][0], [ T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ], true ) ) {
							continue;
						}
						if ( T_STRING === $tokens[ $j ][0] ) {
							$name = $tokens[ $j ][1];
						}
					}
					break;
				}

				// $name === null means an anonymous function or a by-reference
				// declaration; neither introduces a global function name here.
				if ( null !== $name ) {
					$names[] = $name;
				}
			}

			continue;
		}

		if ( '{' === $token ) {
			$depth++;
		} elseif ( '}' === $token ) {
			$depth--;
			// Leaving a class-like body
			if ( ! empty( $scopes ) && end( $scopes ) === $depth ) {
				array_pop( $scopes );
			}
		}
	}

	return $names;
}

Because a tokenizer is used, several other false-positive classes disappear at the same time:

CaseText matchingTokenizer

class Foo { function get_categories() {} }

flagged

correctly ignored

if ( ! function_exists( 'x' ) ) { function x() {} }

flagged

can be recognised as guarded

namespace Acme; function get_categories() {}

flagged

distinct FQN, no conflict

$fn = function () {};

may be flagged

anonymous, no name

// function get_categories() in a comment

flagged

token type is a comment

'function get_categories(' inside a string

flagged

token type is a string

The function_exists() guard case is worth special mention: that pattern exists precisely to make redeclaration safe, so flagging it inverts the author's intent.

Additional request

Even with correct detection, an explicit per-snippet override — "I understand the risk, save anyway" — would be a useful safety valve. Static analysis of PHP can never be complete (conditional declarations, eval, generated code), so a way for the author to take responsibility avoids hard-blocking valid code when the analyser is unsure.

Current workarounds, for anyone searching this

  • Omit get_categories() from the Elementor widget class. Elementor falls back to its default category, so the widget still works and simply appears under a different panel group.

  • Move the code out of the snippet manager into wp-content/mu-plugins/ (a single file at the top level of that directory — subdirectories are not auto-loaded).

  • Inserting a comment between function and the method name defeats the text match, but this depends on the current implementation and is not something I would rely on.

Thank you for the plugin — the editor and validation are genuinely useful, which is why this particular false positive stands out.

Please authenticate to join the conversation.

Upvoters
Status

In Review

Board
💡

Feature Request

Date

2 days ago

Author

pearlknowledge

Subscribe to post

Get notified by email when there are changes.