Le 27/05/2026 à 14:12, [email protected] a écrit :
Hi internals,

I intend to submit an RFC introducing a new file extension for pure-code PHP source files (no leading <?php required) and would like to gather feedback before drafting.

Proposal in brief:

Files ending in .phpc would be parsed starting in ST_IN_SCRIPTING state. No <?php or ?> tags permitted inside such files. Existing .php files and their semantics are completely unchanged. This is purely additive and BC-clean.

Motivation:

PHP's mixed-mode default reflects its 1995 templating origins. Since PHP 7+, the language has evolved into a credible general-purpose tool: strict types, enums, readonly classes, property hooks, JIT compilation. I personally maintain PHPolygon, a CPU-bound 3D engine written in PHP – a use case where the templating heritage is pure ceremony. Other modern uses (CLI tooling, queue workers, code generators) share this pattern. A dedicated pure-code file format would be a small but meaningful acknowledgment that PHP-as-language is now a first-class use case alongside PHP-as-template.

Prior art and what's different:

I have read both rfc/source_files_without_opening_tag (Boutell, 2012, abandoned by author) and rfc/nophptags (Ohgaki, 2014, inactive). My proposal deliberately avoids what I believe were the two design choices that killed them:

- No new include syntax (Boutell's AS keyword). Extension-based detection only. - No php.ini-based mode switch (Ohgaki's template_mode). No global config side effects. - No security framing. The mode-switch overhead is parse-time only and OPcache/JIT eliminate it in practice; this proposal is about conceptual clarity and tooling, not performance or LFI mitigation.

Implementation:

I will write and maintain the implementation patch. Initial scope: extension registration in zend_compile_file, lexer state initialization, OPcache awareness, CLI support, and rejection of <?php/?> tokens inside .phpc files. I will also coordinate with Composer maintainers ahead of RFC submission to confirm autoload support.

Open questions for the list:

1. Is the .phpc extension acceptable as the disambiguator, or is there appetite for something else (e.g. shebang line, declare directive – both of which I think are worse, but I'd hear the case)? 2. Should #! shebang lines and UTF-8 BOM be permitted before the implicit scripting state begins? My intent is yes for both. 3. Should __halt_compiler() retain its current behavior in .phpc files? My intent is yes.

I welcome substantive critique. If the concept itself is unwanted, I would rather know now than discover it during a vote.

Thanks.

Hendrik Mennen
Maintainer, PHPolygon



Hey, I'm answering with a new thread here about the potential other ways to handle this.

If your goal is to make sure a PHP file has no accidental "echo", here is my proposal: a `declare(pure);` statement that would prevent all potential calls to "echo" (as well as many notice/warning errors).
Nothing more.
All the constraints you are talking about would be fixed with something like that, on the price of having one single line containing "<?php" , and all the rest being PHP code.

Just like the `declare(strict_types=1);` statement enforces types usage in said file calls, declaring a file to be pure would only add constraints to how the file is written.

The `declare(pure);` statement would enforce this at file-level:

 * Forbid the use of `?>` in the file, obviously, even as last file
   character (this prevents potential accidental line feed after `?>` too)
 * The file must not contain call statements, only definitions. Of
   course, this implies `echo`, but it also implies `exit`/`die`.
 * This only allows declarations
   like `(include|require)(_once)?` `const`, `function`, `namespace`,
   `class`, `return`, `interface`, `trait`, `use` and `enum`. This also
   means that constant definitions cannot use global scope other than
   built-in constants (see later points), and `define()` would be
   forbidden.
 * `if`/`else`/`elseif`, `switch` or `match` statements can also be
   allowed, only if they respect the two previous points. This way, you
   can still define functions/constants/classes depending on PHP
   versions. I'm not sure about iterators (`for`, `while`, `do...while`
   or `foreach`), because I see no proper use-case, but they can still
   be allowed if they imply no global call statements, which seems
   unlikely anyway for iterators.
 * Previous point implies that `try/catch/finally`, `break`
   or `continue` or `new` also forbidden in the global scope (as a
   reminder).
 * `return` is allowed, because it might stop the file's execution
   process in case one doesn't want to define classes, functions and
   whatnot, and it can even return a value, as long as this value is
   either a literal or a built-in constant (see later). You couldn't
   return the result of a function call, for example, since they are
   disallowed in the file.
 * Since the file must not contain statements, the global scope of the
   file must not refer to any variable, and must not define variables
   either. Even superglobals.
 * The file's global scope can refer to constants defined internally by
   PHP or its extensions. (this means every constant that isn't in the
   `user` key when calling `get_defined_constants(true)` in PHP, as
   well as magic constants like `__DIR__`). Only constants that are
   *always* available at compile-time will be checked. This way, it
   can't accidentaly trigger an `Undefined constant` warning for
   userland, but it *can* trigger one if a native extension isn't
   enabled or doesn't have said constant, which can also be detected at
   compile-time.
 * The previous point implies that when a pure PHP file is compiled,
   constants can be checked. It would put more load on the compile-time
   engine, but it allows optimizing these values, therefore it
   /might/ improve runtime performances just a bit. Especially since
   constant checks also search in the namespace (and no existing PHP
   extension seem to define namespaced constants, and even if they did,
   the compiler would still be able to have access to them anyway since
   they are engine-based, not runtime-based, and users would have to
   either do `use const` or write the fully-qualified constant name).
 * Maybe other constraints I'm not yet thinking about, feel free to
   suggest, as I might have forgotten something.


As said, this adds a new layer of complexity to an already big compiling process, but this brings a lot of advantages:

 * Your file is still pure PHP
 * Can still be interpreted by IDEs
 * Doesn't need a different file extension
 * Is explicitly visible at the beginning of the file when you open it
 * Still allows things that frameworks do for conditional
   function/class declaration
 * Potential compile-time built-in constant optimization (if not
   already done by the engine, I didn't search for this)
 * Everything that is not global/namespace-scope (functions, classes,
   etc.) can still contain whatever code they need.
 * All potential errors will be compile-time errors, therefore if the
   file is "correct", compiling it definitely means that it has its
   place in the opcache for a very long time as no runtime can alter
   its global context.
 * Having no actual call statements in the global/namespaced scope
   ensures no "echo", but overall has absolutely zero runtime impact
   other than compile-time errors, since there cannot be notice/warning
   errors that might also pollute the current buffer. (I might have
   forgotten what else can throw a notice/warning, but feel free to
   correct me if I do)


There are only a tiny amount of drawbacks to this (from what I've thought about so far):

 * All "pure PHP" files will have to begin with `<?php declare(pure);`
 * Potential tiny compile-time performance drop, because all global
   statements would have to be checked and analysed. And a bit more if
   global scope constants are also analysed.

To me, this seems like the best compromise with your proposal.


Reply via email to