<?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>Attributes on Guillaume Delré</title><link>https://guillaumedelre.github.io/tags/attributes/</link><description>Recent content in Attributes on Guillaume Delré</description><generator>Hugo</generator><language>en</language><lastBuildDate>Fri, 12 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/tags/attributes/index.xml" rel="self" type="application/rss+xml"/><item><title>Symfony 7.0: PHP 8.2 minimum and annotations finally gone</title><link>https://guillaumedelre.github.io/2024/01/12/symfony-7.0-php-8.2-minimum-and-annotations-finally-gone/</link><pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/2024/01/12/symfony-7.0-php-8.2-minimum-and-annotations-finally-gone/</guid><description>Part 9 of 11 in &amp;quot;Symfony Releases&amp;quot;: Symfony 7.0 requires PHP 8.2, drops Doctrine annotations entirely, and ships a rebuilt Workflow component.</description><category>symfony-releases</category><content:encoded><![CDATA[<p>Symfony 7.0 landed November 29, 2023, same day as 6.4. The pattern holds: the X.0 release cuts deprecated code and raises the PHP floor. 7.0 requires PHP 8.2 and removes everything that 6.4 flagged as deprecated.</p>
<p>The most visible removal: Doctrine annotations. <code>@Route</code>, <code>@ORM\Column</code>, <code>@Assert</code> - gone. Native PHP attributes have been the recommended approach since Symfony 5.2. 7.0 just makes it official.</p>
<h2 id="attributes-everywhere">Attributes everywhere</h2>
<p>The migration from annotations to attributes is mostly mechanical: syntax changes from <code>@</code> to <code>#[]</code>, and the class references move from Doctrine annotation classes to PHP attribute classes:</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">// before
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">/** @Route(&#39;/users&#39;, methods={&#34;GET&#34;}) */</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// after
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#[Route(&#39;/users&#39;, methods: [&#39;GET&#39;])]
</span></span></span></code></pre></div><p>The real win isn&rsquo;t just the syntax: attributes are validated by the PHP engine, not a docblock parser. IDEs can resolve them without custom plugins. Static analysis tools understand them natively. No more &ldquo;it fails silently at runtime because of a typo in a comment.&rdquo;</p>
<h2 id="workflow-with-php-attributes">Workflow with PHP attributes</h2>
<p>Workflow event listeners and guards can now be registered via attributes:</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">#[AsGuard(workflow: &#39;order&#39;, transition: &#39;ship&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">canShip</span>(<span style="color:#a6e22e">Event</span> $event)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span>$event<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getSubject</span>()<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">isPaymentConfirmed</span>()) {
</span></span><span style="display:flex;"><span>        $event<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setBlocked</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The workflow profiler, a dedicated panel showing the current marking and available transitions, is a genuinely useful debugging tool if you&rsquo;re working with complex state machines.</p>
<h2 id="clock1-datepoint-in-the-clock-component">:clock1: DatePoint in the Clock component</h2>
<p><code>DatePoint</code>, the immutable <code>DateTime</code> with strict error handling introduced in 6.4, is now the recommended way to work with dates. Combine it with PHP 8.2&rsquo;s readonly properties and date value objects in domain code become almost trivially clean:</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">Order</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">DatePoint</span> $createdAt,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">DatePoint</span> $shippedAt <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="what-70-removes">What 7.0 removes</h2>
<p>The full removal list: Doctrine annotations support, the <code>Templating</code> component bridge, <code>ProxyManager</code> bridge, the <code>Monolog</code> bridge for versions below 3.0, and the Sendinblue transport (replaced by Brevo). PHP 8.0 and 8.1 support also ends. 8.2 is the floor now.</p>
<p>Upgrade from 6.4 with all deprecation notices fixed, and 7.0 is smooth. Skip that step and you&rsquo;re in for a bad time.</p>
<h2 id="scheduler-and-assetmapper-graduate">Scheduler and AssetMapper graduate</h2>
<p>Two components that shipped as experimental in 6.3 are now stable: Scheduler and AssetMapper. Stable means locked APIs, no more <code>@experimental</code> caveats, and they show up properly in the upgrade guide. You can actually rely on them now.</p>
<p>Scheduler gets <code>#[AsCronTask]</code> and <code>#[AsPeriodicTask]</code> for attribute-based task registration, runtime schedule modification with heap recalculation, <code>FailureEvent</code>, and a <code>--date</code> option on <code>schedule:debug</code>. AssetMapper adds CSS file support in importmap, an <code>outdated</code> command, an <code>audit</code> command, and automatic preloading via WebLink.</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">#[AsCronTask(&#39;0 2 * * *&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">NightlyReportMessage</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[AsPeriodicTask(frequency: &#39;1 hour&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">HourlyCleanupMessage</span> {}
</span></span></code></pre></div><h2 id="service-wiring-gets-two-new-attributes">Service wiring gets two new attributes</h2>
<p><code>#[AutowireLocator]</code> and <code>#[AutowireIterator]</code> landed in 6.4 and graduate to stable in 7.0. They replace the verbose XML/YAML tagged service locator config with something you can just put directly in PHP:</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">HandlerRegistry</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">__construct</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#[AutowireLocator(&#39;app.handler&#39;, indexAttribute: &#39;key&#39;)]
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">ContainerInterface</span> $handlers,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>#[Target]</code> also gets smarter: when a service has a named autowiring alias like <code>invoice.lock.factory</code>, you can now write <code>#[Target('invoice')]</code> instead of the full alias name. Less noise when the type already tells you what you want.</p>
<h2 id="messenger-gets-more-precise-failure-handling">Messenger gets more precise failure handling</h2>
<p><code>RejectRedeliveredMessageException</code> tells the worker to not retry a message, which is handy when a message arrives twice because of a transport ack timeout and you need exactly-once semantics. <code>messenger:failed:remove --all</code> clears the entire failure transport in one shot, no loop required. Failed retries can also go directly to the failure transport, bypassing the retry queue entirely.</p>
<p>Multiple Redis Sentinel hosts are now supported in the DSN:</p>
<pre tabindex="0"><code>redis-sentinel://host1:26379,host2:26379,host3:26379/mymaster
</code></pre><h2 id="console-gets-signal-names-and-command-profiling">Console gets signal names and command profiling</h2>
<p><code>SignalMap</code> maps signal integers to their POSIX names. When a worker catches <code>SIGTERM</code>, the log now says <code>SIGTERM</code> instead of <code>15</code>. Small thing, real improvement. <code>ConsoleTerminateEvent</code> is dispatched even when the process exits via signal, which wasn&rsquo;t the case before 7.0.</p>
<p>Command profiling lands too: pass <code>--profile</code> to <code>bin/console</code> and the collected data goes straight into the Symfony profiler, browsable from the web UI.</p>
<h2 id="form-small-things-that-add-up">Form: small things that add up</h2>
<p><code>ChoiceType</code> gets a <code>duplicate_preferred_choices</code> option. Set it to <code>false</code> and you stop showing the same option twice when preferred choices overlap with the full list. <code>FormEvent::setData()</code> is deprecated for events where the data is already locked at that point in the lifecycle. The self-closing slash on <code>&lt;input&gt;</code> elements is also gone: <code>&lt;input&gt;</code> is a void element in HTML5 and the slash was technically invalid.</p>
<p>Enum support in forms is a nice one: <code>ChoiceType</code> renders backed enums directly, and translatable enums get their labels through the translator without any custom wiring.</p>
<h2 id="httpfoundation-small-but-useful">HttpFoundation: small but useful</h2>
<p><code>Response::send()</code> gets a <code>$flush</code> parameter. Pass <code>false</code> to buffer the output without flushing to the client, useful when chaining middleware that needs to inspect the response before it leaves the process.</p>
<p><code>UriSigner</code> moves from HttpKernel to HttpFoundation, where it belongs semantically. Same class name, different namespace.</p>
<p>Cookies get CHIPS support (Cookies Having Independent Partitioned State), the browser mechanism for cross-site cookies in a first-party partition. Only matters if you build embeddable widgets, but good to know it&rsquo;s there.</p>
<h2 id="translation-phrase-provider-and-tree-output">Translation: Phrase provider and tree output</h2>
<p>Phrase joins Crowdin and Lokalise as a supported translation provider. Configure it in <code>config/packages/translation.yaml</code> and the <code>translation:push</code> / <code>translation:pull</code> commands handle the sync.</p>
<p><code>translation:pull</code> gets an <code>--as-tree</code> option that writes translation files in nested YAML rather than flat dot-notation keys. Whether that&rsquo;s actually better depends entirely on your team.</p>
<p><code>LocaleSwitcher::runWithLocale()</code> now passes the current locale as an argument to the callback, saving you a <code>getLocale()</code> call inside:</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>$switcher<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">runWithLocale</span>(<span style="color:#e6db74">&#39;fr&#39;</span>, <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">string</span> $locale) <span style="color:#66d9ef">use</span> ($mailer) {
</span></span><span style="display:flex;"><span>    $mailer<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">send</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">buildEmail</span>($locale));
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="a-few-things-in-serializer-and-domcrawler">A few things in Serializer and DomCrawler</h2>
<p>The Serializer&rsquo;s <code>Context</code> attribute can now target specific classes, so a single DTO can behave differently during (de)serialization depending on which class holds the context. <code>TranslatableNormalizer</code> lands for normalizing objects that implement <code>TranslatableInterface</code>: the translator is called during normalization, not before.</p>
<p><code>Crawler::attr()</code> gains a <code>$default</code> parameter. Instead of null-checking the return value, pass a fallback:</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>$src <span style="color:#f92672">=</span> $crawler<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">attr</span>(<span style="color:#e6db74">&#39;src&#39;</span>, <span style="color:#e6db74">&#39;/placeholder.png&#39;</span>);
</span></span></code></pre></div><p><code>assertAnySelectorText()</code> and <code>assertAnySelectorTextContains()</code> join the DomCrawler assertion set. They pass if at least one matching element satisfies the condition, rather than requiring all of them to match.</p>
<h2 id="httpclient-har-responses-for-testing">HttpClient: HAR responses for testing</h2>
<p><code>MockResponse</code> now accepts HAR (HTTP Archive) files. Record real HTTP interactions in your browser or with a proxy, drop the <code>.har</code> file in your test fixtures, and replay them:</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>$client <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MockHttpClient</span>(<span style="color:#a6e22e">HarFileResponseFactory</span><span style="color:#f92672">::</span><span style="color:#a6e22e">createFromFile</span>(<span style="color:#66d9ef">__DIR__</span><span style="color:#f92672">.</span><span style="color:#e6db74">&#39;/fixtures/api.har&#39;</span>));
</span></span></code></pre></div><p>Much better than writing response stubs by hand when you&rsquo;re dealing with a complex API.</p>
]]></content:encoded></item><item><title>PHP 8.0: match, named arguments, attributes, and JIT</title><link>https://guillaumedelre.github.io/2021/01/10/php-8.0-match-named-arguments-attributes-and-jit/</link><pubDate>Sun, 10 Jan 2021 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/2021/01/10/php-8.0-match-named-arguments-attributes-and-jit/</guid><description>Part 6 of 11 in &amp;quot;PHP Releases&amp;quot;: PHP 8.0 reshapes the language: JIT compiler, named arguments, match expressions, union types, and nullsafe operator.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.0 shipped November 26th. I&rsquo;ve been running it for six weeks on a side project and a greenfield service at work. It&rsquo;s the most significant PHP release since 7.0, and in some ways more impactful, because the changes pile on top of each other in useful ways.</p>
<h2 id="jit">JIT</h2>
<p>The Just-In-Time compiler was the headline announcement. The reality in production is more nuanced: for typical web apps (database queries, HTTP calls, template rendering) the gains are modest, because those workloads are I/O bound, not compute bound. Where JIT actually shines is CPU-intensive code: image manipulation, data transformation, mathematical computation.</p>
<p>For most web apps, the performance improvement comes from the overall engine work in 8.0, not JIT specifically. Still worth enabling though: it costs nothing on I/O-bound work.</p>
<h2 id="match-expressions">Match expressions</h2>
<p><code>switch</code> has three problems: it uses loose comparison, it falls through by default, and it can&rsquo;t be used as an expression. <code>match</code> fixes all three:</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>$result <span style="color:#f92672">=</span> <span style="color:#a6e22e">match</span>($status) {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;active&#39;</span>, <span style="color:#e6db74">&#39;pending&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;processing&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;done&#39;</span>             <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;finished&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">default</span>            <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\UnexpectedValueException</span>($status),
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Strict comparison. No fall-through. Expression that returns a value. Non-exhaustive match throws. After one week with <code>match</code> I stopped writing <code>switch</code>.</p>
<h2 id="named-arguments">Named arguments</h2>
<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">array_slice</span>(<span style="color:#66d9ef">array</span><span style="color:#f92672">:</span> $users, <span style="color:#a6e22e">offset</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">length</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">10</span>, <span style="color:#a6e22e">preserve_keys</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>);
</span></span></code></pre></div><p>Named arguments let you pass arguments in any order and skip optional ones. The obvious win is readability on functions with multiple boolean flags. The less obvious win: named arguments survive PHP version upgrades even when parameter order changes, because you&rsquo;re naming what you mean.</p>
<h2 id="attributes">Attributes</h2>
<p>Out with docblock annotations (the <code>@Route</code>, <code>@ORM\Column</code> style that frameworks have relied on for years), in with first-class PHP syntax:</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">#[Route(&#39;/users&#39;, methods: [&#39;GET&#39;])]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#[IsGranted(&#39;ROLE_ADMIN&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">list</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">Response</span> { <span style="color:#f92672">...</span> }
</span></span></code></pre></div><p>Attributes are validated by the engine, not parsed from strings. IDE support just works, no plugin magic needed. For Symfony and Doctrine users, this is the real daily win of PHP 8.0.</p>
<h2 id="constructor-promotion">Constructor promotion</h2>
<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>    <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">readonly</span> <span style="color:#a6e22e">int</span> $id,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $name,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $email <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Properties declared and assigned in one line in the constructor signature. The most immediate refactoring win in 8.0: every data class I&rsquo;ve touched since upgrading is half the lines it used to be.</p>
<h2 id="nullsafe-operator">Nullsafe operator</h2>
<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>$city <span style="color:#f92672">=</span> $user<span style="color:#f92672">?-&gt;</span><span style="color:#a6e22e">getAddress</span>()<span style="color:#f92672">?-&gt;</span><span style="color:#a6e22e">getCity</span>()<span style="color:#f92672">?-&gt;</span><span style="color:#a6e22e">getName</span>();
</span></span></code></pre></div><p><code>null</code> at any point in the chain short-circuits the rest and returns <code>null</code>. The alternative was nested null checks or a chain of early returns. This composes naturally.</p>
<h2 id="union-types">Union types</h2>
<p>Named arguments make function signatures more explicit at the call site. Union types make them more honest at the declaration site:</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">processInput</span>(<span style="color:#a6e22e">int</span><span style="color:#f92672">|</span><span style="color:#a6e22e">float</span><span style="color:#f92672">|</span><span style="color:#a6e22e">string</span> $value)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span><span style="color:#f92672">|</span><span style="color:#a6e22e">int</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">is_string</span>($value)) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">strlen</span>($value);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> (<span style="color:#a6e22e">int</span>) <span style="color:#a6e22e">round</span>($value);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The union <code>int|float|string</code> is a literal OR. The engine enforces it on entry and exit. Before 8.0, &ldquo;this parameter accepts int or float&rdquo; lived in a docblock that nothing enforced. There&rsquo;s also <code>null</code> as a type component: <code>?string</code> is just syntactic sugar for <code>string|null</code>, both are valid.</p>
<p>One special case: <code>false</code>. PHP has a bunch of built-in functions that return a typed value on success and <code>false</code> on failure. The 8.0 type system accommodates that: <code>array|false</code>, <code>string|false</code>. It&rsquo;s an honest acknowledgment that the codebase can&rsquo;t be rewritten overnight.</p>
<h2 id="static-return-type">static return type</h2>
<p><code>static</code> as a return type was possible informally through docblocks, but 8.0 makes it official. The distinction between <code>self</code> and <code>static</code> matters in inheritance:</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">Builder</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">array</span> $config <span style="color:#f92672">=</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">set</span>(<span style="color:#a6e22e">string</span> $key, <span style="color:#a6e22e">mixed</span> $value)<span style="color:#f92672">:</span> <span style="color:#66d9ef">static</span> {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">config</span>[$key] <span style="color:#f92672">=</span> $value;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $this;
</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">SpecialBuilder</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Builder</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> (<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">SpecialBuilder</span>())<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;foo&#39;</span>, <span style="color:#e6db74">&#39;bar&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// $result is SpecialBuilder, not Builder
</span></span></span></code></pre></div><p>With <code>self</code> as the return type, that chain would return <code>Builder</code>, breaking fluent interfaces in subclasses. <code>static</code> makes fluent APIs work correctly across inheritance hierarchies without manual overrides.</p>
<h2 id="mixed-type">mixed type</h2>
<p><code>mixed</code> was a docblock convention for years. 8.0 makes it a real type that shows up in signatures:</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">debug</span>(<span style="color:#a6e22e">mixed</span> $value)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">var_dump</span>($value);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It accepts everything: <code>null</code>, objects, resources, scalars, arrays. Semantically it&rsquo;s the same as having no type declaration, but it&rsquo;s explicit rather than absent. The difference between &ldquo;this parameter is untyped&rdquo; and &ldquo;this parameter intentionally accepts anything.&rdquo; Worth using when you&rsquo;re writing a general-purpose utility that would be dishonest with a narrower type.</p>
<h2 id="throw-as-expression">throw as expression</h2>
<p>Before 8.0, <code>throw</code> was a statement. Sounds like a pedantic distinction until you hit the places where you actually want an expression:</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">// In a ternary:
</span></span></span><span style="display:flex;"><span>$value <span style="color:#f92672">=</span> $input <span style="color:#f92672">??</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\InvalidArgumentException</span>(<span style="color:#e6db74">&#39;input required&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// In an arrow function:
</span></span></span><span style="display:flex;"><span>$getId <span style="color:#f92672">=</span> <span style="color:#a6e22e">fn</span>(<span style="color:#a6e22e">User</span> $u) <span style="color:#f92672">=&gt;</span> $u<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">id</span> <span style="color:#f92672">??</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\RuntimeException</span>(<span style="color:#e6db74">&#39;no id&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// In a match arm (which is already an expression):
</span></span></span><span style="display:flex;"><span>$status <span style="color:#f92672">=</span> <span style="color:#a6e22e">match</span>($code) {
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">200</span>     <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;ok&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">404</span>     <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;not found&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">default</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\UnexpectedValueException</span>(<span style="color:#e6db74">&#34;unknown code: </span><span style="color:#e6db74">$code</span><span style="color:#e6db74">&#34;</span>),
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The last one is particularly useful: match without a default will throw <code>UnhandledMatchError</code> automatically, but sometimes you want to control the exception type and message.</p>
<h2 id="catch-without-a-variable">catch without a variable</h2>
<p>Small quality-of-life fix. When you catch an exception but don&rsquo;t actually use the object, 8.0 lets you omit the variable:</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">try</span> {
</span></span><span style="display:flex;"><span>    $result <span style="color:#f92672">=</span> $cache<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">get</span>($key);
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">CacheMissException</span>) {
</span></span><span style="display:flex;"><span>    $result <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">compute</span>($key);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Before 8.0, you had to write <code>catch (CacheMissException $e)</code> and then either use <code>$e</code> or live with the IDE warning about an unused variable. Neither was satisfying.</p>
<h2 id="string-functions-that-should-have-existed-years-ago">String functions that should have existed years ago</h2>
<p>Three functions that every PHP developer has written manually at least once:</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">str_contains</span>(<span style="color:#e6db74">&#39;hello world&#39;</span>, <span style="color:#e6db74">&#39;world&#39;</span>);  <span style="color:#75715e">// true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">str_starts_with</span>(<span style="color:#e6db74">&#39;hello world&#39;</span>, <span style="color:#e6db74">&#39;hell&#39;</span>); <span style="color:#75715e">// true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">str_ends_with</span>(<span style="color:#e6db74">&#39;hello world&#39;</span>, <span style="color:#e6db74">&#39;world&#39;</span>); <span style="color:#75715e">// true
</span></span></span></code></pre></div><p>Before 8.0, the go-to approaches were <code>strpos() !== false</code>, <code>strncmp()</code>, or <code>substr() ===</code>, all of which require stopping to remember the semantics. These new functions are just direct and readable. No regex, no offset arithmetic.</p>
<h2 id="stable-sort">Stable sort</h2>
<p>PHP&rsquo;s sorting functions weren&rsquo;t stable before 8.0. &ldquo;Not stable&rdquo; means elements that compare as equal could end up in any order relative to each other. In practice this caused subtle bugs in UI code that needed consistent ordering, pagination that shifted between loads, and tests that only passed by luck.</p>
<p>8.0 guarantees stability across all sorting functions: <code>sort()</code>, <code>usort()</code>, <code>array_multisort()</code>, and the rest. Equal elements keep their original relative position. This is the behavior most people assumed was already there.</p>
<h2 id="weakmap">WeakMap</h2>
<p>7.4 brought <code>WeakReference</code> for single objects. 8.0 brings <code>WeakMap</code>: a map where both the keys (objects) and their associated data can be garbage collected when no other reference to the key object exists:</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">RequestCache</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">WeakMap</span> $cache;
</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">__construct</span>() {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">cache</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">WeakMap</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">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">Request</span> $request)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Response</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">cache</span>[$request] <span style="color:#f92672">??=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">compute</span>($request);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The moment <code>$request</code> is no longer referenced anywhere else, the entry disappears from the map. No manual cleanup needed. It&rsquo;s the right pattern for memoization and computed property caches where you don&rsquo;t want to be the sole reason an object stays alive.</p>
<h2 id="new-exception-types">New exception types</h2>
<p><code>ValueError</code> is thrown when a function gets the right type but an invalid value, as opposed to <code>TypeError</code> which fires on wrong 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:#a6e22e">array_chunk</span>([], <span style="color:#f92672">-</span><span style="color:#ae81ff">5</span>); <span style="color:#75715e">// ValueError: array_chunk(): Argument #2 ($length) must be greater than 0
</span></span></span></code></pre></div><p>Before 8.0, many of these were warnings that returned <code>false</code> or <code>null</code>. Now they throw. The engine is stricter, which means you catch problems earlier instead of getting weird results somewhere downstream.</p>
<h2 id="get_debug_type-and-fdiv">get_debug_type() and fdiv()</h2>
<p>Two utility functions worth knowing.</p>
<p><code>get_debug_type()</code> returns a normalized string representation of any value, handy for error messages:</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">get_debug_type</span>(<span style="color:#ae81ff">1</span>);          <span style="color:#75715e">// &#34;int&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>(<span style="color:#ae81ff">1.0</span>);        <span style="color:#75715e">// &#34;float&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>(<span style="color:#66d9ef">null</span>);       <span style="color:#75715e">// &#34;null&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>(<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Foo</span>());  <span style="color:#75715e">// &#34;Foo&#34; (not &#34;object&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>([]);         <span style="color:#75715e">// &#34;array&#34;
</span></span></span></code></pre></div><p>The difference from <code>gettype()</code>: it returns class names for objects and uses normalized names (<code>&quot;int&quot;</code> not <code>&quot;integer&quot;</code>). Exactly what you want when building an exception message that says what you got versus what you expected.</p>
<p><code>fdiv()</code> performs floating-point division following IEEE 754, meaning division by zero returns <code>INF</code>, <code>-INF</code>, or <code>NAN</code> instead of a warning:</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">fdiv</span>(<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">0</span>);   <span style="color:#75715e">// INF
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">fdiv</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">0</span>);  <span style="color:#75715e">// -INF
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">fdiv</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);    <span style="color:#75715e">// NAN
</span></span></span></code></pre></div><h2 id="the-changes-that-break-things">The changes that break things</h2>
<p>8.0 also ships a few changes that aren&rsquo;t features, they&rsquo;re corrections.</p>
<p>The big one: <code>0 == &quot;foo&quot;</code> is now <code>false</code>. In PHP 7, comparing an integer to a non-numeric string would cast the string to 0, so <code>0 == &quot;anything-non-numeric&quot;</code> evaluated to <code>true</code>. That was a persistent source of bugs and security headaches. PHP 8 flips it: the integer gets cast to a string instead:</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">var_dump</span>(<span style="color:#ae81ff">0</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;foo&#34;</span>);  <span style="color:#75715e">// bool(false) in 8.0, bool(true) in 7.x
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>(<span style="color:#ae81ff">0</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span>);     <span style="color:#75715e">// bool(false) in 8.0, bool(true) in 7.x
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>(<span style="color:#ae81ff">0</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;0&#34;</span>);    <span style="color:#75715e">// bool(true) in both (&#34;0&#34; is numeric)
</span></span></span></code></pre></div><p>If you relied on this intentionally, you already knew it was sketchy. If you didn&rsquo;t know you relied on it, 8.0 will find those code paths for you.</p>
<p>Several functions that used to return resources now return proper objects: <code>curl_init()</code> returns a <code>CurlHandle</code>, <code>imagecreate()</code> returns a <code>GdImage</code>, <code>xml_parser_create()</code> returns an <code>XMLParser</code>. Code that checks <code>is_resource($curl)</code> will break, because <code>is_resource()</code> returns <code>false</code> for these objects. The fix is to check against <code>false</code> (the return value on failure) rather than checking the type of the success case.</p>
<p>PHP 8.0 is the kind of release where the features reinforce each other. Attributes play well with constructor promotion. Match pairs naturally with union types. The string functions cut noise that was hiding intent. The corrections are occasionally breaking, but they push the language toward consistency it should have had years ago.</p>
]]></content:encoded></item></channel></rss>