Paul M. Jones

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

Upload-Interop Now Open For Public Review

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

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 to define these interfaces:

Upload-Interop also defines an UploadTypeAliases interface with PHPStan types to aid static analysis.

The reference implementations demonstrate how the interfaces can be used.

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


Uri-Interop Standard Now Stable

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

The standard includes interfaces for readable, mutable, and immutable URI objects, as well as URI-specific PHPStan type aliases.

The standard also includes interfaces for a factory. normalizer, resolver, and parser.

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



Uri-Interop Now Open For Public Review

After a period of private review, the uri-interop/interface package is now open for public review.

Uri-Interop publishes a standard set of interoperable URI interfaces for PHP 8.4+. It reflects, refines, and reconciles the common practices identified within several pre-existing projects.

The package defines separate interfaces to afford reading and modifying URI component values:

  • Uri StringableComponents affords reading of the URI component values and recomposing them into a string.
  • MutableUri MutableComponents extends Uri StringableComponents to afford direct modification of component values.
  • ImmutableUri ImmutableComponents extends Uri StringableComponents to afford immutable modification of component values.

Uri-Interop defines factory and parser interfaces:

  • UriFactory UriEncodedFactory affords creating a new URI instance from URI component values.
  • UriParser UriEncodedParser affords creating a new URI instance from a URI string.

Uri-Interop defines these marker interfaces to codify expectations around RFC compliance:

  • Rfc3986Compliant UriEncoded marks any of the other interfaces to indicate RFC 3986 compliance.
  • Rfc3987Compliant IriEncoded marks any of the other interfaces to indicate RFC 3987 compliance.

Finally, Uri-Interop defines an interface of PHPStan type aliases, UriTypeAliases, to aid static analysis.

If you use URI objects (who doesn't!) and are interested an interoperable set of interfaces, please offer your comments and criticism as issues or pull requests out at Github.


UPDATE (Mon 17 Mar 2025): Based on review, some of the interface names have changed since the initial announcement, though their purpose remains the same.


Stream-Interop Now Open For Public Review

Many thanks to the private reviewers and collaborators on this project thus far. Special thanks to Nathan Bishop for starting this project, and for his generous donation of the Github organization and the Packagist vendor space. Special thanks also to Akihito Koriyama for his encouragement and advice on this and related *-interop projects.

After a period of private review, the stream-interop/interface package is now open for public review.

Stream-Interop publishes interoperable interfaces providing a more object-oriented approach to encapsulating and interacting with stream resources in PHP 8.4+. It reflects, refines, and reconciles the common practices identified within several pre-existing projects.

The package defines separate interfaces for various affordances around stream resources so that (1) implementations can advertise well-tailored affordances, and (2) consumers can typehint to the specific affordances they require for specific situations:

The reference implementations demonstrate how the interfaces can be combined in various ways to implement everything from a simple ConsumableFileStream to a lazy ghost ReadWriteFile.

If you use streams and are interested an interoperable set of interfaces, please offer your comments and criticism as issues or PRs out at Github.


Front-Interop: Interoperable Front Controller Interfaces

The front-interop project defines a set of interoperable interfaces for the FrontController pattern in PHP to encapsulate the request-receiving and response-sending behaviors at the outermost boundary of your HTTP presentation layer:

  • RequestHandler::handleRequest() : ResponseHandler encapsulates the logic to transform an incoming HTTP request to an outgoing HTTP response.

  • ResponseHandler::handleResponse() : void encapsulates the logic to send or emit an outgoing response.

These interfaces are completely independent of any particular request/response library. That is, they will work with PSR-7, Symfony HttpFoundation, Sapien, or any other request/response library.

Read more about the project here.


PHP-Styler 0.13.0 Released

PHP-Styler is a companion to PHP-Parser for reconstructing PHP code after it has been deconstructed into an abstract syntax tree.

I've done quite a bit with PHP-Styler since the last-announced 0.5.0 release a few weeks ago; the most important things are:

  1. Comment handling has been greatly improved, including greater fidelity regarding inline comments.
  2. There are now instructions on how to customize the Styler presentation.
  3. The demo site looks a lot nicer, and has a pretty good logo now.

Even though PHP-Styler is still in its 0.x release series, and is good enough for me to use on my own projects, it would benefit from wider usage to find edge cases. If you try out the Styler and find problems with it, please enter an issue!

Further, if you build a customized style, please let me know, and I can advertise it on the site.


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.


Teller: Money for Legacy Applications in PHP

I am happy to say that MoneyPHP 4.2.0 incorporates my Teller contribution for handling monetary math in legacy codebases.

Legacy codebases often use float math for monetary calculations, which leads to problems with fractions-of-pennies in monetary amounts. The proper solution is to introduce a Money object, and use Money objects in place of float math. However, doing so can be quite a hardship, especially when the float values need to be moved to and from database storage. Intercepting and converting the float values (often represented by strings) into Money objects on their way out of the database, then reconverting them to their original format on their way back to the database, can be very difficult and time-consuming.

To help ease the transition from float math to Money objects, use a Teller instance to replace float math for monetary calculations in place. Without Teller, your math might look like this ...

$price = 234.56;
$discount = 0.05;
$discountAmount = $price * $discount;
// => 11.728

... and you end up with an extraneous $0.008. Whereas with Teller, you can replace the money math in place, without needing to create Money objects:

// create a Teller, perhaps in a DI container ...
$teller = \Money\Teller::USD();

// ... then use it for monetary math:
$price = 234.56;
$discount = 0.05;
$discountAmount = $teller->multiply($price, $discount);
// => '11.73'

The main drawback is that you cannot use two different currencies with the Teller; you can use only one.

Teller supports the full range of Money operations. You can read the documentation for it here.


There Is Only One Science

"All science is either physics or stamp-collecting."

-- Ernest Rutherford

I recall hearing it first another way, I think via Heinlein: "There is only one science. It is called 'physics.' Everything else is stamp-collecting." I like that phrasing better, because it highlights the disparity more plainly.

Anything that calls itself "science" must be based on observation, but only physics provides a predictive capacity from those observations. The other "sciences" consist only of collections of observations; those observations can be categorized, organized, and reorganized as new observations are collected, but they do not provide a predictive capacity. They do not tell you what your next observation will be with much certainty. If you find a strongly predictive capacity, chances are that there is a physics component underlying the observations.

Thus, the further you get from "physics", the further you get from "science."