Skip to content

Typed Properties #1797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 210 commits into from
Closed

Typed Properties #1797

wants to merge 210 commits into from

Conversation

krakjoe
Copy link
Member

@krakjoe krakjoe commented Mar 10, 2016

RFC

@mindplay-dk
Copy link

@BurakDev anything is possible, but who's going to maintain it? :-)

@IceReaper
Copy link

Imagine how huge that project would be: Setting up a project, collect all the patches, applying them. Dealing with conflicts between patches. Maintaining patches which get dropped as the author abandons them after he got a reject. Pulling newest php repo changes and merging them. In The end you come to a point where you are the one responsible to maintain all the RFCs, and they get more and more...

Scripts and frameworks would require you to re-compile php with the specific extensions enabled. There are two possible scenarios how this ends: Either no-one will use the new features, to avoid being incompatible with 95% of the environments, or they get used, and php ends up in recompiling the interpreter for every new script.

Another point is that you will get incompatibilities. Some RFCs would be incompatible to each other, as they are using the same syntax to do something different. So in the end you need a php interpreter compiled and running per script to avoid running into this problem. And to go a little further: This makes composer or working with libraries at all a huge problem, as they might require a feature which is incompatible with your current feature set. I could go on deeper and deeper like per-folder-interpreter etc. But it should be clear by now why going any of these ways is no good idea at all.

Also I think the amount of work required to maintain that project would not match the benefits you get from declined RFCs. So there is only one solution: take the feedback from declined RFCs, analyse them, make them better, and push for another vote run. Just keep on working on the good RFCs like typed properties or generics and generic arrays.

@mindplay-dk
Copy link

@IceReaper nicely detailed :-) tldr: a complete clusterfuck ;-)

@RyanNerd
Copy link

I'm dumbfounded as to why this RFC has any nay votes at all. I build from source anyway and I am sorely tempted to annex this PR into my personal PHP fork. Keep up the good work.

@krakjoe
Copy link
Member Author

krakjoe commented Jul 6, 2016

