Skip to content
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

Asymmetric Visiblity and Final properties #3828

Merged
merged 13 commits into from
Oct 15, 2024
37 changes: 27 additions & 10 deletions language/oop5/final.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<sect1 xml:id="language.oop5.final" xmlns="http://docbook.org/ns/docbook">
<title>Final Keyword</title>
<para>
The final keyword prevents child classes from overriding a method or constant by
The final keyword prevents child classes from overriding a method, property, or constant by
prefixing the definition with <literal>final</literal>. If the class
itself is being defined final then it cannot be extended.
</para>
Expand Down Expand Up @@ -59,10 +59,26 @@ class ChildClass extends BaseClass {
</programlisting>
</example>
</para>
<para>
<example xml:id="language.oop5.final.example.php81">
<title>Final constants example as of PHP 8.1.0</title>
<programlisting role="php">
<example>
<title>Final property example as of PHP 8.4.0</title>
<programlisting role="php">
<![CDATA[
<?php
class BaseClass {
final protected string $test;
}

class ChildClass extends BaseClass {
public string $test;
}
// Results in Fatal error: Cannot override final property BaseClass::$test
?>
]]>
</programlisting>
</example>
<example xml:id="language.oop5.final.example.php81">
<title>Final constants example as of PHP 8.1.0</title>
<programlisting role="php">
<![CDATA[
<?php
class Foo
Expand All @@ -78,16 +94,17 @@ class Bar extends Foo
// Fatal error: Bar::X cannot override final constant Foo::X
?>
]]>
</programlisting>
</example>
</para>
</programlisting>
</example>

<note>
<simpara>
Properties cannot be declared final: only classes, methods, and constants (as of PHP 8.1.0) may be declared as final.
As of PHP 8.0.0, private methods may not be declared final except for the <link linkend="#language.oop5.decon.constructor">constructor</link>.
Crell marked this conversation as resolved.
Show resolved Hide resolved
</simpara>
</note>
<note>
<simpara>
As of PHP 8.0.0, private methods may not be declared final except for the constructor.
A property that is declared <link linkend="language.oop5.visibility-members-aviz"><literal>private(set)</literal></link> is implicitly <literal>final</literal>.
</simpara>
</note>
</sect1>
Expand Down
8 changes: 7 additions & 1 deletion language/oop5/properties.xml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,13 @@ Fatal error: Uncaught Error: Typed property Shape::$numberOfSides must not be ac
<sect2 xml:id="language.oop5.properties.readonly-properties">
<title>Readonly properties</title>
<para>
As of PHP 8.1.0, a property can be declared with the <code>readonly</code> modifier, which prevents modification of the property after initialization.
As of PHP 8.1.0, a property can be declared with the <literal>readonly</literal> modifier,
Crell marked this conversation as resolved.
Show resolved Hide resolved
which prevents modification of the property after initialization. Prior to PHP 8.4.0
a <literal>readonly</literal> property is implicitly private-set, and may only be written to
from the same class. As of PHP 8.4.0, <literal>readonly</literal> properties are implicitly
<link linkend="language.oop5.visibility-members-aviz"><literal>protected(set)</literal></link>,
so may be set from child classes. That may be overridden
explicitly if desired.
Crell marked this conversation as resolved.
Show resolved Hide resolved
<example>
<title>Example of readonly properties</title>
<programlisting role="php">
Expand Down
152 changes: 133 additions & 19 deletions language/oop5/visibility.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
protected. Properties declared without any explicit visibility
keyword are defined as public.
</para>
<para>
<example>
<title>Property declaration</title>
<programlisting role="php">
<example>
<title>Property declaration</title>
<programlisting role="php">
<![CDATA[
<?php
/**
Expand Down Expand Up @@ -74,10 +73,129 @@ echo $obj2->private; // Undefined
$obj2->printHello(); // Shows Public2, Protected2, Undefined

?>
]]>
</programlisting>
</example>
<sect3 xml:id="language.oop5.visibility-members-aviz">
<title>Asymmetric Property Visibility</title>
<para>
As of PHP 8.4, properties may also have their
visibility set asymmetrically, with a different scope for
reading (<literal>get</literal>) and writing (<literal>set</literal>).
Specifically, the <literal>set</literal> visibility may be
specified separately, provided it is not more permissive than the
default visibility.
</para>
<example>
<title>Asymmetric Property visibility</title>
<programlisting role="php">
<![CDATA[
<?php
class Book
{
public function __construct(
public private(set) string $title,
public protected(set) string $author,
protected private(set) int $pubYear,
) {}
}

class SpecialBook extends Book
{
public function update(string $author, int $year): void
{
$this->author = $author; // OK
$this->pubYear = $year; // Fatal Error
}
}

$b = new Book('How to PHP', 'Peter H. Peterson', 2024);

echo $b->title; // Works
echo $b->author; // Works
echo $b->pubYear; // Fatal Error

$b->title = 'How not to PHP'; // Fatal Error
$b->author = 'Pedro H. Peterson'; // Fatal Error
$b->pubYear = 2023; // Fatal Error
?>
]]>
</programlisting>
</example>
</para>
<para>There are a few caveats regarding asymmetric visibility:</para>
<itemizedlist>
<listitem>
<simpara>
Only typed properties may have a separate <literal>set</literal> visibility.
</simpara>
</listitem>
<listitem>
<simpara>
The <literal>set</literal> visibility must be the same
as <literal>get</literal> or more restrictive. That is,
<code>public protected(set)</code> and <code>protected protected(set)</code>
are allowed, but <code>protected public(set)</code> will cause a syntax error.
</simpara>
</listitem>
<listitem>
<simpara>
If a property is <literal>public</literal>, then the main visibility may be
omitted. That is, <code>public private(set)</code> and <code>private(set)</code>
will have the same result.
</simpara>
</listitem>
<listitem>
<simpara>
A property with <literal>private(set)</literal> visibility
is automatically <literal>final</literal>, and may not be redeclared in a child class.
</simpara>
</listitem>
<listitem>
<simpara>
Obtaining a reference to a property follows <literal>set</literal> visibility, not <literal>get</literal>.
That is because a reference may be used to modify the property value.
</simpara>
</listitem>
<listitem>
<simpara>
Similarly, trying to write to an array property involves both a <literal>get</literal> and
<literal>set</literal> operation internally, and therefore will follow the <literal>set</literal>
visibility, as that is always the more restrictive.
</simpara>
</listitem>
</itemizedlist>
Comment on lines +125 to +166
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the itemizedlist tag inside the para tag as they form "one block".

<para>
When a class extends another, the child class may redefine
any property that is not <literal>final</literal>. When doing so,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there should be a link to final keyword here.

Feels like we should add more keywords entities to doc-base/entities/global.ent

it may widen either the main visibility or the <literal>set</literal>
visibility, provided that the new visibility is the same or wider
than the parent class. However, be aware that if a <literal>private</literal>
property is overridden, it does not actually change the parent's property
but creates a new property with a different internal name.
Crell marked this conversation as resolved.
Show resolved Hide resolved
</para>
<example>
<title>Asymmetric Property inheritance</title>
<programlisting role="php">
<![CDATA[
<?php
class Book
{
protected string $title;
public protected(set) string $author;
protected private(set) int $pubYear;
}

class SpecialBook extends Book
{
public protected(set) $title; // OK, as reading is wider and writing the same.
public string $author; // OK, as reading is the same and writing is wider.
public protected(set) int $pubYear; // Fatal Error. private(set) properties are final.
}
?>
]]>
</programlisting>
</example>
</sect3>
</sect2>

<sect2 xml:id="language.oop5.visiblity-methods">
Expand All @@ -87,10 +205,9 @@ $obj2->printHello(); // Shows Public2, Protected2, Undefined
protected. Methods declared without any explicit visibility
keyword are defined as public.
</para>
<para>
<example>
<title>Method Declaration</title>
<programlisting role="php">
<example>
<title>Method Declaration</title>
<programlisting role="php">
<![CDATA[
<?php
/**
Expand Down Expand Up @@ -176,9 +293,8 @@ $myFoo->test(); // Bar::testPrivate
// Foo::testPublic
?>
]]>
</programlisting>
</example>
</para>
</programlisting>
</example>
</sect2>

<sect2 xml:id="language.oop5.visiblity-constants">
Expand All @@ -188,10 +304,9 @@ $myFoo->test(); // Bar::testPrivate
protected. Constants declared without any explicit visibility
keyword are defined as public.
</para>
<para>
<example>
<title>Constant Declaration as of PHP 7.1.0</title>
<programlisting role="php">
<example>
<title>Constant Declaration as of PHP 7.1.0</title>
<programlisting role="php">
<![CDATA[
<?php
/**
Expand Down Expand Up @@ -242,9 +357,8 @@ echo MyClass2::MY_PUBLIC; // Works
$myclass2->foo2(); // Public and Protected work, not Private
?>
]]>
</programlisting>
</example>
</para>
</programlisting>
</example>
</sect2>

<sect2 xml:id="language.oop5.visibility-other-objects">
Expand Down
5 changes: 5 additions & 0 deletions reference/reflection/reflectionproperty/isprivate.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
<para>
&true; if the property is private, &false; otherwise.
</para>
<note>
<simpara>
Note this refers only to the main visibility, and not to a <link linkend="language.oop5.visibility-members-aviz">set-visibility</link>, if specified.
</simpara>
</note>
</refsect1>

<refsect1 role="seealso">
Expand Down
5 changes: 5 additions & 0 deletions reference/reflection/reflectionproperty/isprotected.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
<para>
&true; if the property is protected, &false; otherwise.
</para>
<note>
<simpara>
Note this refers only to the main visibility, and not to a <link linkend="language.oop5.visibility-members-aviz">set-visibility</link>, if specified.
</simpara>
</note>
</refsect1>

<refsect1 role="seealso">
Expand Down
7 changes: 6 additions & 1 deletion reference/reflection/reflectionproperty/ispublic.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@
<refsect1 role="returnvalues">
&reftitle.returnvalues;
<para>
&true; if the property is public, &false; otherwise.
&true; if the property is marked public, &false; otherwise.
</para>
<note>
<simpara>
Note this refers only to the main visibility, and not to a <link linkend="language.oop5.visibility-members-aviz">set-visibility</link>, if specified.
</simpara>
</note>
</refsect1>

<refsect1 role="seealso">
Expand Down
Loading