Paul M. Jones

Don't listen to the crowd, they say "jump."

PHP-Styler: A Back-To-Formula Rewrite

bluf: PHP-Styler will turn this ...

namespace App\Report;use App\Db\{Connection,Result}; function
buildUserReport(Connection $db,string $region,int $limit):Result{
return $db->table('users')->select('id','display_name','email_address',
'last_login_at','current_region')->where('status','=','active')->where(
'region','=',$region)->whereIn('role',['administrator','editor',
'contributing_author','subscriber'])->orderBy('last_login_at','desc')->
limit($limit)->get();}

... into this:

namespace App\Report;

use App\Db\Connection;
use App\Db\Result;

function buildUserReport(Connection $db, string $region, int $limit) : Result
{
    return $db->table('users')
        ->select(
            'id',
            'display_name',
            'email_address',
            'last_login_at',
            'current_region',
        )
        ->where('status', '=', 'active')
        ->where('region', '=', $region)
        ->whereIn(
            'role', ['administrator', 'editor', 'contributing_author', 'subscriber'],
        )
        ->orderBy('last_login_at', 'desc')
        ->limit($limit)
        ->get();
}

Try reformatting your own code at the php-styler.com demo site.


I.

PHP CS Fixer, PHP_CodeSniffer/PHPCBF, ECS, and PHP_Beautifier are code fixers. They detect rule violations in your existing code and patch them in place. You turn on the rules you care about, and they reshape the parts of your source that break those rules.

PHP-Styler, on the other hand, is a complete code re-formatter. It parses PHP source files into tokens, applies configurable formatting rules and styles, and reconstructs the code with consistent horizontal spacing, vertical spacing, and automatic line splitting. It discards your existing layout entirely and arranges each element of the source code one by one. That puts it in the same family as Prettier for JavaScript, Black for Python, dart_style for Dart, and gofmt for Go.

This nets some benefits and some drawbacks. Among others:

  • Line-length-aware reflow. Long function calls, arrays, and fluent chains are split across lines automatically. (Fixers generally do not split lines for you.)

  • Deterministic pipeline. The same input always produces the same output regardless of its prior layout, and running the tool twice is idempotent.

  • However, it does not preserve hand-aligned columns, and it compresses runs of blank lines to one.

As a side note, parallel execution is built in. The --workers=auto flag uses proc_open to spread files across child processes, speeding up the processing of large codebases.

II.

In the 0.16 release from 2+ years ago, PHP-Styler would style code based on PHP-Parser AST tokens. That approach went a long way, but it brought two persistent problems along with it. The AST-based approach could lose or misplace comments in several contexts -- inside argument lists, at the end of switch cases, on concatenation lines, and as the sole content of blocks. Interpolated strings and heredocs were also reconstructed from AST nodes, which meant literal newlines inside double-quoted strings turned into \n escape sequences on the way out. (In fairness, the PHP-Parser docs said that the AST itself was not suitable for code formatting and styling, but early success encouraged me to keep going; it was only later that these behaviors became apparent.)

After taking the excellent Sandi Metz "99 Bottles of OOP" course, I began to wonder if PHP-Styler could build up a custom set of tokens based not on an AST, but instead on the code elements to be formatted. Those tokens would be more polymorphic than not, and carry most (if not all) of the parsing and dispatching logic themselves.

That approach has been hugely successful. For one, comments and interpolated strings were finally honored properly. But it also ended up creating a catalog of about 500+ highly specific token classes. The level of detail is at "this is the opening brace of an if statement" -- with a separate token for each of the different kinds of opening and closing braces, brackets, parentheses, etc.

The resolution is very finely grained, and the parsing process is specific to each token, including whether or not it should dispatch to another token for parsing. For example, when the parser encounters T_AS, the TAs token can look at the surrounding context to determine if it is a foreach ... as or a use ClassName as or a use TraitName as, and so on. Thus, the 500-odd token classes.

And when you look at the tests, you can see exactly what the source code parsed as. For example, this source code ...

<?php
abstract class Foo
{
    abstract function bar();
}

... gets parsed into these styler token classes:

[
    TPhpOpeningTag::class,
    TAbstract::class,
    TClass::class,
    TClassName::class,
    TClassOpeningBrace::class,
    TAbstract::class,
    TFunction::class,
    TFunctionName::class,
    TParamsOpeningParen::class,
    TParamsClosingParen::class,
    TAbstractMethodEndSemicolon::class,
    TClassClosingBrace::class,
]

That level of detail means all the styling for each token can live with that token: space before or after, line break before or after, blank line before or after, etc. That in turn means the styling can be as specific or as generic as you like. A colon is not just a colon, it is a "ternary colon" or a "return type colon" or "an alternative-if colon" and so on. You can set very specific styling for each one, which gives tremendous flexibility -- not all of which is needed, of course.

