PHP-Styler 0.5.0 Released
PHP-Styler is a companion to PHP-Parser for reconstructing PHP code after it has been deconstructed into an abstract syntax tree. As a pretty-printer for AST nodes, PHP-Styler will completely reformat your PHP code, discarding any previous formatting entirely. It will also split lines at idiomatically appropriate points for the given maximum line length, a feature I'm especially happy with.
Improvements and Additions
This release sees dramatic speed improvements (about 600% !) from the last-publicized 0.1.0 release, mostly from removing the php -l
linting step in favor of using the error-checking from PHP-Parser. In addition, the code reassembly logic has been completely rewritten to use a Line object that splits into sub-Line objects, and applies splits to each Line independently. This results in a much more robust line-splitting process.
I have also added a bare-bones document on how to customize Styler output -- including notes on how to customize spacing around operators, and on opening-brace placement. If you have a coding style guide that you like, I invite you to try writing a customized Styler for it; I can advertise it on the PHP-Styler site.
And although you can use the preview
command after installing PHP-Styler to safely preview any reformatting, I have put up a temporary online demonstration site -- type or paste in any PHP code to watch it get reformatted live. (As a side note, this gave me an excuse to try HTMX which really is quite easy to use.)
Line-Splitting Example
In testing the line-splitting logic, I applied PHP-Styler to several public codebases, including the HttpFoundation package from Symfony. Here is some original Symfony code before PHP-Styler gets hold of it:
class ExpressionRequestMatcher extends RequestMatcher
{
// ...
public function matches(): bool
{
// ...
return $this->language->evaluate($this->expression, [
'request' => $request,
'method' => $request->getMethod(),
'path' => rawurldecode($request->getPathInfo()),
'host' => $request->getHost(),
'ip' => $request->getClientIp(),
'attributes' => $request->attributes->all(),
]) && parent::matches($request);
}
}
Now to apply PHP-Styler. (The following is a simplified explanation of the line-splitting logic, looking just at the matches()
return expression.)
In the first evolution of line-splitting, PHP-Styler begins by placing the entire statement on a single line with no splits at all.
return $this->language->evaluate($this->expression, ['request' => $request, 'method' => $request->getMethod(), 'path' => rawurldecode($request->getPathInfo()), 'host' => $request->getHost(), 'ip' => $request->getClientIp(), 'attributes' => $request->attributes->all()]) && parent::matches($request);
Clearly this line is too long. PHP-Styler recognizes this and applies a second evolution to split lines at booleans:
return $this->language->evaluate($this->expression, ['request' => $request, 'method' => $request->getMethod(), 'path' => rawurldecode($request->getPathInfo()), 'host' => $request->getHost(), 'ip' => $request->getClientIp(), 'attributes' => $request->attributes->all()])
&& parent::matches($request);
The boolean line is now within the limit, but the fluent method call above it is not. The third evolution applies an idiomatic member-operator split:
return $this->language
->evaluate($this->expression, ['request' => $request, 'method' => $request->getMethod(), 'path' => rawurldecode($request->getPathInfo()), 'host' => $request->getHost(), 'ip' => $request->getClientIp(), 'attributes' => $request->attributes->all()])
&& parent::matches($request);
The $language
property stays with $this
, but the evaluate()
method call gets split -- even then, the line remains too long. The fourth evolution applies an argument split on that line:
return $this->language
->evaluate(
$this->expression,
['request' => $request, 'method' => $request->getMethod(), 'path' => rawurldecode($request->getPathInfo()), 'host' => $request->getHost(), 'ip' => $request->getClientIp(), 'attributes' => $request->attributes->all()]
)
&& parent::matches($request);
Now the arguments are split out to their own lines and indented; the only remaining over-long line is the second argument. The fifth evolution applies an array split:
return $this->language
->evaluate(
$this->expression,
[
'request' => $request,
'method' => $request->getMethod(),
'path' => rawurldecode($request->getPathInfo()),
'host' => $request->getHost(),
'ip' => $request->getClientIp(),
'attributes' => $request->attributes->all()
],
)
&& parent::matches($request);
Now that the array argument has been split and indented, all of the lines are within the limit, and PHP-Styler moves on to the next chunk of code.
As we can see, the end result is similar but not identical to the original Symfony code. Still, the PHP-Styler presentation is arguably a reasonable alternative to the original.
What's important here is that no matter the style of the original code, the restyled code will always end up the same way. The original code could be an absolute mess, but PHP-Styler will present it coherently regardless. All style decisions will look like they have been made by a single mind, granting at least one aspect of consistency and coherence to any codebase.
III.
There's still more to be done with PHP-Styler, such as adding annotations to ignore a file for styling, or to force array expansion across lines. Inline comments present some problems, as does variable interpolation into double-quoted strings with newlines. Unfortunately, those are because of how the upstream PHP-Parser works, so I'll have to report those and hope for a solution there.
Even so, PHP-Styler is good enough for my own projects now, and I'm happy to have spent the time working it up.