# [RFC Proposal] Allow methods to 'become' static

- Thread: https://externals.io/message/113516
- Original archive: https://news-web.php.net/php.internals/113516
- Started by Gert de Pagter on 2021-03-14T16:01:41+00:00
- Messages: 8
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #113516 — Gert de Pagter — 2021-03-14T16:01:41+00:00

URL: https://externals.io/message/113516#113516

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)

---

## #113520 — Matthew Brown — 2021-03-14T19:40:31+00:00

URL: https://externals.io/message/113516#113520 · In reply to: #113516

> 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();
    }
}

https://3v4l.org/WnaES

---

## #113524 — Rowan Tommins — 2021-03-14T22:09:02+00:00

URL: https://externals.io/message/113516#113524 · In reply to: #113520

> 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]

---

## #113531 — drealecs@gmail.com — 2021-03-15T06:39:05+00:00

URL: https://externals.io/message/113516#113531 · In reply to: #113516

> 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

---

## #113535 — Matthew Brown — 2021-03-15T13:16:18+00:00

URL: https://externals.io/message/113516#113535 · In reply to: #113524

> 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

---

## #113537 — Nikita Popov — 2021-03-15T13:35:59+00:00

URL: https://externals.io/message/113516#113537 · In reply to: #113535

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:
>
> > […]
>  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

---

## #113539 — Rowan Tommins — 2021-03-15T14:49:02+00:00

URL: https://externals.io/message/113516#113539 · In reply to: #113535

> 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]

---

## #113547 — Matthew Brown — 2021-03-15T18:12:20+00:00

URL: https://externals.io/message/113516#113547 · In reply to: #113537

> 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
