Re: Static class

From: Date: Sun, 16 Jun 2024 17:15:48 +0000
Subject: Re: Static class
References: 1 2 3 4 5 6 7 8 9  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message
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 <[email protected]>
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
>


Thread (32 messages)

« previous php.internals (#123640) next »