<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Readonly on Guillaume Delré</title><link>https://guillaumedelre.github.io/tags/readonly/</link><description>Recent content in Readonly on Guillaume Delré</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 22 Jan 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/tags/readonly/index.xml" rel="self" type="application/rss+xml"/><item><title>PHP 8.2: readonly classes and the deprecation that matters</title><link>https://guillaumedelre.github.io/2023/01/22/php-8.2-readonly-classes-and-the-deprecation-that-matters/</link><pubDate>Sun, 22 Jan 2023 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/2023/01/22/php-8.2-readonly-classes-and-the-deprecation-that-matters/</guid><description>Part 8 of 11 in &amp;quot;PHP Releases&amp;quot;: PHP 8.2 introduces readonly classes, deprecates dynamic properties, and adds disjunctive normal form types.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.2 dropped December 8th. Readonly classes are the headline. The deprecation of dynamic properties is the one that actually requires your attention.</p>
<h2 id="dynamic-properties-deprecated">Dynamic properties deprecated</h2>
<p>PHP has always allowed adding properties to objects that weren&rsquo;t declared in the class:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">User</span> {}
</span></span><span style="display:flex;"><span>$user <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">User</span>();
</span></span><span style="display:flex;"><span>$user<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Alice&#39;</span>; <span style="color:#75715e">// no declaration, no error... until now
</span></span></span></code></pre></div><p>In 8.2, this triggers a deprecation notice. In PHP 9.0 it becomes a fatal error. The grace period exists, but the migration clock is running.</p>
<p>The reasoning is solid: dynamic properties are a classic source of typos that silently pass (write <code>$user-&gt;nmae</code> and PHP just creates a new property instead of complaining). Explicit declarations make the class contract clear and make tooling actually useful.</p>
<p>Migration is mostly mechanical: declare the properties, or slap <code>#[AllowDynamicProperties]</code> on legacy classes you can&rsquo;t touch yet.</p>
<h2 id="readonly-classes">Readonly classes</h2>
<p>8.1 added <code>readonly</code> for individual properties. 8.2 adds it to the class declaration itself:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#a6e22e">readonly</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Point</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">__construct</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $x,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $y,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $z,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>All promoted and explicitly declared properties become readonly automatically. Value objects (coordinates, money amounts, identifiers) are the obvious target. The syntax is clean and the intent reads clearly.</p>
<p>One constraint: readonly classes can&rsquo;t have non-typed properties, which were already a bad idea with readonly anyway.</p>
<h2 id="dnf-types">DNF types</h2>
<p>Disjunctive Normal Form types let you combine union and intersection types:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">Countable</span><span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Iterator</span><span style="color:#f92672">|</span><span style="color:#66d9ef">null</span> $collection)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> { <span style="color:#f92672">...</span> }
</span></span></code></pre></div><p><code>(Countable&amp;Iterator)|null</code>: an object that implements both interfaces, or null. This covers type expressions that 8.0 union types and 8.1 intersection types each got partway to but couldn&rsquo;t represent together.</p>
<h2 id="the-random-extension">The Random extension</h2>
<p>A dedicated <code>Random</code> extension replaces the scattered <code>rand()</code>, <code>mt_rand()</code>, <code>random_int()</code> functions with an object-oriented API:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>$rng <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Random\Randomizer</span>();
</span></span><span style="display:flex;"><span>$rng<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getInt</span>(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">100</span>);
</span></span><span style="display:flex;"><span>$rng<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">shuffleArray</span>($items);
</span></span></code></pre></div><p>Engines are swappable: <code>Mersenne Twister</code>, <code>PCG64</code>, <code>Xoshiro256StarStar</code>, or <code>CryptoSafeEngine</code> for security-sensitive contexts. Same code, seeded deterministic engine in tests, cryptographic engine in production.</p>
<p>8.2 is a consolidation release. The dynamic properties deprecation is the one decision you need to make now.</p>
<h2 id="null-false-and-true-as-standalone-types"><code>null</code>, <code>false</code>, and <code>true</code> as standalone types</h2>
<p>PHP has had <code>nullable</code> types since 7.1 and union types since 8.0, but <code>null</code> as a standalone type declaration wasn&rsquo;t valid. 8.2 fixes that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">alwaysNull</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">null</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">disabled</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">enabled</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>false</code> and <code>true</code> as standalone types are useful when you need to be precise about what a function can actually return. It&rsquo;s narrow but correct: a function that returns <code>false</code> on failure and a string on success should declare <code>string|false</code>, and now both sides of that union are real types.</p>
<h2 id="constants-in-traits">Constants in traits</h2>
<p>Traits could hold properties and methods. Constants were the odd gap. 8.2 closes it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">trait</span> <span style="color:#a6e22e">Timestamps</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DATE_FORMAT</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Y-m-d H:i:s&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">formatCreatedAt</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">createdAt</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">format</span>(<span style="color:#a6e22e">self</span><span style="color:#f92672">::</span><span style="color:#a6e22e">DATE_FORMAT</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Article</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Timestamps</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">Article</span><span style="color:#f92672">::</span><span style="color:#a6e22e">DATE_FORMAT</span>; <span style="color:#75715e">// &#39;Y-m-d H:i:s&#39;
</span></span></span></code></pre></div><p>The constant belongs to the class that uses the trait, not the trait itself, so you can&rsquo;t access <code>Timestamps::DATE_FORMAT</code> directly. Expected scoping behavior, consistent with how trait methods already work.</p>
<h2 id="sensitiveparameter"><code>#[SensitiveParameter]</code></h2>
<p>Stack traces have always been a liability: function arguments get logged verbatim, which means passwords and tokens end up in your error logs and monitoring dashboards. 8.2 adds an attribute to stop that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">authenticate</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">string</span> $user,
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\SensitiveParameter] string $password,
</span></span></span><span style="display:flex;"><span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">bool</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// if this throws, the stack trace shows:
</span></span></span><span style="display:flex;"><span>    <span style="color:#75715e">// authenticate(&#39;alice&#39;, Object(SensitiveParameterValue))
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">hash</span>(<span style="color:#e6db74">&#39;sha256&#39;</span>, $password) <span style="color:#f92672">===</span> <span style="color:#a6e22e">getStoredHash</span>($user);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The parameter value in the trace gets replaced with a <code>SensitiveParameterValue</code> object. One attribute, zero excuses not to add it to every function that touches credentials.</p>
<h2 id="deprecated-string-interpolation-syntaxes">Deprecated string interpolation syntaxes</h2>
<p>Two ways to interpolate expressions inside strings are deprecated in 8.2:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>$name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;world&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// These are deprecated:
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;Hello </span><span style="color:#e6db74">${</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>;       <span style="color:#75715e">// use &#34;$name&#34; or &#34;{$name}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;Hello </span><span style="color:#e6db74">${</span>getName()<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>;  <span style="color:#75715e">// use &#34;{$this-&gt;getName()}&#34;
</span></span></span></code></pre></div><p>The <code>${...}</code> forms created ambiguity between variable variables and expressions. The cleaner <code>{$...}</code> syntax has always been there and does the same thing. This is mostly a search-and-replace job in codebases that picked up the deprecated forms out of habit.</p>
<h2 id="utf8_encode-and-utf8_decode-deprecated"><code>utf8_encode()</code> and <code>utf8_decode()</code> deprecated</h2>
<p>These two functions are deprecated in 8.2 and gone in 9.0. Their behavior was always narrower than the names suggested: <code>utf8_encode()</code> converts ISO-8859-1 to UTF-8, not &ldquo;any encoding to UTF-8.&rdquo;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#75715e">// Deprecated in 8.2:
</span></span></span><span style="display:flex;"><span>$utf8 <span style="color:#f92672">=</span> <span style="color:#a6e22e">utf8_encode</span>($latin1String);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Use instead:
</span></span></span><span style="display:flex;"><span>$utf8 <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_convert_encoding</span>($latin1String, <span style="color:#e6db74">&#39;UTF-8&#39;</span>, <span style="color:#e6db74">&#39;ISO-8859-1&#39;</span>);
</span></span></code></pre></div><p><code>mb_convert_encoding()</code> or <code>iconv()</code> handle the general case. If you&rsquo;re actually dealing with Latin-1 input, the replacement is a direct swap.</p>
<h2 id="locale-independent-string-functions">Locale-independent string functions</h2>
<p>Several string functions silently varied behavior based on the system locale, producing different results in production versus a dev container. In 8.2, they&rsquo;re locale-independent and ASCII-only:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#75715e">// strtolower, strtoupper, stristr, stripos, strripos,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// lcfirst, ucfirst, ucwords, str_ireplace now do ASCII case conversion only.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// For locale-aware behavior, use mb_* equivalents:
</span></span></span><span style="display:flex;"><span>$lowered <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_strtolower</span>($text, <span style="color:#e6db74">&#39;UTF-8&#39;</span>);
</span></span></code></pre></div><p>This is a correctness fix. If your code was relying on locale-sensitive behavior from these functions, it was already broken on systems with different locale configurations. 8.2 makes the behavior deterministic everywhere, which is what you actually wanted.</p>
<h2 id="str_split-on-empty-string"><code>str_split()</code> on empty string</h2>
<p>A quiet behavior change worth noting:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#75715e">// PHP 8.1: str_split(&#39;&#39;) === [&#39;&#39;]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// PHP 8.2: str_split(&#39;&#39;) === []
</span></span></span></code></pre></div><p>The new behavior makes more sense: splitting nothing produces nothing. If you&rsquo;re checking <code>count(str_split($input))</code>, an empty input no longer produces a count of 1.</p>
]]></content:encoded></item></channel></rss>