In the end, the tokens did not carry all of the parsing logic. The Parser class itself still needs to coordinate a lot more activity than I'd like, but it's a lot less coordination than in Printer+Styler classes of the 0.16 version. And the old Visitor pattern has been removed as entirely unnecessary.

III.

There's a ton more, much more than I can put in a blog post:

If you want a complete code reformatter, not just a fixer, try out PHP-Styler, either at the php-styler.com demo site or by installing it with composer require --dev pmjones/php-styler.



Response-Interop: Last Call for Public Review

The Response-Interop standard interface package has just been released at beta2; this is (I hope) a last call for public review before it goes "stable."

Response-Interop provides interoperable interfaces to encapsulate, buffer, and send server-side response values in PHP 8.4 or later, in order to reduce the global mutable state and inspection problems that exist with the PHP response-sending functions. It reflects, refines, and reconciles the common practices identified within several pre-existing projects to define these interfaces:

The reference implementations demonstrate how the interfaces can be used.

If you have to deal with server-side responses and are interested an interoperable set of interfaces, please offer your comments and criticism as issues or PRs out at Github.



Request-Interop Standard Now Stable

I am pleased to announce that the Request-Interop standard interface package is now stable for production use.

Request-Interop provides an interoperable package of standard interfaces for encapsulating readable server-side request values in PHP 8.4 or later, in order to reduce the global mutable state problems that exist with PHP superglobals. It reflects, refines, and reconciles the common practices identified within several pre-existing projects to define these interfaces:

Request-Interop also defines a marker interface, RequestThrowable, for marking an Exception as request-related.

Finally, Request-Interop defines a RequestTypeAliases interface with PHPStan types to aid static analysis.

The reference implementations demonstrate how the interfaces can be used.

If you have to deal with server-side requests and are interested an interoperable set of interfaces, please offer your comments and criticism as issues or PRs out at Github.


Designing a Bootstrap Script

bluf:

require dirname(__DIR__) . '/vendor/autoload.php';

use Project\Bootstrap\ContainerFactory;
use Project\Presentation\Http\HttpFrontController;

new ContainerFactory()
    ->newContainer()
    ->getService(HttpFrontController::class)
    ->run();

I.

Most PHP systems these days have a bootstrap script (index.php for the web, or console.php for the command line). I've had to review a lot of these as part of my legacy modernization work.

The following survey of public PHP frameworks should give you a good idea of the wide range of design choices made by different bootstrap authors:

Each of the bootstrap scripts performs at least a few of the following tasks, though not all of them perform all of the tasks, and none of them necessarily in this order:

  • Require an autoloader
  • Call ini_set(), error_reporting(), etc.
  • Start/stop a timer to measure execution time
  • Load or set environment variables
  • Check the SAPI or the environment to change how the bootstrap behaves
  • Explicitly require framework-specific library files
  • Determine directory paths (e.g. the project path, config path, app path, document root, etc.)
  • Load or set framework-specific configuration files and values
  • Load or set middleware stacks
  • Load or set routes
  • Instantiate a request object
  • Create a service container
  • Add or register services on a container
  • Instantiate or obtain a front controller
  • Dispatch the request through a router
  • Invoke the front controller
  • Send a response object
  • Call exit()

Often enough, one or more of the above tasks is accomplished by including another script from the bootstrap. The CodeIgniter and Joomla bootstrap scripts are typical examples of that. They are a lot like what I see from many in-house PHP bootstrap scripts.

II.

Looking again at that list of tasks above, is there a way to consolidate some of them into more appropriate locations and maybe even describe a common approach to bootstrap scripts?

First, they could use autoloading exclusively, instead of using require to bring in other scripts. That gets rid of:

  • Explicitly require framework-specific library files

Next, given the existence of a service container, all configuration tasks could be encapsulated by configuration objects in (or service providers to) that container. Doing so would consolidate these tasks, moving the relevant logic out of scripts and into more-easily-tested units:

  • Call ini_set(), error_reporting(), etc.
  • Start/stop a timer to measure execution time
  • Add or register services on a container
  • Determine directory paths (e.g. the project path, config path, app path, document root, etc.)
  • Load or set framework-specific configuration files and values
  • Load or set middleware stacks
  • Load or set routes

After that, request and response work can be moved into the front controller itself, removing these tasks from the bootstrap:

  • Instantiate a request object
  • Dispatch the request through a router
  • Send a response object

That leaves only these remaining tasks:

  • Require an autoloader
  • Load or set environment variables
  • Check the SAPI or the environment to change how the bootstrap behaves
  • Create a service container
  • Instantiate or obtain a front controller
  • Invoke the front controller
  • Call exit()

These look like reasonable concerns for a bootstrap script.

Are there any bootstrap scripts that already work that way, or close to it? Yes:

