Hey Internals,
Recently i've been working on an older code base, where we have a lot
of classes with all
static methods. We've been moving to injecting the classes, and
calling the methods as if they
were not static.
I wanted to add interfaces to these classes, with non static methods,
so we can pretend in our implementation they are not static, and allow
us to easier switch to non static methods in the future.
This is (currently) not allowed by the language: https://3v4l.org/WKdvM
Is there any chance of this being possible in future versions of PHP?
This would only allow 'normal' methods to become static. Making static
methods non static would break Liskov substitution principle in PHP as
you may not be able to call the method in the same way. This means you
can't have a 'normal' method become static in an inherited class, and
then become 'normal' again in another inherited class. This works the
same way with co/contra-variance in return/parameter types:
(https://3v4l.org/j1SO9)
Hey Internals,
Recently i've been working on an older code base, where we have a lot
of classes with all
static methods. We've been moving to injecting the classes, and
calling the methods as if they
were not static.I wanted to add interfaces to these classes, with non static methods,
so we can pretend in our implementation they are not static, and allow
us to easier switch to non static methods in the future.This is (currently) not allowed by the language: https://3v4l.org/WKdvM
Is there any chance of this being possible in future versions of PHP?
This would only allow 'normal' methods to become static. Making static
methods non static would break Liskov substitution principle in PHP as
you may not be able to call the method in the same way. This means you
can't have a 'normal' method become static in an inherited class, and
then become 'normal' again in another inherited class. This works the
same way with co/contra-variance in return/parameter types:
(https://3v4l.org/j1SO9)--
To unsubscribe, visit: https://www.php.net/unsub.php
It's maybe possible in the special case of implementing interfaces, but
it's unsound for general inheritance:
<?php
class A {
public int $someInt = 0;
public function getSomeInt() : int {
return $this->someInt;
}
}
class B extends A {
public static function getSomeInt() : int {
return parent::getSomeInt();
}
}
It's maybe possible in the special case of implementing interfaces, but
it's unsound for general inheritance:<?php
class A {
public int $someInt = 0;public function getSomeInt() : int { return $this->someInt; }
}
class B extends A {
public static function getSomeInt() : int {
return parent::getSomeInt();
}
}
Can you explain what this is illustrating? Right now, it doesn't
compile, but that's what Gert was suggesting be changed, so the 3v4l
link isn't very enlightening.
From the point of view of the contract of the class, it's fine:
$foo->getSomeInt() is still a legal call if $foo is actually an instance
of B.
Are you saying that having the parent::getSomeInt() call fail would be
problematic?
Regards,
--
Rowan Tommins
[IMSoP]
Are you saying that having the parent::getSomeInt() call fail would be
problematic?
Yes, that's where this becomes unsound – you can call the child method
statically, but the parent call assumes a dynamic instance.
This is just my perspective (as someone building a tool that helps prevent
people from shooting themselves in the foot), but PHP should not allow
more unsound behaviour than it already does.
Best wishes,
Matt
On Mon, Mar 15, 2021 at 2:16 PM Matthew Brown matthewmatthew@gmail.com
wrote:
On Sun, 14 Mar 2021 at 18:09, Rowan Tommins rowan.collins@gmail.com
wrote:Are you saying that having the parent::getSomeInt() call fail would be
problematic?Yes, that's where this becomes unsound – you can call the child method
statically, but the parent call assumes a dynamic instance.This is just my perspective (as someone building a tool that helps prevent
people from shooting themselves in the foot), but PHP should not allow
more unsound behaviour than it already does.
I'm not sure I follow your point. The fact that something is compatible,
does not mean that you can just blindly perform a forwarding call. For
example, consider this:
class A {
public function method(string $x) {}
}
class B extends A {
public function method(string|int $x) {
parent::method($x);
}
}
B::method() is compatible with A::method() from a typesystem perspective.
But that doesn't mean that a parent::method() call will work. The above
code is simply an implementation bug in B::method(), but doesn't say
anything fundamental about the compatibility of the signatures. I would
argue that the same holds in your example.
Disclaimer: I have some serious doubts that allowing non-static->static
changes is worthwhile, I just don't see why it would be outright unsound.
Regards,
Nikita
I'm not sure I follow your point. The fact that something is compatible,
does not mean that you can just blindly perform a forwarding call. For
example, consider this:class A {
public function method(string $x) {}
}class B extends A {
public function method(string|int $x) {
parent::method($x);
}
}B::method() is compatible with A::method() from a typesystem perspective.
But that doesn't mean that a parent::method() call will work. The above
code is simply an implementation bug in B::method(), but doesn't say
anything fundamental about the compatibility of the signatures. I would
argue that the same holds in your example.Disclaimer: I have some serious doubts that allowing non-static->static
changes is worthwhile, I just don't see why it would be outright unsound.Regards,
Nikita
Ah OK! When I say "unsound" I mean "the type system implied by PHP's
behaviour allows code that a static analysis tool cannot catch".
It's trivial for a static analysis tool to find a bug in your example:
https://psalm.dev/r/3cd498b539
If PHP changed to allow child methods of instance methods to become static,
static analysis tools could follow suit, but that would invite undetectable
unsound behaviour.
The most well-known example of PHP's existing unsoundness is its treatment
of array keys, something that static analysis tools cannot detect:
https://psalm.dev/r/4640ef8f22 vs https://3v4l.org/XQTWc
On Sun, 14 Mar 2021 at 18:09, Rowan Tommins <rowan.collins@gmail.com
mailto:rowan.collins@gmail.com> wrote:Are you saying that having the parent::getSomeInt() call fail would be problematic?
Yes, that's where this becomes unsound – you can call the child
method statically, but the parent call assumes a dynamic instance.
This isn't really to do with changing the signature of the method
though; it will give you exactly the same error as if you gave the
static and non-static methods different names:
class A {
public int $someInt = 0;
public function getSomeInt() : int {
return $this->someInt;
}
}
class B extends A {
public static function getSomeIntStatically() : int {
return parent::getSomeInt(); // This call is invalid; this is
currently only reported when the method is run
}
}
Regards,
--
Rowan Tommins
[IMSoP]
Hey Internals,
Recently i've been working on an older code base, where we have a lot
of classes with all
static methods. We've been moving to injecting the classes, and
calling the methods as if they
were not static.I wanted to add interfaces to these classes, with non static methods,
so we can pretend in our implementation they are not static, and allow
us to easier switch to non static methods in the future.
I've encountered this issue working with legacy codebase.
I recommend having two versions of the class, the old with static methods
and the new with non-static methods.
Logic would stay only in one of them, having the other as a wrapper.
Migrating from static to non-static would change the injection as well as
the calling style.
This is (currently) not allowed by the language: https://3v4l.org/WKdvM
Is there any chance of this being possible in future versions of PHP?
This would only allow 'normal' methods to become static. Making static
methods non static would break Liskov substitution principle in PHP as
you may not be able to call the method in the same way. This means you
can't have a 'normal' method become static in an inherited class, and
then become 'normal' again in another inherited class. This works the
same way with co/contra-variance in return/parameter types:
(https://3v4l.org/j1SO9)
The issue exists also for class extended, not only for interface
implemented.
Even if in general, static and non-static method have different rules for
inheritance and they must stay in general separated,
this idea is not completely bad in PHP where the concept of late static
binding that happens when one calls
$object->staticMethod();
could allow such a thing to have a sense of existence for upgrading from
non-static methods to a static method in the inheritance chain.
As my experience with C is not vast, I'm not sure how the inheritance chain
for static and non-static methods is represented internally
to allow such change.
Also the calling place logic would maybe need changes to seamlessly use
polymorphism logic or late static binding
depending on how the method is defined in the class of the object called,
if that isn't already done in some way.
As I want to learn more here, I will try looking it up to understand it.
But overall, it sounds like it can be avoided by choosing a separate chain
of inheritance approach. Composition over inheritance.
Regards,
Alex