Skip to content

fix: flat files fatal on any snippet using namespace or declare - #482

Merged
imantsk merged 2 commits into
corefrom
fix/flat-file-namespace/core
Aug 28, 2026
Merged

fix: flat files fatal on any snippet using namespace or declare#482
imantsk merged 2 commits into
corefrom
fix/flat-file-namespace/core

Conversation

@TallblokeUK

Copy link
Copy Markdown
Contributor

Reported on Discord by ardittristan, who also diagnosed it correctly. Affects 3.10.0 and 4.0.0-beta.1; 3.9.6 is fine.

The bug

Since 3.10.0, Functions_Snippet_Handler::wrap_code() prepends a direct-access guard to every functions snippet written to a flat file:

return "<?php\n\nif ( ! defined( 'ABSPATH' ) ) { return; }\n\n" . $code;

3.9.6 wrote the code straight after the opening tag, with no guard:

$output = "<?php\n\n" . apply_filters( 'code_snippets_php_snippet_file_code', $code );

PHP requires declare and namespace to come before any other statement, so the guard pushes them out of position:

PHP Fatal error: Namespace declaration statement has to be the very first
statement or after any declare call in the script in .../php/198.php on line 5

The flat file is loaded on every request, so this takes down the whole site — front end and wp-admin. It also explains the "nothing in the error log" part of the report: the failure happens before WordPress can log anything.

The trigger is regeneration, not upgrade, which matches the report exactly — upgrading is quiet until something forces the files to be rewritten (deleting the folder, or saving any setting).

Three broken shapes, not one

Snippet opens with 3.10.0
namespace Foo; fatal
declare(strict_types=1); fatal
namespace Foo { } fatal — no code may sit outside the braces

The fix

Insert the guard after the statement prologue rather than before it — after any declare statements and a namespace declaration, and inside the braces for block syntax. The prologue is located with token_get_all(), so namespace\my_function() (the operator, not a declaration) is not mistaken for one.

namespace My\Plugin;

if ( ! defined( 'ABSPATH' ) ) { return; }

function my_snippet() {}

Snippets with no prologue are wrapped byte-for-byte as before, so existing flat files do not churn.

Testing

8 unit tests in Functions_Snippet_Handler_Test, covering each prologue shape plus the namespace-operator case and a check that every shape keeps its guard. 5 of the 8 fail on core today; the 3 that pass are the invariants the fix must not break. Because a misplaced namespace is a compile error, neither token_get_all( …, TOKEN_PARSE ) nor include can detect it in-process — including the file kills the test run outright — so the generated code is linted in a subprocess.

Full suite: 155 tests, 0 failures. phpcs clean.

Verified end to end on WP 7.1 / PHP 8.3 with stock 3.10.0 and flat files enabled:

before after
front page 500 200
wp-admin 500 302 (login)
php -l on the generated file fatal clean
snippet loaded and callable no yes
direct web request to the file 0 bytes 0 bytes

The last row is the guard doing its job — unchanged, and identical to a non-namespaced snippet's file, so nothing is weakened.

Related, not fixed here

The code_snippets_php_snippet_file_code filter present in 3.9.6 was dropped in the PSR-4 conversion (1377faeb) and has no replacement — zero hits on core. That is a public extension point removed in a minor release; anyone hooking it is silently broken. Worth a separate decision on whether to restore it.

Content_Snippet_Handler has the same prepend shape but closes with ?> before the snippet body, and carried the guard in 3.9.6 too, so it is not a regression and is left alone.

Since 3.10.0 the direct-access guard is prepended to every functions
snippet written to a flat file. PHP requires `declare` and `namespace`
to precede any other statement, so a snippet opening with either was
pushed out of position and fatalled on load:

  Namespace declaration statement has to be the very first statement
  or after any declare call in the script

The flat file is loaded on every request, so this takes down the entire
site — front end and wp-admin — and the guard runs before WordPress can
log anything, which is why nothing reaches the error log.

Insert the guard after the statement prologue instead: after any
`declare` statements and a namespace declaration, and inside the braces
when a namespace uses block syntax, since no code may sit outside
`namespace {}` blocks. Snippets with no prologue are wrapped exactly as
before, so existing flat files do not change.
@TallblokeUK
TallblokeUK force-pushed the fix/flat-file-namespace/core branch from f725557 to c90d7b9 Compare August 28, 2026 07:58
@imantsk imantsk added the build Adding this label will trigger the zip build action label Aug 28, 2026
@imantsk imantsk added build Adding this label will trigger the zip build action and removed build Adding this label will trigger the zip build action labels Aug 28, 2026
@code-snippets-bot

code-snippets-bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Download and install

📦 code-snippets.3.10.0.zip

@imantsk
imantsk merged commit 11da922 into core Aug 28, 2026
33 checks passed
@imantsk
imantsk deleted the fix/flat-file-namespace/core branch August 28, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Adding this label will trigger the zip build action

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants