Formatting Test

[email protected] (Nick Sdot) Sat, 11 Jul 2026 15:06:59 +0700
Newsgroups php.test
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--------------b0iUrBOQSLdt0xG3IKYYuaXt
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

FORMATTING TEST

Hey Larry,

On 11.07.26 06:02, Larry Garfield wrote:
> On Sat, Jul 4, 2026, at 5:03 AM, Nick Sdot wrote:
>> Hey Internals,
>>
>> I would like to present a tiny, five-line-removal RFC allowing default
>> values on readonly properties.
>>
>> Allowing default values on readonly class properties enables creating a
>> strict contract for implementation property values that will not change
>> at runtime -- as in, constant-like behaviour with contract. I would love
>> doing that.
>>
>> Please find the RFC text here:
>> https://wiki.php.net/rfc/readonly_property_defaults
> I had been planning to say "meh, doesn't seem like it would hurt anything," but as Tim pointed out with unserialize, it potentially could hurt something.  That makes me skeptical.
>
> I also want to call out, as the RFC notes, that this is already a solved problem with private(set).  It's not quite the same as readonly, but for most practical purposes it's close enough and already addresses the use case this RFC.


Thanks for the feedback.

I have to push back. It is not a solved problem. In fact, `private(set)` 
is *not* only "not quite the same". Both features solve fundamentally 
different problems -- `private(set)` has a foot gun `readonly` does not 
have.

Because this point comes up repeatedly here is a concrete example:

```

<?php declare(strict_types=1);


final class PipelineOne // private(set)

{
     public private(set)array $orderedPipelineSteps = [
         \Thing\StepDoSomethingTwo::class,
         \Thing\StepAtReportBeginning::class,
         \Thing\StepDoSomethingOne::class,
     ];public function describe(): array {\sort($this->orderedPipelineSteps);// whoopsie, should have copied first $ordered = $this->orderedPipelineSteps;

         // do stuff return $ordered;
     }
}

final readonly class PipelineTwo// readonly{
     public function __construct(public array $orderedPipelineSteps = [
             \Thing\StepDoSomethingTwo::class,
             \Thing\StepAtReportBeginning::class,
             \Thing\StepDoSomethingOne::class,
         ],
     ) {}public function describe(): array {\sort($this->orderedPipelineSteps);// whoopsie, should have copied first $ordered = $this->orderedPipelineSteps;

         // do stuff return $ordered;
     }
}

// ###################### private(set) ###################### $one = new 
PipelineOne();
\var_dump($one->describe());
// array(3) { // [0]=> // string(27) "Thing\StepAtReportBeginning" // 
good, now ordered // [1]=> // string(24) "Thing\StepDoSomethingOne" // 
[2]=> // string(24) "Thing\StepDoSomethingTwo" //} \var_dump($one->orderedPipelineSteps);
// array(3) { // [0]=> // string(27) "Thing\StepAtReportBeginning" // 
whoopsie, this should NOT be first! // [1]=> // string(24) 
"Thing\StepDoSomethingOne" // [2]=> // string(24) 
"Thing\StepDoSomethingTwo" //} // ###################### readonly 
###################### $two = new PipelineTwo();
\var_dump($two->describe());
//Fatal error: Uncaught Error: Cannot indirectly modify readonly 
property PipelineTwo::$orderedPipelineSteps

```

With `private(set)` accidental mutation is legal. With `readonly`, 
modification after initialisation is illegal. Both are great and useful 
features, but `private(set)` only controls visibility not mutation. As 
the above example proves, `readonly` is strict when it comes to mutation 
and would have prevented the bug while `private(set)` happily ignores it 
because mutation happens from within the class. With `readonly` it would 
have been surfaced that I did:

```

<?php

\sort($this->orderedPipelineSteps); // whoopsie, mutated

$ordered = $this->orderedPipelineSteps;
```

instead of:

```

<?php

$ordered = $this->orderedPipelineSteps;

\sort($ordered);

```

This alone is reason enough to use `readonly` over `private(set)`; less 
verbosity is a cherry on top.

