API / Minn\Runtime / Symbols

Symbols final class

Minn\Runtime\Symbols · public/minn/src/Minn/Runtime/Symbols.php · 391 lines

A static read of what a plugin's PHP calls: global functions and classes it uses but does not itself declare, minus the ones it guards with function_exists() or class_exists(). Checked against what the runtime provides, this decides whether a plugin loads at all.

The read is cached in the minn_runtime_symbols option, keeping only the reference's own names (all a verdict reads). An entry is trusted while the folder's fingerprint (its own time and its top-level files' newest) holds, for up to FULL_CHECK_EVERY seconds; then the whole folder is walked again, as an update replaces it. Installing, updating or activating a plugin walks its folder at once (recheck()). Walking every folder on every request cost hundreds of milliseconds on a site with many plugins.

Constants and properties

privateMAX_FILES = 6000
privateSKIP_DIRS = array ( 0 => 'node_modules', 1 => 'tests', 2 => 'test', 3 => '.git', )
privateREADER = 3

Bumped whenever the token reader changes, so every cached scan is made again.

privateFULL_CHECK_EVERY = 300

How long a cached entry is trusted on its fingerprint alone before the folder is walked again.

privatestatic ?array $pending

Public

staticmissing(string $dir, Minn\Runtime\Options $options): array#

What a plugin folder needs that the runtime lacks, cached by mtime.

@return array{functions: list<string>, classes: list<string>, redeclares: list<string>, files: int, truncated: bool}
staticrecheck(string $dir, Minn\Runtime\Options $options): void#

The folder is walked again at its next check instead of trusted on its fingerprint: an install, an update or an activation never waits out the window. The walk still keeps the cached read when nothing changed.

staticredeclaresIn(string $file): array#

The functions one file declares in the global scope, unguarded, that the loaded runtime already defines: including that file would not compile.

@return list<string>
staticmissingAgainst(string $dir, Minn\Runtime\SymbolGap $gap): array#

The same read against an exported gap instead of the running engine, so a folder can be judged with no database, no options, and no facade loaded.

@return array{functions: list<string>, classes: list<string>, redeclares: list<string>, files: int, truncated: bool}
staticsurvey(string $dir, Minn\Runtime\SymbolGap $gap, array $deadEnds): array#

What a folder needs and what it reaches that Minn answers with nothing, in one read, for a machine judging a site before the engine runs it (preflight): the gate's verdict against the gap given, and the dead ends it calls (the wp-admin screens, the editors, the Customizer, XML-RPC, what was left out), by area. A call the folder guards or answers itself is not counted, as the gate counts it.

$deadEndsarray{functions?: array<string, string>, classes?: array<string, string>}name => area, as data/deadend-symbols.json holds them
@return array{functions: list<string>, classes: list<string>, redeclares: list<string>, files: int, truncated: bool, deadEnds: array<string, list<string>>}

Internals 10

Private and protected methods: how the class does its work. Not a contract; they change whenever the code is tidied.

privatekeep(array $cache, Minn\Runtime\Options $options): void#

The cache changed this request: written once, when the request ends.

privatefingerprint(string $dir): string#

A folder's cheap fingerprint: its own time and its top-level PHP files' newest (an update replaces the folder; a version bumps the main file).

privatenamesStamp(): string#

The reference names a reduced scan was kept against: a new interface (a WordPress update) makes every scan again.

privatereduced(array $scan): array#

A scan cut to the reference's own names, the only ones a verdict asks about.

$scanarray<string, mixed>
@return array<string, mixed>
privateverdict(array $scan, int $files, Minn\Runtime\SymbolGap $gap): array#

Only the reference's own interface counts: a function or class the plugin needs from WordPress. Its own integrations with other plugins and PHP extensions are its business.

$scanarray{calls: list<string>, classes: list<string>, declared: array<string, true>, declaredClasses: array<string, true>, guarded: array<string, true>, truncated: bool}
@return array{functions: list<string>, classes: list<string>, redeclares: list<string>, files: int, truncated: bool}
privatephpFiles(string $dir): array#
@return list<string>
privatescan(array $files): array#

Every file's tokens into one table: the functions called, the classes referenced, both minus what the folder declares itself or guards with an existence check.

$fileslist<string>
@return array{calls: list<string>, classes: list<string>, declared: array<string, true>, declaredClasses: array<string, true>, guarded: array<string, true>, truncated: bool}
privatescanTokens(array $tokens, Minn\Runtime\SymbolTable $table): void#
$tokenslist<array{0: int, 1: string, 2: int}|string>
privatenoteName(array $tokens, int $i, bool $namespaced, Minn\Runtime\SymbolTable $table): void#

A name token: a class reference when it follows new / extends / implements / instanceof or precedes ::, a call when it precedes "(", and a guard when the call is an existence check on a literal.

$tokenslist<array{0: int, 1: string, 2: int}|string>
privatesignificant(array $tokens, int $from, int $step): ?array#

The nearest token in either direction that is not whitespace or a comment, as [id, text]. @param list<array|string> $tokens @return array{0: ?int, 1: string}|null

$tokenslist<array|string>@return array{0: ?int, 1: string}|null