php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #71419 Allow ::new() syntax.
Submitted: 2016-01-20 13:06 UTC Modified: 2017-01-29 07:33 UTC
Votes:1
Avg. Score:1.0 ± 0.0
Reproduced:0 of 1 (0.0%)
From: andreas at dqxtech dot net Assigned:
Status: Suspended Package: Class/Object related
PHP Version: 7.0.2 OS:
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: andreas at dqxtech dot net
New email:
PHP Version: OS:

 

 [2016-01-20 13:06 UTC] andreas at dqxtech dot net
Description:
------------
I often create classes that have a normal constructor, and one or more static factory methods.

On instantiation, I need to decide between "new C()" and "C::create()".
For method chaining, the "new C()" needs additional brackets, like so: "(new C())".

Switching between the two notations can be annoying.

Proposal: Allow a syntax "C::new()", which would be equivalent to "(new C())".

--------

Now.. when I had this idea, I was not aware that since PHP 7, and also in hhvm, it is possible to name a method like "new()".
https://3v4l.org/Pp68v with user-defined method new().
https://3v4l.org/YvjJ9 without such a method.
This is unfortunate, and makes this request difficult, if not impossible.

A remaining (but not really great) option would be to give every class an "implicit" static method ::new(), but allow this method to be overridden.

Maybe this was already discussed and discarded, and this is the reason why a method named "new()" is now allowed. In this case I would like this to be considered a support request. The answer could be a link to the discussion where this was decided.

Test script:
---------------
<?php

class C {}

class D {

  /**
   * Allowed since PHP 7
   */
  static function new() {return NULL;}
}

// Call implicit method new, equivalent to "new C()";
$instance = C::new();
assert($instance instanceof C);

// Call explicit method new.
$null = D::new();
assert($null === null);


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-01-25 15:36 UTC] wegvonhier+phpbugs at gmail dot net
I believe the correspondig rfc for that change is this one: https://wiki.php.net/rfc/context_sensitive_lexer (allowing reserved words such as new in more contexts, the rfc even has an example using Collection::new()->method())

Anyway, you could use a trait to make your wished pattern easier to implement (on 7.0+):

https://3v4l.org/ZQLSk
 [2016-01-25 15:44 UTC] andreas at dqxtech dot net
Thanks for the RFC link!

> Anyway, you could use a trait to make your wished pattern easier to implement (on 7.0+):

Sure.. but of course the idea here was to make this a language feature, so it would work for all classes, including those from 3rd party libraries, where I cannot add a "use Newable".

I guess it is not going to happen.
 [2016-01-25 15:54 UTC] andreas at dqxtech dot net
I think before accepting the RFC it would have been a good idea to check which of these reserved words could have a useful meaning with "::", and thus, which doors are being closed by allowing these keywords as method names.

E.g. for C::while() I don't really see anything useful other than interpreting it as a method. So it is fine to make "while" a valid method name.

For C::new(), this could be an alternative syntax for "new C()". So maybe a good idea to keep this reserved..

C::class already has a meaning. C::namespace could be given a meaning.

For now, C::new() is the only one that sticks out to me for method syntax.
 [2016-02-01 16:00 UTC] willfitch@php.net
> Proposal: Allow a syntax "C::new()", which would be equivalent to "(new C())"

You're asking for a factory method as a built-in language construct/feature. While I won't assume your situation warrants this in your opinion, I have/can never imagine why this is warranted. If your reasoning lies strictly on method chaining, static methods (factory or not) is a language feature you should use.

The primary difference between "C::new()" and "(new C())" is one character less in length.  Looking at this from a purely language perspective, you're essentially asking for an exception to a static method call.  At best, that will lead to a slippery-slope (e.g. I'd like C::delete to call C::__destruct - even though I can just use $instance = null or unset($instance)).

If you feel strongly about this, you should write up an RFC, and put it out there for discussion.  You won't get much traction in the bugs area.
 [2016-02-04 00:56 UTC] andreas at dqxtech dot net
> You're asking for a factory method as a built-in language construct/feature.

Yes, what you describe is exactly what I mean.


> If your reasoning lies strictly on method chaining, static methods (factory or not) is a language feature you should use.

But then I need to manually add this to every class. What if the class is from a 3rd party?
And does the DX benefit really justify adding empty static methods everywhere?
This is why I am looking for a build-in, implicit static method.

(you could now ask back, whether this justifies a new language feature.. which is debatable, of course)


> The primary difference between "C::new()" and "(new C())" is one character less in length.

The benefit is not in typing one character less, but:

- It becomes easier to switch between a static factory call and a new() call.
- If you don't know yet whether the class has a static factory method and/or a public constructor, you can start typing the class name and then have the IDE suggest the different options.
- You no longer need to think about adding or not adding the round brackets for method chaining.

Yes, this is only about DX, which is obviously debatable.

Examples:

Replacing "C::new()" with "C::create()" is easier than replacing "(new C())" with "C::create()".

Extending "C::new()" to "C::new()->foo()" is easier than extending "new C()" to "(new C())->foo()".

Reducing "C::new()->foo()" to C::new() is easier than reducing "(new C())->foo() to "new C()".

C::new()
to
C::new()
  ->foo()
is only one line of diff.

new C()
to
(new C())
  ->foo()
is two lines of diff.


