# Fully overridable factory methods

- Thread: https://externals.io/message/126452
- Original archive: https://news-web.php.net/php.internals/126452
- Started by Juris Evertovskis on 2025-02-18T16:52:17+00:00
- Messages: 6
- 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

---

## #126452 — Juris Evertovskis — 2025-02-18T16:52:17+00:00

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

Hi,

 

A recent discussion brought up the need for multiple constructors for the
same class.

https://externals.io/message/126428

 

The need was mostly answered by suggestions to use factory methods.

However on an earlier discussion it was discussed how it's fine and even
useful for constructor methods to violate the LSP.

https://externals.io/message/121789

 

I've felt a similar need on some projects. To have a named factory method
that would have an overridable signature.

 

Maybe a new keyword like `factory`that would make the method only callable
statically? I'm thinking of something like

 

    class Triangle extends Polygon

    {

        public factory method fromPoints(Point $p1, Point $p2, Point $p3):
static
        {

            // .

        }

 

        public factory method wrap(static|string $triangle): static
        {

            return $triangle instanceof static ? $triangle :
static::fromWkt($triangle);

        }

    }

 

Sure, I'm not saying this API is currently impossible. I can do the more
narrow checks within the methods and throw from there. But sometimes it's
useful to precisely describe the acceptable arguments in the method
signature.

 

What do you think? Would such a feature be useless and/or harmful?

 

BR,

Juris

---

## #126453 — Rob Landers — 2025-02-18T17:31:11+00:00

URL: https://externals.io/message/126452#126453 · In reply to: #126452

> Hi,
>  
> A recent discussion brought up the need for multiple constructors for the same class.
> https://externals.io/message/126428
>  
> The need was mostly answered by suggestions to use factory methods.
> However on an earlier discussion it was discussed how it’s fine and even useful for constructor methods to violate the LSP.
> https://externals.io/message/121789
>  
> I’ve felt a similar need on some projects. To have a named factory method that would have an overridable signature.
>  
> Maybe a new keyword like `factory`that would make the method only callable statically? I’m thinking of something like
>  
>     class Triangle extends Polygon
>     {
> 
>         public factory method fromPoints(Point $p1, Point $p2, Point $p3): static
>         {
> 
> 
>             // …
>         }
>  
> 
>         public factory method wrap(static|string $triangle): static
>         {
> 
> 
>             return $triangle instanceof static ? $triangle : static::fromWkt($triangle);
>         }
>     }
>  
> Sure, I’m not saying this API is currently impossible. I can do the more narrow checks within the methods and throw from there. But sometimes it’s useful to precisely describe the acceptable arguments in the method signature.
>  
> What do you think? Would such a feature be useless and/or harmful?
>  
> BR,
> Juris

Hey Juris:

You may be interested in a super-draft RFC that I’ll propose in the next few days… (just fixing a couple of edge cases in the implementation) it allows for nested classes and can support what you are thinking. I will add this use case to the RFC.

Adapting your example:

class Triangle extends Polygon {
  public final class fromPoints extends Triangle {
    public function __construct(Point $p1, Point $p2, Point $p3) {}
  }

  public final class wrap extends Triangle {
    public function __construct(Triangle $triangle) {}
  }
}

$triange = new Triangle::fromPoints(...$points);

I don't know if this is what you are thinking of, but it basically allows the expression you are suggesting. If we were to implement your idea though, then nested classes would probably be impossible (and vice-versa).

— Rob

---

## #126454 — tim@bastelstu.be — 2025-02-18T19:24:41+00:00

URL: https://externals.io/message/126452#126454 · In reply to: #126453

Hi


> You may be interested in a super-draft RFC that I’ll propose in the next few days…

May I kindly request some breathing room with regard to new RFC 
discussions so that folks are able to give each RFC the proper 
attention. The list currently is super active. We have:

- 2 Votes in progress (FCC in constexpr, aviz for static)
- 6 discussions with RFC written (NoDiscard, get_error_handler, URI, 
Compression, Optional Interfaces, Pipe)
- and several non-RFC or pre-RFC discussions (Async, Constructor 
Overloading, Not Null Assertions, …)

This is already approximately 3 RFC discussions too many. Perhaps wait 
until Mid-March or so? I expect at least some of the currently discussed 
RFCs to be in voting by then.

Best regards
Tim Düsterhus

---