These almost work that way ...

  • Laravel creates a request object;
  • Mezzio makes two configuration calls;
  • PHPixie sends the response;

... but otherwise limit themselves to the reduced set of bootstrap tasks.

III.

At this point, it looks like the major source of variation is how the bootstrap scripts obtain and run the front controller (usually called an Application or a Kernel) :

// Aura
$kernel = (new \Aura\Project_Kernel\Factory)->newKernel(...);
$kernel();

// Laminas-MVC
$container = require __DIR__ . '/../config/container.php';
$app = $container->get('Application');

// Laravel
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());

// Mezzio
$app = $container->get(Application::class);
$app->run();

// Nette
$application = $container->getByType(Application::class);
$application->run();

// PHPixie
$pixie = new \App\Pixie()->bootstrap($root);
$pixie->bootstrap($root)->http_request()->execute();

// Tempest
HttpApplication::boot(...)->run();

// Yii
$runner = new HttpApplicationRunner(...);
$runner->run();

On examination, we find three kinds of relationships between the front controller (Application) and the service Container:

  • Combined: the Application extends the Container.

  • Service-located: the Container is created, then passed into the Application so the Application can locate services; alternatively, the Application itself creates and retains the Container.

  • Dependency-injected: the Container is created, then the Application is obtained from it.

All of the above variations look like they could be easily reconciled to use a factory to create a service container, then obtain a front controller from the service container and run it.

IV.

With that one final adjustment toward a factory and dependency injection, we find a prototypical set of bootstrap tasks:

  1. Require an autoloader.
  2. Instantiate a service container factory.
  3. Obtain the service container from the factory.
  4. Obtain a front controller from the service container.
  5. Run the front controller.

This set of tasks should be achievable with little effort in any PHP system. Here is an example with hypothetical implementations of the Star-Interop standard interfaces for IocContainerFactory, IocContainer, and FrontController ...

require dirname(__DIR__) . '/vendor/autoload.php';

use Project\Bootstrap\ContainerFactory;
use Project\Presentation\Http\HttpFrontController;

new ContainerFactory()
    ->newContainer()
    ->getService(HttpFrontController::class)
    ->run();

... but of course using the Star-Interop standards is not a requirement. Any PHP system with an autoloader, container factory, service container, and front controller can follow this idiom.

There are still three activities from the researched bootstrap scripts that are not covered by the prototype ...

  • Check the SAPI or the environment to change how the bootstrap behaves
  • Load or set environment variables
  • Call exit()

... and those could reasonably be added to the bootstrap code. However, all other bootstrap script activity has been consolidated into the container factory, container, or front controller.

V.

This approach also lends itself to command-line systems; we need only to swap the HTTP front controller for a CLI front controller and add an exit():

exit(
    new ContainerFactory()
        ->newContainer()
        ->getService(CliFrontController::class)
        ->run()
);

In conclusion, the prototypical design ...

  • moves the bootstrap logic out of scripts and into classes as early as possible;
  • eliminates the introduction of global variables and constants from the bootstrap; and,
  • decouples the bootstrap script from any specific project directory structure (i.e., there is no need to require a script file from a particular location to get the service container).

In short, it standardizes a common approach to bootstrap scripts that is useful to, and recognizable in, any PHP system.

UPDATES

Mon 09 Feb 2026:

  • An earlier version of this article linked to the Slim skeleton; per commenter equilini on Reddit it now links to the Slim documentation.

  • Likewise, commenter dknx01 claimed confusion regarding Joomla and Symfony links to the bootstrap scripts; those links have been made more explicit, along with the one for FlightPHP.


Ioc-Interop Now Open For Public Review

I am pleased to announce that the Ioc-Interop standard interface package is now open for public review.

Ioc-Interop provides an interoperable package of standard interfaces for inversion-of-control (IOC) service container functionality. It reflects, refines, and reconciles the common practices identified within several pre-existing projects.

Ioc-Interop is functionally almost identical to PSR-11. However, Ioc-Interop is intended to contain only services (object). PSR-11 is intended to contain anything (mixed).

Ioc-Interop also offers an IocContainerFactory interface, whereas PSR-11 offers none.

Ioc-Interop is focused on the concerns around obtaining and consuming services. The affordances for managing and producing services are a set of separate concerns. Earlier drafts of Ioc-Interop were much more expansive, including a resolver subsystem and a service management subsystem. These have been extracted to separate standards, each of which is dependent on Ioc-Interop:

This separation helps to maintain a boundary between the needs of service consumers (afforded by Ioc-Interop) and service producers (afforded by Service-Interop and Resolver-Interop).

Note that Ioc-Interop is independent of Service-Interop and Resolver-Interop. Ioc-Interop implementations can use them, or avoid them, as implementors see fit.

