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 AM Andreas Hennings <andr...@dqxtech.net> 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() === 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 >