Am 2025-02-24 12:08, schrieb Nicolas Grekas: > > The situation I'm telling about is when one will accept an argument > > described as > > function (\Uri\WhatWg\Url $url) > > > > If the Url class is final, this signature means only one possible > > implementation can ever be passed: the native one. Composition cannot > > be > > achieve because there's no type to compose. > > Yes, that's the point: The behavior and the type are intimately tied > together. The Uri/Url classes are representing values, not services. You > wouldn't extend an int either. For DateTimeImmutable inheritance being > legal causes a ton of needless bugs (especially around serialization > behavior). >
DatetimeImmutable is a good example of community-proven usefulness for inheritance: the carbon package has a huge success because it does add a ton of nice helpers (that are better maintained in userland) while still providing compatibility with functions that accept the native type. The fact that the native implementation had bugs when inheritance was used doesn't mean inheritance is a problem. It's just bugs that need to be fixed. Conceptually nothing makes those bugs inevitable. Closing the class would have hindered community-innovation. The same applies here. Then, if people make mistakes in their child classes, their problem. But the community shouldn't be forbidden to extend a class just because mistakes can happen. > > Fine-tuning the behavior provided by the RFC is what we might be most > > interested in, but we should not forget that we also ship a type. By > > making > > For a given specification (RFC 3986 / WHATWG) there is exactly one > correct interpretation of a given URL. “Fine-tuning” means that you are > no longer following the specification. > See Carbon example, it's not specifically about fine-tuning. We cannot anticipate how creative people are. Nor should we prevent them from being so, from the PoV of the PHP engine designers. > the type non-final, we keep things open enough for userland to build on > > it. > > This works: > > final class HttpUrl { > private readonly \Uri\Rfc3986\Uri $uri; > public function __construct(string $uri) { > $this->uri = new \Uri\Rfc3986\Uri($uri); > if ($this->uri->getScheme() !== 'http') { > throw new ValueError('Scheme must be http'); > } > } > public function toRfc3986(): \Uri\Rfc3986\Uri { > return $this->uri; > } > } > > Userland can easily build their convenience wrappers around the classes, > they just need to export them to the native classes which will then > guarantee that the result is fully validated and actually a valid > URI/URL. Keep in mind that the ext/uri extension will always be > available, thus users can rely on the native implementation. > This is an example of what I call community-fragmentation: one hardcoded type that should only be used as an implementation detail, but will leak at type-boundaries and will make things inflexible. Each project will have to think about such designs, and many more will get it wrong. (We will be the ones to blame since we're the ones educated on the topic.) > > By making the classes non-final, there will be one base type to build > > upon > > for userland. > > (the alternative would be to define native UrlInterface, but that'd > > increase complexity for little to no gain IMHO - althought that'd solve > > my > > main concern). > > Mate already explained why a native UriInterface was intentionally > removed from the RFC in https://news-web.php.net/php.internals/126425. > The only one option remains - making the class non-final. Nicolas