Note: only to make the example copy & work without the PR branch I used 
constructor property promotion here. With the RFC, `PipelineTwo` 
can declare the property default directly, without allowing outside 
modification.

--

Cheers
Nick


--------------b0iUrBOQSLdt0xG3IKYYuaXt
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!DOCTYPE html>
<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p> </p>
    <div class="moz-text-html" lang="x-unicode">FORMATTING TEST
      <div class="moz-text-html" lang="x-unicode">
        <p>Hey Larry, </p>
        <div class="moz-cite-prefix">On 11.07.26 06:02, Larry Garfield
          wrote:<br>
        </div>
        <blockquote type="cite"
cite="mid:[email protected]">
          <pre wrap="" class="moz-quote-pre">On Sat, Jul 4, 2026, at 5:03 AM, Nick Sdot wrote:
</pre>
          <blockquote type="cite">
            <pre wrap="" class="moz-quote-pre">Hey Internals,

I would like to present a tiny, five-line-removal RFC allowing default 
values on readonly properties.

Allowing default values on readonly class properties enables creating a 
strict contract for implementation property values that will not change 
at runtime -- as in, constant-like behaviour with contract. I would love 
doing that.

Please find the RFC text here: 
<a class="moz-txt-link-freetext"
            href="https://wiki.php.net/rfc/readonly_property_defaults">https://wiki.php.net/rfc/readonly_property_defaults</a>
</pre>
          </blockquote>
          <pre wrap="" class="moz-quote-pre">I had been planning to say "meh, doesn't seem like it would hurt anything," but as Tim pointed out with unserialize, it potentially could hurt something.  That makes me skeptical.