> At best, that will lead to a slippery-slope (e.g. I'd like C::delete to call C::__destruct - even though I can just use $instance = null or unset($instance)).

Yes, it would be interesting if there are other keywords where this makes sense. But so far I cannot think of any. C::delete($object) is really not useful, because the class can already be determined from $object.

Another examples could be C::reflection() (to create a new reflection class). But I don't think this is as big a use case that it justifies a new language feature.

The refactoring from (new C()) to (C::create()), on the other hand, is something I come across all the time.


> If you feel strongly about this, you should write up an RFC, and put it out there for discussion.

Will see.
 [2016-03-11 05:45 UTC] marcio@php.net
Hi!

The main reason that could make a default ::new() method impracticable, at least to my POV, is that __construct() doesn't follow the 'normal' inheritance rules from other methods (see https://3v4l.org/LeaZ1), a default ::new() method would have to be special cased too and therefore would require a 'magic' behavior.

How should ::new() behave when a third party class __construct requires parameters in order to instantiate an usable instance? Consider:

class ImmutableArray {
   private $data = [];

   function __construct(array $data) {
       $this->data = $data;
   }

   /* then other methods to read $this->data */
}

So far this is what I've been questioning:

- Would ::new() always instantiate an empty (and useless) ImmutableArray?
- Would it be necessary to implement ::new() to ::new(array $data) so it follows the same spec of __construct(array $data)?
- Would we be able to overwrite ::new() at all?

I'd love to have ::new() from day 1 php was designed but now, unfortunately, having __construct() and ::new() on first class at the same time creates more issues than it should even though there is beauty in symmetry :/
 [2016-03-13 15:29 UTC] andreas at dqxtech dot net
@marcio

::new() would always have the same signature as the constructor. It would implictly call the constructor. C::new($x, $y, $z) would be equivalent with (new C($x, $y, $z)). So, I think the problem you see does not exist.

On the other hand, we have to ask if method_exists('C', 'new') would return TRUE or FALSE. And what ReflectionClass would say about the pseudo-method "new". And yes, if we see it as a method, then it would break the Liskov substitution principle - if we say it applies to static methods at all.

So maybe the easiest would be to immediately let the interpreter translate C::new() to (new C()), and never treat it as a method.

And what about is_callable(['C', 'new']) ? This shows both a problem and an opportunity. Passing a ['C', 'new'] around like any regular callback would be quite powerful, because it would unify factory callbacks. But it also means special casing and discussion for a number of core functions/functionality like method_exists(), is_callable(), and reflection.


> So far this is what I've been questioning:
>
> - Would ::new() always instantiate an empty (and useless) ImmutableArray?

No. ImmutableArray::new() will trigger a "Warning: Missing argument 1 for ImmutableArray::__construct()".

> - Would it be necessary to implement ::new() to ::new(array $data) so it follows the same spec of __construct(array $data)?

No. The implicit pseudo-method ::new() already has the signature of the constructor.

- Would we be able to overwrite ::new() at all?

Before PHP 7, I would have said that "new" is a reserved word, and cannot be used as a method name. C::new($x) always means (new C($x)), and triggers the constructor. Hence, if you overwrite the constructor, you have implicitly overwritten the pseudo-method "::new()".

Now with PHP 7 out, methods with the name new() are allowed to exist. The only language design option that does not break existing PHP 7 code would be to say that C::new($x) means (new C($x)), *unless* a real method C::new() exists, in which case this method is called instead. But people can still use the old "new C()" syntax to avoid calling the existing static method.

On the  positive side, this allows to add a "public static function new()", without having to change calling code - if all this calling code is already using the ::new() syntax.
 [2016-03-13 15:29 UTC] andreas at dqxtech dot net
@marcio

::new() would always have the same signature as the constructor. It would implictly call the constructor. C::new($x, $y, $z) would be equivalent with (new C($x, $y, $z)). So, I think the problem you see does not exist.

On the other hand, we have to ask if method_exists('C', 'new') would return TRUE or FALSE. And what ReflectionClass would say about the pseudo-method "new". And yes, if we see it as a method, then it would break the Liskov substitution principle - if we say it applies to static methods at all.

So maybe the easiest would be to immediately let the interpreter translate C::new() to (new C()), and never treat it as a method.

And what about is_callable(['C', 'new']) ? This shows both a problem and an opportunity. Passing a ['C', 'new'] around like any regular callback would be quite powerful, because it would unify factory callbacks. But it also means special casing and discussion for a number of core functions/functionality like method_exists(), is_callable(), and reflection.


> So far this is what I've been questioning:
>
> - Would ::new() always instantiate an empty (and useless) ImmutableArray?

No. ImmutableArray::new() will trigger a "Warning: Missing argument 1 for ImmutableArray::__construct()".

> - Would it be necessary to implement ::new() to ::new(array $data) so it follows the same spec of __construct(array $data)?

No. The implicit pseudo-method ::new() already has the signature of the constructor.

- Would we be able to overwrite ::new() at all?

Before PHP 7, I would have said that "new" is a reserved word, and cannot be used as a method name. C::new($x) always means (new C($x)), and triggers the constructor. Hence, if you overwrite the constructor, you have implicitly overwritten the pseudo-method "::new()".

Now with PHP 7 out, methods with the name new() are allowed to exist. The only language design option that does not break existing PHP 7 code would be to say that C::new($x) means (new C($x)), *unless* a real method C::new() exists, in which case this method is called instead. But people can still use the old "new C()" syntax to avoid calling the existing static method.

On the  positive side, this allows to add a "public static function new()", without having to change calling code - if all this calling code is already using the ::new() syntax.
 [2016-03-26 21:27 UTC] krakjoe@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: krakjoe
 [2016-03-26 21:27 UTC] krakjoe@php.net
For this kind of change, an RFC is required.

Please see: https://wiki.php.net/rfc/howto
 [2016-03-26 21:42 UTC] krakjoe@php.net
-Status: Closed +Status: Suspended
 [2017-01-29 07:33 UTC] krakjoe@php.net
-Assigned To: krakjoe +Assigned To:
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 18:01:29 2024 UTC