## #126455 — Juris Evertovskis — 2025-02-18T19:29:20+00:00

URL: https://externals.io/message/126452#126455 · In reply to: #126453

> 
> 
> > […]
> 
> Hey Juris:
> 
> You may be interested in a super-draft RFC that I'll propose in the 
> next few days... (just fixing a couple of edge cases in the 
> implementation) it allows for nested classes and can support what you 
> are thinking. I will add this use case to the RFC.
> 
> Adapting your example:
> 
> class Triangle extends Polygon {
> public final class fromPoints extends Triangle {
> public function __construct(Point $p1, Point $p2, Point $p3) {}
> }
> 
> public final class wrap extends Triangle {
> public function __construct(Triangle $triangle) {}
> }
> }
> 
> $triange = new Triangle::fromPoints(...$points);
> 
> I don't know if this is what you are thinking of, but it basically 
> allows the expression you are suggesting. If we were to implement your 
> idea though, then nested classes would probably be impossible (and 
> vice-versa).
> 
> -- Rob

Hi Rob,

I'm a bit confused about the use of `final` in your example.

Would it allow defining Polygon::fromPoints such that it accepts 
variable number of arguments and Triangle::fromPoints such that it only 
accepts exactly 3 args?

Or having a hierarchy like `Square extends Rectangle extends Polygon` 
while having a wrap with the following covariant behaviour?
- Polygon::wrap(Polygon $poly) -- accepts any Polygons
- Rectangle::wrap(Rectangle $poly) -- accepts Rectangles and Squares, 
but not other Polygons
- Square::wrap(Square $poly) -- only accepts Square arguments

Juris

---

## #126456 — Rob Landers — 2025-02-18T19:37:09+00:00

URL: https://externals.io/message/126452#126456 · In reply to: #126454

> Hi
> 
> 
> > […]
> 
> May I kindly request some breathing room with regard to new RFC 
> discussions so that folks are able to give each RFC the proper 
> attention. The list currently is super active. We have:
> 
> - 2 Votes in progress (FCC in constexpr, aviz for static)
> - 6 discussions with RFC written (NoDiscard, get_error_handler, URI, 
> Compression, Optional Interfaces, Pipe)
> - and several non-RFC or pre-RFC discussions (Async, Constructor 
> Overloading, Not Null Assertions, …)
> 
> This is already approximately 3 RFC discussions too many. Perhaps wait 
> until Mid-March or so? I expect at least some of the currently discussed 
> RFCs to be in voting by then.
> 
> Best regards
> Tim Düsterhus
> 

Hi Tim,

I assume you are speaking to me, personally?

This will be opened as a draft before proposing it to the list, which will indeed (probably) be around mid-March, pending solicited feedback, so no worries. I also have a couple drafts/in-discussion to close that this new RFC will supersede.

— Rob

---

## #126459 — Rob Landers — 2025-02-18T20:16:29+00:00

URL: https://externals.io/message/126452#126459 · In reply to: #126455

> 
> 
> > […]
> Hi Rob,
> 
> I'm a bit confused about the use of `final` in your example.
> 

The gist is that visibility applies to the outer class while final/readonly/etc apply to the inner class. So, in this case it is public (can instantiate it from outside the class) and final (cannot extend it, which would be weird to extend an inner class. I'm still debating if I want to make inner classes final by default -- but that will probably come up in the actual rfc discussion).

There's already a thread discussing this topic from a few weeks ago, so I don't mind discussing some details and "testing the waters", as they say.

> Would it allow defining Polygon::fromPoints such that it accepts variable number of arguments and Triangle::fromPoints such that it only accepts exactly 3 args?
> 
> Or having a hierarchy like `Square extends Rectangle extends Polygon` while having a wrap with the following covariant behaviour?
> - Polygon::wrap(Polygon $poly) -- accepts any Polygons
> - Rectangle::wrap(Rectangle $poly) -- accepts Rectangles and Squares, but not other Polygons
> - Square::wrap(Square $poly) -- only accepts Square arguments
> Juris
> 

The inner class is its own class and belongs to the outer class -- it is inherited similar to a static property. So to answer your question, yes, all of that is possible. From inside the outer class you can even use static:: or self:: or parent:: to access the inner classes as well, so it should be quite flexible for these types of hierarchies and allow for some nice generic code reuse (e.g. parent::wrap($this))

— Rob