:(

@krakjoe krakjoe closed this Jul 6, 2016
@IceReaper
Copy link

Do you keep working on this for another vote run, or is this complete approach trashed now?

@ghost
Copy link

ghost commented Jul 7, 2016

We need this :(

@krakjoe
Copy link
Member Author

krakjoe commented Jul 7, 2016

@IceReaper it's not worth going again, I don't think.

@bwoebi
Copy link
Member

bwoebi commented Jul 7, 2016

@krakjoe Don't be that pessimistic …

@sirsnyder
Copy link

sirsnyder commented Jul 7, 2016

Many userland devs want this feature. It's so sad we have to wait at least another year. ;-(

@kinncj
Copy link

kinncj commented Jul 7, 2016

@sirsnyder agree that they want. but better have it nicely implemented without all the proposed breaks than screw a bunch of code bases... specially doctrine users, where proxies relies on unset behavioue and __get

@Fleshgrinder
Copy link
Contributor

@krakjoe: keep on it, the feature was not rejected, the implementation details were. I am sure many people will support you in this endeavor to come to a solution that gets accepted. Just look at annotations. 😁

@guilhermeblanco
Copy link

@krakjoe I'd like to ask you if you wanna try this [1] approach instead for 7.2. Might be more receptive.

[1] #1797 (comment)

@kinncj
Copy link

kinncj commented Jul 7, 2016

@guilhermeblanco it doesn't address __get though.

@kinncj
Copy link

kinncj commented Jul 7, 2016

@guilhermeblanco just saw, if they only check the write and not reading it does address __get.

@guilhermeblanco
Copy link

guilhermeblanco commented Jul 7, 2016

@kinncj I briefly mentioned about it, but let me expand.
If the approach taken only handles write checks, but not read checks, it's irrelevant __get behavior. The only way to achieve a __get call would be through "unset" and then try to read. But again, if you unset() the property and then try to write/read a value, there's no type checks anymore because it's a dynamic property (the class one got removed).Also, __get would still return whatever value was provided, without type checking. Write checks, not read checks. =)

@mindplay-dk
Copy link

@guilhermeblanco you can remove a property defined by a class?? That seems beyond wrong. Bizzarre.

@kinncj
Copy link

kinncj commented Jul 7, 2016

The same way you can DEFINE a property you can UN-DEFINE it.
There's nothing wrong sir. it's natural from loose typed languages... the same may happen with JS and Ruby for example.

And he is marking an Instance Property as undefined, the same as you can dynamically create Instance Properties in PHP.

@guilhermeblanco
Copy link

@mindplay-dk It's perfectly possible to unset properties of a class. We use it in Doctrine for lazy-loading proxies.
We unset all properties, so we force __get, __set, __isset call and load the proxy... =)

@RyanNerd
Copy link

RyanNerd commented Jul 8, 2016

Why not:

strict class MyClass
{
    protected int myInt = 0;
    protected myUndeclaredPropertyTypeThatIsNotAllowedHere = null;
}

Dynamic or un-typed properties are not allowed in strict classes, or any other dynamic voodoo __get, __set or adding/removing properties on the fly, or other nonsense that flies in the face of good practice for strong/strict typed classes. Perhaps allow __get and __set to access and set typed properties as a separate RFC.

PHP is written in C which has strong typing. Why in the name of sanity did typed properties get voted down in PHP?

@negreanucalin
Copy link

I think it is a good ideea to have more strict types in php because it is easier to debug and maintain any application. Furthermore I think php should have method overloading because it is a hastle now...my opinion

@ghost
Copy link

ghost commented Jul 9, 2016

@negreanucalin Lol. Core developers doesn't think like you, so you can't see this features
soon.

@negreanucalin
Copy link

negreanucalin commented Jul 9, 2016

@sharifzadesina "you can't see this features soon"- yes...I know unfortunately but at least I can dream of it...I hope I'll be alive to see it :P

@ghost
Copy link

ghost commented Jul 9, 2016

@negreanucalin Hahaha 👍

@GroovyCarrot
Copy link

Would it be possible to reboot discussion on this RFC? With all the great features in PHP 7.1, I feel this is the only one that seems to be missing. I have some suggestions in relation to the above comments and my experience of working with static typed languages.

The first is that I don't believe that enforcing type checking when reading a variable is necessary; setting is a given, and should be perfectly adequate to ensure that the property is the expected type, combined with smarter and efficient checks. When we are initialising an instance of a class, it should be safe to assume that all typed properties that cannot have a nullable state should be initialised via the constructor. For example:

class ExampleClass
{
    protected int $x;
    protected int $y;

    public function __construct(int $x, int $y)
    {
        $this->x = $x; // Type check $this->x on assignment.
    } // We should throw an exception here, as $this->y has not been initialised, and cannot be null.
}

By instead using protected ?int $y then the property is valid to be uninitialised after the class has been initialised, and code using it should account for that. Also using unset() on non-nullable typed properties, which should be the functional equivalent of using $this->y = NULL; and therefore throw a type exception.

There is then the above mentioned situation by @guilhermeblanco where it is necessary for a property that is lazy initialised, without requiring a nullable state, something along the lines of what Swift has may be beneficial:

class ClassMetadata
{
    private string $name;
    private lazy Table $table = new Table($this->name);
    // Alternatively, with a closure (Swift supports both for readability, however we
    // could enforce a closure for consistency and simplicity of implementation)
    private lazy Table $table {
        return new Table($this->name);
    }

    public function __construct($name)
    {
        $this->name = $name;
    }
}

Using a lazy token when declaring a property could then require a proceeding closure to initialise it when accessed for the first time. It's difficult to really tell what the problem was in the example, however it looks potentially more of a design problem than a language feature limitation.

With static properties, unless they are nullable, they should also be initialised.

class ExampleClass
{
    protected static int $x = 1;
    protected static ?int $y; // Allowed.
    protected static int $z; // Throws exception, must be initialised or nullable.
    protected static Object $object; // We would probably only want to allow primitive data types as it works currently?
    protected static ?Object $object; // However, it seems like this could be allowed?
}

Finally, regarding magic methods, there's probably no harm in enforcing the type in __get(), I personally only really see 'magic' methods being appropriate for proxying invocations to and from other objects, in which case the client and the object being proxied should deal with the necessary type checking. A solid lazy property language feature in PHP would address the need to use magic, which I think would replace the vast majority of good cases for using it.

Just trying to get the ball rolling again on this.

@theodorejb
Copy link
Contributor

@GroovyCarrot @bwoebi is currently working on a typed properties implementation. You can follow the progress of this here: master...bwoebi:typed_ref_properties. I'm not sure whether this implementation performs a type check each time a property is accessed.

A patch to improve internal type representation was just committed to the PHP master branch today. Apparently this will make it easier to continue working on typed properties. There will no doubt be a new RFC once the typed properties implementation is completed.

@mindplay-dk
Copy link

mindplay-dk commented Jan 14, 2017 via email

@GroovyCarrot
Copy link

GroovyCarrot commented Jan 14, 2017

Thanks @theodorejb for the update, glad to see some work is being done for this. Is there a place that discussion of these features normally takes place? I am aware that the RFC proposed and implemented here seems to have swung from an initial positive response, to being rejected; and I'm curious as to the process for RFC's regarding planning and development.

@mindplay-dk completely agree that lazy properties would need to be a separate RFC. I do however think that it is worth adding tokens like that for clarity, as code like the above could be misread as a function without carefully considering syntax. It's also probably wise to follow what modern languages are doing in order to easily support the inevitable RFC's down the line.

I'm not sure if this is particularly the forum for talking about the RFC, but reviewing it here (https://wiki.php.net/rfc/typed-properties) I can see that a conscious decision was made to avoid validation of the objects:

To put it another way: Type safety is the goal of this RFC, not validating objects.

The focus here should not be to enforce the type on the variable every time that it is written to and read from. While that will create type safety, I think that this is in reality impractical and inefficient to achieve. There isn't actually any benefit in implementing it like this anyway: this is prone to logic errors as every time you are using the property, you will still have to check whether or not it has been initialised, even though it promises to be of the specified type.

So in objective c, every variable is statically typed (as in C), properties are actually just synthesised instance variables with getter and setter methods for them by the compiler, so when you are doing int x = a.x; you are actually doing int x = [a getX]; (i.e. $a->getX();). Incidentally, lazy loading is therefore easily solved by just implementing the getter method that the compiler would synthesise for you, and assigning the instance variable if it isn't already set.

In objective c an object can be initialised without needing to initialise all of the statically typed properties on it, as we are suggesting it should work here. The difference is that primitive data types are actually initialised to 0 by default (memory is just allocated), and objects types are all nil as we are working with pointers. This is the equivalent of doing:

class A {
    public int $x;
}
$a = new A();
var_dump($a->x); // int(0)

The other part to creating stability for this is to account for the situations where an object type property has not been initialised, yet code might still try to call methods on it. In objective c, you can call methods on nil without consequence, and the resulting invocation will simply return nil. So the equivalent for that:

class A {
    public X $x;
}
class X {
    public int y;
}
$a = new A();
var_dump($a->x); // NULL
$v = $a->x->y; // (no exception thrown)
var_dump($v); // NULL

PHP obviously does not do this, and with various good reasons. Variables can be any type, and we don't know what type $v was supposed to be in the first place, whereas in objective c we would know, because the compiler would only allow you to store that variable with a type: int v = a.x.y; which would just give you 0 - which you can still continue to work with. Objective c isn't a particularly great language to work with for this reason, amongst others. However it does work, and the language has been around for a few decades.

The key point here is that there's a known root issue with potentially uninitialised properties, but the language is built to accommodate for this, and does not complain about many of the symptoms. In the example above, the exception thrown by PHP would be that you cannot get the property of a non-object (NULL), however the actual root cause of this issue is that $a->x promised to be an instance of X, and it was not.

So hopefully I have shown why having typed-uninitialised properties in PHP would just be a large step backwards rather than forwards, as we would be implementing a feature that modern languages are actively trying to stamp out. Which comes back to my original point of the necessity for a property typing feature in PHP to require constructor validation for typed properties that cannot be null; without validating the object after initialisation, then this feature isn't reporting the most likely problem that you are going to want it to raise to you: that a required dependency for an object has not been injected into it's constructor.

Not to mention the fact that it seems like it's a much simpler implementation to just check when you're assigning, and when the object is constructed?

@glen-84
Copy link

glen-84 commented Jun 4, 2018

Is there a document somewhere that summarizes the reasons why this proposal was rejected? Have any of the items been addressed since then?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.