Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:123640 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id AE9E81A009C for ; Sun, 16 Jun 2024 17:16:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1718558233; bh=j8LZlQwEpryuzpLXRaBCrGjoLDtfNuWr8kF8E4fb3bU=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=U6y0gEEP5Wp9wemERpMqrgAiY8gcJbmXJQWda3ttGYEN4gjJqNk7KwCfNR9uWAZap 5RLJ2Vew5hSTM9spGBMh0309izZU67/b2P0jbelkJmVlJKU7gZbJKWURzCrehJNUey YTpb/VINU2Fr+IBkdWU0DYt8eG7FF0hl8UTdRNMVXAInILM3qUrwoO0wydUSo10zpw t1hvZV3YtmdB8qycEdBl9kM3n+u1IpKKFszvJ7UUYRvWp62SlK2hapHBlEJ0c2oCjQ MZPylvGCTrNA+MjOdPTNHISriQBlaJpdJHFUn/6XqRtQ/1f8UN48IhAMQkvpcb/DoT skjlp7+Vg0B+g== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 6D8FB18005A for ; Sun, 16 Jun 2024 17:17:12 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from mail-vk1-f182.google.com (mail-vk1-f182.google.com [209.85.221.182]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 16 Jun 2024 17:17:11 +0000 (UTC) Received: by mail-vk1-f182.google.com with SMTP id 71dfb90a1353d-4ecf1013d73so1068611e0c.2 for ; Sun, 16 Jun 2024 10:16:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1718558160; x=1719162960; darn=lists.php.net; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=H1wWxcFBT/Tr0Mnb31rr4uaDsWzQBz4DRdK/ucHBHHA=; b=mp3g1cjjDfek5b/EQzCzy5PNCjqerFHDk5LuqAVZ3iHwKuDaNlyT/diV/ZircjuKdh uzYJZiBjbZAn2AvAhtT7t/w+FR2fxAs/P6B5QGCkFWVhiY56dDyQT34uTxiaV2cEaVYJ vb9pWNYZWJNs8rsAk6YvuwsW027Nj/9axcBomyuvPAWWLfF3uEfIaPm+DOuK0T1Rw/Za AFYqIFxRsMvA5qTLAHeSmOGuA2eMnbXLXxGptFMMh0v6SefPlxH2QvTarTevdtdsxN4i mAwb8Oo0QRh3jxQGy62rpRM0ypJt42buA0BQ8z/NKYhlWylOCx8tqmY0re4dqy8F8YHX emIA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1718558160; x=1719162960; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=H1wWxcFBT/Tr0Mnb31rr4uaDsWzQBz4DRdK/ucHBHHA=; b=r/Lu+eund6EIrTVEDXLrXfGZryOzBVXVDBm9f4sSw1dWzmEeisUOJHkkqkfHo7KOBX e5ej73D7wY3w8JHyKAXvYoNBtIBia807vk65ue5uORrk+Zw/+EZFTNrkcVUEdo4jzS37 xa8LlRk/zyHP4lHl+5Ozc7Atxd5wimxKeFU0+gtXCNW7n6xHQhxPYcGiCihOXJpDJjbF P/jYWRVMtZlHUmhDuFQesFYqjap/JbImUEPz5Rosa3i0Lx3n17T6vZw79w0Egt1EV0Qp EQSX/NQ5cwoOxrK6q/khrZsT2m6itpwI6+YcLODfSMmNTh5EWYCLXE7Z96HLRcsJFqMR 6MPw== X-Gm-Message-State: AOJu0Yy6f4cqqleluq8xkJ3jV4zNLjEn6nV+d4sHQJu2XiuW4yAvCRWp 1YPbV3rwv0ObImJx7n8kaB+d+lPGHEdB98CLw4vtb4TXVghORPXNoNtzNCziIt+1Ew+IbJDlCwX 9+GKdI2kbQ6R+m/tLhdF7hPYlP72+WKqs X-Google-Smtp-Source: AGHT+IHDwO2fmf4XNlGz2R6mqmt0Uffcw9h0qnbQ4rCkU1yVf35z0XHDj7psESXI9bcJMeVgcFauaFyGuGQ5SxAGhcE= X-Received: by 2002:a05:6122:1351:b0:4ec:fa1c:decf with SMTP id 71dfb90a1353d-4ee3df849e6mr7743594e0c.5.1718558159684; Sun, 16 Jun 2024 10:15:59 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 References: <0cf69a14-f1b5-4077-9d91-d7b579485eec@scriptfusion.com> <936e1aa3-48cc-4552-9f68-676ebcdeb596@rwec.co.uk> <1fc2f2d0-718f-45ec-8968-b66a1bde686e@scriptfusion.com> In-Reply-To: Date: Sun, 16 Jun 2024 11:15:48 -0600 Message-ID: Subject: Re: [PHP-DEV] Static class To: Andreas Hennings Cc: internals@lists.php.net Content-Type: multipart/alternative; boundary="000000000000a24f1c061b0500c2" From: lnearwaju@gmail.com (Lanre) --000000000000a24f1c061b0500c2 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Here is a relevant example usage of static classes/polymorphism on my personal site/app: https://gist.github.com/oplanre/6894f56fb61134ee5ea93cf262ba3662 On Sun, Jun 16, 2024 at 10:38=E2=80=AFAM Andreas Hennings wrote: > Regarding function autoloading: > > A more interesting question to me is a convention where to put each > function. > With classes, PSR-4 tells us exactly what one can expect to find in a > class file with a given name. > Perhaps a separate directory tree per package, with one file per > sub-namespace? > Or just a package-wide functions.php? > > > Regarding static methods vs functions. > > From the older thread, I can see different levels of possible > objection to all-static classes: > 1. "Every static method should instead be either a non-static method, > or a regular procedural function." > 2. "Static methods are acceptable, but no class should exist that has > only static methods." > 3. "Static methods and all-static classes are acceptable, but no new > language feature is needed to specifically support all-static > classes." > 4. "A new language feature for all-static classes could be acceptable, > but it should be very minimal and declarative." > > I see myself somewhere between 3 and 4. > > The DX of static methods can be evaluated from two perspectives: The > calling side, and the declaration side. > On the calling code side, the class of a static method call is usually > visible in the place where the call is made, > For a function call, the namespace is usually only visible in the > imports list at the top of the file. > The class name that is part of a static method call hints at an object > type, whereas for a namespace fragment it may not be really clear what > it describes. > In a static factory call, like `Color::fromRgb($red, $green, $blue)`, > I would argue that having the class name as part of the call improves > DX. > On the declaration side, I like to have a static factory in the same > place as the class itself. > > For other cases, it can vary whether having a class name as part of > the call improves DX or not. > On the declaration side, static methods do bring some additional > features, that is, to split out some parts into private and protected > methods. > This said, in many cases such classes could be converted to non-static > instantiable classes. > > One benefit of static methods is how they can be part of incremental > refactoring. > - Split some functionality into a private method. > - Notice that this method does not depend on any object state, so we > make it static. > - Now that it is static, it could as well be public, if we clean up > the signature a bit. This way it can be reused and tested > independently. > - Now that it is public static, I rather want to move it out of the > class, into a utility class. > - Now that we have this standalone method, we could convert it to a > procedural function. > - OR, we may decide that the entire utility class should actually be > converted to a service, and the method to be not static. > > Or: > - Move some functionality out of the constructor into a static factory. > - Let the static factory return different implementations based on the > parameters. E.g. Color::fromRbg() could return a GrayscaleColor object > or a RainbowColor object, depending on parameter values. > - Make `Color` an all-static class, because the specific > implementations have dedicated classes. > - Convert the static methods on `Color` into object methods on > `ColorFromRgbFactory`. > - OR, we discover static polymorphism (*) and "abstract protected > static function". > - Later we might decide that this was awkward, and convert everything > to object methods. > > In both of these scenarios we have steps with an all-static class. > There are possibilities to move away from that. > But in some cases the all-static class is actually the sweet spot > where we want to stay. > > Another case for an all-static class would be a class that only > contains class constants. > Yes, we can use `define(...)`, but sometimes we want class constants, > for similar arguments as above. > > A third case is if the all-static class already exists, as part of a > legacy codebase, and we could not break it up even if we wanted to. > > If we do end up with an all-static class, even as an intermediate > state, it can be useful to declare this in some way. > Developers should immediately see whether a class is instantiable or not. > > A trait or a base class or an explicit private constructor can do the > job, but it won't be as universal as a language feature. > > > On the other hand: > From the older proposal by Lanre Waju: > > > All methods and variables in the class are implicitly static. > > I would very much oppose this. > These implicit semantics would make it harder and error-prone to move > methods around from a regular class to an all-static class. > And it would be confusing to look at such a method, because typically > the class-level static declaration won't be visible when you look at > the method. > Also think of the noise in git commits caused by the conversion to an > all-static class. > (Btw the same problem applies to class-level readonly) > > To me, the best version of a class-level "static" keyword would be as > a contract, and nothing more: > - The class cannot be instantiated. > - It cannot have a constructor or destructor, or any magic methods > that only apply to objects. > - It can only extend or be extended by other static classes. > (otherwise it should be declared "abstract" instead) > > The same could be achieved with a native base class or trait with > private constructor, or even the same thing from php-fig. > This would be less clear and less powerful than a static keyword, but > also less invasive to the language. > > (One such language feature could be a language-level base class or > trait, simply for the sake of unification) > > From this thread: > > > In particular: static classes are implied final and they cannot inherit > from any other class. > > With the version of the static keyword as above, I would be against > this limitation. > Static polymorphism (*) is possible today, and it should be perfectly > fine to slap a "static" keyword on these classes, if they are > all-static. > This is not about whether or not we like or dislike static > polymorphism, but about not attaching unrelated meaning to a keyword. > An explicit "static final class" is more obvious than "static class" > with implicit final. > > > (*) The term "static polymorphism" is a bit questionable. > I obviously mean this: > > class B { > static function f() { > return static::g(); > } > protected static function g() { > return null; > } > } > > class C extends B { > protected static function g() { > return 5; > } > } > > assert(C::f() =3D=3D=3D 5); > > I have used this in the past, and I am now questioning my choices in > some of these cases. > Still I would not consider it an anti-pattern. > > > --- Andreas > --000000000000a24f1c061b0500c2 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Here is a relevant example usage of static classes/polymor= phism on my personal site/app:
https://gist.github.c= om/oplanre/6894f56fb61134ee5ea93cf262ba3662

On Sun, Jun 16, 2024 at = 10:38=E2=80=AFAM Andreas Hennings <andreas@dqxtech.net> wrote:
Regarding function autoloading:

A more interesting question to me is a convention where to put each functio= n.
With classes, PSR-4 tells us exactly what one can expect to find in a
class file with a given name.
Perhaps a separate directory tree per package, with one file per sub-namesp= ace?
Or just a package-wide functions.php?


Regarding static methods vs functions.

From the older thread, I can see different levels of possible
objection to all-static classes:
1. "Every static method should instead be either a non-static method,<= br> or a regular procedural function."
2. "Static methods are acceptable, but no class should exist that has<= br> only static methods."
3. "Static methods and all-static classes are acceptable, but no new language feature is needed to specifically support all-static
classes."
4. "A new language feature for all-static classes could be acceptable,=
but it should be very minimal and declarative."

I see myself somewhere between 3 and 4.

The DX of static methods can be evaluated from two perspectives: The
calling side, and the declaration side.
On the calling code side, the class of a static method call is usually
visible in the place where the call is made,
For a function call, the namespace is usually only visible in the
imports list at the top of the file.
The class name that is part of a static method call hints at an object
type, whereas for a namespace fragment it may not be really clear what
it describes.
In a static factory call, like `Color::fromRgb($red, $green, $blue)`,
I would argue that having the class name as part of the call improves
DX.
On the declaration side, I like to have a static factory in the same
place as the class itself.

For other cases, it can vary whether having a class name as part of
the call improves DX or not.
On the declaration side, static methods do bring some additional
features, that is, to split out some parts into private and protected
methods.
This said, in many cases such classes could be converted to non-static
instantiable classes.

One benefit of static methods is how they can be part of incremental
refactoring.
- Split some functionality into a private method.
- Notice that this method does not depend on any object state, so we
make it static.
- Now that it is static, it could as well be public, if we clean up
the signature a bit. This way it can be reused and tested
independently.
- Now that it is public static, I rather want to move it out of the
class, into a utility class.
- Now that we have this standalone method, we could convert it to a
procedural function.
- OR, we may decide that the entire utility class should actually be
converted to a service, and the method to be not static.

Or:
- Move some functionality out of the constructor into a static factory.
- Let the static factory return different implementations based on the
parameters. E.g. Color::fromRbg() could return a GrayscaleColor object
or a RainbowColor object, depending on parameter values.
- Make `Color` an all-static class, because the specific
implementations have dedicated classes.
- Convert the static methods on `Color` into object methods on
`ColorFromRgbFactory`.
- OR, we discover static polymorphism (*) and "abstract protected
static function".
- Later we might decide that this was awkward, and convert everything
to object methods.

In both of these scenarios we have steps with an all-static class.
There are possibilities to move away from that.
But in some cases the all-static class is actually the sweet spot
where we want to stay.

Another case for an all-static class would be a class that only
contains class constants.
Yes, we can use `define(...)`, but sometimes we want class constants,
for similar arguments as above.

A third case is if the all-static class already exists, as part of a
legacy codebase, and we could not break it up even if we wanted to.

If we do end up with an all-static class, even as an intermediate
state, it can be useful to declare this in some way.
Developers should immediately see whether a class is instantiable or not.
A trait or a base class or an explicit private constructor can do the
job, but it won't be as universal as a language feature.


On the other hand:
From the older proposal by Lanre Waju:

> All methods and variables in the class are implicitly static.

I would very much oppose this.
These implicit semantics would make it harder and error-prone to move
methods around from a regular class to an all-static class.
And it would be confusing to look at such a method, because typically
the class-level static declaration won't be visible when you look at the method.
Also think of the noise in git commits caused by the conversion to an
all-static class.
(Btw the same problem applies to class-level readonly)

To me, the best version of a class-level "static" keyword would b= e as
a contract, and nothing more:
- The class cannot be instantiated.
- It cannot have a constructor or destructor, or any magic methods
that only apply to objects.
- It can only extend or be extended by other static classes.
(otherwise it should be declared "abstract" instead)

The same could be achieved with a native base class or trait with
private constructor, or even the same thing from php-fig.
This would be less clear and less powerful than a static keyword, but
also less invasive to the language.

(One such language feature could be a language-level base class or
trait, simply for the sake of unification)

From this thread:

> In particular: static classes are implied final and they cannot inheri= t from any other class.

With the version of the static keyword as above, I would be against
this limitation.
Static polymorphism (*) is possible today, and it should be perfectly
fine to slap a "static" keyword on these classes, if they are
all-static.
This is not about whether or not we like or dislike static
polymorphism, but about not attaching unrelated meaning to a keyword.
An explicit "static final class" is more obvious than "stati= c class"
with implicit final.


(*) The term "static polymorphism" is a bit questionable.
I obviously mean this:

class B {
=C2=A0 static function f() {
=C2=A0 =C2=A0 return static::g();
=C2=A0 }
=C2=A0 protected static function g() {
=C2=A0 =C2=A0 return null;
=C2=A0 }
}

class C extends B {
=C2=A0 protected static function g() {
=C2=A0 =C2=A0 return 5;
=C2=A0 }
}

assert(C::f() =3D=3D=3D 5);

I have used this in the past, and I am now questioning my choices in
some of these cases.
Still I would not consider it an anti-pattern.


--- Andreas
--000000000000a24f1c061b0500c2--