I also want to call out, as the RFC notes, that this is already a solved problem with private(set).  It's not quite the same as readonly, but for most practical purposes it's close enough and already addresses the use case this RFC.</pre>
        </blockquote>
        <p><br>
          Thanks for the feedback.</p>
        <p>I have to push back. It is not a solved problem. In fact,
          `private(set)` is *not* only "not quite the same". Both
          features solve fundamentally different problems
          -- `private(set)` has a foot gun `readonly` does not have.</p>
        <p>Because this point comes up repeatedly here is a concrete
          example:</p>
        <p>```<br>
        </p>
        <div style="background-color:#ffffff;color:#cb4247">
          <pre
style="font-family:'JetBrains Mono',monospace;font-size:12,0pt;"><span
          style="color:#4f5b93;">&lt;?php
</span><span style="color:#4f5b93;">
</span><span style="color:#d73a49;">declare</span>(<span
          style="color:#e36209;">strict_types</span><span
          style="color:#b31d28;">=</span><span style="color:#005cc5;">1</span>);
</pre>
        </div>
        <p><br>
          <span style="color:#d73a49;">final class </span><span
            style="color:#808080;">PipelineOne // private(set)</span></p>
        <div style="background-color:#ffffff;color:#cb4247">
          <div style="background-color:#ffffff;color:#cb4247">
            <pre
style="font-family:'JetBrains Mono',monospace;font-size:12,0pt;">{
    <span style="color:#d73a49;">public private</span>(<span
            style="color:#e36209;">set</span>) <span
            style="color:#d73a49;">array </span><span
            style="color:#808080;">$orderedPipelineSteps </span><span
            style="color:#b31d28;">= </span>[
        <span style="color:#808080;">\Thing\</span><span
            style="color:#e36209;">StepDoSomethingTwo</span><span
            style="color:#b31d28;">::</span><span style="color:#d73a49;">class</span>,
        <span style="color:#808080;">\Thing\</span><span
            style="color:#e36209;">StepAtReportBeginning</span><span
            style="color:#b31d28;">::</span><span style="color:#d73a49;">class</span>,
        <span style="color:#808080;">\Thing\</span><span
            style="color:#e36209;">StepDoSomethingOne</span><span
            style="color:#b31d28;">::</span><span style="color:#d73a49;">class</span>,
    ];<span style="color:#969896;">
</span><span style="color:#969896;">
</span><span style="color:#969896;">    </span><span
            style="color:#d73a49;">public function </span><span
            style="color:#6f42c1;">describe</span>()<span
            style="color:#b31d28;">: </span><span style="color:#d73a49;">array
</span><span style="color:#d73a49;">    </span>{<span
            style="color:#969896;">
</span><span style="color:#969896;">        </span>\<span
            style="color:#6f42c1;">sort</span>(<span
            style="color:#000000;">$this</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#005cc5;">orderedPipelineSteps</span>); <span
            style="color:#969896;">// whoopsie, should have copied first
</span><span style="color:#969896;">
</span><span style="color:#969896;">        </span><span
            style="color:#000000;">$ordered </span><span
            style="color:#b31d28;">= </span><span style="color:#000000;">$this</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#005cc5;">orderedPipelineSteps</span>;

        <span style="color:#969896;">// do stuff
</span><span style="color:#969896;">
</span><span style="color:#969896;">        </span><span
            style="color:#d73a49;">return </span><span
            style="color:#000000;">$ordered</span>;
    }
}

<span style="color:#d73a49;">final readonly class </span><span
            style="color:#808080;">PipelineTwo</span><span
            style="color:#969896;"> // readonly</span><span
            style="color:#808080;">
</span>{
    <span style="color:#d73a49;">public function </span><span
            style="color:#6f42c1;">__construct</span>(<span
            style="color:#969896;">
</span><span style="color:#969896;">        </span><span
            style="color:#d73a49;">public</span> <span
            style="color:#d73a49;">array </span><span
            style="color:#000000;">$orderedPipelineSteps </span><span
            style="color:#b31d28;">= </span>[
            <span style="color:#808080;">\Thing\</span><span
            style="color:#e36209;">StepDoSomethingTwo</span><span
            style="color:#b31d28;">::</span><span style="color:#d73a49;">class</span>,
            <span style="color:#808080;">\Thing\</span><span
            style="color:#e36209;">StepAtReportBeginning</span><span
            style="color:#b31d28;">::</span><span style="color:#d73a49;">class</span>,
            <span style="color:#808080;">\Thing\</span><span
            style="color:#e36209;">StepDoSomethingOne</span><span
            style="color:#b31d28;">::</span><span style="color:#d73a49;">class</span>,
        ],
    ) {}<span style="color:#969896;">
</span><span style="color:#969896;">
</span><span style="color:#969896;">    </span><span
            style="color:#d73a49;">public function </span><span
            style="color:#6f42c1;">describe</span>()<span
            style="color:#b31d28;">: </span><span style="color:#d73a49;">array
</span><span style="color:#d73a49;">    </span>{<span
            style="color:#969896;">
</span><span style="color:#969896;">        </span>\<span
            style="color:#6f42c1;">sort</span>(<span
            style="color:#000000;">$this</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#005cc5;">orderedPipelineSteps</span>); <span
            style="color:#969896;">// whoopsie, should have copied first
</span><span style="color:#969896;">
</span><span style="color:#969896;">        </span><span
            style="color:#000000;">$ordered </span><span
            style="color:#b31d28;">= </span><span style="color:#000000;">$this</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#005cc5;">orderedPipelineSteps</span>;

        <span style="color:#969896;">// do stuff
</span><span style="color:#969896;">
</span><span style="color:#969896;">        </span><span
            style="color:#d73a49;">return </span><span
            style="color:#000000;">$ordered</span>;
    }
}

<span style="color:#969896;">// ###################### private(set) ######################
</span><span style="color:#000000;">$one </span><span
            style="color:#b31d28;">= </span><span style="color:#d73a49;">new </span><span
            style="color:#e36209;">PipelineOne</span>();
\<span style="color:#6f42c1;">var_dump</span>(<span
            style="color:#000000;">$one</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#6f42c1;">describe</span>());
<span style="color:#969896;">// array(3) {
</span><span style="color:#969896;">//  [0]=&gt;
</span><span style="color:#969896;">//  string(27) "Thing\StepAtReportBeginning" // good, now ordered
</span><span style="color:#969896;">//  [1]=&gt;
</span><span style="color:#969896;">//  string(24) "Thing\StepDoSomethingOne"
</span><span style="color:#969896;">//  [2]=&gt;
</span><span style="color:#969896;">//  string(24) "Thing\StepDoSomethingTwo"
</span><span style="color:#969896;">//}
</span>\<span style="color:#6f42c1;">var_dump</span>(<span
            style="color:#000000;">$one</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#005cc5;">orderedPipelineSteps</span>);
<span style="color:#969896;">// array(3) {
</span><span style="color:#969896;">//  [0]=&gt;
</span><span style="color:#969896;">//  string(27) "Thing\StepAtReportBeginning" // whoopsie, this should NOT be first!
</span><span style="color:#969896;">//  [1]=&gt;
</span><span style="color:#969896;">//  string(24) "Thing\StepDoSomethingOne"
</span><span style="color:#969896;">//  [2]=&gt;
</span><span style="color:#969896;">//  string(24) "Thing\StepDoSomethingTwo"
</span><span style="color:#969896;">//}
</span><span style="color:#969896;">// ###################### readonly ######################
</span><span style="color:#000000;">$two </span><span
            style="color:#b31d28;">= </span><span style="color:#d73a49;">new </span><span
            style="color:#e36209;">PipelineTwo</span>();
\<span style="color:#6f42c1;">var_dump</span>(<span
            style="color:#000000;">$two</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#6f42c1;">describe</span>());
<span style="color:#969896;">//Fatal error: Uncaught Error: Cannot indirectly modify readonly property PipelineTwo::$orderedPipelineSteps</span></pre>
          </div>
        </div>
        <p>```</p>
        <p>With `private(set)` accidental mutation is legal. With
          `readonly`, modification after initialisation is illegal. Both
          are great and useful features, but `private(set)` only
          controls visibility not mutation. As the above example proves,
          `readonly` is strict when it comes to mutation and would have
          prevented the bug while `private(set)` happily ignores it
          because mutation happens from within the class. With
          `readonly` it would have been surfaced that I did:</p>
        <p>```<br>
        </p>
        <pre
        style="font-family:'JetBrains Mono',monospace;font-size:12,0pt;"><span
        style="color:#4f5b93;">&lt;?php
</span>
</pre>
        <p> \<span style="color:#6f42c1;">sort</span>(<span
            style="color:#000000;">$this</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#005cc5;">orderedPipelineSteps</span>); <span
            style="color:#969896;">// whoopsie, mutated</span><span
            style="color:#000000;"><br>
          </span></p>
        <p><span style="color:#000000;">$ordered <span
              style="color:#b31d28;">= <span style="color:#000000;">$this<span
                  style="color:#b31d28;">-&gt;<span
                    style="color:#005cc5;">orderedPipelineSteps;<br>
                  </span></span></span></span></span>```<br>
          <br>
          instead of:</p>
        <p>```<br>
        </p>
        <pre
        style="font-family:'JetBrains Mono',monospace;font-size:12,0pt;"><span
        style="color:#4f5b93;">&lt;?php
</span>
</pre>
        <p><span style="color:#000000;">$ordered </span><span
            style="color:#b31d28;">= </span><span
            style="color:#000000;">$this</span><span
            style="color:#b31d28;">-&gt;</span><span
            style="color:#005cc5;">orderedPipelineSteps</span>;</p>
        <div style="background-color:#ffffff;color:#cb4247">
          <pre
style="font-family:'JetBrains Mono',monospace;font-size:12,0pt;">\<span
          style="color:#6f42c1;">sort</span>(<span
          style="color:#000000;">$ordered</span>);</pre>
        </div>
        <p>```</p>
        <p>This alone is reason enough to use `readonly` over
          `private(set)`; less verbosity is a cherry on top.</p>
        <p>Note: only to make the example copy &amp; work without the PR
          branch I used constructor property promotion here. <span>With
            the RFC, `PipelineTwo` can</span><span> declare the property
            default directly, without allowing outside modification.</span></p>
        <p>--</p>
        <p>Cheers<br>
          Nick</p>
      </div>
    </div>
    <p><br>
    </p>
  </body>
</html>

--------------b0iUrBOQSLdt0xG3IKYYuaXt--