Skip to content

Commit

Permalink
Asymmetric Visibility and Final properties (#3828)
Browse files Browse the repository at this point in the history
* Document asymmetric property visibility.

* Document the change in implicit visibility for readonly.

* Document final properties.

* Note that isPublic/Protected/Private is only in reference to the main visibility.

* Use more note tags.

* Remove unnecessary para tags.

Co-authored-by: Christoph M. Becker <cmbecker69@gmx.de>
  • Loading branch information
Crell and cmb69 authored Oct 15, 2024
1 parent 04d9ade commit 16f66c0
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 31 deletions.
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>.
</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,
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.
<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>
<para>
When a class extends another, the child class may redefine
any property that is not <literal>final</literal>. When doing so,
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.
</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

0 comments on commit 16f66c0

Please sign in to comment.