The reference implementations demonstrate how the interfaces can be used.

Please offer your comments and criticism as issues or PRs out at Github.


Upload-Interop Standard Now Stable

I am pleased to announce that the Upload-Interop standard interface package has been released at 1.0.0 stable, along with reference implementations for the interfaces.

Upload-Interop provides an interoperable package of standard interfaces for working with upload structures in PHP 8.4+. It reflects, refines, and reconciles the common practices identified within several pre-existing projects.

If you discover omissions or oversights, please raise an issue or PR at Github.


Env-Interop Now Open For Public Review

After a brief round of private review, the Env-Interop standard interface package is now open for public review.

Env-Interop provides interoperable interfaces to load and parse environment files, and encapsulate environment variables, in PHP 8.4 or later. It reflects, refines, and reconciles the common practices identified within several pre-existing projects to define these interfaces:

  • EnvLoaderService affords loading environment variables parsed from an environment file into $_ENV (and possibly elsewhere).

  • EnvParserService affords parsing a string for environment variables.

  • EnvSetterService affords adding or replacing an environment variable in $_ENV (and possibly elsewhere).

  • EnvGetter affords getting environment variable values.

  • EnvThrowable interface extends Throwable to mark an Exception as environment-related.

  • EnvTypeAliases provides PHPStan type aliases to aid static analysis.

The reference implementations demonstrate how the interfaces can be used.

If you have to deal with environment variables and are interested an interoperable set of interfaces, please offer your comments and criticism as issues or PRs out at Github.


Response-Interop Now Open For Public Review

After several rounds of private review, the Response-Interop standard interface package is now open for public review.

Response-Interop provides interoperable interfaces to encapsulate, buffer, and send server-side response values in PHP 8.4 or later, in order to reduce the global mutable state and inspection problems that exist with the PHP response-sending functions. It reflects, refines, and reconciles the common practices identified within several pre-existing projects to define these interfaces:

The reference implementations demonstrate how the interfaces can be used.

If you have to deal with server-side responses and are interested an interoperable set of interfaces, please offer your comments and criticism as issues or PRs out at Github.



More-Than-One Class Per File

In this PHP internals message we read:

I have participated in some internals discussions that point out a fact from the other direction: PHP makes no imposition about file structure. 1-class-per-file is strictly a PSR-4 implementation of a custom autoloader which we have gotten used to for so long. I would really love to be able to declare small related classes/interfaces in a single file ...

There is something to be said for more-than-one class per file, and after a few evenings of tinkering, I can now present the moto/autoload package.

The Moto name-to-file resolution algorithm is an ideological descendant from Horde/PEAR through PSR-0 and PSR-4, with the latest evolution being that everything after an underscore in the terminating class name portion is ignored:

/**
 * Foo_Bar\Baz\ maps to /classes/foo-bar/baz/src/
 */
Foo_Bar\Baz\Dib     => /classes/foo-bar/baz/src/Dib.php
Foo_Bar\Baz\Dib_Zim => /classes/foo-bar/baz/src/Dib.php
Foo_Bar\Baz\Dib_Gir => /classes/foo-bar/baz/src/Dib.php
Foo_Bar\Baz\Dib_Irk => /classes/foo-bar/baz/src/Dib.php

This means the Dib.php file can contain any number of underscore-suffixed classes so long as they are prefixed with Dib ...

// /classes/foo-bar/src/Baz/Dib.php
namespace Foo_Bar/Baz;

class Dib { }

class Dib_Zim { }

class Dib_Gir { }

class Dib_Irk { }

... and a Moto-compliant resolver will find the correct file for that class.

And if function autoloading becomes supported by PHP, the Moto name-to-file resolution algorithm can support multiple functions per file:

// Foo_Bar\Baz => /vendor/foo-bar/baz/src/dib.php
namespace Foo_Bar\Baz;

function dib() {}

function dib_zim() {}

function dib_gir() {}

function dib_irk() {}

I'm soliciting feedback on this approach, so please post your comments and concerns at Github as issues and pull requests.


Request-Interop Now Open For Public Review

After an extensive period of untagged private review, the Request-Interop standard interface package is now open for public review.

Request-Interop provides an interoperable package of standard interfaces for encapsulating readable server-side request values in PHP 8.4 or later, in order to reduce the global mutable state problems that exist with PHP superglobals. It reflects, refines, and reconciles the common practices identified within several pre-existing projects to define these interfaces:

Request-Interop also defines a marker interface, RequestThrowable, for marking an Exception as request-related.

Finally, Request-Interop defines a RequestTypeAliases interface with PHPStan types to aid static analysis.

The reference implementations demonstrate how the interfaces can be used.

If you have to deal with server-side requests and are interested an interoperable set of interfaces, please offer your comments and criticism as issues or PRs out at Github.