<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Andreas Möller</title><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><id>https://localheinz.com/feed.xml</id><lang>en-us</lang><updated>2023-09-12T07:00:00+02:00</updated><description>My name is Andreas Möller, and I am a self-employed Software Engineer and Consultant from Berlin, Germany. What can I do for you?</description><entry><title>Adopting a reasonable PHP version support policy</title><category term="maintenance"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/09/12/adopting-a-reasonable-php-version-support-policy/"/><id>https://localheinz.com/articles/2023/09/12/adopting-a-reasonable-php-version-support-policy/</id><updated>2023-09-12T07:00:00+02:00</updated><content>&lt;h1&gt;
  Adopting a reasonable PHP version support policy
&lt;/h1&gt;


&lt;p&gt;The fourth Thursday in November of every year is a special day for people working on and with PHP.&lt;/p&gt;
&lt;p&gt;On that day, the release managers of PHP release a new major or minor PHP version. On that day, two years after its initial release, another PHP version reaches the end of active support. And on that day, three years after its initial release, another PHP version reaches the end of security support, and thus, its end of life.&lt;/p&gt;
&lt;p&gt;If you work with PHP, whether you like it or not, you are subject to the &lt;a href="/articles/2023/07/16/understanding-the-lifecycle-of-a-php-version/" target="_blank" title="Understanding&amp;#x20;the&amp;#x20;lifecycle&amp;#x20;of&amp;#x20;a&amp;#x20;PHP&amp;#x20;version"&gt;lifecycle of a PHP version&lt;/a&gt;. If you work with PHP, the question for you is how you align yourself with the lifecycle of a PHP version.&lt;/p&gt;
&lt;p&gt;A PHP version policy can help you with that.&lt;/p&gt;
&lt;h2 id="content-php-version-support-policy-for-php-applications" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-php-version-support-policy-for-php-applications" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;PHP version support policy for PHP applications&lt;/h2&gt;
&lt;p&gt;If you build, maintain, and run a PHP application in production, you have control over the environment in which that PHP application runs. You decide which PHP version that application uses and whether and when you update to a new PHP version.&lt;/p&gt;
&lt;p&gt;Typically, a PHP application runs on one PHP version only. If your PHP application supports more than one PHP version and you have control over the environment in which that PHP application runs, you would always prefer to run the PHP application on the newest PHP version, or would you not? Supporting more than one PHP version does not make sense for a PHP application unless you are not running it yourself.&lt;/p&gt;
&lt;p&gt;A PHP version support policy for a PHP application states how you commit to updating that PHP application to a new PHP version in alignment with the lifecycle of a PHP version.&lt;/p&gt;
&lt;p&gt;A PHP version support policy for a PHP application could state the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The maintainers of this application will update to a new major or minor PHP version within three months after its release.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whether you find such a PHP version support policy reasonable depends on whether you and your team can update that PHP application to a new PHP version within three months. You can start with a more relaxed PHP version support policy and tighten it as you become better at maintaining your PHP application.&lt;/p&gt;
&lt;p&gt;Updating to a new PHP version may require development work. If your PHP application currently uses a PHP version that has reached its end of active or security support, then that PHP application is already one or more major or minor PHP versions behind. If you do not know how to update your PHP application to a new PHP version but also do not plan to sunset that application or go out of business soon, get help like everyone else. There is no shame in accepting and admitting that you have painted yourself into a corner.&lt;/p&gt;
&lt;p&gt;By adopting a PHP version support policy for your PHP application, you manage expectations for when stakeholders can expect you to update a PHP application to a new PHP version.&lt;/p&gt;
&lt;p&gt;By committing to a PHP version support policy for your PHP application, you will more frequently update to a new PHP version.&lt;/p&gt;
&lt;p&gt;By updating your PHP application more frequently to a new PHP version, you benefit from the upside and mitigate the downside of updating to a new PHP version as early as you can.&lt;/p&gt;
&lt;p&gt;By adopting and committing to a PHP version support policy, you will become better at maintaining your PHP application.
PHP version support policy for PHP packages
If you build, maintain, and distribute a PHP package, you have limited control over the environments in which others consume that PHP package, but you can decide which PHP versions that PHP package supports.&lt;/p&gt;
&lt;p&gt;Typically, a PHP package supports more than one PHP version. If people use that PHP package in a PHP application, they will have a much easier time updating their PHP application from one PHP version to another when they do not simultaneously have to update that PHP package. Instead, they probably want to update their PHP packages first, then update to a new PHP version. By supporting more than one PHP version, you make it easier for people using that PHP package to update to a new PHP version.&lt;/p&gt;
&lt;p&gt;A PHP version support policy for a PHP package states how you commit to add and drop support for PHP versions in alignment with the lifecycle of a PHP version.&lt;/p&gt;
&lt;p&gt;A PHP version support policy for a PHP package supporting two major or minor PHP versions could state the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The maintainers of this package add support for a PHP version following its initial release and drop support for a PHP version when it has reached its end of active support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A PHP version support policy for a PHP package supporting three major or minor PHP versions could state the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The maintainers of this package add support for a PHP version following its initial release and drop support for a PHP version when it has reached its end of security support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A PHP version support policy for a PHP package supporting four major or minor PHP versions could state the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The maintainers of this package add support for a PHP version following its initial release and drop support for a PHP version one year after it has reached its end of security support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whether you find a progressive or a more conservative PHP version support policy reasonable depends on your goals and maintenance budget. You can always relax your PHP version policy and tighten it when your goals change.&lt;/p&gt;
&lt;p&gt;Adding support for a new PHP version close to its initial release to a PHP package may demonstrate that the package is well-maintained. If you fail to add support for a new PHP version to your PHP package, users may conclude that you have abandoned it.&lt;/p&gt;
&lt;p&gt;Adding support for a new PHP version to a PHP package may require development work. If your PHP package supports many PHP versions, one of these PHP versions may have already deprecated a PHP feature that you use and rely on in that PHP package. The new PHP version may remove that PHP feature. Before you can add support, you must work around the removal of that PHP feature, which may include dropping support for a PHP version.&lt;/p&gt;
&lt;p&gt;Adding support for a new PHP version to a PHP package may come prematurely. If people use your PHP package in other PHP packages, they will often ask to add support for a new PHP version as soon as the release managers of PHP create the branch for that PHP version - which is months before its initial release. Note that &lt;a href="https://github.com/symfony/symfony/pull/36876" target="_blank" title="Symfony&amp;#x3A;&amp;#x20;Use&amp;#x20;&amp;quot;&amp;gt;&amp;#x3D;&amp;quot;&amp;#x20;for&amp;#x20;the&amp;#x20;&amp;quot;php&amp;quot;&amp;#x20;requirement"&gt;allowing users to install your PHP package on a PHP version&lt;/a&gt; differs from explicitly adding support for a PHP version. Using proper version constraints&lt;/a&gt; helps.&lt;/p&gt;
&lt;p&gt;Dropping support for a PHP version without active or security support from a PHP package may also demonstrate that the package is well-maintained.&lt;/p&gt;
&lt;p&gt;Dropping support for a PHP version from a PHP package may require some development work but will reduce the maintenance efforts for that PHP package. If your PHP package supports many PHP versions, you may have to manage multiple execution paths depending on the PHP version. If you drop support for a PHP version, you may be able to remove one or more of these execution paths.&lt;/p&gt;
&lt;p&gt;Dropping support for a PHP version from a PHP package may reduce the environmental impact of that PHP package. If you support many PHP versions, you will need to run automated tests on all of these PHP versions in a continuous integration environment. By reducing the number of PHP versions you support, you reduce the number of test runs and the environmental impact of your PHP package.&lt;/p&gt;
&lt;p&gt;Dropping support for a PHP version from a PHP package may allow you to use features present in newer PHP versions. If you support many PHP versions, you are limited to using the PHP features that are present in the lowest PHP version. You could use so-called polyfills, PHP packages that may partially or fully implement newer PHP features for older PHP versions. If you require polyfills in your PHP packages, the users of your PHP packages who do not need them may have to install or replace these polyfills in their PHP applications.&lt;/p&gt;
&lt;p&gt;Dropping support for a PHP version may reduce the number of people who can use the latest version of that PHP package. If the latest version of that PHP package requires a PHP version that these people are unable or unwilling to update to, they can and will continue to use an older version of that PHP package. That may or may not be a problem, depending on what your PHP package does.&lt;/p&gt;
&lt;p&gt;Dropping support for a PHP version may require persuasion. If you are one of many maintainers of a PHP package, you need to convince other maintainers that it is worth dropping support for a PHP version.&lt;/p&gt;
&lt;p&gt;By adopting a PHP version support policy for your PHP package, you manage expectations for when users, contributors, and maintainers can expect you to add and drop support for a PHP version from your PHP package.&lt;/p&gt;
&lt;p&gt;By committing to a PHP version support policy for your PHP package, you will more frequently drop support for a PHP version with less resistance from users, contributors, and maintainers.&lt;/p&gt;
&lt;p&gt;By dropping support for PHP versions from your PHP package more frequently, you reduce the maintenance effort and environmental impact, you can use features of new PHP versions earlier, and last but not least, you increase the pressure on users of your PHP package to update their PHP applications to a new PHP version.&lt;/p&gt;
&lt;p&gt;By increasing the pressure on users of your PHP package to update their PHP applications to a new PHP version, you become part of the tribe that moves the PHP ecosystem forward.&lt;/p&gt;
&lt;p&gt;Have you adopted and committed to a PHP version support policy yet?&lt;/p&gt;

</content></entry><entry><title>Understanding the lifecycle of a PHP version</title><category term="maintenance"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/07/16/understanding-the-lifecycle-of-a-php-version/"/><id>https://localheinz.com/articles/2023/07/16/understanding-the-lifecycle-of-a-php-version/</id><updated>2023-07-16T12:30:00+02:00</updated><content>&lt;h1&gt;
  Understanding the lifecycle of a PHP version
&lt;/h1&gt;


&lt;p&gt;You build, maintain, run, and distribute PHP applications and packages - but are you aware of the lifecycle of a PHP version?&lt;/p&gt;
&lt;h2 id="content-initial-release" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-initial-release" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Initial release&lt;/h2&gt;
&lt;p&gt;The initial release marks the beginning of the lifecycle of a PHP version.&lt;/p&gt;
&lt;h2 id="content-active-support" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-active-support" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Active support&lt;/h2&gt;
&lt;p&gt;Active support for a PHP version begins with the initial release and ends two years after the initial release.&lt;/p&gt;
&lt;p&gt;During active support, maintainers fix bugs and security issues and release new patch versions.&lt;/p&gt;
&lt;p&gt;The following PHP versions currently have active support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP 8.3
&lt;ul&gt;
&lt;li&gt;initial release on November 23, 2023&lt;/li&gt;
&lt;li&gt;active support ends on November 23, 2025&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 8.2
&lt;ul&gt;
&lt;li&gt;initial release on December 8, 2022&lt;/li&gt;
&lt;li&gt;active support ends on December 8, 2024&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;💡 Also see &lt;a href="https://www.php.net/supported-versions.php" target="_blank" title="PHP: Supported versions"&gt;the official list of supported PHP versions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="content-security-support" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-security-support" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Security support&lt;/h2&gt;
&lt;p&gt;Security support for a PHP version begins with the end of active support and ends one year after the beginning of security support.&lt;/p&gt;
&lt;p&gt;During security support, maintainers fix critical security issues and release new patch versions.&lt;/p&gt;
&lt;p&gt;The following PHP versions currently have security support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP 8.1
&lt;ul&gt;
&lt;li&gt;initial release on November 25, 2021&lt;/li&gt;
&lt;li&gt;active support ended on November 25, 2023&lt;/li&gt;
&lt;li&gt;security support ends on November 25, 2024&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-end-of-life" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-end-of-life" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;End of life&lt;/h2&gt;
&lt;p&gt;The end of security support marks the end of the lifecycle of a PHP version.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP 8.0
&lt;ul&gt;
&lt;li&gt;initial release on November 26, 2020&lt;/li&gt;
&lt;li&gt;active support ended on November 26, 2022&lt;/li&gt;
&lt;li&gt;security support ended on November 26, 2023&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 7.4
&lt;ul&gt;
&lt;li&gt;initial release on November 28, 2019&lt;/li&gt;
&lt;li&gt;active support ended on November 28, 2021&lt;/li&gt;
&lt;li&gt;security support ended on November 28, 2022&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 7.3
&lt;ul&gt;
&lt;li&gt;initial release on December 6, 2018&lt;/li&gt;
&lt;li&gt;active support ended on December 6, 2020&lt;/li&gt;
&lt;li&gt;security support ended on December 6, 2021&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 7.2
&lt;ul&gt;
&lt;li&gt;initial release on November 30, 2017&lt;/li&gt;
&lt;li&gt;active support ended on November 30, 2019&lt;/li&gt;
&lt;li&gt;security support ended on November 30, 2020&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 7.1
&lt;ul&gt;
&lt;li&gt;initial release on December 1, 2016&lt;/li&gt;
&lt;li&gt;active support ended on December 1, 2018&lt;/li&gt;
&lt;li&gt;security support ended on December 1, 2019&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 7.0
&lt;ul&gt;
&lt;li&gt;initial release on December 3, 2015&lt;/li&gt;
&lt;li&gt;active support ended on January 4, 2018&lt;/li&gt;
&lt;li&gt;security support ended on January 10, 2019&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 5.6
&lt;ul&gt;
&lt;li&gt;initial release on August 28, 2014&lt;/li&gt;
&lt;li&gt;active support ended on January 19, 2017&lt;/li&gt;
&lt;li&gt;security support ended on December 31, 2018&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 5.5
&lt;ul&gt;
&lt;li&gt;initial release on June 20, 2013&lt;/li&gt;
&lt;li&gt;active support ended on July 10, 2015&lt;/li&gt;
&lt;li&gt;security support ended on July 21, 2016&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 5.4
&lt;ul&gt;
&lt;li&gt;initial release on March 1, 2012&lt;/li&gt;
&lt;li&gt;active support ended on September 14, 2014&lt;/li&gt;
&lt;li&gt;security support ended on September 3, 2015&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 5.3
&lt;ul&gt;
&lt;li&gt;initial release on June 30, 2009&lt;/li&gt;
&lt;li&gt;active support ended on July 11, 2013&lt;/li&gt;
&lt;li&gt;security support ended on August 14, 2014&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 5.2
&lt;ul&gt;
&lt;li&gt;initial release on November 2, 2006&lt;/li&gt;
&lt;li&gt;active support ended on November 2, 2008&lt;/li&gt;
&lt;li&gt;security support ended on January 6, 2011&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 5.1
&lt;ul&gt;
&lt;li&gt;initial release on November 24, 2005&lt;/li&gt;
&lt;li&gt;active support ended on November 24, 2007&lt;/li&gt;
&lt;li&gt;security support ended on August 24, 2006&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 5.0
&lt;ul&gt;
&lt;li&gt;initial release on July 13, 2004&lt;/li&gt;
&lt;li&gt;active support ended on July 13, 2006&lt;/li&gt;
&lt;li&gt;security support ended on September 5, 2005&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 4.4
&lt;ul&gt;
&lt;li&gt;initial release on July 11, 2005&lt;/li&gt;
&lt;li&gt;active support ended on July 11, 2007&lt;/li&gt;
&lt;li&gt;security support ended on August 7, 2008&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 4.3
&lt;ul&gt;
&lt;li&gt;initial release on December 27, 2002&lt;/li&gt;
&lt;li&gt;active support ended on December 27, 2004&lt;/li&gt;
&lt;li&gt;security support ended on March 31, 2005&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 4.2
&lt;ul&gt;
&lt;li&gt;initial release on April 22, 2002&lt;/li&gt;
&lt;li&gt;active support ended on April 22, 2004&lt;/li&gt;
&lt;li&gt;security support ended on September 6, 2002&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 4.1
&lt;ul&gt;
&lt;li&gt;initial release on December 10, 2001&lt;/li&gt;
&lt;li&gt;active support ended on December 10, 2003&lt;/li&gt;
&lt;li&gt;security support ended on March 12, 2002&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PHP 4.0
&lt;ul&gt;
&lt;li&gt;initial release on May 22, 2000&lt;/li&gt;
&lt;li&gt;active support ended on May 22, 2002&lt;/li&gt;
&lt;li&gt;security support ended on June 23, 2001&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;💡 Also see &lt;a href="https://www.php.net/eol.php" target="_blank" title="PHP: Unsupported branches"&gt;the official list of unsupported PHP branches&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Are you currently running PHP versions in production that have reached their end of life?&lt;/p&gt;

</content></entry><entry><title>Avoiding empty() in PHP</title><category term="maintenance"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/05/10/avoiding-empty-in-php/"/><id>https://localheinz.com/articles/2023/05/10/avoiding-empty-in-php/</id><updated>2023-05-10T15:40:00+02:00</updated><content>&lt;h1&gt;
  Avoiding empty() in PHP
&lt;/h1&gt;


&lt;p&gt;The language construct &lt;a href="https://www.php.net/manual/en/function.empty.php" target="_blank" title="PHP Manual: empty()"&gt;&lt;code&gt;empty()&lt;/code&gt;&lt;/a&gt; appears rather versatile. It's like a Swiss army knife with a thousand blades, ready to hurt you if you grab it by the wrong end. Or a jack of all trades, master of none. Most of all, &lt;code&gt;empty()&lt;/code&gt; is a poor communicator.&lt;/p&gt;
&lt;p&gt;I spot &lt;code&gt;empty()&lt;/code&gt; in poorly-aged closed-source projects. I discover &lt;code&gt;empty()&lt;/code&gt; in brand-new closed-source projects from last week. And I meet &lt;code&gt;empty()&lt;/code&gt; again in open-source projects with millions of downloads.&lt;/p&gt;
&lt;p&gt;So what is the problem with &lt;code&gt;empty()&lt;/code&gt; when so many people use it?&lt;/p&gt;
&lt;h2 id="content-problem" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-problem" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Problem&lt;/h2&gt;
&lt;p&gt;If you refer to the documentation for &lt;code&gt;empty()&lt;/code&gt;, you will find the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Determine whether a variable is considered to be empty. A variable is considered empty if it does not exist or if its value equals &lt;code&gt;false&lt;/code&gt;. &lt;code&gt;empty()&lt;/code&gt; does not generate a warning if the variable does not exist.&lt;/p&gt;
  &lt;div class="source"&gt;
    &lt;a href="https://www.php.net/manual/en/function.empty.php" target="_blank" title="PHP Manual: empty()"&gt;PHP manual: empty()&lt;/a&gt;
  &lt;/span&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you ignore the internal implementation and possible performance issues, using &lt;code&gt;empty()&lt;/code&gt; is identical to using &lt;a href="https://www.php.net/manual/en/function.isset.php" target="_blank" title="PHP Manual: isset()"&gt;&lt;code&gt;isset()&lt;/code&gt;&lt;/a&gt; and a &lt;a href="https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting" target="_blank" title="PHP Manual: Converting to boolean"&gt;loose comparison with &lt;code&gt;false&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if (!isset($value) || $value == false) {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you already know that a variable, a property, a function or method parameter, and a function or method return value are defined, why would you use &lt;code&gt;isset()&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;When you already know that a variable, a property, a function or method parameter, and a function or method return value assume multiple types, why would you use loose comparison and not handle each acceptable type and value separately?&lt;/p&gt;
&lt;p&gt;When you already know that a variable, a property, a function or method parameter, and a function or method return value assume a single type, why would you use loose instead of strict comparison?&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;empty()&lt;/code&gt;, &lt;code&gt;isset()&lt;/code&gt;, or loose comparisons is a wishy-washy business. Is that how you want to work?&lt;/p&gt;
&lt;p&gt;As mentioned, I often find &lt;code&gt;empty()&lt;/code&gt; in legacy code bases. Some of these projects run on outdated versions of PHP and are entirely unaware of property, parameter, and return type declarations. In these projects, you often can not find DocBlocks for properties, function and method parameters, or function and method return types.&lt;/p&gt;
&lt;p&gt;When you pick up maintenance of these projects and begin to fix bugs, update PHP versions and eventually modernize them, you have difficulty figuring out what the original authors intended to do when they used &lt;code&gt;empty()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Were the original authors aware of the quirks of &lt;code&gt;empty()&lt;/code&gt;? Did the authors intend a property, a function, or a method parameter to accept all types and values? Did the authors plan to return all types and values from a function or method? Did the authors, who have long disappeared and ghosted the project owners, really think the use of &lt;code&gt;empty()&lt;/code&gt; through?&lt;/p&gt;
&lt;p&gt;The original author could be you. The maintainer could also be you, picking up the project after years. Perhaps you had a stroke or an accident in the meantime that impaired your cognitive abilities. Now you have difficulty understanding what the original author intended. Perhaps the authors and maintainers are entirely different persons. Maybe you are in perfect health and still struggle to understand the original author's intent.&lt;/p&gt;
&lt;p&gt;While you write the code for the computer to process, you - as the author - also write the code for the person maintaining it after. By more or less carefully selecting language features, you instruct the computer, but you also communicate your intent to the maintainer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Any fool can write code that a computer can understand. Good programmers write code that humans can understand.&lt;/p&gt;
  &lt;div class="source"&gt;
    &lt;a href="https://martinfowler.com/books/refactoring.html" target="_blank" title="Martin Fowler, Refactoring"&gt;Martin Fowler, Refactoring&lt;/a&gt;
  &lt;/span&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let's look at all cases when &lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, and explore alternatives that better communicate your situational awareness and intent!&lt;/p&gt;
&lt;h2 id="content-candidates" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-candidates" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Candidates&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#content-undefined-variable" title="Undefined variable"&gt;undefined variable&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-undefined-instance-property" title="Undefined instance property"&gt;undefined instance property&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-undefined-static-property" title="Undefined static property"&gt;undefined static property&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-inaccessible-instance-property" title="Inaccessible instance property"&gt;inaccessible instance property&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-inaccessible-static-property" title="Inaccessible static property"&gt;inaccessible static property&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-null" title="Null"&gt;null&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-array" title="Array"&gt;array&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-bool" title="Bool"&gt;bool&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-float" title="Float"&gt;float&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-int" title="Int"&gt;int&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-string" title="String"&gt;string&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-simplexmlelement" title="SimpleXMLElement"&gt;SimpleXMLElement&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-undefined-variable" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-undefined-variable" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Undefined variable&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is an undefined variable.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify whether a variable is undefined?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20an%20undefined%20variable" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-expecting-an-included-file-to-declare-a-variable" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-expecting-an-included-file-to-declare-a-variable" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Expecting an included file to declare a variable&lt;/h3&gt;
&lt;p&gt;You include a file that you expect to declare a variable. The file may not exist or may not declare the variable.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify that an included file exists and declares a variable, set the variable to a suitable default value, include the file when it exists, and verify that the variable has an acceptable type and value.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-include __DIR__ . '/file.php';&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+$file = __DIR__ . '/file.php';&lt;/span&gt;

&lt;span class="hljs-deletion"&gt;-if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    // ....&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-}&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+$value = [];&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if (is_file($file)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    include $file;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    if (!is_array($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        // ...&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    }&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify that an included file exists and declares a variable, set the variable to a suitable default value, expect the file to return a value, include the file when it exists, and verify that the returned value has an acceptable type and value.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-include __DIR__ . '/file.php';&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+$file = __DIR__ . '/file.php';&lt;/span&gt;

&lt;span class="hljs-deletion"&gt;-if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    // ....&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-}&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+$value = [];&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if (is_file($file)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $value = include $file;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    if (!is_array($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        // ...&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    }&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing code that relies on including files that declare variables or return values.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-undefined-instance-property" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-undefined-instance-property" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Undefined instance property&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is an undefined instance property.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$object = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; stdClass();

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($object-&amp;gt;value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a side effect, &lt;code&gt;empty()&lt;/code&gt; also invokes the magic method &lt;code&gt;__isset()&lt;/code&gt; when you reference an undefined instance property of an object that declares an &lt;code&gt;__isset()&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$object = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt;() &lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__isset&lt;/span&gt;&lt;span class="hljs-params"&gt;(string $name)&lt;/span&gt;: &lt;span class="hljs-title"&gt;bool&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;echo&lt;/span&gt; &lt;span class="hljs-string"&gt;'👋'&lt;/span&gt;;

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;;
    }
};

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($object-&amp;gt;value)); &lt;span class="hljs-comment"&gt;// 👋(bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; and deal with an undefined instance property?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20an%20undefined%20instance%20property" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="content-undefined-static-property" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-undefined-static-property" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Undefined static property&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is an undefined &lt;code&gt;static&lt;/code&gt; property.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$object = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; stdClass();

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($object::$value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; and deal with an undefined &lt;code&gt;static&lt;/code&gt; property?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20an%20undefined%20static%20property" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="content-inaccessible-instance-property" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-inaccessible-instance-property" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Inaccessible instance property&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is an inaccessible instance property.&lt;/p&gt;
&lt;p&gt;As a side effect, &lt;code&gt;empty()&lt;/code&gt; invokes the magic method &lt;code&gt;__isset()&lt;/code&gt; when it exists.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$object = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt;() &lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; $value = &lt;span class="hljs-number"&gt;9000&lt;/span&gt;;
    &lt;span class="hljs-keyword"&gt;protected&lt;/span&gt; $otherValue = &lt;span class="hljs-number"&gt;9001&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__isset&lt;/span&gt;&lt;span class="hljs-params"&gt;(string $name)&lt;/span&gt;: &lt;span class="hljs-title"&gt;bool&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;echo&lt;/span&gt; &lt;span class="hljs-string"&gt;'👋'&lt;/span&gt;;

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;;
    }
};

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($object-&amp;gt;value)); &lt;span class="hljs-comment"&gt;// 👋(bool)true&lt;/span&gt;
var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($object-&amp;gt;otherValue)); &lt;span class="hljs-comment"&gt;// 👋(bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; and deal with an inaccessible instance property?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20an%20inaccessible%20instance%20property" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="content-inaccessible-static-property" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-inaccessible-static-property" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Inaccessible static property&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is an inaccessible &lt;code&gt;static&lt;/code&gt; property.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$object = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt;() &lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; $value = &lt;span class="hljs-number"&gt;9000&lt;/span&gt;;
    &lt;span class="hljs-keyword"&gt;protected&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; $otherValue = &lt;span class="hljs-number"&gt;9000&lt;/span&gt;;
};

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($object::$value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($object::$otherValue)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; and deal with an inaccessible &lt;code&gt;static&lt;/code&gt; property?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20an%20inaccessible%20static%20property" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-declaring-variables-in-files-and-including-them" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-declaring-variables-in-files-and-including-them" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Declaring variables in files and including them&lt;/h3&gt;
&lt;h2 id="content-null" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-null" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Null&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = &lt;span class="hljs-keyword"&gt;null&lt;/span&gt;;

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify that a value is not &lt;code&gt;null&lt;/code&gt;?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20a%20value%20that%20could%20be%20null" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-instance-or-static-property-could-be-null" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-instance-or-static-property-could-be-null" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Instance or static property could be null&lt;/h3&gt;
&lt;p&gt;You have a class with an instance or a static property and currently use &lt;code&gt;empty()&lt;/code&gt; to verify the property value.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume multiple types, compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;null&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     private $value;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === null) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume &lt;code&gt;null&lt;/code&gt; or a known type, add a nullable property declaration and compare the property value with &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    private $value;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    private ?string $value = null;&lt;/span&gt;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === null) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing classes with instance or static properties that accept multiple types. Add property type declarations or DocBlocks to document property types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-parameter-could-be-null" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-parameter-could-be-null" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method parameter could be null&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a parameter that could be &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume multiple types, compare the parameter with &lt;code&gt;null&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar($value)
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === null) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume &lt;code&gt;null&lt;/code&gt; or a known type, add a nullable parameter type declaration and compare the parameter with &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar($value)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(?string $value)&lt;/span&gt;
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === null) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods with parameters that accept multiple types. Add parameter type declarations or DocBlocks to document function and method parameter types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-return-value-could-be-null" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-return-value-could-be-null" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method return value could be null&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a return value that could be &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume multiple types, compare the return value with &lt;code&gt;null&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar()
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === null) {&lt;/span&gt;
     // ...
 }

&lt;span class="hljs-addition"&gt;+// handle other possible types and values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume &lt;code&gt;null&lt;/code&gt; or a known type, add a nullable return type declaration and compare the return value with &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(): ?string&lt;/span&gt;
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === null) {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods that return values of multiple types. Add return type declarations or DocBlocks to document function and method return types.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-array" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-array" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Array&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the value is an empty &lt;code&gt;array&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = [];

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify that a value is not an empty &lt;code&gt;array&lt;/code&gt;?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20a%20value%20that%20could%20be%20an%20empty%20array" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-instance-or-static-property-could-be-an-empty-array" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-instance-or-static-property-could-be-an-empty-array" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Instance or static property could be an empty array&lt;/h3&gt;
&lt;p&gt;You have a class with an instance or a static property and currently use &lt;code&gt;empty()&lt;/code&gt; to verify the property value.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume multiple types, compare the instance or &lt;code&gt;static&lt;/code&gt; property with an empty &lt;code&gt;array&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     private $value;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === []) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume an &lt;code&gt;array&lt;/code&gt;, add an &lt;code&gt;array&lt;/code&gt; property declaration and compare the property value with an empty &lt;code&gt;array&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    private $value;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    private array $value = [];&lt;/span&gt;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === []) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing classes with instance or static properties that accept multiple types. Add property type declarations or DocBlocks to document property types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-parameter-could-be-an-empty-array" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-parameter-could-be-an-empty-array" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method parameter could be an empty array&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a parameter that could be an empty &lt;code&gt;array&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume multiple types, compare the parameter with an empty &lt;code&gt;array&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar($value)
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === []) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume an &lt;code&gt;array&lt;/code&gt;, add an &lt;code&gt;array&lt;/code&gt; parameter type declaration and compare the parameter with an empty &lt;code&gt;array&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar($value)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(array $value)&lt;/span&gt;
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === []) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods with parameters that accept multiple types. Add parameter type declarations or DocBlocks to document function and method parameter types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-return-value-could-be-an-empty-array" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-return-value-could-be-an-empty-array" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method return value could be an empty array&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a return value that could be an empty &lt;code&gt;array&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume multiple types, compare the return value with an empty &lt;code&gt;array&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar()
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === []) {&lt;/span&gt;
     // ...
 }

&lt;span class="hljs-addition"&gt;+// handle other possible types and values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume an empty &lt;code&gt;array&lt;/code&gt;, add an &lt;code&gt;array&lt;/code&gt; return type declaration and compare the return value with an empty &lt;code&gt;array&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(): array&lt;/span&gt;
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === []) {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods that return values of multiple types. Add return type declarations or DocBlocks to document function and method return types.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-boolean" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-boolean" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Boolean&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is a &lt;code&gt;bool&lt;/code&gt; with the value &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;;

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify that a value is not a &lt;code&gt;bool&lt;/code&gt; with the value &lt;code&gt;false&lt;/code&gt;?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20a%20value%20that%20could%20be%20false" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-instance-or-static-property-could-be-false" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-instance-or-static-property-could-be-false" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Instance or static property could be false&lt;/h3&gt;
&lt;p&gt;You have a class with an instance or a static property and currently use &lt;code&gt;empty()&lt;/code&gt; to verify the property value.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume multiple types, compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;false&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     private $value;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === false) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume a &lt;code&gt;bool&lt;/code&gt;, add a &lt;code&gt;bool&lt;/code&gt; property declaration and use a logical expression.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    private $value;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    private bool $value = false;&lt;/span&gt;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if (!$this-&amp;gt;value) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing classes with instance or static properties that accept multiple types. Add property type declarations or DocBlocks to document property types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-parameter-could-be-false" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-parameter-could-be-false" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method parameter could be false&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a parameter that could be an empty &lt;code&gt;array&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume multiple types, compare the parameter with &lt;code&gt;false&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar($value)
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === false) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume a &lt;code&gt;bool&lt;/code&gt;, add a &lt;code&gt;bool&lt;/code&gt; parameter type declaration and use a logical expression.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar($value)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(bool $value)&lt;/span&gt;
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if (!$value) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods with parameters that accept multiple types. Add parameter type declarations or DocBlocks to document function and method parameter types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-return-value-could-be-false" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-return-value-could-be-false" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method return value could be false&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a return value that could be a &lt;code&gt;bool&lt;/code&gt; with the value &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume multiple types, compare the return value with &lt;code&gt;false&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar()
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === false) {&lt;/span&gt;
     // ...
 }

&lt;span class="hljs-addition"&gt;+// handle other possible types and values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume a &lt;code&gt;bool&lt;/code&gt;, add a &lt;code&gt;bool&lt;/code&gt; return type declaration and use a logical expression.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(): bool&lt;/span&gt;
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if (!$foo-&amp;gt;bar()) {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods that return values of multiple types. Add return type declarations or DocBlocks to document function and method return types.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-float" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-float" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Float&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when a variable is a &lt;code&gt;float&lt;/code&gt; with the value &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = &lt;span class="hljs-number"&gt;0.0&lt;/span&gt;;

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify that a value is not a &lt;code&gt;float&lt;/code&gt; with the value &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt;?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20a%20value%20that%20could%20be%200.0%20or%20-0.0" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-instance-or-static-property-could-be-00" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-instance-or-static-property-could-be-00" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Instance or static property could be 0.0&lt;/h3&gt;
&lt;p&gt;You have a class with an instance or a static property and currently use &lt;code&gt;empty()&lt;/code&gt; to verify the property value.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume multiple types, compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     private $value;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === 0.0) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume a &lt;code&gt;float&lt;/code&gt;, add a &lt;code&gt;float&lt;/code&gt; property declaration and compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    private $value;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    private float $value = 0;&lt;/span&gt;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === 0.0) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing classes with instance or static properties that accept multiple types. Add property type declarations or DocBlocks to document property types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-parameter-could-be-00" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-parameter-could-be-00" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method parameter could be 0.0&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a parameter that could be a &lt;code&gt;float&lt;/code&gt; with the value &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume multiple types, compare the parameter with &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar($value)
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === 0.0) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume a &lt;code&gt;float&lt;/code&gt;, add a &lt;code&gt;float&lt;/code&gt; parameter type declaration and compare the parameter with &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar($value)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(float $value)&lt;/span&gt;
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === 0.0) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods with parameters that accept multiple types. Add parameter type declarations or DocBlocks to document function and method parameter types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-return-value-could-be-00" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-return-value-could-be-00" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method return value could be 0.0&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a return value that could be a &lt;code&gt;float&lt;/code&gt; with the value &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume multiple types, compare the return value with &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;0.0&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar()
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === 0.0) {&lt;/span&gt;
     // ...
 }

&lt;span class="hljs-addition"&gt;+// handle other possible types and values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume a &lt;code&gt;float&lt;/code&gt;, add a &lt;code&gt;float&lt;/code&gt; return type declaration and compare the return value with &lt;code&gt;0.0&lt;/code&gt; or &lt;code&gt;-0.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(): float&lt;/span&gt;
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === 0.0) {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-int" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-int" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Int&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is an &lt;code&gt;int&lt;/code&gt; with the value &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = &lt;span class="hljs-number"&gt;0&lt;/span&gt;;

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// // (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify that a value is not an &lt;code&gt;int&lt;/code&gt; with the value &lt;code&gt;0&lt;/code&gt;?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20a%20value%20that%20could%20be%200" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-instance-or-static-property-could-be-0" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-instance-or-static-property-could-be-0" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Instance or static property could be 0&lt;/h3&gt;
&lt;p&gt;You have a class with an instance or a static property and currently use &lt;code&gt;empty()&lt;/code&gt; to verify the property value.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume multiple types, compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;0&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     private $value;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === 0) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume an &lt;code&gt;int&lt;/code&gt;, add an &lt;code&gt;int&lt;/code&gt; property declaration and compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    private $value;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    private int $value = 0;&lt;/span&gt;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === 0) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing classes with instance or static properties that accept multiple types. Add property type declarations or DocBlocks to document property types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-parameter-could-be-0" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-parameter-could-be-0" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method parameter could be 0&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a parameter that could be an &lt;code&gt;int&lt;/code&gt; with the value &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume multiple types, compare the parameter with &lt;code&gt;0&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar($value)
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === 0) {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume an &lt;code&gt;int&lt;/code&gt;, add an &lt;code&gt;int&lt;/code&gt; parameter type declaration and compare the parameter with &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar($value)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(int $value)&lt;/span&gt;
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === 0) {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods with parameters that accept multiple types. Add parameter type declarations or DocBlocks to document function and method parameter types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-return-value-could-be-0" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-return-value-could-be-0" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method return value could be 0&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a return value that could be an &lt;code&gt;int&lt;/code&gt; with the value &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume multiple types, compare the return value with &lt;code&gt;0&lt;/code&gt; and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar()
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === 0) {&lt;/span&gt;
     // ...
 }

&lt;span class="hljs-addition"&gt;+// handle other possible types and values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume an &lt;code&gt;int&lt;/code&gt;, add an &lt;code&gt;int&lt;/code&gt; return type declaration and compare the return value with &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(): int&lt;/span&gt;
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === 0) {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-string" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-string" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;String&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is a &lt;code&gt;string&lt;/code&gt; with the value &lt;code&gt;''&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = &lt;span class="hljs-string"&gt;''&lt;/span&gt;;

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// // (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; also returns &lt;code&gt;true&lt;/code&gt; when the argument is a &lt;code&gt;string&lt;/code&gt; with the value &lt;code&gt;'0'&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = &lt;span class="hljs-string"&gt;'0'&lt;/span&gt;;

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// // (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify that a value is not a &lt;code&gt;string&lt;/code&gt; with the value &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;)?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20a%20value%20that%20could%20be%20an%20empty%20string%20or%20%270%27" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-instance-or-static-property-could-be-an-empty-string" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-instance-or-static-property-could-be-an-empty-string" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Instance or static property could be an empty string&lt;/h3&gt;
&lt;p&gt;You have a class with an instance or a static property and currently use &lt;code&gt;empty()&lt;/code&gt; to verify the property value.&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume multiple types, compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;) and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     private $value;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === '') {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the property value when the property can assume a &lt;code&gt;string&lt;/code&gt;, add a &lt;code&gt;string&lt;/code&gt; property declaration and compare the instance or &lt;code&gt;static&lt;/code&gt; property with &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    private $value;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    private string $value = '';&lt;/span&gt;

     public function bar()
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($this-&amp;gt;value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($this-&amp;gt;value === '') {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing classes with instance or static properties that accept multiple types. Add property type declarations or DocBlocks to document property types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-parameter-could-be-an-empty-string" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-parameter-could-be-an-empty-string" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method parameter could be an empty string&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a parameter that could be a &lt;code&gt;string&lt;/code&gt; with the value &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume multiple types, compare the parameter with &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;) and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar($value)
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === '') {&lt;/span&gt;
             // ...
         }

&lt;span class="hljs-addition"&gt;+        // handle other possible types and values&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the parameter value when the parameter can assume a &lt;code&gt;string&lt;/code&gt;, add a &lt;code&gt;string&lt;/code&gt; parameter type declaration and compare the parameter with &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar($value)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(string $value)&lt;/span&gt;
     {
&lt;span class="hljs-deletion"&gt;-        if (empty($value)) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        if ($value === '') {&lt;/span&gt;
             // ...
         }

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods with parameters that accept multiple types. Add parameter type declarations or DocBlocks to document function and method parameter types.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-scenario-function-or-method-return-value-could-be-an-empty-string" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario-function-or-method-return-value-could-be-an-empty-string" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario: Function or method return value could be an empty string&lt;/h3&gt;
&lt;p&gt;You have a function or a method with a return value that could be a &lt;code&gt;string&lt;/code&gt; with the value &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume multiple types, compare the return value with &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;) and handle each possible case separately.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
     public function bar()
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === '') {&lt;/span&gt;
     // ...
 }

&lt;span class="hljs-addition"&gt;+// handle other possible types and values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of using &lt;code&gt;empty()&lt;/code&gt; to verify the return value at the call site when the return value can assume a &lt;code&gt;string&lt;/code&gt;, add a &lt;code&gt;string&lt;/code&gt; return type declaration and compare the return value with &lt;code&gt;''&lt;/code&gt; (or &lt;code&gt;'0'&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Foo
 {
&lt;span class="hljs-deletion"&gt;-    public function bar()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function bar(): string&lt;/span&gt;
     {
         // ...

         return $value;
     }
 }

&lt;span class="hljs-deletion"&gt;-if (empty($foo-&amp;gt;bar()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if ($foo-&amp;gt;bar() === '') {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Avoid writing functions or methods that return values of multiple types. Add return type declarations or DocBlocks to document function and method return types.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-simplexmlelement" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-simplexmlelement" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;SimpleXMLElement&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;empty()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; when the argument is an instance of &lt;code&gt;SimpleXMLElement&lt;/code&gt; constructed from an XML string representing an element without attributes and children.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$value = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; SimpleXMLElement(&lt;span class="hljs-string"&gt;'&amp;lt;foo&amp;gt;&amp;lt;/foo&amp;gt;'&lt;/span&gt;);

var_dump(&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;($value)); &lt;span class="hljs-comment"&gt;// (bool)true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What are scenarios where you currently use &lt;code&gt;empty()&lt;/code&gt; to verify whether a &lt;code&gt;SimpleXMLElement&lt;/code&gt; is empty?&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Avoiding%20empty%28%29%20in%20PHP%2C%20scenario%20for%20empty%28%29%20and%20a%20SimpleXMLElement" target="_blank" title="I have a scenario!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have a scenario!&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="content-conclusion" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-conclusion" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Do not use &lt;code&gt;empty()&lt;/code&gt;. Do not write code that allows a variable, a property, a parameter, or a return value to assume multiple types. Use type-safe comparisons.&lt;/p&gt;

</content></entry><entry><title>Introducing PHP-CS-Fixer into legacy projects</title><category term="coding-standards"/><category term="maintenance"/><category term="php"/><category term="php-cs-fixer"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/04/10/introducing-php-cs-fixer-into-legacy-projects/"/><id>https://localheinz.com/articles/2023/04/10/introducing-php-cs-fixer-into-legacy-projects/</id><updated>2023-04-10T16:50:00+02:00</updated><content>&lt;h1&gt;
  Introducing PHP-CS-Fixer into legacy projects
&lt;/h1&gt;


&lt;p&gt;You are working on a legacy PHP project and want to use &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;PHP-CS-Fixer&amp;#x2F;PHP-CS-Fixer" target="_blank" title="PHP-CS-Fixer/PHP-CS-Fixer on GitHub"&gt;&lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;&lt;/a&gt; to enforce a consistent coding standard. But you are unsure how to do that without causing problems.&lt;/p&gt;
&lt;p&gt;What could be a strategy for introducing PHP-CS-Fixer into your legacy PHP that reduces risk and invites other developers to collaborate?&lt;/p&gt;
&lt;h2 id="content-requirements" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-requirements" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Requirements&lt;/h2&gt;
&lt;p&gt;If you want the introduction of PHP-CS-Fixer into your legacy PHP project to be a success, you are going to have the following requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You want to run PHP-CS-Fixer in your continuous integration system. When you commit and push code that does not follow your coding standards, you want the build in your continuous integration system to fail.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want to run PHP-CS-Fixer in your development environment. When you change your PHP code and that code that does not follow your coding standards, you want PHP-CS-Fixer to fix coding standard violations. You do not need to report coding standard violations in a development environment; you want to go straight for the fixes. You also want a simple command for running PHP-CS-Fixer in a development environment so you can run it frequently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want PHP-CS-Fixer to apply fixes only that are compatible with the version of PHP that you use in production. If your project runs on PHP 5.3, you do not want PHP-CS-Fixer to apply fixes that require running on PHP 8.1.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-installing-php-cs-fixer" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-installing-php-cs-fixer" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Installing PHP-CS-Fixer&lt;/h2&gt;
&lt;p&gt;You can install PHP-CS-Fixer with &lt;a href="https://getcomposer.org" target="_blank" title="Composer: A dependency manager for PHP"&gt;&lt;code&gt;composer&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://phar.io" target="_blank" title="The PHAR Installation and Verification Environment (PHIVE)"&gt;&lt;code&gt;phive&lt;/code&gt;&lt;/a&gt;, or by downloading a PHAR from the &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases" target="_blank" title="Releases of friendsofphp/php-cs-fixer"&gt;releases page&lt;/a&gt;. But you can study the installation options and instructions for PHP-CS-Fixer in detail in the &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.16.0#installation" title="friendsofphp/php-cs-fixer: Installation"&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; of PHP-CS-Fixer; I will not repeat them here.&lt;/p&gt;
&lt;p&gt;I recommend using &lt;code&gt;composer&lt;/code&gt; to install PHP-CS-Fixer so that you benefit from automated dependency updates by &lt;a href="https://github.com/dependabot" target="_blank" title="Dependabot on GitHub"&gt;Dependabot&lt;/a&gt;, &lt;a href="https://github.com/renovatebot" target="_blank" title="Renovatebot on GitHub"&gt;Renovatebot&lt;/a&gt;, or similar services. If you install PHP-CS-Fixer with &lt;code&gt;phive&lt;/code&gt; or download it from the releases page, you have to update PHP-CS-Fixer manually.&lt;/p&gt;
&lt;p&gt;Your project does not yet use &lt;code&gt;composer&lt;/code&gt;? Why not start using &lt;code&gt;composer&lt;/code&gt; by making PHP-CS-Fixer your first development dependency?&lt;/p&gt;
&lt;p&gt;You can not use &lt;code&gt;composer&lt;/code&gt; because your project does not yet run on PHP 5.3 in production? Why not use a different version of PHP to run development tools?&lt;/p&gt;
&lt;p&gt;I also recommend using the latest version of PHP-CS-Fixer so that you benefit from features and fixes that the maintainers and contributors consistently add to the tool.&lt;/p&gt;
&lt;p&gt;You can not use the latest version of PHP-CS-Fixer because your project does not yet run PHP 7.4 or above in production? Again, why not use a different version of PHP to run development tools?&lt;/p&gt;
&lt;p&gt;For example, in the last couple of weeks, I have been busy updating a project from PHP 5.6 to PHP 8.1. After setting up a local development environment running on PHP 5.6 in Docker, I have set up a GitHub Actions workflow that uses PHP 8.1 to install and run development tools - including PHP-CS-Fixer.&lt;/p&gt;
&lt;p&gt;If I can use PHP 8.1 to run development tools for a project that runs on PHP 5.6 in production, you can, too.&lt;/p&gt;
&lt;p&gt;Your key to success is a careful configuration of your development tools.&lt;/p&gt;
&lt;h2 id="content-adding-a-basic-configuration-for-php-cs-fixer" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-adding-a-basic-configuration-for-php-cs-fixer" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Adding a basic configuration for PHP-CS-Fixer&lt;/h2&gt;
&lt;p&gt;PHP-CS-Fixer requires two things to report and fix coding standard violations: a list of files it should inspect and a configuration of rules and rulesets it uses to create and configure corresponding fixers.&lt;/p&gt;
&lt;p&gt;The configuration file &lt;code&gt;.php-cs-fixer.php&lt;/code&gt; below configures a finder and empty array of rules.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$finder = PhpCsFixer\Finder::create()
    -&amp;gt;exclude([
        &lt;span class="hljs-string"&gt;'.build/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.docker/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.github/'&lt;/span&gt;,
    ])
    -&amp;gt;ignoreDotFiles(&lt;span class="hljs-keyword"&gt;false&lt;/span&gt;)
    -&amp;gt;in(&lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt;)
    -&amp;gt;name(&lt;span class="hljs-string"&gt;'.php-cs-fixer.php'&lt;/span&gt;);

$config = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; PhpCsFixer\Config();

$config
    -&amp;gt;setFinder($finder);
    -&amp;gt;setRules([]);

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The finder allows you to configure a list of directories, exclusions, file names, and more and returns a list of files that PHP-CS-Fixer should inspect.&lt;/p&gt;
&lt;p&gt;Depending on your project layout, your configuration of the finder may look different.&lt;/p&gt;
&lt;p&gt;The empty array of rules will override the default rules configuration. PHP-CS-Fixer lints a PHP file before and after applying fixers to ensure that it neither attempts to fix a file that contains invalid PHP code nor leaves a file with invalid PHP code behind. When PHP-CS-Fixer finds a file that does not contain valid PHP code, it will skip fixing it and emit a warning.&lt;/p&gt;
&lt;p&gt;Even with an empty array of rules, PHP-CS-Fixer is a valuable tool for you as it can help you find files that contain invalid PHP code.&lt;/p&gt;
&lt;p&gt;Now that you have an initial configuration for PHP-CS-Fixer, it is time to get started running PHP-CS-Fixer.&lt;/p&gt;
&lt;h2 id="content-running-php-cs-fixer-on-github-actions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-running-php-cs-fixer-on-github-actions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Running PHP-CS-Fixer on GitHub Actions&lt;/h2&gt;
&lt;p&gt;As mentioned, you want to run PHP-CS-Fixer in two environments: your continuous integration system and your local development environment.&lt;/p&gt;
&lt;p&gt;The following command will run PHP-CS-Fixer with the &lt;code&gt;--dry-run&lt;/code&gt; option and report coding standard violations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --dry-run --show-progress=dots --verbose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on how you installed PHP-CS-Fixer and named your configuration file, the command may look different.&lt;/p&gt;
&lt;p&gt;But it is important to use the &lt;code&gt;--dry-run&lt;/code&gt; option when running PHP-CS-Fixer in a continuous integration system: PHP-CS-Fixer will end its execution with a non-zero exit code when it has found coding standard violations and break the build.&lt;/p&gt;
&lt;p&gt;I feel at home at &lt;a href="https://github.com/localheinz" target="_blank" title="localheinz on GitHub"&gt;GitHub&lt;/a&gt; and like to use &lt;a href="https://github.com/features/actions" target="_blank" title="GitHub Actions"&gt;GitHub Actions&lt;/a&gt; as a continuous integration system. The GitHub Actions workflow below will check out your repository, set up PHP, install dependencies with &lt;code&gt;composer&lt;/code&gt;, and run PHP-CS-Fixer with the &lt;code&gt;--dry-run&lt;/code&gt; option.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-less hljs less" data-lang="less"&gt;&lt;span class="hljs-attribute"&gt;name&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Integrate"&lt;/span&gt;

&lt;span class="hljs-attribute"&gt;on&lt;/span&gt;:
  &lt;span class="hljs-attribute"&gt;pull_request&lt;/span&gt;: null
  &lt;span class="hljs-attribute"&gt;push&lt;/span&gt;:
    &lt;span class="hljs-attribute"&gt;branches&lt;/span&gt;:
      - &lt;span class="hljs-string"&gt;"main"&lt;/span&gt;

&lt;span class="hljs-attribute"&gt;jobs&lt;/span&gt;:
  &lt;span class="hljs-attribute"&gt;coding-standards&lt;/span&gt;:
    &lt;span class="hljs-attribute"&gt;name&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Coding Standards"&lt;/span&gt;

    &lt;span class="hljs-attribute"&gt;runs-on&lt;/span&gt;: &lt;span class="hljs-string"&gt;"ubuntu-latest"&lt;/span&gt;

    &lt;span class="hljs-attribute"&gt;strategy&lt;/span&gt;:
      &lt;span class="hljs-attribute"&gt;matrix&lt;/span&gt;:
        &lt;span class="hljs-attribute"&gt;php-version&lt;/span&gt;:
          - &lt;span class="hljs-string"&gt;"8.1"&lt;/span&gt;

    &lt;span class="hljs-attribute"&gt;steps&lt;/span&gt;:
      - &lt;span class="hljs-attribute"&gt;name&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Checkout"&lt;/span&gt;
        &lt;span class="hljs-attribute"&gt;uses&lt;/span&gt;: &lt;span class="hljs-string"&gt;"actions/checkout@v3.5.0"&lt;/span&gt;

      - &lt;span class="hljs-attribute"&gt;name&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Set up PHP"&lt;/span&gt;
        &lt;span class="hljs-attribute"&gt;uses&lt;/span&gt;: &lt;span class="hljs-string"&gt;"shivammathur/setup-php@v2.24.0"&lt;/span&gt;
        &lt;span class="hljs-attribute"&gt;with&lt;/span&gt;:
          &lt;span class="hljs-attribute"&gt;coverage&lt;/span&gt;: &lt;span class="hljs-string"&gt;"none"&lt;/span&gt;
          &lt;span class="hljs-attribute"&gt;php-version&lt;/span&gt;: &lt;span class="hljs-string"&gt;"${{ matrix.php-version }}"&lt;/span&gt;

      - &lt;span class="hljs-attribute"&gt;name&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Validate composer.json and composer.lock"&lt;/span&gt;
        &lt;span class="hljs-attribute"&gt;run&lt;/span&gt;: &lt;span class="hljs-string"&gt;"composer validate --ansi --no-check-publish"&lt;/span&gt;

      - &lt;span class="hljs-attribute"&gt;name&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Install locked dependencies with composer"&lt;/span&gt;
        &lt;span class="hljs-attribute"&gt;run&lt;/span&gt;: &lt;span class="hljs-string"&gt;"composer install --ansi --no-interaction --no-progress"&lt;/span&gt;

      - &lt;span class="hljs-attribute"&gt;name&lt;/span&gt;: &lt;span class="hljs-string"&gt;"Run friendsofphp/php-cs-fixer"&lt;/span&gt;
        &lt;span class="hljs-attribute"&gt;run&lt;/span&gt;: &lt;span class="hljs-string"&gt;"vendor/bin/php-cs-fixer fix --ansi --config=.php-cs-fixer.php --diff --dry-run --show-progress=dots --verbose"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on your project setup and continuous integration system, your configuration may look different - but the steps will be roughly the same.&lt;/p&gt;
&lt;p&gt;With this GitHub Actions workflow in place, you can not commit and push PHP code that does not follow your coding standards without failing the build.&lt;/p&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can improve the &lt;code&gt;coding-standards&lt;/code&gt; job by &lt;a href="https://github.com/ergebnis/php-cs-fixer-config/blob/5.4.0/.github/workflows/integrate.yaml#L105-L113" target="_blank" title="Caching dependencies installed with composer in ergebnis/php-cs-fixer-config"&gt;caching dependencies installed with composer&lt;/a&gt; and &lt;a href="https://github.com/ergebnis/php-cs-fixer-config/blob/5.4.0/.github/workflows/integrate.yaml#L123-L133" target="_blank" title="Caching the directory containing the cache file for PHP-CS-Fixer in ergebnis/php-cs-fixer-config"&gt;caching the directory containing the cache file for PHP-CS-Fixer&lt;/a&gt; between runs.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-running-php-cs-fixer-in-a-development-environment" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-running-php-cs-fixer-in-a-development-environment" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Running PHP-CS-Fixer in a development environment&lt;/h2&gt;
&lt;p&gt;The following command will run PHP-CS-Fixer and fix coding standard violations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --show-progress=dots --verbose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on how you installed PHP-CS-Fixer and named your configuration file, the command may look different. Again, you do not care about reporting coding standard violations in a development environment; you are going straight for the fixes.&lt;/p&gt;
&lt;p&gt;The command is a bit long to type, and if you want to run PHP-CS-Fixer frequently, you probably want to use a task runner or some other tool that makes it easier to run tools in a development environment.&lt;/p&gt;
&lt;p&gt;If you use &lt;code&gt;Makefile&lt;/code&gt;s, add a &lt;code&gt;coding-standards&lt;/code&gt; target to your Makefile.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-makefile hljs makefile" data-lang="makefile"&gt;&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: coding-standards&lt;/span&gt;
&lt;span class="hljs-section"&gt;coding-standards: vendor&lt;/span&gt;
	vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --show-progress=dots --verbose

&lt;span class="hljs-section"&gt;vendor: composer.json composer.lock&lt;/span&gt;
	composer validate --strict
	composer install --no-interaction --no-progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a &lt;code&gt;coding-standards&lt;/code&gt; target in your &lt;code&gt;Makefile&lt;/code&gt;, you can run the following command to let PHP-CS-Fixer fix coding standard violations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;make coding-standards
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 If you do not yet use &lt;code&gt;Makefile&lt;/code&gt;s or find that the command is still to long, read &lt;a href="/articles/2018/01/24/makefile-for-lazy-developers/" target="_blank" title="Makefile&amp;#x20;for&amp;#x20;lazy&amp;#x20;developers"&gt;Makefile for lazy developers&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;If you prefer &lt;code&gt;composer&lt;/code&gt; scripts, add a &lt;code&gt;coding-standards&lt;/code&gt; script to your &lt;code&gt;composer.json&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json hljs json" data-lang="json"&gt;{
  &lt;span class="hljs-attr"&gt;"scripts"&lt;/span&gt;: {
    &lt;span class="hljs-attr"&gt;"coding-standards"&lt;/span&gt;: &lt;span class="hljs-string"&gt;"@php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --show-progress=dots --verbose"&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a &lt;code&gt;coding-standards&lt;/code&gt; script in your &lt;code&gt;composer.json&lt;/code&gt;, you can run the following command to let PHP-CS-Fixer fix coding standard violations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer coding-standards
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-evolving-the-configuring-for-php-cs-fixer" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-evolving-the-configuring-for-php-cs-fixer" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Evolving the configuring for PHP-CS-Fixer&lt;/h2&gt;
&lt;p&gt;Now that you are running PHP-CS-Fixer in your continuous integration system and your development environment, there is one thing left: you want PHP-CS-Fixer to apply fixes that are compatible with the version of PHP that you use in production, and so far, you have only configured an empty array of rules.&lt;/p&gt;
&lt;p&gt;Here is what has worked well for me.&lt;/p&gt;
&lt;p&gt;First, obtain a complete list of rules for all available fixers, for example, from the &lt;a href="https://github.com/ergebnis/php-cs-fixer-config-template/blob/307f0451dbc04f4f3c6314297b3ccc49067cb515/src/RuleSet/Custom.php#L20-L271" target="_blank" title="Custom ruleset of ergebnis/php-cs-fixer-config-template"&gt;&lt;code&gt;Custom&lt;/code&gt; ruleset&lt;/a&gt; in &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;php-cs-fixer-config-template" target="_blank" title="ergebnis/php-cs-fixer-config-template on GitHub"&gt;&lt;code&gt;ergebnis/php-cs-fixer-config-template&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Second, adjust your rules configuration to configure, but disable all these fixers. At the time of writing, there should be 250 rules.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$finder = PhpCsFixer\Finder::create()
    -&amp;gt;exclude([
        &lt;span class="hljs-string"&gt;'.build/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.docker/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.github/'&lt;/span&gt;,
    ])
    -&amp;gt;ignoreDotFiles(&lt;span class="hljs-keyword"&gt;false&lt;/span&gt;)
    -&amp;gt;in(&lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt;)
    -&amp;gt;name(&lt;span class="hljs-string"&gt;'.php-cs-fixer.php'&lt;/span&gt;);

$config = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; PhpCsFixer\Config();

$config
    -&amp;gt;setFinder($finder);
    -&amp;gt;setRules([
        &lt;span class="hljs-string"&gt;'align_multiline_comment'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'array_indentation'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'array_push'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'array_syntax'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
        &lt;span class="hljs-string"&gt;'visibility_required'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'void_return'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'whitespace_after_comma_in_array'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'yoda_style'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
   ]);

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Alternatively, if you already share configurations for PHP-CS-Fixer across projects as described in &lt;a href="/articles/2023/03/10/sharing-configurations-for-php-cs-fixer-across-projects/" target="_blank" title="Sharing&amp;#x20;configurations&amp;#x20;for&amp;#x20;PHP-CS-Fixer&amp;#x20;across&amp;#x20;projects"&gt;Sharing configurations for PHP-CS-Fixer across projects&lt;/a&gt;, override the existing rule configuration by disabling all rules.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Third, go through the list of rules, from top to bottom, from bottom to top, or whatever works best for you, pick a rule, carefully inspect its documentation, and enable and configure one rule at a time - but only when you can be sure that it only applies fixes that are compatible with the version of PHP running in production.&lt;/p&gt;
&lt;h2 id="content-example-of-enabling-and-configuring-a-rule-at-a-time" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-example-of-enabling-and-configuring-a-rule-at-a-time" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Example of enabling and configuring a rule at a time&lt;/h2&gt;
&lt;p&gt;Let's look at concrete examples, considering and configuring the &lt;code&gt;array_syntax&lt;/code&gt; rule, when you are &lt;a href="https://leanpub.com/working-with-pull-requests" target="_blank" title="Working with pull requests"&gt;working with pull requests&lt;/a&gt; and pre-merge code reviews.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, create a branch, for example, &lt;code&gt;feature/array-syntax.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Second, copy the name of the rule.&lt;/li&gt;
&lt;li&gt;Third, navigate to &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.16.0" target="_blank" title=""&gt;https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.16.0&lt;/a&gt; (replace &lt;code&gt;3.16.0&lt;/code&gt; with the version of PHP-CS-Fixer that you actually use). Press &lt;kbd&gt;T&lt;/kbd&gt; to open the file navigation. Paste the name of the rule into the input field (here &lt;code&gt;array_syntax&lt;/code&gt;). Select the corresponding documentation file in the drop-down (here &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.16.0/doc/rules/array_notation/array_syntax.rst" target="_blank" title=""&gt;&lt;code&gt;array_syntax.rst&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Fourth, inspect the documentation. Can you safely enable the &lt;code&gt;array_syntax&lt;/code&gt; fixer? For example, PHP 5.4 introduced the short array syntax. Is your legacy PHP project running on PHP 5.3 in production? Then you should probably enable the &lt;code&gt;array_syntax&lt;/code&gt; fixer and configure the &lt;code&gt;syntax&lt;/code&gt; option to use &lt;code&gt;long&lt;/code&gt; as value. Or is your legacy PHP project running on PHP 5.4 or above? Then you should probably enable the fixer and configure the &lt;code&gt;syntax&lt;/code&gt; option to use &lt;code&gt;short&lt;/code&gt; as value.&lt;/li&gt;
&lt;li&gt;Fifth, commit and push the changes to your &lt;code&gt;.php-cs-fixer.php&lt;/code&gt; configuration file.&lt;/li&gt;
&lt;li&gt;Sixth, open a pull request and document in the body that this pull request will enable the &lt;code&gt;array_syntax&lt;/code&gt; fixer. Opening the pull request will start a run of your GitHub Actions workflow.&lt;/li&gt;
&lt;li&gt;Seventh, run PHP-CS-Fixer in your development environment. If PHP-CS-Fixer has applied fixes, the GitHub Actions workflow will fail. Commit and push the fixes in a separate commit to make the build pass.&lt;/li&gt;
&lt;li&gt;Eighth, review the changes in the pull request and verify that they make sense. If necessary, have someone else review your pull request as well.&lt;/li&gt;
&lt;li&gt;Ninth, merge the pull request and delete the branch.&lt;/li&gt;
&lt;li&gt;Tenth, check out your default branch in your development environment.&lt;/li&gt;
&lt;li&gt;Eleventh, pull the latest changes.&lt;/li&gt;
&lt;li&gt;Twelfth, delete the feature branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Repeat the steps above for every rule.&lt;/p&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can find an example of a pull request that enables and configures the &lt;code&gt;array_syntax&lt;/code&gt; fixer for the official PHP website &lt;a href="https://github.com/php/web-php/pull/659" target="_blank" title="Enhancement: Enable array_syntax fixer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-disadvantages-of-enabling-and-configuring-one-rule-at-a-time" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-disadvantages-of-enabling-and-configuring-one-rule-at-a-time" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Disadvantages of enabling and configuring one rule at a time&lt;/h2&gt;
&lt;p&gt;At the time of writing, PHP-CS-Fixer ships with 250 rules. Enabling and configuring one rule at a time can take a while, even more so when you are working with pull requests and require pre-merge code reviews. But you could significantly speed up the process by using &lt;a href="https://martinfowler.com/articles/ship-show-ask.html" target="_blank" title=""&gt;Ship/Show/Ask&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are unwilling to invest the time, good luck applying all rules and reviewing all fixes at once!&lt;/p&gt;
&lt;h2 id="content-advantages-of-enabling-and-configuring-one-rule-at-a-time" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-advantages-of-enabling-and-configuring-one-rule-at-a-time" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Advantages of enabling and configuring one rule at a time&lt;/h2&gt;
&lt;p&gt;In my opinion, enabling and configuring a single rule at a time has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You minimize risk: each pull request contains only related changes that are easier to review.&lt;/li&gt;
&lt;li&gt;You invite other developers to collaborate on your coding standard.&lt;/li&gt;
&lt;li&gt;You will probably touch code in all corners of your legacy PHP project, which allows you to discover and take note of weird spots that need closer inspection.&lt;/li&gt;
&lt;/ul&gt;

</content></entry><entry><title>Collecting line, branch, and path coverage with PHPUnit</title><category term="code-coverage"/><category term="php"/><category term="phpunit"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/03/22/collecting-line-branch-and-path-coverage-with-phpunit/"/><id>https://localheinz.com/articles/2023/03/22/collecting-line-branch-and-path-coverage-with-phpunit/</id><updated>2023-03-22T16:40:00+01:00</updated><content>&lt;h1&gt;
  Collecting line, branch, and path coverage with PHPUnit
&lt;/h1&gt;


&lt;p&gt;&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann&amp;#x2F;phpunit" target="_blank" title="sebastianbergmann/phpunit on GitHub"&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt;&lt;/a&gt; allows the collection of code coverage through &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann&amp;#x2F;php-code-coverage" target="_blank" title="sebastianbergmann/php-code-coverage on GitHub"&gt;&lt;code&gt;phpunit/php-code-coverage&lt;/code&gt;&lt;/a&gt;, which currently supports two code coverage drivers: &lt;a href="https://github.com/krakjoe/pcov" target="_blank" title="PCOV on GitHub"&gt;PCOV&lt;/a&gt; and &lt;a href="https://xdebug.org" target="_blank" title="Xdebug"&gt;Xdebug&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With PCOV, you can collect line coverage; with Xdebug, you can collect line, branch, and path coverage.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://docs.phpunit.de/en/10.0/code-coverage.html#software-metrics-for-code-coverage" target="_blank" title="PHPUnit: Software Metrics for Code Coverage"&gt;PHPUnit documentation&lt;/a&gt; explains line, branch, and path coverage as follows:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    The Line Coverage software metric measures whether each executable line was executed.
  &lt;/p&gt;
  &lt;p&gt;
    The Branch Coverage software metric measures whether the boolean expression of each control structure evaluated to both &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; while running the test suite.
  &lt;/p&gt;
  &lt;p&gt;
    The Path Coverage software metric measures whether each of the possible execution paths in a function or method has been followed while running the test suite. An execution path is a unique sequence of branches from the entry of the function or method to its exit.
  &lt;/p&gt;
  &lt;div class="source"&gt;
    &lt;a href="https://docs.phpunit.de/en/10.0/code-coverage.html#software-metrics-for-code-coverage" target="_blank" title="PHPUnit: Software Metrics for Code Coverage"&gt;PHPUnit documentation&lt;/a&gt;
  &lt;/span&gt;
&lt;/blockquote&gt;
&lt;p&gt;As you can see, line, branch, and path coverage provide insights into different aspects of your safety net of automated tests.&lt;/p&gt;
&lt;p&gt;But what are the costs of running tests and collecting line, branch, and path coverage? Let's find out by running tests and collecting code coverage with PHPUnit for PHPUnit!&lt;/p&gt;
&lt;h2 id="content-preparing-to-run-tests-for-phpunit" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-preparing-to-run-tests-for-phpunit" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Preparing to run tests for PHPUnit&lt;/h2&gt;
&lt;p&gt;I assume you have installed PHP 8.1 (or PHP 8.2) with the PCOV and Xdebug extensions.&lt;/p&gt;
&lt;p&gt;First, run the following command to clone PHPUnit:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;git clone git@github.com/sebastianbergmann/phpunit.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second, run the following command to create a branch with the name &lt;code&gt;10.0.18&lt;/code&gt; based on the tag &lt;code&gt;10.0.18&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;git checkout -b 10.0.18 10.0.18
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Third, run the following command to install dependencies with composer:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you are ready to run tests and collect code coverage &lt;em&gt;with&lt;/em&gt; PHPUnit &lt;em&gt;for&lt;/em&gt; PHPUnit!&lt;/p&gt;
&lt;h2 id="content-collecting-line-branch-and-path-coverage-with-phpunit-for-phpunit" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-collecting-line-branch-and-path-coverage-with-phpunit-for-phpunit" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Collecting line, branch, and path coverage with PHPUnit for PHPUnit&lt;/h2&gt;
&lt;p&gt;First, disable PCOV and Xdebug (if you are on macOS, take a look at &lt;a href="/articles/2020/05/16/quickly-switching-between-pcov-and-xdebug/" target="_blank" title="Quickly&amp;#x20;switching&amp;#x20;between&amp;#x20;PCOV&amp;#x20;and&amp;#x20;Xdebug"&gt;Quickly switching between PCOV and Xdebug&lt;/a&gt;). Then run the following command to run tests for PHPUnit and take note of time and memory:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;./phpunit --no-progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On my machine, a 2021 Apple MacBook Pro with an Apple M1 Max chip, 64 GB of RAM, and running PHP 8.2.4, the output looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;PHPUnit 10.0.18 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.4
Configuration: /Users/am/Sites/sebastianbergmann/phpunit/phpunit.xml

Time: 00:22.092, Memory: 38.00 MB

OK, but some tests were skipped!
Tests: 2804, Assertions: 7302, Skipped: 3.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second, enable PCOV and run the following command to collect line coverage for PHPUnit and take note of time and memory:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;./phpunit --coverage-clover=clover.xml --no-progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On my machine, the output looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;PHPUnit 10.0.18 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.4 with PCOV 1.0.11
Configuration: /Users/am/Sites/sebastianbergmann/phpunit/phpunit.xml

Time: 00:34.150, Memory: 96.00 MB

OK, but some tests were skipped!
Tests: 2804, Assertions: 7304, Skipped: 1.

Generating code coverage report in Clover XML format ... done [00:00.131]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Third, disable PCOV and enable Xdebug, then run the following command to collect line coverage for PHPUnit and take note of time and memory:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;./phpunit --coverage-clover=clover.xml --no-progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On my machine, the output looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;PHPUnit 10.0.18 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.4 with Xdebug 3.2.1
Configuration: /Users/am/Sites/sebastianbergmann/phpunit/phpunit.xml

Time: 00:49.498, Memory: 90.00 MB

OK, but some tests were skipped!
Tests: 2804, Assertions: 7300, Skipped: 5.

Generating code coverage report in Clover XML format ... done [00:00.185]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fourth, keeping Xdebug enabled, run the following command to collect line, branch, and path coverage for PHPUnit and take note of time and memory:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;./phpunit --coverage-clover=clover.xml --no-progress --path-coverage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On my machine, the output looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;PHPUnit 10.0.18 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.4 with Xdebug 3.2.1
Configuration: /Users/am/Sites/sebastianbergmann/phpunit/phpunit.xml

Time: 07:30.861, Memory: 451.34 MB
Time: 07:30.861, Memory: 451.34 MB

OK, but some tests were skipped!
Tests: 2804, Assertions: 7300, Skipped: 5.

Generating code coverage report in Clover XML format ... done [00:00.715]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's take a look at the results to understand the costs of running tests and collecting code coverage!&lt;/p&gt;
&lt;h2 id="content-costs-of-collecting-code-coverage" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-costs-of-collecting-code-coverage" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Costs of collecting code coverage&lt;/h2&gt;
&lt;p&gt;As mentioned before, I ran the tests on a 2021 Apple MacBook Pro with an Apple M1 Max chip, 64 GB of RAM, and running PHP 8.2.4. You will probably observe different results on another machine, but the relative results will be similar.&lt;/p&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th scope="col"&gt;Code Coverage Driver&lt;/th&gt;
        &lt;th scope="col"&gt;Code Coverage&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Time (in i:s.u)&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Memory (in MB)&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
        &lt;th scope="row"&gt;none&lt;/th&gt;
        &lt;td&gt;none&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:22.092&lt;br&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          38.00 MB&lt;br&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
        &lt;tr&gt;
        &lt;th scope="row"&gt;PCOV&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:34.150&lt;br&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          96.00 MB&lt;br&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
        &lt;tr&gt;
        &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:49.498&lt;br&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          90.00 MB&lt;br&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
        &lt;tr&gt;
        &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line, branch, path&lt;/td&gt;
        &lt;td class="text-right"&gt;
          07:30.861&lt;br&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          451.34 MB&lt;br&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;/tbody&gt;
  &lt;/table&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 As &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann" target="_blank" title="Sebastian&amp;#x20;Bergmann&amp;#x20;on&amp;#x20;GitHub"&gt;Sebastian Bergmann&lt;/a&gt; points out in &lt;a href="https://thephp.cc/presentations/optimizing-your-test-suite" target="_blank" title="thePHP.cc: Optimizing Your Test Suite"&gt;Optimizing Your Test Suite (slide 49)&lt;/a&gt; , Xdebug does not use PHP's memory manager, so the memory shown by PHPUnit is incorrect when collecting code coverage with Xdebug.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You will probably observe different results, so let's compare the relative results!&lt;/p&gt;
&lt;h2 id="content-running-tests-for-phpunit-as-a-baseline" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-running-tests-for-phpunit-as-a-baseline" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Running tests for PHPUnit as a baseline&lt;/h2&gt;
&lt;p&gt;First, let's consider running tests for PHPUnit as a baseline.&lt;/p&gt;
&lt;p&gt;If you drive the development of your PHP projects with tests (&lt;a href="http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd" target="_blank" title="Robert C. Martin: The three laws of TDD"&gt;Test-Driven Development&lt;/a&gt;), you would never write a bit of production code without writing a failing test first. In theory, your code coverage would be 100%. Unless you wanted to report code coverage, for example, because you have external requirements or would like to display a shiny badge in your repository, you would never need to collect code coverage.&lt;/p&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th scope="col"&gt;Code Coverage Driver&lt;/th&gt;
        &lt;th scope="col"&gt;Code Coverage&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Time (in i:s.u)&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Memory (in MB)&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
            &lt;tr class="table-active"&gt;
            &lt;th scope="row"&gt;none&lt;/th&gt;
        &lt;td&gt;none&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:22.092&lt;br&gt;
          &lt;small&gt;100%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          38.00 MB&lt;br&gt;
          &lt;small&gt;100%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;PCOV&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:34.150&lt;br&gt;
          &lt;small&gt;154.6%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          96.00 MB&lt;br&gt;
          &lt;small&gt;252.6%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:49.498&lt;br&gt;
          &lt;small&gt;224.1%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          90.00 MB&lt;br&gt;
          &lt;small&gt;236.8%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line, branch, path&lt;/td&gt;
        &lt;td class="text-right"&gt;
          07:30.861&lt;br&gt;
          &lt;small&gt;2040.8%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          451.34 MB&lt;br&gt;
          &lt;small&gt;1187.7%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;/tbody&gt;
  &lt;/table&gt;
&lt;p&gt;As you can see above,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;collecting line coverage with PCOV takes 1.5 times&lt;/li&gt;
&lt;li&gt;collecting line coverage with Xdebug takes 2.2 times&lt;/li&gt;
&lt;li&gt;collecting line, branch, and path coverage with Xdebug takes 20.4 times&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;as long as running tests for PHPUnit without collecting code coverage.&lt;/p&gt;
&lt;h2 id="content-collecting-line-coverage-for-phpunit-with-pcov-as-a-baseline" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-collecting-line-coverage-for-phpunit-with-pcov-as-a-baseline" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Collecting line coverage for PHPUnit with PCOV as a baseline&lt;/h2&gt;
&lt;p&gt;Second, as I have yet to meet someone who consistently applies Test-Driven Development, let's consider collecting line coverage for PHPUnit with PCOV as a baseline.&lt;/p&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th scope="col"&gt;Code Coverage Driver&lt;/th&gt;
        &lt;th scope="col"&gt;Code Coverage&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Time (in i:s.u)&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Memory (in MB)&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;none&lt;/th&gt;
        &lt;td&gt;none&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:22.092&lt;br&gt;
          &lt;small&gt;64.7%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          38.00 MB&lt;br&gt;
          &lt;small&gt;39.6%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr class="table-active"&gt;
            &lt;th scope="row"&gt;PCOV&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:34.150&lt;br&gt;
          &lt;small&gt;100%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          96.00 MB&lt;br&gt;
          &lt;small&gt;100%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:49.498&lt;br&gt;
          &lt;small&gt;144.9%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          90.00 MB&lt;br&gt;
          &lt;small&gt;93.8%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line, branch, path&lt;/td&gt;
        &lt;td class="text-right"&gt;
          07:30.861&lt;br&gt;
          &lt;small&gt;1320.2%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          451.34 MB&lt;br&gt;
          &lt;small&gt;470.1%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;/tbody&gt;
  &lt;/table&gt;
&lt;p&gt;As you can see above,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;collecting line coverage with Xdebug takes 1.4 times&lt;/li&gt;
&lt;li&gt;collecting line, branch, and path coverage with Xdebug takes 13.2 times&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;as long as collecting line coverage for PHPUnit with PCOV.&lt;/p&gt;
&lt;h2 id="content-collecting-line-coverage-for-phpunit-with-xdebug-as-a-baseline" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-collecting-line-coverage-for-phpunit-with-xdebug-as-a-baseline" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Collecting line coverage for PHPUnit with Xdebug as a baseline&lt;/h2&gt;
&lt;p&gt;Third, since Xdebug also provides a step debugger and you may not want to switch back and forth between PCOV and Xdebug all the time, let's consider collecting line coverage for PHPUnit with Xdebug as a baseline.&lt;/p&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th scope="col"&gt;Code Coverage Driver&lt;/th&gt;
        &lt;th scope="col"&gt;Code Coverage&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Time (in i:s.u)&lt;/th&gt;
        &lt;th class="text-right" scope="col"&gt;Memory (in MB)&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;none&lt;/th&gt;
        &lt;td&gt;none&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:22.092&lt;br&gt;
          &lt;small&gt;44.6%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          38.00 MB&lt;br&gt;
          &lt;small&gt;42.2%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;PCOV&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:34.150&lt;br&gt;
          &lt;small&gt;69%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          96.00 MB&lt;br&gt;
          &lt;small&gt;106.7%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr class="table-active"&gt;
            &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line&lt;/td&gt;
        &lt;td class="text-right"&gt;
          00:49.498&lt;br&gt;
          &lt;small&gt;100%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          90.00 MB&lt;br&gt;
          &lt;small&gt;100%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
            &lt;tr&gt;
            &lt;th scope="row"&gt;Xdebug&lt;/th&gt;
        &lt;td&gt;line, branch, path&lt;/td&gt;
        &lt;td class="text-right"&gt;
          07:30.861&lt;br&gt;
          &lt;small&gt;910.9%&lt;/small&gt;
        &lt;/td&gt;
        &lt;td class="text-right"&gt;
          451.34 MB&lt;br&gt;
          &lt;small&gt;501.5%&lt;/small&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;/tbody&gt;
  &lt;/table&gt;
&lt;p&gt;As you can see above,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;collecting line, branch, and path coverage with Xdebug takes 9.1 times&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;as long as collecting line coverage for PHPUnit with Xdebug.&lt;/p&gt;
&lt;h2 id="content-conclusion" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-conclusion" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Collecting line, branch, and path coverage is costly if you value your time.&lt;/p&gt;
&lt;p&gt;While waiting for the line, branch, and path coverage collection to finish (07:30.861!), I zoned out and checked Instagram on my phone. &lt;a href="https://www.ics.uci.edu/~gmark/chi08-mark.pdf" target="_blank" title=""&gt;Context switches&lt;/a&gt; indirectly affect the costs of your business.&lt;/p&gt;
&lt;p&gt;Even if you don't value your time, external services, such as &lt;a href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates" target="_blank" title="GitHub Actions: Per-minute rates"&gt;GitHub Actions&lt;/a&gt;, put a price on the time you use for collecting code coverage using their infrastructure (currently $0.008 per minute). The invoices you need to pay directly affect the costs of your business.&lt;/p&gt;
&lt;p&gt;Last but not least, the &lt;a href="https://en.wikipedia.org/wiki/Green_computing" target="_blank" title="Wikipedia: Green Computing"&gt;consumption of computing resources&lt;/a&gt; indirectly affects the environment, increasing social costs that we all have to pay in different terms.&lt;/p&gt;
&lt;p&gt;What are your arguments for collecting line, branch, and path coverage when line coverage would suffice?&lt;/p&gt;

</content></entry><entry><title>Avoiding one-liners in PHP</title><category term="maintenance"/><category term="php"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/03/18/avoiding-one-liners-in-php/"/><id>https://localheinz.com/articles/2023/03/18/avoiding-one-liners-in-php/</id><updated>2023-03-18T07:30:00+01:00</updated><content>&lt;h1&gt;
  Avoiding one-liners in PHP
&lt;/h1&gt;


&lt;p&gt;PHP developers frequently share how they transform multiple lines of PHP code into one-liners. Through the eye of a maintainer, how do these one-liners compare to their multi-line counterparts? After all, writing PHP code is not a problem, but maintaining it is even more so.&lt;/p&gt;
&lt;h2 id="content-maintenance-of-php-projects" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-maintenance-of-php-projects" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Maintenance of PHP projects&lt;/h2&gt;
&lt;p&gt;With a safety net of automated tests, the maintenance of a PHP project is easy.&lt;/p&gt;
&lt;p&gt;If you have that safety net, the readability of the production does not matter: you can always refactor it into something more or less readable without worrying about breaking it.&lt;/p&gt;
&lt;p&gt;If you have that safety net, the syntactic sugar you use in the production code does not matter: you can always be more progressive or conservative, depending on the minimum version of PHP you support and the features you are comfortable using.&lt;/p&gt;
&lt;p&gt;If you have that safety net, you could probably let ChatGPT generate your production code based on yiur automated tests and consider the production code a build artifact that you do not even bother checking into version control. Well, maybe we are not quite there yet!&lt;/p&gt;
&lt;p&gt;But, if you have seen your fair share of PHP projects, you know that most of these, particularly closed-source PHP projects, suffer from a lack of tests.&lt;/p&gt;
&lt;p&gt;Without a safety net of automated tests, the maintenance of a PHP project can quickly become a nightmare, and there is little reason to make it even more difficult for you.&lt;/p&gt;
&lt;p&gt;But how good is your safety net of automated tests?&lt;/p&gt;
&lt;h2 id="content-effects-of-one-liners-on-code-coverage" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-effects-of-one-liners-on-code-coverage" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Effects of one-liners on code coverage&lt;/h2&gt;
&lt;p&gt;As a maintainer of a PHP project with less than 100% code coverage, you understand how deleting, adding, and formatting code can affect code coverage metrics.&lt;/p&gt;
&lt;p&gt;Code coverage will decrease when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you delete tests&lt;/li&gt;
&lt;li&gt;you reformat tested production code from more to fewer executable lines (one-liners, anyone?)&lt;/li&gt;
&lt;li&gt;you add untested code&lt;/li&gt;
&lt;li&gt;you reformat untested production code from fewer to more executable lines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Code coverage will increase when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you add tests&lt;/li&gt;
&lt;li&gt;you reformat tested production code from fewer to more executable lines&lt;/li&gt;
&lt;li&gt;you deleted untested production code&lt;/li&gt;
&lt;li&gt;you reformat untested production code from more to fewer executable lines (one-liners, anyone?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By using clever one-liners in your production code, you can gain misleadingly high confidence in your production code and your safety net of automated tests.&lt;/p&gt;
&lt;h2 id="content-collecting-line-path-and-branch-code-coverage" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-collecting-line-path-and-branch-code-coverage" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Collecting line, path, and branch code coverage&lt;/h2&gt;
&lt;p&gt;When you use PHPUnit to collect code coverage, PHPUnit will only collect line coverage by default. You could additionally collect line, branch, and path coverage, but as you can see in &lt;a href="/articles/2023/03/22/collecting-line-branch-and-path-coverage-with-phpunit/" target="_blank" title="Collecting&amp;#x20;line,&amp;#x20;branch,&amp;#x20;and&amp;#x20;path&amp;#x20;coverage&amp;#x20;with&amp;#x20;PHPUnit"&gt;Collecting line, branch, and path coverage with PHPUnit&lt;/a&gt;, the collection of branch and path coverage comes at a cost.&lt;/p&gt;
&lt;p&gt;Are the conditional one-liners in your PHP project worth these costs?&lt;/p&gt;
&lt;p&gt;Instead of spending time, resources, and money to collect branch and path coverage when line coverage would suffice, why not avoid conditional one-liners in the first place?&lt;/p&gt;
&lt;h2 id="content-running-mutation-tests" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-running-mutation-tests" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Running mutation tests&lt;/h2&gt;
&lt;p&gt;When you use &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;infection&amp;#x2F;infection" target="_blank" title="infection/infection on GitHub"&gt;&lt;code&gt;infection/infection&lt;/code&gt;&lt;/a&gt; to run mutation tests, you can combine your code coverage metrics with &lt;a href="https://infection.github.io/guide/#Metrics-Mutation-Score-Indicator-MSI" target="_blank" title="Infection: Mutation Score Indicators"&gt;mutation score indicators&lt;/a&gt; to get a better idea of the quality of your test suites.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;infection/infection&lt;/code&gt; will mutate your production code and run the tests against the mutations. If the tests pass after applying mutants, your tests are not good enough, and &lt;code&gt;infection/infection&lt;/code&gt;  will log the uncovered mutations.&lt;/p&gt;
&lt;p&gt;But you do not maintain your PHP project in a log file, do you? You maintain your production code in an IDE.&lt;/p&gt;
&lt;h2 id="content-visualizing-code-coverage-in-phpstorm" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-visualizing-code-coverage-in-phpstorm" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Visualizing code coverage in PhpStorm&lt;/h2&gt;
&lt;p&gt;When you use &lt;a href="https://www.jetbrains.com/phpstorm/" target="_blank" title="PhpStorm"&gt;PhpStorm&lt;/a&gt; and &lt;a href="https://www.jetbrains.com/help/phpstorm/running-test-with-coverage.html" target="_blank" title="PhpStorm: Running with coverage"&gt;run tests with code coverage&lt;/a&gt;, PhpStorm will mark tested and untested lines of code as green and red, so you can immediately identify which lines of code require tests.&lt;/p&gt;
&lt;p&gt;PhpStorm &lt;a href="https://youtrack.jetbrains.com/issue/WI-68081" target="_blank" title="YouTrack: Support Branch Coverage (--path-coverage) feature when running tests with coverage"&gt;does not yet support branch coverage&lt;/a&gt;, so if you use conditional one-liners in your PHP project, PhpStorm does not show whether your tests cover both branches of a condition.&lt;/p&gt;
&lt;p&gt;By using conditional one-liners in your PHP project, you are potentially hiding untested branches in your production code in plain sight.&lt;/p&gt;
&lt;p&gt;Instead of hiding these untested parts of your production code in your IDE, why not avoid conditional one-liners in the first place?&lt;/p&gt;
&lt;h2 id="content-effects-of-one-liners-on-debugging" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-effects-of-one-liners-on-debugging" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Effects of one-liners on debugging&lt;/h2&gt;
&lt;p&gt;When you use &lt;a href="https://www.jetbrains.com/phpstorm/" target="_blank" title="PhpStorm"&gt;PhpStorm&lt;/a&gt; and &lt;a href="https://xdebug.org" target="_blank" title="Xdebug"&gt;Xdebug&lt;/a&gt; to &lt;a href="https://www.jetbrains.com/help/phpstorm/debugging-with-phpstorm-ultimate-guide.html" target="_blank" title="Debug with PhpStorm: Ultimate Guide"&gt;debug PHP code&lt;/a&gt;, you can set &lt;a href="https://www.jetbrains.com/help/phpstorm/using-breakpoints.html" target="_blank" title="PhpStorm: Breakpoints"&gt;breakpoints&lt;/a&gt; to halt the execution, &lt;a href="https://www.jetbrains.com/help/phpstorm/examining-suspended-program.html" target="_blank" title="PhpStorm: Examining a Suspended Program"&gt;inspect variables in the current scope&lt;/a&gt;, and &lt;a href="https://www.jetbrains.com/help/phpstorm/stepping-through-the-program.html" target="_blank" title="PhpStorm: Step through the program"&gt;step through the program&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you set breakpoints in your one-liners, where exactly is the execution going to halt? How will you understand where you are in the current execution, step into, over, and out of the current expression?&lt;/p&gt;
&lt;p&gt;The widespread use of clever one-liners will make it rather difficult for you to use a step debugger.&lt;/p&gt;
&lt;h2 id="content-candidates-for-clever-one-liners" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-candidates-for-clever-one-liners" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Candidates for clever one-liners&lt;/h2&gt;
&lt;p&gt;Let's look at a few PHP language features that allow you to use one-liners instead of their multi-line counterparts (all of these are from the PHP website).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#content-ternary-operators" title="Ternary operators"&gt;ternary operators&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-short-ternary-operators" title="Short ternary operator"&gt;short ternary operators&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-null-coalescing-operators" title="Null-coalescing operator"&gt;null-coalescing operators&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-null-coalescing-assignment-operators" title="Null-coalescing assignment operator"&gt;null-coalescing assignment operators&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-arrow-functions" title="Arrow functions"&gt;arrow functions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-null-safe-operators" title="Null-safe operator"&gt;null-safe operators&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-throw-expressions" title="Throw expressions"&gt;throw expressions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-ternary-operators" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-ternary-operators" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Ternary operators&lt;/h2&gt;
&lt;p&gt;The following example demonstrates the use of the &lt;a href="https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary" target="_blank" title="PHP: Ternary operator"&gt;ternary operator&lt;/a&gt;. Unfortunately, I do not know which PHP version introduced it, but &lt;a href="https://3v4l.org/m8rFR" target="_blank" title="34vl.org: Ternary operator"&gt;3v4l.org&lt;/a&gt; shows that the &lt;a href="https://3v4l.org/NY5P1/vld" target="_blank" title=""&gt;ternary operator works on PHP 4.3.0&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$username = &lt;span class="hljs-keyword"&gt;isset&lt;/span&gt;($_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;]) ? $_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;] : &lt;span class="hljs-string"&gt;'nobody'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;isset&lt;/span&gt;($_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;])) {
    $username = $_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;];
} &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; {
    $username = &lt;span class="hljs-string"&gt;'nobody'&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is also equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$username = &lt;span class="hljs-string"&gt;'nobody'&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;isset&lt;/span&gt;($_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;])) {
    $username = $_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the use of &lt;a href="https://www.php.net/manual/en/function.isset.php" target="_blank" title="PHP: isset"&gt;&lt;code&gt;isset()&lt;/code&gt;&lt;/a&gt; is debatable, the multi-line examples will immediately expose a lack of code coverage and allow you to set a breakpoint on a line with a single expression.&lt;/p&gt;
&lt;h2 id="content-short-ternary-operators" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-short-ternary-operators" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Short ternary operators&lt;/h2&gt;
&lt;p&gt;The following example demonstrates the use of the &lt;a href="https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary" target="_blank" title="PHP: Short ternary operator"&gt;short ternary operator&lt;/a&gt;, introduced with &lt;a href="https://www.php.net/releases/5_3_0.php" target="_blank" title="PHP 5.3.0 Release Announcement"&gt;PHP 5.3.0&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$result = $action ?: &lt;span class="hljs-string"&gt;'default'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code (see &lt;a href="#content-ternary-operators" title="Ternary operators"&gt;ternary operators&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$result = $action ? $action : &lt;span class="hljs-string"&gt;'default'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; ($action) {
    $result = $action;
} &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; {
    $result = &lt;span class="hljs-string"&gt;'default'&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$result = &lt;span class="hljs-string"&gt;'default'&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; ($action) {
    $result = $action;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the use of non-boolean expressions in conditions is debatable, the multi-line examples will immediately expose a lack of code coverage and allow you to set a breakpoint on a line with a single expression.&lt;/p&gt;
&lt;h2 id="content-null-coalescing-operators" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-null-coalescing-operators" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Null-coalescing operators&lt;/h2&gt;
&lt;p&gt;The following example demonstrates the use of the &lt;a href="https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.null-coalesce-op" target="_blank" title="PHP 7.0.0: Null-coalescing operator"&gt;null-coalescing operator&lt;/a&gt;, introduced with &lt;a href="https://www.php.net/releases/7_0_0.php" target="_blank" title="PHP 7.0.0 Release Announcement"&gt;PHP 7.0.0&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$username = $_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;] ?? &lt;span class="hljs-string"&gt;'nobody'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code (see &lt;a href="#content-ternary-operators" title="Ternary operators"&gt;ternary operators&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$username = &lt;span class="hljs-keyword"&gt;isset&lt;/span&gt;($_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;]) ? $_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;] : &lt;span class="hljs-string"&gt;'nobody'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;isset&lt;/span&gt;($_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;])) {
    $username = $_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;];
} &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; {
    $username = &lt;span class="hljs-string"&gt;'nobody'&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is also equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$username = &lt;span class="hljs-string"&gt;'nobody'&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;isset&lt;/span&gt;($_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;])) {
    $username = $_GET[&lt;span class="hljs-string"&gt;'user'&lt;/span&gt;];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, while the use of &lt;a href="https://www.php.net/manual/en/function.isset.php" target="_blank" title="PHP: isset"&gt;&lt;code&gt;isset()&lt;/code&gt;&lt;/a&gt; is debatable, the multi-line examples will immediately expose a lack of code coverage and allow you to set a breakpoint on a line with a single expression.&lt;/p&gt;
&lt;h2 id="content-null-coalescing-assignment-operators" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-null-coalescing-assignment-operators" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Null-coalescing assignment operators&lt;/h2&gt;
&lt;p&gt;The following example demonstrates the use of the &lt;a href="https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.null-coalescing-assignment-operator" target="_blank" title="PHP 7.4.0: Null-coalescing assignment operator"&gt;null-coalescing assignment operator&lt;/a&gt;, introduced with &lt;a href="https://www.php.net/releases/7_4_0.php" target="_blank" title="PHP 7.4.0 Release Announcement"&gt;PHP 7.4.0&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$array[&lt;span class="hljs-string"&gt;'key'&lt;/span&gt;] ??= computeDefault();


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!&lt;span class="hljs-keyword"&gt;isset&lt;/span&gt;($array[&lt;span class="hljs-string"&gt;'key'&lt;/span&gt;])) {
    $array[&lt;span class="hljs-string"&gt;'key'&lt;/span&gt;] = computeDefault();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, while the use of &lt;a href="https://www.php.net/manual/en/function.isset.php" target="_blank" title="PHP: isset"&gt;&lt;code&gt;isset()&lt;/code&gt;&lt;/a&gt; is debatable, the multi-line example will immediately expose a lack of code coverage and allow you to set a breakpoint on a line with a single expression.&lt;/p&gt;
&lt;h2 id="content-arrow-functions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-arrow-functions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Arrow functions&lt;/h2&gt;
&lt;p&gt;The following example demonstrates the use of &lt;a href="https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.arrow-functions" target="_blank" title="PHP 7.4.0: Arrow functions"&gt;arrow functions&lt;/a&gt;, introduced with &lt;a href="https://www.php.net/releases/7_4_0.php" target="_blank" title="PHP 7.4.0 Release Announcement"&gt;PHP 7.4.0&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$factor = &lt;span class="hljs-number"&gt;10&lt;/span&gt;;

$nums = array_map(fn($n) =&amp;gt; $n * $factor, [&lt;span class="hljs-number"&gt;1&lt;/span&gt;, &lt;span class="hljs-number"&gt;2&lt;/span&gt;, &lt;span class="hljs-number"&gt;3&lt;/span&gt;, &lt;span class="hljs-number"&gt;4&lt;/span&gt;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$factor = &lt;span class="hljs-number"&gt;10&lt;/span&gt;;

$nums = array_map(&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-params"&gt;($n)&lt;/span&gt; &lt;span class="hljs-title"&gt;use&lt;/span&gt; &lt;span class="hljs-params"&gt;($factor)&lt;/span&gt; &lt;/span&gt;{
   &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $n * $factor;
}, [&lt;span class="hljs-number"&gt;1&lt;/span&gt;, &lt;span class="hljs-number"&gt;2&lt;/span&gt;, &lt;span class="hljs-number"&gt;3&lt;/span&gt;, &lt;span class="hljs-number"&gt;4&lt;/span&gt;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the lack of type and return type declarations is debatable, the multi-line example will allow you to set a breakpoint on a line with a single expression. Also, the arrow function only implicitly captures the variable &lt;code&gt;$factor&lt;/code&gt;. At the same time, the multi-line example requires you to explicitly declare which variables you want to import into the scope of the closure. Explicit is better than implicit.&lt;/p&gt;
&lt;h2 id="content-null-safe-operator" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-null-safe-operator" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Null-safe operator&lt;/h2&gt;
&lt;p&gt;The following example demonstrates the use of the &lt;a href="https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.nullsafe" target="_blank" title="PHP: Null-safe operator"&gt;null-safe operator&lt;/a&gt;, introduced with &lt;a href="https://www.php.net/releases/8_0_0.php" target="_blank" title="PHP 8.0.0 Release Announcement"&gt;PHP 8.0.0&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$result = $repository?-&amp;gt;getUser(&lt;span class="hljs-number"&gt;5&lt;/span&gt;)?-&amp;gt;name;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (is_null($repository)) {
    $result = &lt;span class="hljs-keyword"&gt;null&lt;/span&gt;;
} &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; {
    $user = $repository-&amp;gt;getUser(&lt;span class="hljs-number"&gt;5&lt;/span&gt;);

    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (is_null($user)) {
        $result = &lt;span class="hljs-keyword"&gt;null&lt;/span&gt;;
    } &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; {
        $result = $user-&amp;gt;name;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

$result = &lt;span class="hljs-keyword"&gt;null&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; ($repository !== &lt;span class="hljs-keyword"&gt;null&lt;/span&gt;) {
    $user = $repository-&amp;gt;getUser(&lt;span class="hljs-number"&gt;5&lt;/span&gt;);

    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; ($user !== &lt;span class="hljs-keyword"&gt;null&lt;/span&gt;) {
        $result = $user-&amp;gt;name;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the use of &lt;a href="https://www.php.net/manual/en/function.is-null.php" target="_blank" title="PHP: is_null"&gt;&lt;code&gt;is_null()&lt;/code&gt;&lt;/a&gt; is debatable, the multi-line examples will immediately expose a lack of code coverage and allow you to set a breakpoint on a line with a single expression.&lt;/p&gt;
&lt;h2 id="content-throw-expressions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-throw-expressions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Throw expressions&lt;/h2&gt;
&lt;p&gt;The following example demonstrates the use of the &lt;a href="https://www.php.net/manual/en/language.exceptions.php#example-336" target="_blank" title="PHP: Throw expressions"&gt;throw expression&lt;/a&gt;, introduced with &lt;a href="https://www.php.net/releases/8_0_0.php" target="_blank" title="PHP 8.0.0 Release Announcement"&gt;PHP 8.0.0&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;test&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt; &lt;/span&gt;{
    do_something_risky() &lt;span class="hljs-keyword"&gt;or&lt;/span&gt; &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;Exception&lt;/span&gt;(&lt;span class="hljs-string"&gt;'It did not work'&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;test&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt; &lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!do_something_risky()) {
        &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;Exception&lt;/span&gt;(&lt;span class="hljs-string"&gt;'It did not work'&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above is equivalent to the following PHP code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;test&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt; &lt;/span&gt;{
    $result = do_something_risky();

    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!$result) {
        &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;Exception&lt;/span&gt;(&lt;span class="hljs-string"&gt;'It did not work'&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the use of non-boolean expressions in conditions is debatable, the multi-line examples will immediately expose a lack of code coverage and allow you to set a breakpoint on a line with a single expression.&lt;/p&gt;
&lt;h2 id="content-recommendation" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-recommendation" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Recommendation&lt;/h2&gt;
&lt;p&gt;Avoid clever one-liners or use them with care. Focus on writing code that solves real problems, is well-tested, and is easy to maintain. The developers who follow your path will thank you!&lt;/p&gt;

</content></entry><entry><title>Sharing configurations for PHP-CS-Fixer across projects</title><category term="coding-standards"/><category term="maintenance"/><category term="php"/><category term="php-cs-fixer"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/03/10/sharing-configurations-for-php-cs-fixer-across-projects/"/><id>https://localheinz.com/articles/2023/03/10/sharing-configurations-for-php-cs-fixer-across-projects/</id><updated>2023-03-10T08:40:00+01:00</updated><content>&lt;h1&gt;
  Sharing configurations for PHP-CS-Fixer across projects
&lt;/h1&gt;


&lt;p&gt;You are working on PHP applications and packages and use &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;PHP-CS-Fixer&amp;#x2F;PHP-CS-Fixer" target="_blank" title="PHP-CS-Fixer/PHP-CS-Fixer on GitHub"&gt;&lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;&lt;/a&gt; to enforce coding standards.&lt;/p&gt;
&lt;p&gt;What are your options for sharing your configurations for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; to enforce a consistent coding standard across these projects in your organization?&lt;/p&gt;
&lt;h2 id="content-configuring-php-cs-fixer" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-configuring-php-cs-fixer" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Configuring PHP-CS-Fixer&lt;/h2&gt;
&lt;p&gt;You can configure &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; with &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.15.1/doc/usage.rst" target="_blank" title="PHP-CS-Fixer: Usage"&gt;command line options&lt;/a&gt; only or by using a &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.15.1/doc/config.rst" target="_blank" title="PHP-CS-Fixer: Config file"&gt;configuration file&lt;/a&gt;. I recommend using a configuration file.&lt;/p&gt;
&lt;p&gt;The configuration file, typically with the name &lt;code&gt;.php-cs-fixer.php&lt;/code&gt; or &lt;code&gt;.php-cs-fixer.dist.php&lt;/code&gt;, contains PHP and needs to return a configuration object.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Config&lt;/span&gt;;

$config = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Config();

&lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration object must configure a finder that supplies &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; with a list of files to inspect.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Config&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Finder&lt;/span&gt;;

$finder = Finder::create()
    -&amp;gt;exclude([
        &lt;span class="hljs-string"&gt;'.build/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.github/'&lt;/span&gt;,
    ])
    -&amp;gt;in(&lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt;);

$config = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Config();

$config-&amp;gt;setFinder($finder)

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration object can optionally configure zero or more of the &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.15.1/doc/ruleSets/index.rst" target="_blank" title="PHP-CS-Fixer: List of Available Rulesets"&gt;38 rulesets&lt;/a&gt; and &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.15.1/doc/rules/index.rst" target="_blank" title="PHP-CS-Fixer: List of Available Rules"&gt;250 rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you do not configure any rulesets or rules, &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; currently uses &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.15.1/src/Config.php#L60" target="_blank" title="PhpCsFixer\Config on GitHub"&gt;&lt;code&gt;@PSR12&lt;/code&gt;&lt;/a&gt; as default ruleset.&lt;/p&gt;
&lt;p&gt;If you configure one or more of the available rulesets and rules, &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; will create, configure, and apply the corresponding fixers to the PHP files.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Config&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Finder&lt;/span&gt;;

$finder = Finder::create()
    -&amp;gt;exclude([
        &lt;span class="hljs-string"&gt;'.build/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.github/'&lt;/span&gt;,
    ])
    -&amp;gt;in(&lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt;);

$config = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Config();

$config
    -&amp;gt;setFinder($finder)
    -&amp;gt;setRules([
        &lt;span class="hljs-comment"&gt;// one or more of 38 rulesets&lt;/span&gt;
        &lt;span class="hljs-string"&gt;'@PSR12'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,

        &lt;span class="hljs-comment"&gt;// one or more of 250 rules for fixers&lt;/span&gt;
        &lt;span class="hljs-string"&gt;'blank_line_before_statement'&lt;/span&gt; =&amp;gt; [
            &lt;span class="hljs-string"&gt;'statements'&lt;/span&gt; =&amp;gt; [
                &lt;span class="hljs-string"&gt;'break'&lt;/span&gt;,
                &lt;span class="hljs-string"&gt;'case'&lt;/span&gt;,
                &lt;span class="hljs-string"&gt;'continue'&lt;/span&gt;,
                &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
        ],

        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

        &lt;span class="hljs-string"&gt;'yoda_style'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,
    ]);

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the location of PHP files will differ from one project to another, you will want to configure the finder per project and are only concerned with sharing the configuration of rulesets and rules across projects.&lt;/p&gt;
&lt;h2 id="content-sharing-configurations-for-php-cs-fixer-using-a-github-template-repository" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-sharing-configurations-for-php-cs-fixer-using-a-github-template-repository" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Sharing configurations for PHP-CS-Fixer using a GitHub template repository&lt;/h2&gt;
&lt;p&gt;If you use &lt;a href="https://github.com" target="_blank" title="GitHub"&gt;GitHub&lt;/a&gt; and do not feel like setting up a new project from scratch every time, you probably already have a &lt;a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository" target="_blank" title="GitHub Docs: Creating a template repository"&gt;GitHub template repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A GitHub repository template, such as &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;php-package-template" target="_blank" title="ergebnis/php-package-template on GitHub"&gt;&lt;code&gt;ergebnis/php-package-template&lt;/code&gt;&lt;/a&gt; enables you to set up a new project quickly - which could be an ideal location for maintaining a configuration of rulesets and rules for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But, you may realize that the PHP applications and packages you build have very different requirements and setups, so maybe you need more than one GitHub repository template.&lt;/p&gt;
&lt;p&gt;So which of these GitHub template repositories should now be the source of truth for your configurations of rulesets and rules for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Also, you may realize that you need to support a wide range of PHP versions, requiring different configurations for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;. So how can you make sure that you use the correct configuration?&lt;/p&gt;
&lt;p&gt;And, even if you had only a single GitHub repository template where you maintain a configuration of rulesets and rules for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;, how would you propagate the configuration of rulesets and rules to all of your other projects?&lt;/p&gt;
&lt;p&gt;Copying the configurations from there and pasting them into a PHP project whenever you pick up work could be an option.&lt;/p&gt;
&lt;p&gt;But this option is prone to errors. And how do you know whether the configuration in the central location has changed since you last worked on a specific project?&lt;/p&gt;
&lt;p&gt;If we only had a delivery mechanism for distributing configurations!&lt;/p&gt;
&lt;h2 id="content-sharing-configurations-for-php-cs-fixer-using-a-php-package" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-sharing-configurations-for-php-cs-fixer-using-a-php-package" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Sharing configurations for PHP-CS-Fixer using a PHP package&lt;/h2&gt;
&lt;p&gt;As &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;twitter.com&amp;#x2F;paulredmond" target="_blank" title="Paul&amp;#x20;Redmond&amp;#x20;on&amp;#x20;Twitter"&gt;Paul Redmond&lt;/a&gt; rightly points out in &lt;a href="https://laravel-news.com/sharing-phpcs-rules" target="_blank" title="Sharing PHPCS Rules Across Projects and Teams"&gt;his recent article&lt;/a&gt; about sharing configurations of rules for &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;squizlabs&amp;#x2F;PHP_CodeSniffer" target="_blank" title="squizlabs/PHP_CodeSniffer on GitHub"&gt;&lt;code&gt;squizlabs/php_codesniffer&lt;/code&gt;&lt;/a&gt; (an alternative to &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;), the ideal solution for sharing and propagating configurations is a PHP package.&lt;/p&gt;
&lt;p&gt;A PHP package can not only provide files, such as the XML configuration files for PHPCS in Paul's article, or PHP files or classes returning or creating arrays of rulesets and rules configurations for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;, but also declare dependencies.&lt;/p&gt;
&lt;p&gt;As &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; evolves, contributors and maintainers add, deprecate, and remove fixers and configuration options. You certainly want to ensure that your configuration of rulesets and rules works well with the version of &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; you use in your projects. You can achieve that by declaring the dependency on &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; in the PHP package instead of the consuming application or package.&lt;/p&gt;
&lt;p&gt;A PHP package can receive automated updates via &lt;a href="https://github.com/dependabot" target="_blank" title="Dependabot on GitHub"&gt;Dependabot&lt;/a&gt;, &lt;a href="https://github.com/renovatebot" target="_blank" title="Renovate Bot on GitHub"&gt;Renovate Bot&lt;/a&gt;, or similar services.&lt;/p&gt;
&lt;p&gt;If you set up the PHP package cleverly, you can use these automated updates to discover added, deprecated, and removed fixers and configuration options with automated tests. Automatic discovery of new fixers and configuration options allows you to use the full potential of fixers that &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; provides and can free up time for more pressing problems than manually fixing coding standard violations.&lt;/p&gt;
&lt;p&gt;Last but not least, by using Dependabot, Renovate Bot or similar services, you can propagate the configurations of rulesets and rules from your PHP package, which can now become the single source of truth of configurations for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;, to all of your other PHP applications and packages.&lt;/p&gt;
&lt;p&gt;The simplest solution would be to create one or more PHP files in your package that return the configuration of rulesets and rules.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; [
    &lt;span class="hljs-comment"&gt;// one or more of 38 rulesets&lt;/span&gt;
    &lt;span class="hljs-string"&gt;'@PSR12'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,

    &lt;span class="hljs-comment"&gt;// one or more of 250 rules for fixers&lt;/span&gt;
    &lt;span class="hljs-string"&gt;'blank_line_before_statement'&lt;/span&gt; =&amp;gt; [
        &lt;span class="hljs-string"&gt;'statements'&lt;/span&gt; =&amp;gt; [
            &lt;span class="hljs-string"&gt;'break'&lt;/span&gt;,
            &lt;span class="hljs-string"&gt;'case'&lt;/span&gt;,
            &lt;span class="hljs-string"&gt;'continue'&lt;/span&gt;,
            &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    ],

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-string"&gt;'yoda_style'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can require the PHP file downstream in your configuration for PHP-CS-Fixer.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Config&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Finder&lt;/span&gt;;

$rules = &lt;span class="hljs-keyword"&gt;require&lt;/span&gt; &lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt; . &lt;span class="hljs-string"&gt;'/vendor/ergebnis/php-cs-fixer-config/config/php82.php'&lt;/span&gt;;

$finder = Finder::create()
    -&amp;gt;exclude([
        &lt;span class="hljs-string"&gt;'.build/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.github/'&lt;/span&gt;,
    ])
    -&amp;gt;in(&lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt;);

$config = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Config();

$config
    -&amp;gt;setFinder($finder)
    -&amp;gt;setRules($rules);

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But why not extract a factory and concrete classes providing configurations of rulesets and rules that create the configurations for you?&lt;/p&gt;
&lt;p&gt;For example, a factory could ensure that you are running &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt; on the appropriate version of PHP. A factory could also further prepare the configuration object. Concrete classes for PHP-version-specific rulesets could allow you to override rules for project-specific configurations. A factory and classes also have the advantage that they are autoloadable, and you do not have to deal with exact file locations.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;PhpCsFixer&lt;/span&gt;;

$config = PhpCsFixer\Config\Factory::fromRuleSet(&lt;span class="hljs-keyword"&gt;new&lt;/span&gt; PhpCsFixer\Config\RuleSet\Php82(&lt;span class="hljs-string"&gt;''&lt;/span&gt;, [
    &lt;span class="hljs-string"&gt;'date_time_immutable'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;'mb_str_functions'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
]);

$config-&amp;gt;getFinder()
    -&amp;gt;exclude([
        &lt;span class="hljs-string"&gt;'.build/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'.github/'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'var/'&lt;/span&gt;,
    ])
    -&amp;gt;in(&lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-concrete-examples-from-the-field" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-concrete-examples-from-the-field" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Concrete examples from the field&lt;/h2&gt;
&lt;p&gt;In 2015, while supplying services for &lt;a href="https://www.refinery29.com" target="_blank" title="Refinery29, Inc."&gt;Refinery29, Inc&lt;/a&gt;., I started working on &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;refinery29&amp;#x2F;php-cs-fixer-config" target="_blank" title="refinery29/php-cs-fixer-config on GitHub"&gt;&lt;code&gt;refinery29/php-cs-fixer-config&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As far as I can tell, this was the first PHP package that shared configurations of rulesets and rules for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;, inspiring many others you can find on &lt;a href="https://packagist.org/?query=php-cs-fixer-conf" target="_blank" title="Packages with php-cs-fixer-config on Packagist"&gt;Packagist&lt;/a&gt; today.&lt;/p&gt;
&lt;p&gt;Since 2015, I have successfully applied and refined this approach in &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;php-cs-fixer-config" target="_blank" title="ergebnis/php-cs-fixer-config on GitHub"&gt;&lt;code&gt;ergebnis/php-cs-fixer-config&lt;/code&gt;&lt;/a&gt;, which I use in all my projects.&lt;/p&gt;
&lt;p&gt;For your convenience, I have extracted a GitHub template repository at &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;php-cs-fixer-config-template" target="_blank" title="ergebnis/php-cs-fixer-config-template on GitHub"&gt;&lt;code&gt;ergebnis/php-cs-fixer-config-template&lt;/code&gt;&lt;/a&gt; as a perfect starting point to create, share, and distribute configurations of rulesets and rules for &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Take a look - you will not regret it!&lt;/p&gt;

</content></entry><entry><title>Organizing test code in PHP</title><category term="maintenance"/><category term="php"/><category term="phpunit"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/03/03/organizing-test-code-in-php/"/><id>https://localheinz.com/articles/2023/03/03/organizing-test-code-in-php/</id><updated>2023-03-03T15:05:00+01:00</updated><content>&lt;h1&gt;
  Organizing test code in PHP
&lt;/h1&gt;


&lt;p&gt;You are working on a PHP application or a package. You have decided that you want to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;infection&amp;#x2F;infection" target="_blank" title="infection/infection on GitHub"&gt;&lt;code&gt;infection/infection&lt;/code&gt;&lt;/a&gt; for running mutation tests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;phpbench&amp;#x2F;phpbench" target="_blank" title="phpbench/phpbench on GitHub"&gt;&lt;code&gt;phpbench/phpbench&lt;/code&gt;&lt;/a&gt; for running performance tests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann&amp;#x2F;phpunit" target="_blank" title="sebastianbergmann/phpunit on GitHub"&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt;&lt;/a&gt; for running unit, integration, functional, and end-to-end tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perhaps you are considering other design and testing frameworks as well, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;codeception&amp;#x2F;codeception" target="_blank" title="codeception/codeception on GitHub"&gt;&lt;code&gt;codeception/codeception&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;Behat&amp;#x2F;Behat" target="_blank" title="Behat/Behat on GitHub"&gt;&lt;code&gt;behat/behat&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;pestphp&amp;#x2F;pest" target="_blank" title="pestphp/pest on GitHub"&gt;&lt;code&gt;pestphp/pest&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;phpspec&amp;#x2F;phpspec" target="_blank" title="phpspec/phpspec on GitHub"&gt;&lt;code&gt;phpspec/phpspec&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What are your &lt;a href="#content-drivers-for-organizing-test-code" title="Drivers for organizing test code"&gt;drivers&lt;/a&gt; and &lt;a href="#content-options-for-organizing-test-code" title="Options for organizing test code"&gt;options&lt;/a&gt; for organizing test code?&lt;/p&gt;
&lt;h2 id="content-systems-under-test" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-systems-under-test" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Systems under test&lt;/h2&gt;
&lt;p&gt;For this article, let's assume you have relatively simple production code organized in a directory structure as follows (listing generated with the &lt;a href="http://mama.indstate.edu/users/ice/tree/" target="_blank" title="The Tree Command for Linux"&gt;&lt;code&gt;tree&lt;/code&gt;&lt;/a&gt; command):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
└── src
    ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/main" target="_blank" title="main of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; in the &lt;code&gt;main&lt;/code&gt; branch of &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/main" target="_blank" title="localheinz/organizing-test-code-in-php on GitHub"&gt;&lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-test-code" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-test-code" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Test code&lt;/h2&gt;
&lt;p&gt;Let's also clarify what we understand as test code.&lt;/p&gt;
&lt;p&gt;First, you have test files.&lt;/p&gt;
&lt;p&gt;When you use a class-based test case framework such as &lt;code&gt;phpunit/phpunit&lt;/code&gt;, these test files declare tests in classes. Or, when you use a file-based test case framework such as &lt;code&gt;pestphp/pest&lt;/code&gt;, these test files declare tests in functions.&lt;/p&gt;
&lt;p&gt;Second, you have supporting test files.&lt;/p&gt;
&lt;p&gt;You may extract abstract test cases, helper traits, data providers, and test doubles to share functionality across tests. You may also have fixtures for use in tests. You can organize all of these in classes, functions, or files.&lt;/p&gt;
&lt;p&gt;Third, you have configuration files.&lt;/p&gt;
&lt;p&gt;When you use a test framework that works with configuration files, you will have at least one configuration per test framework.&lt;/p&gt;
&lt;p&gt;When you use &lt;a href="https://getcomposer.org" target="_blank" title="composer: A dependency manager for PHP"&gt;&lt;code&gt;composer&lt;/code&gt;&lt;/a&gt;, you have a &lt;a href="https://getcomposer.org/doc/04-schema.md#the-composer-json-schema" target="_blank" title="composer: The composer.json schema"&gt;&lt;code&gt;composer.json&lt;/code&gt;&lt;/a&gt; file in which you not only configure your project's dependencies but also &lt;a href="/articles/2023/01/29/documenting-namespaces-for-test-code-in-composer.json/" target="_blank" title="Documenting&amp;#x20;namespaces&amp;#x20;for&amp;#x20;test&amp;#x20;code&amp;#x20;in&amp;#x20;composer.json"&gt;configure and document namespaces for autoloading production and test code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When you use &lt;a href="https://git-scm.com" target="_blank" title="git"&gt;&lt;code&gt;git&lt;/code&gt;&lt;/a&gt; and are working on a package, you may have a &lt;a href="https://git-scm.com/docs/gitattributes" target="_blank" title="git Documentation: gitattributes"&gt;&lt;code&gt;.gitattributes&lt;/code&gt;&lt;/a&gt; configuration to prevent the distribution of code that users of your package do not need.&lt;/p&gt;
&lt;h2 id="content-drivers-for-organizing-test-code" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-drivers-for-organizing-test-code" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Drivers for organizing test code&lt;/h2&gt;
&lt;p&gt;Let's also talk about potential drivers for organizing test code.&lt;/p&gt;
&lt;p&gt;First, the mapping of test classes or files to production code drives the organization of your test code.&lt;/p&gt;
&lt;p&gt;You could have a single test class or test file that contains tests for the entire application or package.&lt;/p&gt;
&lt;p&gt;As you add more tests, you may find it challenging to decide where to add those tests - or you may have problems finding a specific test. Perhaps you conclude that it is better to have one test class or file per system under test, with a consistent naming (using prefixes, infixes, or suffixes) that makes it easier to find a test class or file.&lt;/p&gt;
&lt;p&gt;Second, the kinds of test you intend to write drive the organization of your test code.&lt;/p&gt;
&lt;p&gt;When you realize that you have different kinds of tests, for example, unit, integration, functional, or end-to-end tests, you could move different kinds of tests into separate files - each test class or file containing only a specific kind of test.&lt;/p&gt;
&lt;p&gt;You could also group these tests by kind in separate directories and namespaces.&lt;/p&gt;
&lt;p&gt;Third, the requirement to configure and bootstrap different kinds of tests drives the organization of your test code.&lt;/p&gt;
&lt;p&gt;When you have different kinds of tests, you may also realize that these tests require completely different test bootstrapping. You may have to set up a database, run database migrations, and seed the database for integration, functional, and end-to-end tests, but certainly not for unit tests.&lt;/p&gt;
&lt;p&gt;A single configuration file will only get you so far. You could use dedicated configuration files for different kinds of tests instead.&lt;/p&gt;
&lt;p&gt;Forth, the number of testing frameworks drives the organization of your test code.&lt;/p&gt;
&lt;p&gt;You could place all test classes and test files in a single directory and leave it up to your testing frameworks to decide whether they should consider a specific test class or file as a test case.&lt;/p&gt;
&lt;p&gt;You could also group tests by testing framework. A grouping like that could considerably simplify the configuration, maintenance, and speed up running of tests.&lt;/p&gt;
&lt;h2 id="content-options-for-organizing-test-code" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-options-for-organizing-test-code" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Options for organizing test code&lt;/h2&gt;
&lt;p&gt;As far as I can tell, you have the following options for organizing test code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#content-no-tests" title="No tests"&gt;no tests&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-the-same-file" title="Declaring test code in the same file"&gt;in the same file&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-the-same-directory" title="Declaring test code in the same directory"&gt;in the same directory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-a-subdirectory" title="Declaring test code in a subdirectory"&gt;in a subdirectory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-subdirectories" title="Declaring test code in subdirectories"&gt;in subdirectories&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-subdirectories-with-separate-configuration-files" title="Declaring test code in subdirectories with separate configuration files"&gt;in subdirectories with separate configuration files&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-a-separate-directory" title="Declaring test code in a separate directory"&gt;in a separate directory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-separate-directories" title="Declaring test code in separate directories"&gt;in separate directories&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-declaring-test-code-in-separate-directories-with-separate-configuration-files" title="Declaring test code in subdirectories with separate configuration files"&gt;in separate directories with separate configuration files&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-no-tests" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-no-tests" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;No tests&lt;/h2&gt;
&lt;p&gt;By far, this is the simplest solution for organizing test code.&lt;/p&gt;
&lt;p&gt;You have fewer lines of code, dependencies, and configuration files. You probably also have more bugs, downtimes, and sleepless nights.&lt;/p&gt;
&lt;p&gt;If you are comfortable with that, you can stop reading now.&lt;/p&gt;
&lt;h2 id="content-declaring-test-code-in-the-same-file" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-the-same-file" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in the same file&lt;/h2&gt;
&lt;p&gt;You can declare test code in the same file as production code, but unfortunately, I have not been able to configure &lt;code&gt;phpbench/phpbench&lt;/code&gt; or &lt;code&gt;phpunit/phpunit&lt;/code&gt; to find benchmarks or tests in the production files.&lt;/p&gt;
&lt;p&gt;Unless you have found a way to make this work, declaring test code in the same file is not an option. If you have found a way, I would like to hear about it.&lt;/p&gt;
&lt;div class="not-prose"&gt;
  &lt;a class="bg-fun-blue-700 hover:bg-fun-blue-800 border-2 border-fun-blue-700 hover:border-fun-blue-800 pt-2 pr-3 pb-2 pl-3 rounded-md font-bold text-base text-white hover:text-white visited:text-white whitespace-nowrap" href="mailto:hello%40localheinz.com?subject=Organizing%20test%20code%20in%20PHP%2C%20declaring%20test%20code%20in%20the%20same%20file" target="_blank" title="I have found a way!"&gt;&lt;i class="fa-solid fa-fw fa-envelope mr-2"&gt;&lt;/i&gt;I have found a way!&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="content-declaring-test-code-in-the-same-directory" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-the-same-directory" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in the same directory&lt;/h2&gt;
&lt;p&gt;You can declare test code in the same directory as production code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
├── src
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleBench&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Helper&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlankTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmptyTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
├── phpbench.json
└── phpunit.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/feature/same-directory" target="_blank" title="feature/same-directory of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; and a &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/pull/1" target="_blank" title="Pull request that declares test code in the same directory"&gt;pull request&lt;/a&gt; that declares test code in the &lt;code&gt;src/&lt;/code&gt; directory in &lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In my opinion, this approach has the following disadvantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It is hard to tell whether the code in the &lt;code&gt;src/&lt;/code&gt; directory is production or test code. How can you know whether a piece of code is only intended for use in test environments, for example, a test helper or a value object? How can you prevent developers from using test code in production?&lt;/li&gt;
&lt;li&gt;If you want to run unit, integration, functional, and end-to-end tests, how can you distinguish between these different kinds of tests?&lt;/li&gt;
&lt;li&gt;If you want to run unit, integration, functional, and end-to-end tests, how can you ensure appropriate test bootstrapping for each test? It is not necessary to bootstrap a database and run migrations for running unit tests, but it might be for integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;li&gt;Where are you going to place abstract test cases, helper traits, data providers, test doubles, and test fixtures if you need any?&lt;/li&gt;
&lt;li&gt;You must excessively configure exclusions in &lt;code&gt;composer.json&lt;/code&gt; to exclude test code from being autoloadable in production systems.&lt;/li&gt;
&lt;li&gt;You need to configure exclusions for test code in &lt;code&gt;infection.json&lt;/code&gt; to prevent &lt;code&gt;infection/infection&lt;/code&gt; from mutating test code and reporting false negatives from mutated test code.&lt;/li&gt;
&lt;li&gt;You need to configure exclusions for test code in &lt;code&gt;phpunit.xml&lt;/code&gt; to prevent &lt;code&gt;phpunit/phpunit&lt;/code&gt; from reporting test code as used in tests and not being covered by tests.&lt;/li&gt;
&lt;li&gt;If you are working on a package, you need to excessively configure exclusions for test code in &lt;code&gt;.gitattributes&lt;/code&gt; to prevent the unnecessary distribution of test code to users of your package.&lt;/li&gt;
&lt;li&gt;If you are working on a package, how are you dealing with users who expect a &lt;code&gt;test/&lt;/code&gt; or &lt;code&gt;tests/&lt;/code&gt; directory in the root of your project and skip the inspection and consideration of your package entirely because they suspect you have not written any tests?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you are interested in what others say about declaring test code in the same directory, check out the replies to this tweet by &lt;a href="https://github.com/brendt" target="_blank" title="@brendt on GitHub"&gt;Brent Roose&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-dnt="true"&gt;&lt;p lang="en" dir="ltr"&gt;An out of the box idea: why would/wouldn&amp;#39;t you store your tests alongside your source code, instead of keeping them completely separated? I can come up with a few pros and cons, but would like to hear your opinion.&lt;/p&gt;&amp;mdash; Brent (@brendt_gd) &lt;a href="https://twitter.com/brendt_gd/status/1438026483482648581?ref_src=twsrc%5Etfw"&gt;September 15, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;h2 id="content-declaring-test-code-in-a-subdirectory" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-a-subdirectory" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in a subdirectory&lt;/h2&gt;
&lt;p&gt;You can declare test code in a subdirectory of your production code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
├── src
│   ├── Test
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleBench&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Helper&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlankTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmptyTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
├── phpbench.json
└── phpunit.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/feature/subdirectory" target="_blank" title="feature/subdirectory of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; and a &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/pull/2" target="_blank" title="Pull request that declares test code in a subdirectory"&gt;pull request&lt;/a&gt; that declares test code in a subdirectory of the &lt;code&gt;src/&lt;/code&gt; directory in &lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You will often see this organization of test code in repositories split from mono-repositories. For an example, inspect &lt;a href="https://github.com/symfony/var-dumper/tree/v6.2.7" target="_blank" title="symfony/var-dumper on GitHub"&gt;&lt;code&gt;symfony/var-dumper&lt;/code&gt;&lt;/a&gt; which is a split from &lt;a href="https://github.com/symfony/symfony/tree/v6.2.7" target="_blank" title="symfony/symfony on GitHub"&gt;&lt;code&gt;symfony/symfony&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my opinion, this approach has the following advantages over &lt;a href="#content-declaring-test-code-in-the-same-directory" title="Declaring test code in the same directory"&gt;declaring test code in the same directory&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It is easier to tell whether code is production or test code.&lt;/li&gt;
&lt;li&gt;The configuration of exclusions in &lt;code&gt;composer.json&lt;/code&gt; to exclude test code from being autoloadable in production systems has become simpler.&lt;/li&gt;
&lt;li&gt;If you are working on a package, the configuration of exclusions for test code in &lt;code&gt;.gitattributes&lt;/code&gt; to prevent the unnecessary distribution of test code to users of your package has become simpler.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, this approach still has the following disadvantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You still are not distinguishing between unit, integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;li&gt;You are still not ensuring appropriate test bootstrapping for unit, integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;li&gt;You still need to configure exclusions for test code in &lt;code&gt;infection.json&lt;/code&gt; to prevent infection/infection from mutating test code and reporting false negatives from mutated test code.&lt;/li&gt;
&lt;li&gt;You still need to configure exclusions for test code in &lt;code&gt;phpunit.xml&lt;/code&gt; to prevent &lt;code&gt;phpunit/phpunit&lt;/code&gt; from reporting test code as used in tests and from reporting test code as not being covered by tests.&lt;/li&gt;
&lt;li&gt;You are still placing test fixtures and utilities along with test code.&lt;/li&gt;
&lt;li&gt;If you are working on a package, potential users may still skip the inspection and consideration of your package entirely because they can not see a &lt;code&gt;test/&lt;/code&gt; or &lt;code&gt;tests/&lt;/code&gt; directory in the root of your project and suspect you have not written any tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="content-declaring-test-code-in-subdirectories" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-subdirectories" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in subdirectories&lt;/h2&gt;
&lt;p&gt;You can declare test code in subdirectories of your production code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
├── src
│   ├── Test
│   │   ├── Performance
│   │   │   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleBench&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   ├── Unit
│   │   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlankTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   │   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmptyTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   └── Util
│   │       └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Helper&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
├── phpbench.json
└── phpunit.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/feature/subdirectories" target="_blank" title="feature/subdirectories of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; and a &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/pull/3" target="_blank" title="Pull request that declares test code in subdirectories"&gt;pull request&lt;/a&gt; that declares test code in subdirectories of the &lt;code&gt;src/&lt;/code&gt; directory in &lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In my opinion, this approach has the following advantages over &lt;a href="#content-declaring-test-code-in-a-subdirectory" title="Declaring test code in a subdirectory"&gt;declaring test code in a subdirectory&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You distinguish between unit, integration, functional, and end-to-end tests by grouping them in subdirectories and corresponding namespaces. Developers will understand where to place performance and unit tests more quickly.&lt;/li&gt;
&lt;li&gt;If you need test fixtures or utilities, they can all go into subdirectories and corresponding namespaces.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, this approach still has the following disadvantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You are still not ensuring appropriate test bootstrapping for unit, integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;li&gt;You still need to configure exclusions for test code in &lt;code&gt;infection.json&lt;/code&gt; to prevent &lt;code&gt;infection/infection&lt;/code&gt; from mutating test code and reporting false negatives from mutated test code.&lt;/li&gt;
&lt;li&gt;You still need to configure exclusions for test code in &lt;code&gt;phpunit.xml&lt;/code&gt; to prevent &lt;code&gt;phpunit/phpunit&lt;/code&gt; from reporting test code as used in tests and from reporting test code as not being covered by tests.&lt;/li&gt;
&lt;li&gt;If you are working on a package, potential users may still skip the inspection and consideration of your package entirely because they can not see a &lt;code&gt;test/&lt;/code&gt; or &lt;code&gt;tests/&lt;/code&gt; directory in the root of your project and suspect you have not written any tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="content-declaring-test-code-in-subdirectories-with-separate-configuration-files" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-subdirectories-with-separate-configuration-files" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in subdirectories with separate configuration files&lt;/h2&gt;
&lt;p&gt;You can declare test code in subdirectories of your production code with separate configuration files.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
└── src
    ├── Test
    │   ├── Performance
    │   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleBench&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   │   └── phpbench.json
    │   ├── Unit
    │   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   │   ├── phpunit.xml
    │   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlankTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   │   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmptyTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   └── Util
    │       └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Helper&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/feature/subdirectories-with-separate-configuration-files" target="_blank" title="feature/subdirectories-with-separate-configuration-files of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; and a &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/pull/4" target="_blank" title="Pull request that declares test code in subdirectories with separate configuration files"&gt;pull request&lt;/a&gt; that declares test code in subdirectories of the &lt;code&gt;src/&lt;/code&gt; directory with separate configuration files in &lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In my opinion, this approach has the following advantages over &lt;a href="#content-declaring-test-code-in-subdirectories" title="Declaring test code in subdirectories"&gt;declaring test code in subdirectories&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You ensure appropriate test bootstrapping for unit, integration, functional, and end-to-end tests by extracting dedicated configuration files.&lt;/li&gt;
&lt;li&gt;If you are working on a package, the configuration of exclusions for test code in &lt;code&gt;.gitattributes&lt;/code&gt; to prevent the unnecessary distribution of test code to users of your package has become even simpler.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, this approach still has the following disadvantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You still need to configure exclusions for test code in &lt;code&gt;infection.json&lt;/code&gt; to prevent infection/infection from mutating test code and reporting false negatives from mutated test code.&lt;/li&gt;
&lt;li&gt;You still need to configure exclusions for test code in &lt;code&gt;phpunit.xml&lt;/code&gt; to prevent &lt;code&gt;phpunit/phpunit&lt;/code&gt; from reporting test code as used in tests and from reporting test code as not being covered by tests, and that configuration has become even worse.&lt;/li&gt;
&lt;li&gt;If you are working on a package, potential users may still skip the inspection and consideration of your package entirely because they can not see a &lt;code&gt;test/&lt;/code&gt; or &lt;code&gt;tests/&lt;/code&gt; directory in the root of your project and suspect you have not written any tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="content-declaring-test-code-in-a-separate-directory" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-a-separate-directory" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in a separate directory&lt;/h2&gt;
&lt;p&gt;You can declare test code in a directory entirely separate from production code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
├── src
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
├── test
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleBench&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Helper&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlankTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmptyTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
├── phpbench.json
└── phpunit.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/feature/separate-directory" target="_blank" title="feature/separate-directory of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; and a &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/pull/5" target="_blank" title="Pull request that declares test code in a separate directory"&gt;pull request&lt;/a&gt; that declares test code in a separate &lt;code&gt;test/&lt;/code&gt; directory in &lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You can find documentation for this approach in the &lt;a href="https://docs.phpunit.de/en/10.0/organizing-tests.html#composing-a-test-suite-using-the-filesystem" target="_blank" title="PHPUnit: Composing a Test Suite Using the Filesystem"&gt;official PHPUnit documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my opinion, this approach has the following advantages over &lt;a href="#content-declaring-test-code-in-the-same-directory" title="Declaring test code in the same directory"&gt;declaring test code in the same directory&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It is trivial to tell whether code is production or test code.&lt;/li&gt;
&lt;li&gt;You do not need to configure exclusions in &lt;code&gt;composer.json&lt;/code&gt; to exclude test code from being autoloadable in production systems. Instead, you configure an autoloader for test code in the &lt;code&gt;autoload-dev&lt;/code&gt; section.&lt;/li&gt;
&lt;li&gt;You do not need to configure exclusions for test code in &lt;code&gt;infection.json&lt;/code&gt; for &lt;code&gt;infection/infection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You do not need to configure exclusions for test code in &lt;code&gt;phpunit.xml&lt;/code&gt; for &lt;code&gt;phpunit/phpunit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you are working on a package, you only need to configure exclusions for test configuration files and the &lt;code&gt;test/&lt;/code&gt; directory in &lt;code&gt;.gitattributes&lt;/code&gt; to prevent the unnecessary distribution of test code to users of your package.&lt;/li&gt;
&lt;li&gt;If you are working on a package, potential users will not skip the inspection and consideration of your package entirely as they can see a &lt;code&gt;test/&lt;/code&gt; or &lt;code&gt;tests&lt;/code&gt;/ directory in the root of your project.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, this approach still has the following disadvantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You still are not distinguishing between unit, integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;li&gt;You are still not ensuring appropriate test bootstrapping for unit, integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;li&gt;You are still placing test fixtures and utilities along with test code.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="content-declaring-test-code-in-separate-directories" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-separate-directories" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in separate directories&lt;/h2&gt;
&lt;p&gt;You can declare test code in subdirectories of a directory entirely separate from production code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
├── src
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
├── test
│   ├── Performance
│   │   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleBench&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── Unit
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlankTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   │   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmptyTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── Util
│       └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Helper&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
├── phpbench.json
└── phpunit.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/feature/separate-directories" target="_blank" title="feature/separate-directories of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; and a &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/pull/6" target="_blank" title="Pull request that declares test code in separate directories"&gt;pull request&lt;/a&gt; that declares test code in subdirectories of a separate &lt;code&gt;test/&lt;/code&gt; directory in &lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In my opinion, this approach has the following advantages over &lt;a href="#content-declaring-test-code-in-a-subdirectory" title="Declaring test code in a subdirectory"&gt;declaring test code in a subdirectory&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You distinguish between unit, integration, functional, and end-to-end tests by grouping them in subdirectories and corresponding namespaces. Developers will understand where to place performance and unit tests more quickly.&lt;/li&gt;
&lt;li&gt;If you need test fixtures or utilities, they can all go into subdirectories and corresponding namespaces.&lt;/li&gt;
&lt;li&gt;If you are working on a package, potential users will not skip the inspection and consideration of your package entirely, as they can see a &lt;code&gt;test/&lt;/code&gt; or &lt;code&gt;tests/&lt;/code&gt; directory in the root of your project.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, this approach still has the following disadvantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You are still not ensuring appropriate test bootstrapping for unit, integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="content-declaring-test-code-in-separate-directories-with-separate-configuration-files" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-declaring-test-code-in-separate-directories-with-separate-configuration-files" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Declaring test code in separate directories with separate configuration files&lt;/h2&gt;
&lt;p&gt;You can declare test code in subdirectories of a directory entirely separate from production code with separate configuration files.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-reasonml hljs reasonml" data-lang="reasonml"&gt;.
├── src
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Example&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlank&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
│   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
└── test
    ├── Performance
    │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleBench&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   └── phpbench.json
    ├── Unit
    │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ExampleTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   ├── phpunit.xml
    │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeBlankTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   ├── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmpty&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    │   └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;ValueCanNotBeEmptyTest&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
    └── Util
        └── &lt;span class="hljs-module-access"&gt;&lt;span class="hljs-module"&gt;&lt;span class="hljs-identifier"&gt;Helper&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;php
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can inspect the &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/tree/feature/separate-directories-with-separate-configuration-files" target="_blank" title="feature/separate-directories-with-separate-configuration-files of localheinz/organizing-test-code-in-php on GitHub"&gt;source code&lt;/a&gt; and a &lt;a href="https://github.com/localheinz/organizing-test-code-in-php/pull/7" target="_blank" title="Pull request that declares test code in separate directories with separate configuration files"&gt;pull request&lt;/a&gt; that declares test code in subdirectories of a separate &lt;code&gt;test/&lt;/code&gt; directory with separate configuration files in &lt;code&gt;localheinz/organizing-test-code-in-php&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In my opinion, this approach has the following advantages over &lt;a href="#content-declaring-test-code-in-separate-directories" title="Declaring test code in separate directories"&gt;declaring test code in separate directories&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You ensure appropriate test bootstrapping for unit, integration, functional, and end-to-end tests.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, this approach has the following disadvantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You may need to merge code coverage reports if you use different configuration files for unit, integration, function, and end-to-end tests and want to collect code coverage from multiple test runs.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="content-recommendation" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-recommendation" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Recommendation&lt;/h2&gt;
&lt;p&gt;I prefer to declare test code in separate directories with separate configuration files, but I am open to changing my mind and adapting to the circumstances.&lt;/p&gt;
&lt;p&gt;Do what works best for you!&lt;/p&gt;

</content></entry><entry><title>Documenting the system under test in PHPUnit</title><category term="maintenance"/><category term="php"/><category term="phpunit"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/02/22/documenting-the-system-under-test-in-phpunit/"/><id>https://localheinz.com/articles/2023/02/22/documenting-the-system-under-test-in-phpunit/</id><updated>2023-02-22T14:35:00+01:00</updated><content>&lt;h1&gt;
  Documenting the system under test in PHPUnit
&lt;/h1&gt;


&lt;p&gt;You are working in a code base you inherited from someone else or have not touched in years. Customers have repeatedly asked for a feature or demanded that you fix a bug. You already know which class or classes you need to change, but - you still have difficulty understanding how to use them and what they do.&lt;/p&gt;
&lt;p&gt;In your attempts to understand the code before you can change it, you &lt;a href="https://www.jetbrains.com/help/phpstorm/find-highlight-usages.html" target="_blank" title="PhpStorm: Search for usages"&gt;search for usages&lt;/a&gt; of that class. Luckily, there are a few references in test cases because, surprisingly, the code base has a few tests written in &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann&amp;#x2F;phpunit" target="_blank" title="sebastianbergmann/phpunit on GitHub"&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="content-identifying-the-system-under-test" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-identifying-the-system-under-test" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Identifying the system under test&lt;/h2&gt;
&lt;p&gt;Unfortunately, the &lt;a href="/articles/2023/03/03/organizing-test-code-in-php/" target="_blank" title="Organizing&amp;#x20;test&amp;#x20;code&amp;#x20;in&amp;#x20;PHP"&gt;organization of test code&lt;/a&gt; is unclear.&lt;/p&gt;
&lt;p&gt;You inspect test class names, but they do not reveal the &lt;a href="http://xunitpatterns.com/SUT.html" target="_blank" title="xUnit Patterns: System under test"&gt;system under test (SUT)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some test cases set up one or more objects in the &lt;a href="https://docs.phpunit.de/en/9.6/fixtures.html" target="_blank" title="PHPUnit: Fixtures"&gt;&lt;code&gt;setUp()&lt;/code&gt;&lt;/a&gt; and assign these objects to fields. Which of these fields represents the system under test? Or are these collaborators of the system under test?&lt;/p&gt;
&lt;p&gt;You inspect test method names, but they also do not reveal the system under test.&lt;/p&gt;
&lt;p&gt;You dive deeper into the test methods, hoping to find a clear structure, trying to identify the system under test. The test methods, however, are long and miss a clear structure. Does this section arrange something needed for the tests? Does this part act on the system under test? Here are a few assertions. Are these expectations yet? Hmm, there is more code after expectations, invoking methods on objects. Perhaps these expectations are pre-conditions only and have nothing to do with asserting the behavior of the system under test?&lt;/p&gt;
&lt;p&gt;Unfortunately, it's not the class you are interested in, so you continue your investigation.&lt;/p&gt;
&lt;p&gt;Finally, after rummaging in the test cases for a while, you have identified the system under test.&lt;/p&gt;
&lt;h2 id="content-documenting-the-system-under-test" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-documenting-the-system-under-test" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Documenting the system under test&lt;/h2&gt;
&lt;p&gt;Once you have identified in existing test cases what the system under test is, how can you document the system under test for yourself? Or if you use &lt;a href="http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd" target="_blank" title="Robert C. Martin:L The Three Rules of TDD"&gt;Test-Driven Development&lt;/a&gt;, how can you document the system under test for someone else?&lt;/p&gt;
&lt;p&gt;How can you prevent yourself and others from going through this time-consuming research again?&lt;/p&gt;
&lt;p&gt;As I suggested, you could discuss and establish naming conventions for test cases and test methods. But naming conventions are difficult to enforce and a matter of taste.&lt;/p&gt;
&lt;p&gt;As I pointed out, you could use the &lt;code&gt;setUp()&lt;/code&gt; method in test classes to set up the system under test and use explicit naming for fields referencing the system under test. But you may realize that the &lt;code&gt;setUp()&lt;/code&gt; method was never intended for setting up the system under test, but the environment. You may also conclude that tests are harder to understand if you must go back and forth between the &lt;code&gt;setUp()&lt;/code&gt; and test methods to understand what is happening.&lt;/p&gt;
&lt;p&gt;Aren't there better options?&lt;/p&gt;
&lt;h2 id="content-using-annotations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-using-annotations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Using annotations&lt;/h2&gt;
&lt;p&gt;If you have read DocBlocks before, you know &lt;a href="https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/link.html" target="_blank" title="phpDocumentor: @link"&gt;&lt;code&gt;@link&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/see.html" target="_blank" title="phpDocumentor: @link"&gt;&lt;code&gt;@see&lt;/code&gt;&lt;/a&gt; annotations. You can use these annotations to link to a URI, a class, a method, or a field.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@see&lt;/span&gt; \Ergebnis\Package\Example
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, these annotations are missing context.&lt;/p&gt;
&lt;p&gt;Therefore, since you already use &lt;code&gt;phpunit/phpunit&lt;/code&gt;, why not use annotations that &lt;code&gt;phpunit/phpunit&lt;/code&gt; understands and add context to that relation?&lt;/p&gt;
&lt;h3 id="content-covers-annotation" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-covers-annotation" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;@covers annotation&lt;/h3&gt;
&lt;p&gt;You can use the &lt;a href="https://docs.phpunit.de/en/9.6/annotations.html#covers" target="_blank" title="PHPUnit: @covers annotation"&gt;&lt;code&gt;@covers&lt;/code&gt;&lt;/a&gt; annotation on the class level to document the system under test.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@covers&lt;/span&gt; \Ergebnis\Package\Example
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use the &lt;code&gt;@covers&lt;/code&gt; annotation on the method level to document the system under test.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@covers&lt;/span&gt; \Ergebnis\Package\Example
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testFromStringRejectsEmptyValue&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@covers&lt;/span&gt; \Ergebnis\Package\Example::fromString
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testFromStringReturnsExample&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-coversdefaultclass-annotation" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-coversdefaultclass-annotation" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;@coversDefaultClass annotation&lt;/h3&gt;
&lt;p&gt;You can also use the &lt;a href="https://docs.phpunit.de/en/9.6/annotations.html#coversdefaultclass" target="_blank" title="PHPUnit: @coversDefaultClass annotation"&gt;&lt;code&gt;@coversDefaultClass&lt;/code&gt;&lt;/a&gt; annotation on the method level in combination with the &lt;code&gt;@covers&lt;/code&gt; annotation on the class level to avoid documenting the fully-qualified class name on the test method again.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@coversDefaultClass&lt;/span&gt; \Ergebnis\Package\Example
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testFromStringRejectsEmptyValue&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@covers&lt;/span&gt; ::fromString
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testFromStringReturnsExample&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-coversnothing-annotation" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-coversnothing-annotation" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;@coversNothing annotation&lt;/h3&gt;
&lt;p&gt;You can use the &lt;a href="https://docs.phpunit.de/en/9.6/annotations.html#coversnothing" target="_blank" title="PHPUnit: @coversNothing annotation"&gt;&lt;code&gt;@coversNothing&lt;/code&gt;&lt;/a&gt; annotation on the class level to document that there is no clear system under test.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@coversNothing&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use the &lt;code&gt;@coversNothing&lt;/code&gt; annotation on method level to indicate that there is no clear system under test.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@coversNothing&lt;/span&gt;
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testFromStringReturnsExample&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-using-attributes" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-using-attributes" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Using attributes&lt;/h2&gt;
&lt;p&gt;Since the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md#1000---2023-02-01" target="_blank" title="Changelog for phpunit/phpunit:10.0.0"&gt;&lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt;&lt;/a&gt;, you can use attributes instead of annotations.&lt;/p&gt;
&lt;h3 id="content-coversclass-attribute" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-coversclass-attribute" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;CoversClass attribute&lt;/h3&gt;
&lt;p&gt;You can use the &lt;a href="https://docs.phpunit.de/en/10.0/attributes.html#coversclass" target="_blank" title="PHPUnit: CoversClass attribute"&gt;&lt;code&gt;CoversClass&lt;/code&gt;&lt;/a&gt; attribute on the class level to document the system under test.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;#[Framework\CoversClass(Example::class)]&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-coversfunction-attribute" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-coversfunction-attribute" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;CoversFunction attribute&lt;/h3&gt;
&lt;p&gt;You can use the &lt;a href="https://docs.phpunit.de/en/10.0/attributes.html#coversfunction" target="_blank" title="PHPUnit: CoversFunction attribute"&gt;&lt;code&gt;CoversFunction&lt;/code&gt;&lt;/a&gt; attribute on the class level to document the system under test.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;#[Framework\Attributes\CoversFunction('dd')]&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-coversnothing-attribute" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-coversnothing-attribute" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;CoversNothing attribute&lt;/h3&gt;
&lt;p&gt;You can use the &lt;a href="https://docs.phpunit.de/en/10.0/attributes.html#coversnothing" target="_blank" title="PHPUnit: CoversNothing attribute"&gt;&lt;code&gt;CoversNothing&lt;/code&gt;&lt;/a&gt; attribute on the class level to document that there is no clear system under test.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Unit&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Package&lt;/span&gt;\&lt;span class="hljs-title"&gt;Example&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;#[Framework\Attributes\CoversNothing]&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-advantages-of-documenting-the-system-under-test" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-advantages-of-documenting-the-system-under-test" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Advantages of documenting the system under test&lt;/h2&gt;
&lt;p&gt;Documenting the system under test with annotations or attributes has the following advantages:&lt;/p&gt;
&lt;p&gt;If you add these annotations or attributes to test classes or test methods, &lt;code&gt;phpunit/phpunit&lt;/code&gt; will filter out code that is executed in a test but not intended to contribute to code coverage.&lt;/p&gt;
&lt;p&gt;This avoids unintentional code coverage: you execute code in tests, but the tests do not test and only use the code. Your actual test coverage may unintentionally be lower than what you think it is.&lt;/p&gt;
&lt;p&gt;Adding annotations or attributes not only documents the systems under test but also forces you to write dedicated tests for code that otherwise you only use in tests.&lt;/p&gt;
&lt;p&gt;If you add annotations or attributes to test classes or test methods, you can easily navigate from the test class or test method to the system under test.&lt;/p&gt;
&lt;p&gt;This is very useful when you want to see test and system under test at the same time. In &lt;a href="/articles/2019/10/14/test-to-the-left-production-to-the-right/" target="_blank" title="Test&amp;#x20;to&amp;#x20;the&amp;#x20;left,&amp;#x20;production&amp;#x20;to&amp;#x20;the&amp;#x20;right"&gt;Test to the left, production to the right&lt;/a&gt;, I suggest using a split view in PhpStorm. I find it very convenient to open the class that contains the test first, then &lt;a href="https://www.jetbrains.com/help/phpstorm/navigating-through-the-source-code.html#go_to_declaration" target="_blank" title="PhpStorm: Go to declaration and its type"&gt;quickly navigate to the system under test.&lt;/p&gt;
&lt;h2 id="content-disadvantages-of-documenting-the-system-under-test" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-disadvantages-of-documenting-the-system-under-test" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Disadvantages of documenting the system under test&lt;/h2&gt;
&lt;p&gt;An obvious disadvantage of documenting the system under test is that you need to add annotations or attributes to the test class.&lt;/p&gt;
&lt;h2 id="content-enforcing-the-documentation-of-the-system-under-test" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-enforcing-the-documentation-of-the-system-under-test" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Enforcing the documentation of the system under test&lt;/h2&gt;
&lt;p&gt;Now that you have decided to document the system under test using annotations or attributes, how can you enforce it?&lt;/p&gt;
&lt;p&gt;If you use annotations and &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;PHP-CS-Fixer&amp;#x2F;PHP-CS-Fixer" target="_blank" title="PHP-CS-Fixer/PHP-CS-Fixer on GitHub"&gt;&lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;&lt;/a&gt;, you can enable the &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.14.4/doc/rules/php_unit/php_unit_test_class_requires_covers.rst" target="_blank" title="friendsofphp/php-cs-fixer: Rule php_unit_test_class_requires_covers"&gt;&lt;code&gt;php_unit_test_class_requires_covers&lt;/code&gt;&lt;/a&gt; fixer.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$finder = PhpCsFixer\Finder::create()-&amp;gt;in(&lt;span class="hljs-keyword"&gt;__DIR__&lt;/span&gt;);

$config = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; PhpCsFixer\Config();

$config
    -&amp;gt;setFinder($finder)
    -&amp;gt;setRules([
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
        &lt;span class="hljs-string"&gt;'php_unit_test_class_requires_covers'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    ]);

&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $config;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fixer will add a &lt;code&gt;@coversNothing&lt;/code&gt; annotation to a test class when the test class does not have an existing &lt;code&gt;@covers&lt;/code&gt; or &lt;code&gt;@coversNothing&lt;/code&gt; annotation. While this fixer can not add a &lt;code&gt;@covers&lt;/code&gt; annotation to the test class for you, it can at least remind you that you need to add it yourself.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;phpunit/phpunit:9.0.0&lt;/code&gt; and below, you can set the &lt;a href="https://docs.phpunit.de/en/9.6/configuration.html#the-forcecoversannotation-attribute" target="_blank" title="PHPUnit: The forceCoversAnnotation attribute"&gt;&lt;code&gt;forceCoversAnnotation&lt;/code&gt;&lt;/a&gt; attribute to &lt;code&gt;true&lt;/code&gt; in &lt;code&gt;phpunit.xml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;forceCoversAnnotation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"true"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    // ...
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt; and above, you can set the &lt;a href="https://docs.phpunit.de/en/10.0/configuration.html#the-requirecoveragemetadata-attribute" target="_blank" title="PHPUnit: The requireCoverageMetadata attribute"&gt;&lt;code&gt;requireCoverageMetadata&lt;/code&gt;&lt;/a&gt; attribute to &lt;code&gt;true&lt;/code&gt; in &lt;code&gt;phpunit.xml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;requireCoverageMetadata&lt;/span&gt;=&lt;span class="hljs-string"&gt;"true"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    // ...
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run tests with &lt;code&gt;phpunit/phpunit&lt;/code&gt; and execute tests that do not have a &lt;code&gt;@covers&lt;/code&gt; or &lt;code&gt;@coversNothing&lt;/code&gt; annotation, or &lt;code&gt;CoversClass&lt;/code&gt;, &lt;code&gt;CoversFunction&lt;/code&gt; or &lt;code&gt;CoversNothing&lt;/code&gt; attribute, PHPUnit will mark these tests as risky.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-string"&gt;PHPUnit&lt;/span&gt; &lt;span class="hljs-number"&gt;10.0&lt;/span&gt;&lt;span class="hljs-number"&gt;.0&lt;/span&gt; &lt;span class="hljs-string"&gt;by&lt;/span&gt; &lt;span class="hljs-string"&gt;Sebastian&lt;/span&gt; &lt;span class="hljs-string"&gt;Bergmann&lt;/span&gt; &lt;span class="hljs-string"&gt;and&lt;/span&gt; &lt;span class="hljs-string"&gt;contributors.&lt;/span&gt;

&lt;span class="hljs-attr"&gt;Runtime:&lt;/span&gt;       &lt;span class="hljs-string"&gt;PHP&lt;/span&gt; &lt;span class="hljs-number"&gt;8.1&lt;/span&gt;&lt;span class="hljs-number"&gt;.15&lt;/span&gt;
&lt;span class="hljs-attr"&gt;Configuration:&lt;/span&gt; &lt;span class="hljs-string"&gt;test/Unit/phpunit.xml&lt;/span&gt;
&lt;span class="hljs-attr"&gt;Random Seed:&lt;/span&gt;   &lt;span class="hljs-number"&gt;1677075031&lt;/span&gt;

&lt;span class="hljs-string"&gt;R&lt;/span&gt;                                                                  &lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-string"&gt;/&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-string"&gt;(100%)&lt;/span&gt;

&lt;span class="hljs-attr"&gt;Time:&lt;/span&gt; &lt;span class="hljs-number"&gt;00&lt;/span&gt;&lt;span class="hljs-string"&gt;:00.010,&lt;/span&gt; &lt;span class="hljs-attr"&gt;Memory:&lt;/span&gt; &lt;span class="hljs-number"&gt;12.00&lt;/span&gt; &lt;span class="hljs-string"&gt;MB&lt;/span&gt;

&lt;span class="hljs-attr"&gt;There was 1 risky test:&lt;/span&gt;

&lt;span class="hljs-number"&gt;1&lt;/span&gt;&lt;span class="hljs-string"&gt;)&lt;/span&gt; &lt;span class="hljs-string"&gt;Ergebnis\Package\Test\Unit\ExampleTest::testFromStringReturnsExample&lt;/span&gt;
&lt;span class="hljs-string"&gt;This&lt;/span&gt; &lt;span class="hljs-string"&gt;test&lt;/span&gt; &lt;span class="hljs-string"&gt;does&lt;/span&gt; &lt;span class="hljs-string"&gt;not&lt;/span&gt; &lt;span class="hljs-string"&gt;define&lt;/span&gt; &lt;span class="hljs-string"&gt;a&lt;/span&gt; &lt;span class="hljs-string"&gt;code&lt;/span&gt; &lt;span class="hljs-string"&gt;coverage&lt;/span&gt; &lt;span class="hljs-string"&gt;target&lt;/span&gt; &lt;span class="hljs-string"&gt;but&lt;/span&gt; &lt;span class="hljs-string"&gt;is&lt;/span&gt; &lt;span class="hljs-string"&gt;expected&lt;/span&gt; &lt;span class="hljs-string"&gt;to&lt;/span&gt; &lt;span class="hljs-string"&gt;do&lt;/span&gt; &lt;span class="hljs-string"&gt;so&lt;/span&gt;

&lt;span class="hljs-string"&gt;/Users/am/Sites/ergebnis/php-package-template/test/Unit/ExampleTest.php:24&lt;/span&gt;

&lt;span class="hljs-string"&gt;OK,&lt;/span&gt; &lt;span class="hljs-string"&gt;but&lt;/span&gt; &lt;span class="hljs-string"&gt;some&lt;/span&gt; &lt;span class="hljs-string"&gt;tests&lt;/span&gt; &lt;span class="hljs-string"&gt;have&lt;/span&gt; &lt;span class="hljs-string"&gt;issues!&lt;/span&gt;
&lt;span class="hljs-attr"&gt;Tests:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt; &lt;span class="hljs-attr"&gt;Assertions:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt; &lt;span class="hljs-attr"&gt;Risky:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;&lt;span class="hljs-string"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-aiding-the-documentation-of-the-system-under-test" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-aiding-the-documentation-of-the-system-under-test" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Aiding the documentation of the system under test&lt;/h2&gt;
&lt;p&gt;How can you aid the documentation of the system under test?&lt;/p&gt;
&lt;p&gt;When you use PhpStorm to &lt;a href="https://www.jetbrains.com/help/phpstorm/using-phpunit-framework.html#generate_phpunit_test_for_a_class_in_a_separate_file" target="_blank" title="PhpStorm: Generate a PHPUnit test for a class"&gt;generate a test class&lt;/a&gt;, PhpStorm uses a &lt;a href="https://www.jetbrains.com/help/phpstorm/using-file-and-code-templates.html" target="_blank" title="PhpStorm: File templates"&gt;file template&lt;/a&gt; you can easily adjust.&lt;/p&gt;
&lt;p&gt;On macOS, press &lt;kbd&gt;CMD&lt;/kbd&gt; + &lt;kbd&gt;.&lt;/kbd&gt;. Go to &lt;em&gt;Editor&lt;/em&gt; and &lt;em&gt;File and Code Templates&lt;/em&gt;, and in the list of files, you will find file templates for PHPUnit test classes.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;phpunit\phpunit:9.0.0&lt;/code&gt; and below I use a template similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-comment"&gt;#if (${NAMESPACE})&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; ${&lt;span class="hljs-title"&gt;NAMESPACE&lt;/span&gt;};
&lt;span class="hljs-comment"&gt;#end&lt;/span&gt;

&lt;span class="hljs-comment"&gt;#if (${TESTED_NAME} &amp;amp;&amp;amp; ${NAMESPACE} &amp;amp;&amp;amp; !${TESTED_NAMESPACE})&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; ${&lt;span class="hljs-title"&gt;TESTED_NAME&lt;/span&gt;};
&lt;span class="hljs-comment"&gt;#elseif (${TESTED_NAME} &amp;amp;&amp;amp; ${TESTED_NAMESPACE} &amp;amp;&amp;amp; ${NAMESPACE} != ${TESTED_NAMESPACE})&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; ${&lt;span class="hljs-title"&gt;TESTED_NAMESPACE&lt;/span&gt;}\\${&lt;span class="hljs-title"&gt;TESTED_NAME&lt;/span&gt;};
&lt;span class="hljs-comment"&gt;#end&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
#if (${TESTED_NAME} &amp;amp;&amp;amp; ${NAMESPACE} &amp;amp;&amp;amp; !${TESTED_NAMESPACE})
 * &lt;span class="hljs-doctag"&gt;@covers&lt;/span&gt; \\${TESTED_NAME}
#elseif (${TESTED_NAME} &amp;amp;&amp;amp; ${TESTED_NAMESPACE} &amp;amp;&amp;amp; ${NAMESPACE} != ${TESTED_NAMESPACE})
 * &lt;span class="hljs-doctag"&gt;@covers&lt;/span&gt; \\${TESTED_NAMESPACE}\\${TESTED_NAME}
#end
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; $&lt;/span&gt;{NAME} extends Framework\TestCase
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-recommendations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-recommendations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;If you use &lt;code&gt;phpunit/phpunit:9.0.0&lt;/code&gt; and below, use &lt;a href="#content-covers-annotation" title="@covers annotation"&gt;&lt;code&gt;@covers&lt;/code&gt; annotations&lt;/a&gt; to document the system under test on the class level. If there is no clear system under test, use the &lt;a href="#content-covers-nothing-annotation" title="@coversNothing annotation"&gt;&lt;code&gt;@coversNothing annotation&lt;/code&gt;&lt;/a&gt; annotation on the class level. Do not document the system under test on the method level, and do not bother with documenting individual methods - this functionality is not present in &lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt;. Set the &lt;a href="https://docs.phpunit.de/en/9.6/configuration.html#the-forcecoversannotation-attribute" target="_blank" title="PHPUnit: The forceCoversAnnotation attribute"&gt;&lt;code&gt;forceCoversAnnotation&lt;/code&gt;&lt;/a&gt; attribute to &lt;code&gt;true&lt;/code&gt; in &lt;code&gt;phpunit.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you use &lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt; and above, use &lt;a href="#content-coversclass-attribute" title="CoversClass attribute"&gt;&lt;code&gt;CoversClass&lt;/code&gt; attributes&lt;/a&gt; to document the system under test on the class level. If there is no clear system under test, use the &lt;a href="#content-coversnothing-attribute" title="CoversNothing attribute"&gt;&lt;code&gt;CoversNothing&lt;/code&gt; attribute&lt;/a&gt; on the class level. Set the &lt;a href="https://docs.phpunit.de/en/10.0/configuration.html#the-requirecoveragemetadata-attribute" target="_blank" title="PHPUnit: The requireCoverageMetadata attribute"&gt;&lt;code&gt;requireCoverageMetadata&lt;/code&gt;&lt;/a&gt; attribute to &lt;code&gt;true&lt;/code&gt; in &lt;code&gt;phpunit.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you use &lt;code&gt;@covers&lt;/code&gt; annotations in &lt;code&gt;phpunit/phpunit&lt;/code&gt; and &lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;, enable the &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.14.4/doc/rules/php_unit/php_unit_test_class_requires_covers.rst" target="_blank" title="friendsofphp/php-cs-fixer: Rule php_unit_test_class_requires_covers"&gt;&lt;code&gt;php_unit_test_class_requires_covers&lt;/code&gt;&lt;/a&gt; fixer.&lt;/p&gt;

</content></entry><entry><title>Extending PHPUnit with its new event system</title><category term="php"/><category term="phpunit"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/02/14/extending-phpunit-with-its-new-event-system/"/><id>https://localheinz.com/articles/2023/02/14/extending-phpunit-with-its-new-event-system/</id><updated>2023-02-14T20:15:00+01:00</updated><content>&lt;h1&gt;
  Extending PHPUnit with its new event system
&lt;/h1&gt;


&lt;p&gt;You can extend &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann&amp;#x2F;phpunit" target="_blank" title="sebastianbergmann/phpunit on GitHub"&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt;&lt;/a&gt; by creating and using abstract test classes and traits - or by using the event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As one of the contributors to the new event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;, I will give you an overview of the iterations of the event systems of &lt;code&gt;phpunit/phpunit&lt;/code&gt;, show how you can migrate from any of the previous event systems to PHPUnit's new event system, and share insights on components and the events in the new event system of PHPUnit.&lt;/p&gt;
&lt;h2 id="content-tldr" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-tldr" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;Refer to the official &lt;code&gt;phpunit/phpunit&lt;/code&gt; documentation for learning &lt;a href="https://docs.phpunit.de/en/10.2/extending-phpunit.html" target="_blank" title="PHPUnit Manual: Extending PHPUnit"&gt;how to extend PHPUnit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Inspect &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;phpunit-slow-test-detector" target="_blank" title="ergebnis/phpunit-slow-test-detector on GitHub"&gt;&lt;code&gt;ergebnis/phpunit-slow-test-detector&lt;/code&gt;&lt;/a&gt;, an extension for detecting slow tests in PHPUnit.&lt;/p&gt;
&lt;p&gt;This extension, inspired by &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;johnkary&amp;#x2F;phpunit-speedtrap" target="_blank" title="johnkary/phpunit-speedtrap on GitHub"&gt;&lt;code&gt;johnkary/phpunit-speedtrap&lt;/code&gt;&lt;/a&gt;, uses the new event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;. I started working on this package on January 23, 2021, so it is probably the first extension using the new event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;. It provided helpful feedback during the development of the new event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="content-iterations-of-the-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-iterations-of-the-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Iterations of the event system&lt;/h2&gt;
&lt;p&gt;So far, the event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt; has seen three iterations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;a href="#content-test-listener-event-system" title=""&gt;test listener event system&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;a href="#content-hooks-event-system" title=""&gt;hooks event system&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;a href="#content-new-event-system" title=""&gt;new event system&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-test-listener-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-test-listener-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Test listener event system&lt;/h2&gt;
&lt;p&gt;The first iteration of the event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;, the test listener event system, has been available since the release of &lt;code&gt;phpunit/phpunit:0.3&lt;/code&gt; in 2002.&lt;/p&gt;
&lt;p&gt;The test listener event system has been deprecated since the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/8.0.0/ChangeLog-8.0.md#800---2019-02-01" target="_blank" title="Changelog for phpunit/phpunit:8.0.0"&gt;&lt;code&gt;phpunit/phpunit:8.0.0&lt;/code&gt;&lt;/a&gt; on February 1, 2019, and it has been removed with the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md#1000---2023-02-01" target="_blank" title="Changelog for phpunit/phpunit:10.0.0"&gt;&lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt;&lt;/a&gt; on February 3, 2023.&lt;/p&gt;
&lt;h3 id="content-events-in-the-test-listener-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-events-in-the-test-listener-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Events in the test listener event system&lt;/h3&gt;
&lt;p&gt;The test listener event system in &lt;a href="https://github.com/sebastianbergmann/phpunit/tree/9.6.3" target="_blank" title="phpunit/phpunit:9.6.3"&gt;&lt;code&gt;phpunit/phpunit:9.6.3&lt;/code&gt;&lt;/a&gt; ships with a single &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/9.6.3/src/Framework/TestListener.php" target="_blank" title="Testlistener interface of phpunit/phpunit:9.6.3"&gt;&lt;code&gt;TestListener&lt;/code&gt;&lt;/a&gt; interface that defines nine methods.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;TestListener&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addError&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addWarning&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        Warning $e,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addFailure&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        AssertionFailedError $e,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addIncompleteTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addRiskyTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addSkippedTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;startTestSuite&lt;/span&gt;&lt;span class="hljs-params"&gt;(TestSuite $suite)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;endTestSuite&lt;/span&gt;&lt;span class="hljs-params"&gt;(TestSuite $suite)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;startTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(Test $test)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;endTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each of the nine methods in the &lt;code&gt;TestListener&lt;/code&gt; interface corresponds to an event during a test run. When you have registered the test listener, &lt;code&gt;phpunit/phpunit&lt;/code&gt; will invoke these methods as it sees fit.&lt;/p&gt;
&lt;h3 id="content-implementing-an-extension-using-the-test-listener-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-implementing-an-extension-using-the-test-listener-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Implementing an extension using the test listener event system&lt;/h3&gt;
&lt;p&gt;To implement an extension using the test listener event system, you must create a class that implements the &lt;code&gt;TestListener&lt;/code&gt; interface.&lt;/p&gt;
&lt;p&gt;Here is an example of an implementation of the &lt;code&gt;TestListener&lt;/code&gt; interface from &lt;a href="https://github.com/johnkary/phpunit-speedtrap/blob/v4.0.1/src/SpeedTrapListener.php" target="_blank" title="SpeedTrapListener in johnkary/phpunit-speedtrap:4.0.1"&gt;&lt;code&gt;johnkary/phpunit-speedtrap:4.0.1&lt;/code&gt;&lt;/a&gt;, a popular extension that detects and reports slow tests - and can even fail a test run.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Listener&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;SpeedTrapListener&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestListener&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestListenerDefaultImplementation&lt;/span&gt;;

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;endTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Framework\Test $test,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;startTestSuite&lt;/span&gt;&lt;span class="hljs-params"&gt;(Framework\TestSuite $suite)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;endTestSuite&lt;/span&gt;&lt;span class="hljs-params"&gt;(Framework\TestSuite $suite)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test listener uses the &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/9.6.3/src/Framework/TestListenerDefaultImplementation.php" target="_blank" title="TestListenerDefaultImplementation trait of phpunit/phpunit:9.6.3"&gt;&lt;code&gt;TestListenerDefaultImplementation&lt;/code&gt;&lt;/a&gt; trait.&lt;/p&gt;
&lt;p&gt;The trait conveniently provides stubs for methods that the &lt;code&gt;TestListener&lt;/code&gt; interface defines. If you import the trait into your test listener, you can focus on implementing methods for events that concern you - and ignore the rest.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;trait&lt;/span&gt; TestListenerDefaultImplementation
{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addError&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addWarning&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        Warning $e,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addFailure&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        AssertionFailedError $e,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addIncompleteTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addRiskyTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;addSkippedTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        \Throwable $t,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;startTestSuite&lt;/span&gt;&lt;span class="hljs-params"&gt;(TestSuite $suite)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;endTestSuite&lt;/span&gt;&lt;span class="hljs-params"&gt;(TestSuite $suite)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;startTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(Test $test)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;endTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Test $test,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To allow the configuration of a test listener, you need to add a constructor to your test listener:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Listener&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;SpeedTrapListener&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;TestListener&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestListenerDefaultImplementation&lt;/span&gt;;

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(array $options = [])&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-registering-an-extension-using-the-test-listener-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-registering-an-extension-using-the-test-listener-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Registering an extension using the test listener event system&lt;/h3&gt;
&lt;p&gt;To register an extension using the test listener event system, you need to configure it using the &lt;a href="https://phpunit.readthedocs.io/en/9.6/configuration.html#the-listeners-element" target="_blank" title="The listeners element of the PHPUnit XML configuration file"&gt;&lt;code&gt;&amp;lt;listeners&amp;gt;&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://phpunit.readthedocs.io/en/9.6/configuration.html#the-listener-element" target="_blank" title="The listener element of the PHPUnit XML configuration file"&gt;&lt;code&gt;&amp;lt;listener&amp;gt;&lt;/code&gt;&lt;/a&gt; elements of &lt;a href="https://phpunit.readthedocs.io/en/9.6/configuration.html" target="_blank" title="The PHPUnit XML configuration file"&gt;&lt;code&gt;phpunit.xml&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following configuration registers &lt;code&gt;johnkary/phpunit-speedtrap:4.0.1&lt;/code&gt; with default options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;listeners&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;listener&lt;/span&gt; &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;"JohnKary\PHPUnit\Listener\SpeedTrapListener"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;listeners&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following configuration registers &lt;code&gt;johnkary/phpunit-speedtrap:4.0.1&lt;/code&gt; with custom options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;listeners&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;listener&lt;/span&gt; &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;"JohnKary\PHPUnit\Listener\SpeedTrapListener"&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;arguments&lt;/span&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;array&lt;/span&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;element&lt;/span&gt; &lt;span class="hljs-attr"&gt;key&lt;/span&gt;=&lt;span class="hljs-string"&gt;"slowThreshold"&lt;/span&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;integer&lt;/span&gt;&amp;gt;&lt;/span&gt;250&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;integer&lt;/span&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;element&lt;/span&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;element&lt;/span&gt; &lt;span class="hljs-attr"&gt;key&lt;/span&gt;=&lt;span class="hljs-string"&gt;"stopOnSlow"&lt;/span&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;boolean&lt;/span&gt;&amp;gt;&lt;/span&gt;true&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;boolean&lt;/span&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;element&lt;/span&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;array&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;arguments&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;listener&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;listeners&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt; provides facilities for casting values from the &lt;code&gt;&amp;lt;arguments&amp;gt;&lt;/code&gt; element. &lt;code&gt;phpunit/phpunit&lt;/code&gt; will pass these values to the constructor of the test listener.&lt;/p&gt;
&lt;p&gt;In the example above, &lt;code&gt;phpunit/phpunit&lt;/code&gt; will pass the following value to the constructor of &lt;code&gt;SpeedTrapListener&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;[
    &lt;span class="hljs-string"&gt;'slowThreshold'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-number"&gt;250&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;'stopOnSlow'&lt;/span&gt;    =&amp;gt; &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-disadvantages-of-the-test-listener-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-disadvantages-of-the-test-listener-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Disadvantages of the test listener event system&lt;/h3&gt;
&lt;p&gt;As you have seen above, the test listener system has a few disadvantages.&lt;/p&gt;
&lt;p&gt;To implement a test listener, you need to create a class that implements all methods of the &lt;code&gt;TestListener&lt;/code&gt; interface, even when you are only interested in a single or a subset of all available events. This requirement violates the interface segregation principle.&lt;/p&gt;
&lt;p&gt;The methods of the test listener accept mutable implementations of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/9.6.3/src/Framework/Test.php" target="_blank" title="Test interface of phpunit/phpunit:9.6.3"&gt;&lt;code&gt;Test&lt;/code&gt;&lt;/a&gt; and mutable instances of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/9.6.3/src/Framework/TestSuite.php" target="_blank" title="TestSuite of phpunit/phpunit:9.6.3"&gt;&lt;code&gt;TestSuite&lt;/code&gt;&lt;/a&gt;. Passing around mutable objects has an undesirable effect: a test listener can modify the outcome of test runs by manipulating these mutable objects.&lt;/p&gt;
&lt;p&gt;Examples of test listener implementations that modify the outcome of test runs include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/hugues-m/phpunit-vw/blob/1.5/src/HMLB/PHPUnit/Listener/VWListener.php#L56-L58" target="_blank" title="VWListener of hmlb/phpunit-vw:1.5"&gt;&lt;code&gt;hmlb/phpunit-vw&lt;/code&gt;&lt;/a&gt;, which makes failing tests pass when it has detected that it runs in a continuous integration system&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/johnkary/phpunit-speedtrap/blob/v4.0.1/src/SpeedTrapListener.php#L144-L146" target="_blank" title="SpeedTrapListener of johnkary/phpunit-speedtrap:4.0.1"&gt;&lt;code&gt;johnkary/phpunit-speedtrap:4.0.1&lt;/code&gt;&lt;/a&gt;, which optionally stops a test run when it has detected a slow test&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="content-deprecation-and-removal-of-the-test-listener-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-deprecation-and-removal-of-the-test-listener-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Deprecation and removal of the test listener event system&lt;/h3&gt;
&lt;p&gt;The test listener event system has been deprecated since the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/8.0.0/ChangeLog-8.0.md#800---2019-02-01" target="_blank" title="Changelog for phpunit/phpunit:8.0.0"&gt;&lt;code&gt;phpunit/phpunit:8.0.0&lt;/code&gt;&lt;/a&gt; on February 1, 2019. It has been removed with the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md#1000---2023-02-01" target="_blank" title="Changelog for phpunit/phpunit:10.0.0"&gt;&lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt;&lt;/a&gt; on February 3, 2023.&lt;/p&gt;
&lt;h2 id="content-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Hooks event system&lt;/h2&gt;
&lt;p&gt;The second iteration of the event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;, the hooks event system, has been available since the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/7.1.0/ChangeLog-7.1.md#710---2018-04-06" target="_blank" title="Changelog for phpunit/phpunit:7.1.0"&gt;&lt;code&gt;phpunit/phpunit:7.1.0&lt;/code&gt;&lt;/a&gt; on April 6, 2018.&lt;/p&gt;
&lt;p&gt;The hooks event system has been deprecated since the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/8.5.23/ChangeLog-8.5.md#8523---2022-01-21" target="_blank" title="Changelog for phpunit/phpunit:8.5.23"&gt;&lt;code&gt;phpunit/phpunit:8.5.23&lt;/code&gt;&lt;/a&gt; on February 1, 2022. It has been removed with the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md#1000---2023-02-01" target="_blank" title="Changelog for phpunit/phpunit:10.0.0"&gt;&lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt;&lt;/a&gt; on February 3, 2023.&lt;/p&gt;
&lt;h3 id="content-events-in-the-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-events-in-the-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Events in the hooks event system&lt;/h3&gt;
&lt;p&gt;The hooks event system in &lt;a href="https://github.com/sebastianbergmann/phpunit/tree/9.6.3" target="_blank" title="phpunit/phpunit:9.6.3"&gt;&lt;code&gt;phpunit/phpunit:9.6.3&lt;/code&gt;&lt;/a&gt; ships with eleven segregated interfaces, each of which defines a single method.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;BeforeFirstTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Hook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeBeforeFirstTest&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;BeforeTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeBeforeTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(string $test)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterIncompleteTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterIncompleteTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        string $message,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterRiskyTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterRiskyTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        string $message,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterSkippedTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterSkippedTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        string $message,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterSuccessfulTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterSuccessfulTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterTestErrorHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterTestError&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        string $message,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterTestFailureHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterTestFailure&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        string $message,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterTestWarningHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;TestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterTestWarning&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        string $message,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AfterLastTestHook&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Hook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterLastTest&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unsurprisingly, each method corresponds to an event during a test run. When you have registered an extension with the hooks event system, &lt;code&gt;phpunit/phpunit&lt;/code&gt; will invoke these methods as it sees fit.&lt;/p&gt;
&lt;h3 id="content-implementing-an-extension-using-the-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-implementing-an-extension-using-the-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Implementing an extension using the hooks event system&lt;/h3&gt;
&lt;p&gt;To implement an extension using the hooks event system, you must create one or more classes that implement one or more hook interfaces for events of your concern.&lt;/p&gt;
&lt;p&gt;Here is an example of an implementation of an extension using the hooks event system from &lt;a href="https://github.com/johnkary/phpunit-speedtrap/blob/a05623c3e211a857a546ae5bab19dca8d3d378f9/src/SpeedTrap.php" target="_blank" title="SpeedTrap extension in johnkary/phpunit-speedtrap:dev-master#a05623c"&gt;&lt;code&gt;johnkary/phpunit-speedtrap:dev-master#a05623c&lt;/code&gt;&lt;/a&gt; (not yet released at the time of writing):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;SpeedTrap&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt;
    &lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;AfterSuccessfulTestHook&lt;/span&gt;,
    &lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;BeforeFirstTestHook&lt;/span&gt;,
    &lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;AfterLastTestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterSuccessfulTest&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        string $test,
        float $time
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeBeforeFirstTest&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;executeAfterLastTest&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To allow the configuration of an extension using the hooks event system, you need to add a constructor to your extension:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;SpeedTrap&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt;
    &lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;AfterSuccessfulTestHook&lt;/span&gt;,
    &lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;BeforeFirstTestHook&lt;/span&gt;,
    &lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;AfterLastTestHook&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(array $options = [])&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-registering-an-extension-using-the-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-registering-an-extension-using-the-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Registering an extension using the hooks event system&lt;/h3&gt;
&lt;p&gt;To register an extension using the hooks event system, you need to configure it using the &lt;a href="https://phpunit.readthedocs.io/en/9.6/configuration.html#the-extensions-element" target="_blank" title="The extensions element of the PHPUnit XML configuration file"&gt;&lt;code&gt;&amp;lt;extensions&amp;gt;&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://phpunit.readthedocs.io/en/9.6/configuration.html#the-extension-element" target="_blank" title="The extension element of the PHPUnit XML configuration file"&gt;&lt;code&gt;&amp;lt;extension&amp;gt;&lt;/code&gt;&lt;/a&gt; elements of &lt;a href="https://phpunit.readthedocs.io/en/9.6/configuration.html" target="_blank" title="The PHPUnit XML configuration file"&gt;&lt;code&gt;phpunit.xml&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following example registers &lt;code&gt;johnkary/phpunit-speedtrap:dev-master#a05623c&lt;/code&gt; with default options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;extension&lt;/span&gt; &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;"JohnKary\PHPUnit\Extension\SpeedTrap"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following example registers &lt;code&gt;johnkary/phpunit-speedtrap:dev-master#a05623c&lt;/code&gt; with custom options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;listener&lt;/span&gt; &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;"JohnKary\PHPUnit\Extension\SpeedTrap"&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;extension&lt;/span&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;array&lt;/span&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;element&lt;/span&gt; &lt;span class="hljs-attr"&gt;key&lt;/span&gt;=&lt;span class="hljs-string"&gt;"slowThreshold"&lt;/span&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;integer&lt;/span&gt;&amp;gt;&lt;/span&gt;250&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;integer&lt;/span&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;element&lt;/span&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;array&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;extension&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;listener&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt; provides facilities for casting values from the &lt;code&gt;&amp;lt;arguments&amp;gt;&lt;/code&gt; element. &lt;code&gt;phpunit/phpunit&lt;/code&gt; will pass these values to the constructor of the extension.&lt;/p&gt;
&lt;p&gt;In the example above, &lt;code&gt;phpunit/phpunit&lt;/code&gt; will pass the following value as &lt;code&gt;$options&lt;/code&gt; argument to the constructor of SpeedTrapListener:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;[
    &lt;span class="hljs-string"&gt;'slowThreshold'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-number"&gt;250&lt;/span&gt;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-migrating-an-extension-from-the-test-listener-event-system-to-the-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-migrating-an-extension-from-the-test-listener-event-system-to-the-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Migrating an extension from the test listener event system to the hooks event system&lt;/h3&gt;
&lt;p&gt;To migrate an extension from the test listener to the hooks event system, you need to require and support one ore more of &lt;code&gt;phpunit/phpunit:^7.1.0&lt;/code&gt;, &lt;code&gt;phpunit/phpunit:^8.0.0&lt;/code&gt;, and &lt;code&gt;phpunit/phpunit:^9.0.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, instead of implementing the &lt;code&gt;TestListener&lt;/code&gt; interface, you need to change the extension to implement one or more interfaces from the hooks event system. Consequently, you must remove the import of the &lt;code&gt;TestListenerDefaultImplementation&lt;/code&gt; trait.&lt;/p&gt;
&lt;p&gt;Furthermore, since the method signatures of the hook interfaces are different from the method signatures of the &lt;code&gt;TestListener&lt;/code&gt; interface, you need to change the extension so it can work with scalars instead of mutable objects.&lt;/p&gt;
&lt;p&gt;Moreover, since the extension can not modify test outcomes anymore, you need to remove functionality that attempts to do so. If the extension's purpose is solely the modification of test outcomes, it will not work with the test listener event system.&lt;/p&gt;
&lt;p&gt;Finally, instead of using the &lt;code&gt;&amp;lt;listeners&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;listener&amp;gt;&lt;/code&gt; elements, you need to instruct users of your extension to use the &lt;code&gt;&amp;lt;extensions&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;extension&amp;gt;&lt;/code&gt; elements of &lt;code&gt;phpunit.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is a &lt;a href="https://github.com/johnkary/phpunit-speedtrap/pull/83" target="_blank" title="Use PHPUnit Extension system instead of Listener"&gt;pull request by John Kary&lt;/a&gt;, migrating &lt;code&gt;johnkary/phpunit-speedtrap&lt;/code&gt; from the test listener event system to the hooks event system.&lt;/p&gt;
&lt;h3 id="content-advantages-of-the-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-advantages-of-the-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Advantages of the hooks event system&lt;/h3&gt;
&lt;p&gt;The hooks event system has a few advantages compared to the test listener event system.&lt;/p&gt;
&lt;p&gt;To implement hooks, you no longer need to create a class that implements methods for events that are not of your concern.&lt;/p&gt;
&lt;p&gt;The methods of the hooks interfaces no longer accept mutable objects, so it is no longer possible to modify the outcome of a test run.&lt;/p&gt;
&lt;h3 id="content-disadvantages-of-the-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-disadvantages-of-the-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Disadvantages of the hooks event system&lt;/h3&gt;
&lt;p&gt;However, the hooks event system also has at least one disadvantage compared to the test listener event system.&lt;/p&gt;
&lt;p&gt;Since the methods of the hooks interfaces only accept scalars, you have to do more work in an extension using the hooks event system yourself.&lt;/p&gt;
&lt;p&gt;Instead of passing around scalars, the event system could benefit from additional test-related information and value objects.&lt;/p&gt;
&lt;h3 id="content-deprecation-and-removal-of-the-hooks-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-deprecation-and-removal-of-the-hooks-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Deprecation and removal of the hooks event system&lt;/h3&gt;
&lt;p&gt;The hooks event system has been deprecated since the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/8.5.23/ChangeLog-8.5.md#8523---2022-01-21" target="_blank" title="Changelog for phpunit/phpunit:8.5.23"&gt;&lt;code&gt;phpunit/phpunit:8.5.23&lt;/code&gt;&lt;/a&gt; on February 1, 2022. It has been removed with the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md#1000---2023-02-01" target="_blank" title="Changelog for phpunit/phpunit:10.0.0"&gt;&lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt;&lt;/a&gt; on February 3, 2023.&lt;/p&gt;
&lt;h2 id="content-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;New event system&lt;/h2&gt;
&lt;p&gt;The third and current iteration of the event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;, the new event system, has been a long time coming and is finally available since the release of &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md#1000---2023-02-01" target="_blank" title="Changelog for phpunit/phpunit:10.0.0"&gt;&lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt;&lt;/a&gt; on February 3, 2023.&lt;/p&gt;
&lt;p&gt;Initial work on the new event system started when the &lt;code&gt;phpunit/phpunit&lt;/code&gt; development team - &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann" target="_blank" title="Sebastian&amp;#x20;Bergmann&amp;#x20;on&amp;#x20;GitHub"&gt;Sebastian Bergmann&lt;/a&gt;, &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;theseer" target="_blank" title="Arne&amp;#x20;Blankerts&amp;#x20;on&amp;#x20;GitHub"&gt;Arne Blankerts&lt;/a&gt;, &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;spriebsch" target="_blank" title="Stefan&amp;#x20;Priebsch&amp;#x20;on&amp;#x20;GitHub"&gt;Stefan Priebsch&lt;/a&gt;, &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;epdenouden" target="_blank" title="Ewout&amp;#x20;Pieter&amp;#x20;den&amp;#x20;Ouden&amp;#x20;on&amp;#x20;GitHub"&gt;Ewout Pieter den Ouden&lt;/a&gt;, and I - attended the &lt;a href="https://github.com/eufossa/eu-hackathon-2019/blob/master/achievements/phpunit.md" target="_blank" title="EU-FOSSA hackaton achievements for PHPUnit"&gt;EU-FOSSA hackathon&lt;/a&gt; from October 5 to October 6, 2019, in Brussels.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Arne Blankerts, Ewout Pieter den Ouden, and Andreas Möller at the EU-FOSSA 2019 hackathon" class="figure-img img-fluid" src="/dist/img/article/2023/02/14/extending-phpunit-with-its-new-event-system/68747470.jpg?c6b293c" title="Arne Blankerts, Ewout Pieter den Ouden, and Andreas Möller at the EU-FOSSA 2019 hackathon"&gt;
  &lt;figcaption&gt;
    &lt;p&gt;
      Arne Blankerts, Andreas Möller, and Ewout Pieter den Ouden (from left to right) at the EU-FOSSA 2019 hackathon. Not pictured: Sebastian Bergmann (who took the picture) and Stefan Priebsch.
    &lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Find out more about the release of &lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt; in the &lt;a href="https://phpunit.de/announcements/phpunit-10.html" target="PHPUnit 10" title=""&gt;official release announcement&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="content-components-in-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-components-in-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Components in the new event system&lt;/h3&gt;
&lt;p&gt;The new event system in &lt;a href="https://github.com/sebastianbergmann/phpunit/tree/10.0.7" target="_blank" title="phpunit/phpunit:10.0.7"&gt;&lt;code&gt;phpunit/phpunit:10.0.7&lt;/code&gt;&lt;/a&gt; ships with an event &lt;code&gt;Facade&lt;/code&gt;, an event &lt;code&gt;Emitter&lt;/code&gt; interface, a &lt;code&gt;DispatchingEmitter&lt;/code&gt;, a &lt;code&gt;Dispatcher&lt;/code&gt; interface, a &lt;code&gt;SubscribableDispatcher&lt;/code&gt; interface, a few implementations of the &lt;code&gt;Dispatcher&lt;/code&gt; and &lt;code&gt;SubscribableDispatcher&lt;/code&gt; interfaces, and an extension &lt;code&gt;Facade&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The event &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Facade.php" target="_blank" title="Event Facade of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Facade&lt;/code&gt;&lt;/a&gt; provides methods that allow registering subscribers and tracers and, most importantly, registers known events, and, most importantly, gives access to the event &lt;code&gt;Emitter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The event &lt;code&gt;Facade&lt;/code&gt; is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Facade&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerSubscribers&lt;/span&gt;&lt;span class="hljs-params"&gt;(Subscriber ...$subscribers)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerSubscriber&lt;/span&gt;&lt;span class="hljs-params"&gt;(Subscriber $subscriber)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerTracer&lt;/span&gt;&lt;span class="hljs-params"&gt;(Tracer\Tracer $tracer)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;emitter&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;Emitter&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The event &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Emitter/Emitter.php" target="_blank" title="Event Emitter of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Emitter&lt;/code&gt;&lt;/a&gt; interface provides methods that allow emitting events.&lt;/p&gt;
&lt;p&gt;The event &lt;code&gt;Emitter&lt;/code&gt; is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;Emitter&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;applicationStarted&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testAssertionFailed&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        mixed $value,
        Framework\Constraint\Constraint $constraint,
        string $message
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;applicationFinished&lt;/span&gt;&lt;span class="hljs-params"&gt;(int $shellExitCode)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Emitter/DispatchingEmitter.php" target="_blank" title="DispatchingEmitter of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;DispatchingEmitter&lt;/code&gt;&lt;/a&gt; implements the event &lt;code&gt;Emitter&lt;/code&gt; interface, creates events, and uses a &lt;code&gt;Dispatcher&lt;/code&gt; to dispatch these events.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;DispatchingEmitter&lt;/code&gt; is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;DispatchingEmitter&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Emitter&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;applicationStarted&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testAssertionFailed&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        mixed $value,
        Framework\Constraint\Constraint $constraint,
        string $message
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;applicationFinished&lt;/span&gt;&lt;span class="hljs-params"&gt;(int $shellExitCode)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Dispatcher/Dispatcher.php" target="_blank" title="Dispatcher of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Dispatcher&lt;/code&gt;&lt;/a&gt; defines a single method for dispatching an event.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Dispatcher&lt;/code&gt; interface is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;Dispatcher&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;dispatch&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Dispatcher/CollectingDispatcher.php" target="_blank" title="CollectingDispatcher of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;CollectingDispatcher&lt;/code&gt;&lt;/a&gt; implements the &lt;code&gt;Dispatcher&lt;/code&gt; interface and collects events.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;CollectingDispatcher&lt;/code&gt; is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;CollectingDispatcher&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Dispatcher&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;dispatch&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;flush&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;EventCollection&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Dispatcher/SubscribableDispatcher.php" target="_blank" title="SubscribableDispatcher of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;SubscribableDispatcher&lt;/code&gt;&lt;/a&gt; interface extends the &lt;code&gt;Dispatcher&lt;/code&gt; interface and defines methods for registering subscribers and tracers.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;SubscribableDispatcher&lt;/code&gt; interface is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;SubscribableDispatcher&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Dispatcher&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerSubscriber&lt;/span&gt;&lt;span class="hljs-params"&gt;(Subscriber $subscriber)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerTracer&lt;/span&gt;&lt;span class="hljs-params"&gt;(Tracer\Tracer $tracer)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Dispatcher/DirectDispatcher.php" target="_blank" title="DirectDispatcher of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;DirectDispatcher&lt;/code&gt;&lt;/a&gt; implements the &lt;code&gt;SubscribableDispatcher&lt;/code&gt; interface and immediately traces an event with all tracers, and notifies all subscribers that subscribe to an event of that event.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;DirectDispatcher&lt;/code&gt; is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;DirectDispatcher&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;SubscribableDispatcher&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerTracer&lt;/span&gt;&lt;span class="hljs-params"&gt;(Tracer\Tracer $tracer)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerSubscriber&lt;/span&gt;&lt;span class="hljs-params"&gt;(Subscriber $subscriber)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;dispatch&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Dispatcher/DeferringDispatcher.php" target="_blank" title="DirectDispatcher of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;DeferringDispatcher&lt;/code&gt;&lt;/a&gt; implements the &lt;code&gt;SubscribableDispatcher&lt;/code&gt; interface and defers tracing events and notifying subscribers of an event until it is flushed.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;DeferringDispatcher&lt;/code&gt; is for internal use only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@internal&lt;/span&gt;
 */&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;DeferringDispatcher&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;SubscribableDispatcher&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerTracer&lt;/span&gt;&lt;span class="hljs-params"&gt;(Tracer\Tracer $tracer)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerSubscriber&lt;/span&gt;&lt;span class="hljs-params"&gt;(Subscriber $subscriber)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;dispatch&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;flush&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The extension &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Runner/Extension/Facade.php" target="_blank" title="Extension Facade of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Facade&lt;/code&gt;&lt;/a&gt; provides methods for registering subscribers and traces.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Facade&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerSubscribers&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\Subscriber ...$subscribers)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerSubscriber&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\Subscriber $subscriber)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;registerTracer&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\Tracer $tracer)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-events-in-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-events-in-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Events in the new event system&lt;/h3&gt;
&lt;p&gt;In addition to the components above, the new event system in &lt;a href="https://github.com/sebastianbergmann/phpunit/tree/10.0.7" target="_blank" title="phpunit/phpunit:10.0.7"&gt;&lt;code&gt;phpunit/phpunit:10.0.7&lt;/code&gt;&lt;/a&gt; ships with an &lt;code&gt;Event&lt;/code&gt; interface and sixty-three concrete events, a &lt;code&gt;Subscriber&lt;/code&gt; interface and sixty-three event subscriber interfaces, a &lt;code&gt;Tracer&lt;/code&gt; interface, and an &lt;code&gt;Extension&lt;/code&gt; interface.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Events/Event.php" target="_blank" title="Event interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Event&lt;/code&gt;&lt;/a&gt; interface declares methods that give access to basic telemetry information, such as duration and memory usage, and a &lt;code&gt;string&lt;/code&gt; representation of an event.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;Event&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;telemetryInfo&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;Telemetry&lt;/span&gt;\&lt;span class="hljs-title"&gt;Info&lt;/span&gt;&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;asString&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All sixty-three events implement the &lt;code&gt;Event&lt;/code&gt; interface and provide access to test-related information available when the event occurred and of interest to &lt;code&gt;phpunit/phpunit&lt;/code&gt; internally and potential users of the new event system.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Application\Started&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Application\Finished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\MarkedIncomplete&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\AfterLastTestMethodCalled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\AfterLastTestMethodFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\AfterTestMethodCalled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\AfterTestMethodFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\AssertionSucceeded&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\AssertionFailed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\BeforeFirstTestMethodCalled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\BeforeFirstTestMethodErrored&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\BeforeFirstTestMethodFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\BeforeTestMethodCalled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\BeforeTestMethodFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\ComparatorRegistered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\ConsideredRisky&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\DeprecationTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\Errored&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\ErrorTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\Failed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\Finished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\NoticeTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\Passed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PhpDeprecationTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PhpNoticeTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PhpunitDeprecationTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PhpunitErrorTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PhpunitWarningTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PhpWarningTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PostConditionCalled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PostConditionFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PreConditionCalled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PreConditionFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PreparationStarted&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\Prepared&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\Skipped&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\WarningTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\MockObjectCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\MockObjectForAbstractClassCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\MockObjectForIntersectionOfInterfacesCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\MockObjectForTraitCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\MockObjectFromWsdlCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\PartialMockObjectCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\TestProxyCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\TestStubCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\Test\TestStubForIntersectionOfInterfacesCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\BootstrapFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\Configured&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\EventFacadeSealed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\ExecutionFinished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\ExecutionStarted&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\ExtensionLoadedFromPhar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\ExtensionBootstrapped&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\Finished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\Started&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\DeprecationTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestRunner\WarningTriggered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestSuite\Filtered&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestSuite\Finished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestSuite\Loaded&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestSuite\Skipped&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestSuite\Sorted&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PHPUnit\Event\TestSuite\Started&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Events/Test/Assertion/AssertionFailed.php" target="_blank" title="AssertionFailed event of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;AssertionFailed&lt;/code&gt;&lt;/a&gt; event:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;AssertionFailed&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;telemetryInfo&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Telemetry&lt;/span&gt;\&lt;span class="hljs-title"&gt;Info&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;telemetryInfo;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;value&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;value;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;count&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;int&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;count;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;message&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;message;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;asString&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        $message = &lt;span class="hljs-string"&gt;''&lt;/span&gt;;

        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!&lt;span class="hljs-keyword"&gt;empty&lt;/span&gt;(&lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;message)) {
            $message = sprintf(
                &lt;span class="hljs-string"&gt;', Message: %s'&lt;/span&gt;,
                &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;message
            );
        }

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; sprintf(
            &lt;span class="hljs-string"&gt;'Assertion Failed (Constraint: %s, Value: %s%s)'&lt;/span&gt;,
            &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;constraint,
            &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;value,
            $message
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Subscriber.php" target="_blank" title="Suscriber interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Subscriber&lt;/code&gt;&lt;/a&gt; interface is a marker interface for all sixty-three event subscribers.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;Subscriber&lt;/span&gt;
&lt;/span&gt;{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Events/Test/Assertion/AssertionFailedSubscriber.php" target="_blank" title="AssertionFailedSubscriber interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;AssertionFailedSubscriber&lt;/code&gt;&lt;/a&gt; interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;AssertionFailedSubscriber&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Subscriber&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;notify&lt;/span&gt;&lt;span class="hljs-params"&gt;(AssertionFailed $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Tracer.php" target="_blank" title="Tracer interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Tracer&lt;/code&gt;&lt;/a&gt; interface declares a single method that allows &lt;code&gt;phpunit/phpunit&lt;/code&gt; to notify a concrete tracer about every event that occurred during the execution of PHPUnit.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Tracer&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;Tracer&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;trace&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\Event $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Runner/Extension/Extension.php" target="_blank" title="Extension interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Extension&lt;/code&gt;&lt;/a&gt; interface declares a method that accepts an immutable configuration object, an extension facade, and an immutable parameter collection and allows to register event subscribers and event tracers with the new event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;TextUI&lt;/span&gt;;

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;Extension&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;bootstrap&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        TextUI\Configuration\Configuration $configuration,
        Facade $facade,
        ParameterCollection $parameters
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-implementing-event-subscribers-in-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-implementing-event-subscribers-in-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Implementing event subscribers in the new event system&lt;/h3&gt;
&lt;p&gt;To implement an event subscriber in the new event system, you must create a class that implements an event subscriber interface corresponding to the event.&lt;/p&gt;
&lt;p&gt;By design, an event subscriber processes a &lt;em&gt;single&lt;/em&gt; event only.&lt;/p&gt;
&lt;p&gt;If you are interested in more than one event, you must create one or more subscribers per event.&lt;/p&gt;
&lt;p&gt;Here is an example of an implementation of an event subscriber using the new event system from &lt;a href="https://github.com/localheinz/phpunit-speedtrap/blob/feature/phpunit-10/src/RecordThatTestHasBeenPrepared.php" target="_blank" title="RecordThatTestHasBeenPrepared subscriber from localheinz/phpunit-speedtrap"&gt;&lt;code&gt;localheinz/phpunit-speedtrap:dev-feature-phpunit-10&lt;/code&gt;&lt;/a&gt; (from a pull request that has not yet been merged at the time of writing):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;RecordThatTestHasBeenPrepared&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;PreparedSubscriber&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(private readonly SpeedTrap $speedTrap)&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;notify&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\Test\Prepared $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;speedTrap-&amp;gt;recordThatTestHasBeenPrepared(
            $event-&amp;gt;test(),
            $event-&amp;gt;telemetryInfo()-&amp;gt;time(),
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is another example of an implementation of an event subscriber using the new event system from &lt;a href="https://github.com/localheinz/phpunit-speedtrap/blob/feature/phpunit-10/src/RecordThatTestHasBeenPrepared.php" target="_blank" title="RecordThatTestHasPassed subscriber from localheinz/phpunit-speedtrap"&gt;&lt;code&gt;localheinz/phpunit-speedtrap:dev-feature-phpunit-10&lt;/code&gt;&lt;/a&gt; (from the same pull request):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;RecordThatTestHasPassed&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;PassedSubscriber&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(private readonly SpeedTrap $speedTrap)&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;notify&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\Test\Passed $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;speedTrap-&amp;gt;recordThatTestHasPassed(
            $event-&amp;gt;test(),
            $event-&amp;gt;telemetryInfo()-&amp;gt;time(),
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is one more example of an implementation of an event subscriber using the new event system from &lt;a href="https://github.com/localheinz/phpunit-speedtrap/blob/feature/phpunit-10/src/ShowSlowTests.php" target="_blank" title="ShowSlowTests subscriber from localheinz/phpunit-speedtrap"&gt;&lt;code&gt;localheinz/phpunit-speedtrap:dev-feature-phpunit-10&lt;/code&gt;&lt;/a&gt; (from the same pull request):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ShowSlowTests&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestRunner&lt;/span&gt;\&lt;span class="hljs-title"&gt;ExecutionFinishedSubscriber&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(private readonly SpeedTrap $speedTrap)&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;notify&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\TestRunner\ExecutionFinished $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;speedTrap-&amp;gt;showSlowTests();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the event subscribers are small. Each event subscriber serves a simple purpose and delegates the actual work to another service.&lt;/p&gt;
&lt;p&gt;How you design your event subscribers largely depends on whether you need to share state between them.&lt;/p&gt;
&lt;h3 id="content-implementing-event-tracers-in-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-implementing-event-tracers-in-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Implementing event tracers in the new event system&lt;/h3&gt;
&lt;p&gt;To implement an event tracer in the new event system, you must create a class that implements the &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Event/Tracer.php" target="_blank" title="Tracer interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Tracer&lt;/code&gt;&lt;/a&gt; interface.&lt;/p&gt;
&lt;p&gt;By design, an event tracer processes &lt;em&gt;all&lt;/em&gt; events.&lt;/p&gt;
&lt;p&gt;Unless you are interested in all events, you probably want to implement an event subscriber instead.&lt;/p&gt;
&lt;p&gt;Here is the &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Logging/EventLogger.php" target="_blank" title="EventLogger of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;EventLogger&lt;/code&gt;&lt;/a&gt;, an example of an implementation of a tracer from &lt;a href="https://github.com/sebastianbergmann/phpunit/tree/10.0.7" target="_blank" title="phpunit/phpunit:10.0.7"&gt;&lt;code&gt;phpunit/phpunit:10.0.7&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Logging&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Event&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;EventLogger&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Event&lt;/span&gt;\&lt;span class="hljs-title"&gt;Tracer&lt;/span&gt;\&lt;span class="hljs-title"&gt;Tracer&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;trace&lt;/span&gt;&lt;span class="hljs-params"&gt;(Event\Event $event)&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        $telemetryInfo = &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;telemetryInfo($event);
        $indentation   = PHP_EOL . str_repeat(&lt;span class="hljs-string"&gt;' '&lt;/span&gt;, strlen($telemetryInfo));
        $lines         = explode(PHP_EOL, $event-&amp;gt;asString());

        file_put_contents(
            &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;path,
            $telemetryInfo . implode($indentation, $lines) . PHP_EOL,
            FILE_APPEND | LOCK_EX
        );
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;EventLogger&lt;/code&gt; is one of the first implementations in &lt;code&gt;phpunit/phpunit:10.0.0&lt;/code&gt; that uses the new event system. It dumps &lt;code&gt;string&lt;/code&gt; representations of all events into a text file.&lt;/p&gt;
&lt;h3 id="content-implementing-an-extension-in-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-implementing-an-extension-in-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Implementing an extension in the new event system&lt;/h3&gt;
&lt;p&gt;To allow others to register your event subscribers or event tracers, you must create a class that implements the &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Runner/Extension/Extension.php" target="_blank" title="Extension interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Extension&lt;/code&gt;&lt;/a&gt; interface.&lt;/p&gt;
&lt;p&gt;Here is an example of an implementation of an extension using the new event system from &lt;a href="https://github.com/localheinz/phpunit-speedtrap/blob/feature/phpunit-10/src/SpeedTrapExtension.php" target="_blank" title="SpeedTrapExtension of localheinz/phpunit-speedtrap:dev-feature-phpunit-10"&gt;&lt;code&gt;localheinz/phpunit-speedtrap:dev-feature-phpunit-10&lt;/code&gt;&lt;/a&gt; (from a pull request that has not yet been merged at the time of writing) with default options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;JohnKary&lt;/span&gt;\&lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Runner&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;TextUI&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;SpeedTrapExtension&lt;/span&gt; &lt;span class="hljs-keyword"&gt;implements&lt;/span&gt; &lt;span class="hljs-title"&gt;Runner&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;\&lt;span class="hljs-title"&gt;Extension&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;bootstrap&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        TextUI\Configuration\Configuration $configuration,
        Runner\Extension\Facade $facade,
        Runner\Extension\ParameterCollection $parameters
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (getenv(&lt;span class="hljs-string"&gt;'PHPUNIT_SPEEDTRAP'&lt;/span&gt;) === &lt;span class="hljs-string"&gt;'disabled'&lt;/span&gt;) {
            &lt;span class="hljs-keyword"&gt;return&lt;/span&gt;;
        }

        $slowThreshold = &lt;span class="hljs-number"&gt;500&lt;/span&gt;;

        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; ($parameters-&amp;gt;has(&lt;span class="hljs-string"&gt;'slowThreshold'&lt;/span&gt;)) {
            $slowThreshold = (int) $parameters-&amp;gt;get(&lt;span class="hljs-string"&gt;'slowThreshold'&lt;/span&gt;);
        }

        $reportLength = &lt;span class="hljs-number"&gt;10&lt;/span&gt;;

        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; ($parameters-&amp;gt;has(&lt;span class="hljs-string"&gt;'reportLength'&lt;/span&gt;)) {
            $reportLength = (int) $parameters-&amp;gt;get(&lt;span class="hljs-string"&gt;'reportLength'&lt;/span&gt;);
        }

        $speedTrap = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; SpeedTrap(
            $slowThreshold,
            $reportLength
        );

        $facade-&amp;gt;registerSubscribers(
            &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; RecordThatTestHasBeenPrepared($speedTrap),
            &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; RecordThatTestHasPassed($speedTrap),
            &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; ShowSlowTests($speedTrap),
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see from the example above, the &lt;code&gt;SpeedTrapExtension&lt;/code&gt; checks whether an environment variable was set to determine whether it should register itself with the extension facade or not.&lt;/p&gt;
&lt;p&gt;Next, it declares default configuration values and inspects the parameter collection to determine whether it should override any default configuration values.&lt;/p&gt;
&lt;p&gt;Finally, it creates a service and event subscribers and registers these event subscribers using the extension facade.&lt;/p&gt;
&lt;h3 id="content-registering-an-extension-using-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-registering-an-extension-using-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Registering an extension using the new event system&lt;/h3&gt;
&lt;p&gt;To register an extension using the new event system, you need to configure it using the &lt;a href="https://phpunit.readthedocs.io/en/10.0/configuration.html#the-extensions-element" target="_blank" title="The extensions element of the PHPUnit XML configuration file"&gt;&lt;code&gt;&amp;lt;extensions&amp;gt;&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://phpunit.readthedocs.io/en/10.0/configuration.html#the-bootstrap-element" target="_blank" title="The bootstrap element of the PHPUnit XML configuration file"&gt;&lt;code&gt;&amp;lt;extension&amp;gt;&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://phpunit.readthedocs.io/en/10.0/configuration.html#the-parameter-element" target="_blank" title="The parameter element of the PHPUnit XML configuration file"&gt;&lt;code&gt;&amp;lt;parameter&amp;gt;&lt;/code&gt;&lt;/a&gt; elements of &lt;a href="https://phpunit.readthedocs.io/en/10.0/configuration.html#" target="_blank" title="The PHPUnit XML configuration file"&gt;&lt;code&gt;phpunit.xml&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following example registers &lt;a href="https://github.com/localheinz/phpunit-speedtrap/tree/feature/phpunit-10" target="_blank" title="localheinz/phpunit-speedtrap:dev-feature-phpunit-10"&gt;&lt;code&gt;localheinz/phpunit-speedtrap:dev-feature-phpunit-10&lt;/code&gt;&lt;/a&gt; (from a pull request that has not yet been merged at the time of writing) with default options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;bootstrap&lt;/span&gt; &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;"JohnKary\PHPUnit\Extension\SpeedTrapExtension"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following example registers &lt;a href="https://github.com/localheinz/phpunit-speedtrap/tree/feature/phpunit-10" target="_blank" title="localheinz/phpunit-speedtrap:dev-feature-phpunit-10"&gt;&lt;code&gt;localheinz/phpunit-speedtrap:dev-feature-phpunit-10&lt;/code&gt;&lt;/a&gt; (from a pull request that has not yet been merged at the time of writing) with custom options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs xml" data-lang="xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xmlns:xsi&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;xsi:noNamespaceSchemaLocation&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/phpunit/phpunit/phpunit.xsd"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;bootstrap&lt;/span&gt;=&lt;span class="hljs-string"&gt;"vendor/autoload.php"&lt;/span&gt;
&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;bootstrap&lt;/span&gt; &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;"JohnKary\PHPUnit\Extension\SpeedTrapExtension"&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;parameter&lt;/span&gt; &lt;span class="hljs-attr"&gt;name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"slowThreshold"&lt;/span&gt; &lt;span class="hljs-attr"&gt;value&lt;/span&gt;=&lt;span class="hljs-string"&gt;"500"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;bootstrap&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;extensions&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;phpunit&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-migrating-an-extension-from-the-hooks-event-system-to-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-migrating-an-extension-from-the-hooks-event-system-to-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Migrating an extension from the hooks event system to the new event system&lt;/h3&gt;
&lt;p&gt;To migrate an extension from the hooks event system to the new event system, you need to require and support &lt;code&gt;phpunit/phpunit:^10.0.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, instead of implementing the hook interfaces, you need to extract event subscribers from your extension that subscribe to the corresponding events of the new event system.&lt;/p&gt;
&lt;p&gt;Additionally, you must create a class that implements the &lt;a href="https://github.com/sebastianbergmann/phpunit/blob/10.0.7/src/Runner/Extension/Extension.php" target="_blank" title="Extension interface of phpunit/phpunit:10.0.7"&gt;&lt;code&gt;Extension&lt;/code&gt;&lt;/a&gt; interface and registers your event subscribers. If your extension is configurable, it needs to inspect the parameter collection and process parameters from there instead of relying on &lt;code&gt;phpunit/phpunit&lt;/code&gt; to cast values from &lt;code&gt;phpunit.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, instead of using the &lt;code&gt;&amp;lt;extensions&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;extension&amp;gt;&lt;/code&gt; elements, you need to instruct users of your extension to use the &lt;code&gt;&amp;lt;extensions&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;bootstrap&amp;gt;&lt;/code&gt; elements of &lt;code&gt;phpunit.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is a &lt;a href="https://github.com/johnkary/phpunit-speedtrap/pull/98/files" target="_blank" title="Enhancement: Update extension to use event system of phpunit/phpunit:10.0.0 "&gt;pull request from me&lt;/a&gt;, migrating &lt;code&gt;johnkary/phpunit-speedtrap&lt;/code&gt; from the hooks event system to the new event system. Does the code look familiar? You have seen it before in this article!&lt;/p&gt;
&lt;h3 id="content-advantages-of-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-advantages-of-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Advantages of the new event system&lt;/h3&gt;
&lt;p&gt;While implementing the new event system, the &lt;code&gt;phpunit/phpunit&lt;/code&gt; development team identified many new events. The previous test listener and hooks event systems are aware of less than a dozen events. The new event system has more granularity, with a whopping sixty-three events.&lt;/p&gt;
&lt;p&gt;All of these events are immutable and provide access to more data with higher accuracy.&lt;/p&gt;
&lt;p&gt;Using the new event system internally, the &lt;code&gt;phpunit/phpunit&lt;/code&gt; development team cleaned up a lot of code and simplified maintenance.&lt;/p&gt;
&lt;h3 id="content-disadvantages-of-the-new-event-system" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-disadvantages-of-the-new-event-system" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Disadvantages of the new event system&lt;/h3&gt;
&lt;p&gt;Admittedly, an obvious disadvantage of the new event system is that developers interested in supporting the new event system need to make some effort to migrate existing extensions.&lt;/p&gt;
&lt;h2 id="content-questions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-questions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Questions&lt;/h2&gt;
&lt;p&gt;Do you have any questions regarding the new event system of &lt;code&gt;phpunit/phpunit&lt;/code&gt;? Do you have any feedback? Do you know of any exciting new extensions based on PHPUnit's new event system?&lt;/p&gt;

</content></entry><entry><title>Indenting YAML files</title><category term="coding-standards"/><category term="yaml"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/02/06/indenting-yaml-files/"/><id>https://localheinz.com/articles/2023/02/06/indenting-yaml-files/</id><updated>2023-02-06T14:25:00+01:00</updated><content>&lt;h1&gt;
  Indenting YAML files
&lt;/h1&gt;


&lt;p&gt;YAML files are omnipresent: whether you use continuous integration systems, external code quality tools, or container virtualization, the chances are that you are dealing with YAML files.&lt;/p&gt;
&lt;p&gt;At first glance, the YAML specification is straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dashes create &lt;a href="#content-sequences" title="Sequences"&gt;sequences&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;colons separate key-value pairs to create &lt;a href="#content-mappings" title="Mappings"&gt;mappings&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-indentation-spaces" title="Indentation Spaces"&gt;indentation spaces&lt;/a&gt; can create structure&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-separation-spaces" title="Separation Spaces"&gt;separation spaces&lt;/a&gt; separate tokens&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whitespace is essential - but how much?&lt;/p&gt;
&lt;p&gt;I recommend using two spaces of indentation for YAML files. But let's take a closer look!&lt;/p&gt;
&lt;h2 id="content-sequences" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-sequences" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Sequences&lt;/h2&gt;
&lt;p&gt;A &lt;a href="https://yaml.org/spec/1.2.2/#741-flow-sequences" target="_blank" title="YAML Specification: Flow Sequences"&gt;flow sequence&lt;/a&gt; contains values separated by commas and surrounded by square brackets.&lt;/p&gt;
&lt;p&gt;The example below shows a flow sequence.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-string"&gt;["8.0",&lt;/span&gt; &lt;span class="hljs-string"&gt;"8.1"&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;"8.2"&lt;/span&gt;&lt;span class="hljs-string"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;a href="https://yaml.org/spec/1.2.2/#821-block-sequences" target="_blank" title="YAML Specification: Block Sequences"&gt;block sequence&lt;/a&gt; is a series of nodes, each denoted by a leading dash.&lt;/p&gt;
&lt;p&gt;The example below shows a block sequence.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"8.0"&lt;/span&gt;
&lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"8.1"&lt;/span&gt;
&lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"8.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to the YAML specification, the examples above are equivalent.&lt;/p&gt;
&lt;h2 id="content-mappings" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-mappings" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Mappings&lt;/h2&gt;
&lt;p&gt;A &lt;a href="https://yaml.org/spec/1.2.2/#742-flow-mappings" target="_blank" title="YAML Specification: Flow Mappings"&gt;flow mapping&lt;/a&gt; contains values separated by commas and surrounded by curly braces.&lt;/p&gt;
&lt;p&gt;The example below shows a flow mapping.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-string"&gt;{&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt; &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt; &lt;span class="hljs-string"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;a href="https://yaml.org/spec/1.2.2/#822-block-mappings" target="_blank" title="YAML Specification: Block Mappings"&gt;block mapping&lt;/a&gt; is a series of entries, each representing pairs of keys and values.&lt;/p&gt;
&lt;p&gt;The example below shows a block mapping.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
&lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to the YAML specification, the examples above are equivalent.&lt;/p&gt;
&lt;h2 id="content-indentation-spaces" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-indentation-spaces" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Indentation spaces&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://yaml.org/spec/1.2.2/#61-indentation-spaces" target="_blank" title="YAML Specification: Indentation Spaces"&gt;Indentation spaces&lt;/a&gt; are zero or more space characters at the beginning of a line.&lt;/p&gt;
&lt;p&gt;Following a key and a colon, you start a new block mapping by indenting a line of YAML with more spaces than the previous line. By indenting a line of YAML with fewer spaces than the previous line, you close a block mapping.&lt;/p&gt;
&lt;p&gt;According to the YAML specification, the amount of indentation is an implementation detail.&lt;/p&gt;
&lt;p&gt;The example below shows a combination of sequences and mappings using two spaces of indentation. Note how the sequence assigned to the mapping is indented.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;default_branch:&lt;/span&gt; &lt;span class="hljs-string"&gt;"main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example below shows a combination of sequences and mappings using two spaces of indentation. Note how the sequence assigned to the mapping does not use indentation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
&lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

&lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;default_branch:&lt;/span&gt; &lt;span class="hljs-string"&gt;"main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example below shows a combination of sequences and mappings using four (and two) spaces of indentation. Note how the sequence assigned to the mapping inconsistently uses two spaces of indentation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;default_branch:&lt;/span&gt; &lt;span class="hljs-string"&gt;"main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example below shows a combination of sequences and mappings using four spaces of indentation. Note how the sequence assigned to the mapping consistently uses four spaces of indentation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt;   &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt;   &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example below shows a combination of sequences and mappings using four spaces of indentation. Note how the sequence assigned to the mapping consistently uses four spaces of indentation and how the mappings in the sequence wrap to ensure consistent indentation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to the YAML specification, the above examples are equivalent.&lt;/p&gt;
&lt;h2 id="content-separation-spaces" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-separation-spaces" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Separation spaces&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://yaml.org/spec/1.2.2/#62-separation-spaces" target="_blank" title="YAML Specification: Separation Spaces"&gt;Separation spaces&lt;/a&gt; are sequences of space or tab characters outside of indentation and string literals for separating tokens within a single line. For example, you use spaces and tabs to separate a colon and a value when creating mappings or a dash from a value when creating block sequences.&lt;/p&gt;
&lt;p&gt;The example below uses a single space to separate colons and dashes from values.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;default_branch:&lt;/span&gt; &lt;span class="hljs-string"&gt;"main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example below uses varying numbers of spaces before and a single space after colons to separate colons from values.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name :&lt;/span&gt; &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name :&lt;/span&gt; &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;default_branch    :&lt;/span&gt; &lt;span class="hljs-string"&gt;"main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example below uses varying numbers of spaces after colons to separate colons from values.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt;  &lt;span class="hljs-string"&gt;"bug"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ee0701"&lt;/span&gt;

  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt;  &lt;span class="hljs-string"&gt;"enhancement"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;color:&lt;/span&gt; &lt;span class="hljs-string"&gt;"0e8a16"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;repository:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_merge_commit:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;allow_rebase_merge:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;default_branch:&lt;/span&gt;     &lt;span class="hljs-string"&gt;"main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;According to the YAML specification, the above examples are equivalent.&lt;/p&gt;
&lt;h2 id="content-indentation-in-yaml-in-the-real-world" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-indentation-in-yaml-in-the-real-world" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Indentation in YAML in the real world&lt;/h2&gt;
&lt;p&gt;So how do people use indentation spaces in the real world?&lt;/p&gt;
&lt;h3 id="content-indentation-of-block-sequences-and-mappings-with-two-spaces" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-indentation-of-block-sequences-and-mappings-with-two-spaces" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Indentation of block sequences and mappings with two spaces&lt;/h3&gt;
&lt;p&gt;The following indent block sequences and mappings with two spaces:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;
  &lt;a target="_blank" href="https://support.atlassian.com/bitbucket-cloud/docs/bitbucket-pipelines-configuration-reference/" title="Bitbucket Pipelines"&gt;Bitbucket Pipelines&lt;/a&gt;
&lt;/li&gt;
    &lt;li&gt;
  &lt;a target="_blank" href="https://circleci.com/docs/config-intro/" title="Circle CI"&gt;Circle CI&lt;/a&gt;
&lt;/li&gt;
    &lt;li&gt;
  &lt;a target="_blank" href="https://docs.codeclimate.com/docs/advanced-configuration" title="Code Climate"&gt;Code Climate&lt;/a&gt;
&lt;/li&gt;
    &lt;li&gt;
  &lt;a target="_blank" href="https://docs.codecov.com/docs/codecovyml-reference" title="Codecov"&gt;Codecov&lt;/a&gt;
&lt;/li&gt;
    &lt;li&gt;
  &lt;a target="_blank" href="https://docs.docker.com/compose/compose-file/" title="Docker compose"&gt;Docker compose&lt;/a&gt;
&lt;/li&gt;
    &lt;li&gt;
  &lt;a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions" title="GitHub Actions"&gt;GitHub Actions&lt;/a&gt;
&lt;/li&gt;
    &lt;li&gt;
  &lt;a target="_blank" href="https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html" title="GitLab CI"&gt;GitLab CI&lt;/a&gt;
&lt;/li&gt;
          &lt;/ul&gt;
&lt;h3 id="content-indentation-of-block-mappings-with-two-spaces" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-indentation-of-block-mappings-with-two-spaces" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Indentation of block mappings with two spaces&lt;/h3&gt;
&lt;p&gt;The following indent block mappings with two spaces but do not indent block sequences:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
  &lt;a target="_blank" href="https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/?view=azure-pipelines" title="Azure Pipelines"&gt;Azure Pipelines&lt;/a&gt;
&lt;/li&gt;
                  &lt;li&gt;
  &lt;a target="_blank" href="https://kubernetes.io/docs/concepts/configuration/configmap/" title="Kubernetes"&gt;Kubernetes&lt;/a&gt;
&lt;/li&gt;
        &lt;li&gt;
  &lt;a target="_blank" href="https://config.travis-ci.com" title="Travis CI"&gt;Travis CI&lt;/a&gt;
&lt;/li&gt;
  &lt;/ul&gt;
&lt;h3 id="content-indentation-of-block-sequences-and-mappings-with-four-spaces" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-indentation-of-block-sequences-and-mappings-with-four-spaces" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Indentation of block sequences and mappings with four spaces&lt;/h3&gt;
&lt;p&gt;The following indent block sequences and mappings with four spaces:&lt;/p&gt;
&lt;ul&gt;
                        &lt;/ul&gt;
&lt;h3 id="content-indentation-of-block-mappings-with-four-spaces" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-indentation-of-block-mappings-with-four-spaces" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Indentation of block mappings with four spaces&lt;/h3&gt;
&lt;p&gt;The following indent block mappings with four spaces, but do not indent block sequences:&lt;/p&gt;
&lt;ul&gt;
                        &lt;/ul&gt;
&lt;h2 id="content-recommendation" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-recommendation" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Recommendation&lt;/h2&gt;
&lt;h3 id="content-avoid-yaml" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-avoid-yaml" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Avoid YAML&lt;/h3&gt;
&lt;p&gt;Avoid YAML when possible.&lt;/p&gt;
&lt;p&gt;For example, it is not necessary to configure a Symfony application with YAML files. Symfony not only supports YAML, but also XML (cough), and PHP configuration file formats. You can easily convert Symfony YAML (and XML) configuration files to PHP files with &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;symplify&amp;#x2F;config-transformer" target="_blank" title="symplify/config-transformer on GitHub"&gt;&lt;code&gt;symplify/config-transformer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="content-use-two-spaces-of-indentation" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-use-two-spaces-of-indentation" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Use two spaces of indentation&lt;/h3&gt;
&lt;p&gt;Do yourself a favor, and indent YAML files with two instead of four spaces. When you use two spaces of indentation for block sequences and mappings, you can avoid inconsistent indentation and awkward wrapping.&lt;/p&gt;
&lt;p&gt;As you can see from the collection above, most services that use YAML files implicitly suggest to use two spaces of indentation.&lt;/p&gt;
&lt;h3 id="content-use-editorconfig" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-use-editorconfig" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Use EditorConfig&lt;/h3&gt;
&lt;p&gt;Use &lt;a href="https://editorconfig.org" target="_blank" title=""&gt;EditorConfig&lt;/a&gt; to configure indentation size and indentation type for YAML files in every project that uses YAML files. Most IDEs have built-in support for EditorConfig and will adjust their code style configuration.&lt;/p&gt;
&lt;p&gt;Here is an example of an &lt;code&gt;.editorconfig&lt;/code&gt; that configures an indentation of two spaces for YAML files:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ini hljs ini" data-lang="ini"&gt;&lt;span class="hljs-section"&gt;[*.{yaml,yml}]&lt;/span&gt;
&lt;span class="hljs-attr"&gt;indent_size&lt;/span&gt; = &lt;span class="hljs-number"&gt;2&lt;/span&gt;
&lt;span class="hljs-attr"&gt;indent_style&lt;/span&gt; = space
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-use-yamllint" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-use-yamllint" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Use Yamllint&lt;/h3&gt;
&lt;p&gt;Use &lt;a href="https://github.com/adrienverge/yamllint" target="_blank" title=""&gt;Yamllint&lt;/a&gt; to lint YAML files in local development environments and continuous integration systems.&lt;/p&gt;
&lt;p&gt;Here is an example of a &lt;code&gt;.yamllint.yaml&lt;/code&gt; that configures an indentation of two spaces and a separation of one space.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;extends:&lt;/span&gt; &lt;span class="hljs-string"&gt;"default"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;ignore:&lt;/span&gt; &lt;span class="hljs-string"&gt;|
  .build/
  vendor/
&lt;/span&gt;
&lt;span class="hljs-attr"&gt;rules:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;braces:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-inside-empty:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-inside:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;min-spaces-inside-empty:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;min-spaces-inside:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;brackets:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-inside-empty:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-inside:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;min-spaces-inside-empty:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;min-spaces-inside:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;colons:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-after:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-before:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;commas:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-after:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-before:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;min-spaces-after:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;comments:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;ignore-shebangs:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;min-spaces-from-content:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;require-starting-space:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;comments-indentation:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enable"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;document-end:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;present:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;document-start:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;present:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;indentation:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;check-multi-line-strings:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;indent-sequences:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;spaces:&lt;/span&gt; &lt;span class="hljs-number"&gt;2&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;empty-lines:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-end:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-start:&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max:&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;empty-values:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;forbid-in-block-mappings:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;forbid-in-flow-mappings:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;hyphens:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;max-spaces-after:&lt;/span&gt; &lt;span class="hljs-number"&gt;2&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;key-duplicates:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enable"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;key-ordering:&lt;/span&gt; &lt;span class="hljs-string"&gt;"disable"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;line-length:&lt;/span&gt; &lt;span class="hljs-string"&gt;"disable"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;new-line-at-end-of-file:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enable"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;new-lines:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;type:&lt;/span&gt; &lt;span class="hljs-string"&gt;"unix"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;octal-values:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;forbid-implicit-octal:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;quoted-strings:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;quote-type:&lt;/span&gt; &lt;span class="hljs-string"&gt;"double"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;trailing-spaces:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enable"&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;truthy:&lt;/span&gt; &lt;span class="hljs-string"&gt;"enable"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;yaml-files:&lt;/span&gt;
  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"*.yaml"&lt;/span&gt;
  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"*.yml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

</content></entry><entry><title>Documenting namespaces for test code in composer.json</title><category term="composer"/><category term="php"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2023/01/29/documenting-namespaces-for-test-code-in-composer.json/"/><id>https://localheinz.com/articles/2023/01/29/documenting-namespaces-for-test-code-in-composer.json/</id><updated>2023-01-29T13:25:00+01:00</updated><content>&lt;h1&gt;
  Documenting namespaces for test code in composer.json
&lt;/h1&gt;


&lt;p&gt;When you build an application or library and use &lt;a href="https://getcomposer.org" target="_blank" title="composer: A Dependency Manager for PHP"&gt;&lt;code&gt;composer&lt;/code&gt;&lt;/a&gt; for autoloading, you typically configure one or more autoloaders for production code in the &lt;a href="https://getcomposer.org/doc/04-schema.md#autoload" target="_blank" title="The composer.json schema: autoload"&gt;&lt;code&gt;autoload&lt;/code&gt;&lt;/a&gt; section of &lt;a href="https://getcomposer.org/doc/04-schema.md" target="_blank" title="The composer.json schema"&gt;&lt;code&gt;composer.json&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json hljs json" data-lang="json"&gt;{
  &lt;span class="hljs-attr"&gt;"autoload"&lt;/span&gt;: {
    &lt;span class="hljs-attr"&gt;"psr-4"&lt;/span&gt;: {
      &lt;span class="hljs-attr"&gt;"App\\"&lt;/span&gt;: &lt;span class="hljs-string"&gt;"src/"&lt;/span&gt;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann&amp;#x2F;phpunit" target="_blank" title="sebastianbergmann/phpunit on GitHub"&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt;&lt;/a&gt;, the popular testing framework, will search directories configured in &lt;code&gt;phpunit.xml&lt;/code&gt; or passed as arguments to the test runner for instantiable classes that directly or indirectly extend &lt;code&gt;PHPUnit\Framework\TestCase&lt;/code&gt; and with a class name ending with &lt;code&gt;Test&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;phpbench&amp;#x2F;phpbench" target="_blank" title="phpbench/phpbench on GitHub"&gt;&lt;code&gt;phpbench/phpbench&lt;/code&gt;&lt;/a&gt;, the popular benchmarking framework, will search a directory passed to the test runner for instantiable classes with a class name ending with &lt;code&gt;Bench&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Neither of these tools is concerned with whether these classes are declared in namespaces or can be autoloaded.&lt;/p&gt;
&lt;p&gt;So why bother configuring an autoloader for test code in the &lt;a href="https://getcomposer.org/doc/04-schema.md#autoload-dev" target="_blank" title="The composer.json schema: autoload-dev"&gt;&lt;code&gt;autoload-dev&lt;/code&gt;&lt;/a&gt; section of &lt;a href="https://getcomposer.org/doc/04-schema.md" target="_blank" title="The composer.json schema"&gt;&lt;code&gt;composer.json&lt;/code&gt;&lt;/a&gt;?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json hljs json" data-lang="json"&gt;{
  &lt;span class="hljs-attr"&gt;"autoload"&lt;/span&gt;: {
    &lt;span class="hljs-attr"&gt;"psr-4"&lt;/span&gt;: {
      &lt;span class="hljs-attr"&gt;"App\\"&lt;/span&gt;: &lt;span class="hljs-string"&gt;"src/"&lt;/span&gt;
    }
  },
  &lt;span class="hljs-attr"&gt;"autoload-dev"&lt;/span&gt;: {
    &lt;span class="hljs-attr"&gt;"psr-4"&lt;/span&gt;: {
      &lt;span class="hljs-attr"&gt;"App\\Test\\"&lt;/span&gt;: &lt;span class="hljs-string"&gt;"test/"&lt;/span&gt;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configuring an autoloader for test code in the &lt;code&gt;autoload-dev&lt;/code&gt; section of &lt;code&gt;composer.json&lt;/code&gt; has the following advantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You document the namespace for tests for yourself and other developers.&lt;/p&gt;
&lt;p&gt;When other developers join your closed-source project or want to contribute to your open-source project, or even when you pick up work on this project after a pause, everyone will understand which namespace they should declare in tests.&lt;/p&gt;
&lt;p&gt;By documenting the namespace for tests in the &lt;code&gt;autoload-dev&lt;/code&gt; section of &lt;code&gt;composer.json&lt;/code&gt;, you can avoid confusing other developers or repeatedly having to answer the question of which namespace you should use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You document the namespace and allow autoloading of abstract test cases, test helpers, test doubles, data providers, and other helpful objects.&lt;/p&gt;
&lt;p&gt;When your project grows, you may extract abstract test cases to share functionality across test cases when inheritance is an option. Alternatively, you may implement one or more helper traits to share functionality across test cases when inheritance is not an option. For example, the PHP framework of your choice may ship with abstract test cases that you need to extend, but you still want to reuse functionality.&lt;/p&gt;
&lt;p&gt;When you have had your share of mocking frameworks, you may prefer to implement test doubles for specific scenarios by hand.&lt;/p&gt;
&lt;p&gt;When you find that your tests could benefit from reusing data providers, you may want to extract data providers into classes.&lt;/p&gt;
&lt;p&gt;When you extensively use data providers with complex scenarios, you may decide to extract test scenarios into value objects.&lt;/p&gt;
&lt;p&gt;By documenting the namespace for auxiliary test code in the &lt;code&gt;autoload-dev&lt;/code&gt; section of &lt;code&gt;composer.json&lt;/code&gt;, you acknowledge and encourage developers to extract functionality in abstract test cases, test helpers, test doubles, data providers, and other helpful objects into autoloadable classes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You document the namespace for your favorite IDE.&lt;/p&gt;
&lt;p&gt;When PhpStorm indexes and analyzes a project, it also inspects &lt;code&gt;composer.json&lt;/code&gt; and synchronizes the autoloading configuration with the &lt;a href="https://www.jetbrains.com/help/phpstorm/keeping-namespaces-in-compliance-with-psr0-and-psr4.html" target="_blank" title="PhpStorm: Configure PHP namespaces in a project"&gt;PHP namespace configuration&lt;/a&gt; in the project.&lt;/p&gt;
&lt;p&gt;When you create &lt;a href="https://www.jetbrains.com/help/phpstorm/creating-php-classes.html" target="_blank" title="PhpStorm: Creating PHP Classes"&gt;classes&lt;/a&gt; or &lt;a href="https://www.jetbrains.com/help/phpstorm/generating-php-unit-test-class.html" target="_blank" title="PhpStorm: Generate PHP tests"&gt;test cases&lt;/a&gt; using the user interface of PhpStorm, it creates classes and test cases in the appropriate directories based on the namespace configuration.&lt;/p&gt;
&lt;p&gt;When you &lt;a href="https://www.jetbrains.com/help/phpstorm/move-refactorings.html" target="_blank" title="PhpStorm: Copy and Move Refactorings"&gt;copy or move classes&lt;/a&gt;, or &lt;a href="https://www.jetbrains.com/help/phpstorm/move-namespace-dialog.html" target="_blank" title="PhpStorm: Move Namespace Dialog"&gt;move or rename namespaces&lt;/a&gt; using the user interface of PhpStorm, PhpStorm will move classes to the appropriate location based on the namespace configuration.&lt;/p&gt;
&lt;p&gt;By documenting the namespace for test code in the &lt;code&gt;autoload-dev&lt;/code&gt; section of &lt;code&gt;composer.json&lt;/code&gt;, you avoid having to configure namespace mappings manually in PhpStorm and allow PhpStorm to do its magic right out of the box.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By documenting namespaces for test code in the &lt;code&gt;autoload-dev&lt;/code&gt; section of &lt;code&gt;composer.json&lt;/code&gt; of a project, you make the life of every developer working on that project easier and can be more productive.&lt;/p&gt;
&lt;p&gt;Do you have any projects where you have not yet documented namespaces for test code?&lt;/p&gt;

</content></entry><entry><title>Enhancing types in PHP</title><category term="maintenance"/><category term="php"/><category term="type"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2022/09/20/enhancing-types-in-php/"/><id>https://localheinz.com/articles/2022/09/20/enhancing-types-in-php/</id><updated>2022-09-20T13:15:00+02:00</updated><content>&lt;h1&gt;
  Enhancing types in PHP
&lt;/h1&gt;


&lt;p&gt;&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;matthiasnoback" target="_blank" title="Matthias&amp;#x20;Noback&amp;#x20;on&amp;#x20;GitHub"&gt;Matthias Noback&lt;/a&gt; has published an article questioning whether &lt;a href="https://matthiasnoback.nl/2022/09/can-we-consider-datetimeimmutable-a-primitive-type/" target="_blank" title="Can we consider DateTimeImmutable a primitive type?"&gt;&lt;code&gt;DateTimeImmutable&lt;/code&gt; can be considered a primitive type&lt;/a&gt;. Matthias concludes that a &lt;code&gt;DateTimeImmutable&lt;/code&gt; is not a primitive type.&lt;/p&gt;
&lt;h2 id="content-primitive-type" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-primitive-type" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Primitive type&lt;/h2&gt;
&lt;p&gt;We can probably agree that the following types&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;array&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bool&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;float&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;int&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;object&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;string&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;are all primitive types.&lt;/p&gt;
&lt;h2 id="content-simple-type" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-simple-type" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Simple type&lt;/h2&gt;
&lt;p&gt;We can probably also agree that a &lt;code&gt;DateTimeImmutable&lt;/code&gt; is better than a &lt;code&gt;string&lt;/code&gt;, a non-empty &lt;code&gt;string&lt;/code&gt;, and better than a string that matches a certain regular expression. So if you are dealing with a &lt;code&gt;DateTimeImmutable&lt;/code&gt;, you can be sure it holds a valid date.&lt;/p&gt;
&lt;p&gt;If you look at all the usages of a &lt;code&gt;DateTimeImmutable&lt;/code&gt; in a code base, the only thing the corresponding values have in common is that they are valid dates. But are these values reasonable in a specific context? For example, do they refer to a date in the next quarter or a date of birth?&lt;/p&gt;
&lt;p&gt;I am arguing that, at best, a &lt;code&gt;DateTimeImmutable&lt;/code&gt; is a sophisticated primitive type, perhaps something we can call a &lt;em&gt;simple type&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;jitterted" target="_blank" title="Ted&amp;#x20;M.&amp;#x20;Young&amp;#x20;on&amp;#x20;GitHub"&gt;Ted M. Young&lt;/a&gt; calls these types &lt;em&gt;unconstrained types&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-dnt="true"&gt;&lt;p lang="en" dir="ltr"&gt;This is why I include all of the Collection classes under the &amp;quot;Primitive Obsession&amp;quot; umbrella. They&amp;#39;re &amp;quot;primitives&amp;quot;, too, i.e., unconstrained types, that need encapsulation.&lt;br&gt;&lt;br&gt;In IntelliJ IDEA, it&amp;#39;s a few automated steps to refactor to a new class in case you waited too long. &lt;a href="https://t.co/yEs7FWKokP"&gt;https://t.co/yEs7FWKokP&lt;/a&gt;&lt;/p&gt;&amp;mdash; Ted M. Young (@jitterted) &lt;a href="https://twitter.com/jitterted/status/1565766487943417856?ref_src=twsrc%5Etfw"&gt;September 2, 2022&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;Also see the slides for Ted's talk &lt;a href="https://speakerdeck.com/jitterted/stop-obsessing-about-primitives" target="_blank" title="Stop Obessing About Primitives"&gt;Stop Obsessing About Primitives&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="content-proper-type" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-proper-type" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Proper type&lt;/h2&gt;
&lt;p&gt;In my opinion, a &lt;em&gt;proper type&lt;/em&gt; describes an object&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;that carries meaning (&lt;em&gt;it's a date of birth&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;that enforces constraints for the primitive(s) it encapsulates (&lt;em&gt;it's today or in the past&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;that encapsulates operations when necessary, for example, comparison with other instances (&lt;em&gt;it refers to the same day&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;that a developer can trace across a code base (&lt;em&gt;all of these instances refer to dates of birth&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A &lt;code&gt;DateTimeImmutable&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;does not carry meaning beyond referring to a date&lt;/li&gt;
&lt;li&gt;can not enforce whether the encapsulated value is in the future or the past&lt;/li&gt;
&lt;li&gt;does not encapsulate necessary operations, for example, comparison with other instances without knowing which components need to be considered&lt;/li&gt;
&lt;li&gt;can not be reliably traced across a code base&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without context, a &lt;code&gt;DateTimeImmutable&lt;/code&gt; object does not satisfy the requirements of a proper type.&lt;/p&gt;
&lt;h2 id="content-from-primitive-to-proper-type" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-from-primitive-to-proper-type" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;From primitive to proper type&lt;/h2&gt;
&lt;p&gt;Replacing primitive types with simple types certainly enhances legacy code bases. However, it is only a step in the right direction while figuring out requirements.&lt;/p&gt;
&lt;p&gt;For example, assume a legacy code base has a &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;User&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; string $dateOfBirth;

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(string $dateOfBirth)&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;dateOfBirth = $dateOfBirth;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;dateOfBirth&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;dateOfBirth;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a first step, we can replace the primitive string with a &lt;code&gt;DateTimeImmutable&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;User&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; \DateTimeImmutable $dateOfBirth;

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(DateTimeImmutable $dateOfBirth)&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;dateOfBirth = $dateOfBirth;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;dateOfBirth&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;DateTimeImmutable&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;dateOfBirth;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a second step, we can introduce a simple &lt;code&gt;DateOfBirth&lt;/code&gt; type to replace the simple &lt;code&gt;DateTimeImmutable&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;DateOfBirth&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; DateTimeImmutable $value;

    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(DateTimeImmutable $value)&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;value = $value;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;fromDateTimeImmutable&lt;/span&gt;&lt;span class="hljs-params"&gt;(DateTimeImmutable $value)&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;($value);
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;toString&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;value;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The simple &lt;code&gt;DateOfBirth&lt;/code&gt; type&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carries meaning (&lt;em&gt;it's a date of birth&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;encapsulates necessary operations, for example, comparison with other instances (&lt;em&gt;it refers to the same day&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;can be traced by a developer across a code base (&lt;em&gt;all of these instances refer to dates of birth&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The simple &lt;code&gt;DateOfBirth&lt;/code&gt; type does not yet enforce constraints for the simple type it encapsulates.&lt;/p&gt;
&lt;p&gt;However, the &lt;code&gt;User&lt;/code&gt; class and corresponding call sites can now already start using the &lt;code&gt;DateOfBirth&lt;/code&gt; type.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;User&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; DateOfBirth $dateOfBirth;

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(DateOfBirth $dateOfBirth)&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;dateOfBirth = $dateOfBirth;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;name&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;DateOfBirth&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;dateOfBirth;
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a third step, when requirements are clear, we can turn the simple &lt;code&gt;DateOfBirth&lt;/code&gt; into a proper type that also enforces constraints for the simple &lt;code&gt;DateTimeImmutable&lt;/code&gt; it encapsulates.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;DateOfBirth&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@throws&lt;/span&gt; InvalidDateOfBirth
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;fromDateTimeImmutable&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        DateTimeImmutable $value,
        DateTimeImmutable $threshold
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; ($threshold-&amp;gt;format(&lt;span class="hljs-string"&gt;'Y-m-d'&lt;/span&gt;) &amp;lt; $value-&amp;gt;format(&lt;span class="hljs-string"&gt;'Y-m-d'&lt;/span&gt;)) {
            &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; InvalidDateOfBirth::with($value);
        }

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;($value);
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The proper &lt;code&gt;DateOfBirth&lt;/code&gt; type now&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carries meaning (&lt;em&gt;it's a date of birth&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;enforces constraints for the primitive(s) it encapsulates (&lt;em&gt;it's today or in the past&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;encapsulates comparison (&lt;em&gt;it refers to the same day&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;can be traced by a developer across a code base (&lt;em&gt;all of these instances refer to dates of birth&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What do you think? Are you better off with primitive, simple, or proper types?&lt;/p&gt;

</content></entry><entry><title>Asserting the output of Symfony console commands</title><category term="console"/><category term="php"/><category term="phpunit"/><category term="symfony"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2022/08/29/asserting-the-output-of-symfony-console-commands/"/><id>https://localheinz.com/articles/2022/08/29/asserting-the-output-of-symfony-console-commands/</id><updated>2022-08-29T22:30:00+02:00</updated><content>&lt;h1&gt;
  Asserting the output of Symfony console commands
&lt;/h1&gt;


&lt;p&gt;If you write console commands using &lt;a href="https://symfony.com/doc/current/components/console.html" target="_blank" title="Symfony: The Console Component"&gt;&lt;code&gt;symfony/console&lt;/code&gt;&lt;/a&gt; and have not used &lt;a href="https://symfony.com/doc/current/console/style.html" target="_blank" title="Symfony: How to Style a Console Command"&gt;&lt;code&gt;SymfonyStyle&lt;/code&gt;&lt;/a&gt;yet, you should give it a try.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SymfonyStyle&lt;/code&gt; provides additional methods for rendering output in the terminal. These methods allow rendering titles, section headings, success and error messages, tables, bullet lists, and more - all of them nicely formatted and indented.&lt;/p&gt;
&lt;p&gt;Here is an example from &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;day-one-to-obsidian-converter" target="_blank" title="ergebnis/day-one-to-obsidian-converter on GitHub"&gt;&lt;code&gt;ergebnis/day-one-to-obsidian-converter&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;DayOneToObsidianConverter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Outside&lt;/span&gt;\&lt;span class="hljs-title"&gt;Adapter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Primary&lt;/span&gt;\&lt;span class="hljs-title"&gt;Console&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;DayOneToObsidianConverter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Inside&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;Console&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ConvertCommand&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Console&lt;/span&gt;\&lt;span class="hljs-title"&gt;Command&lt;/span&gt;\&lt;span class="hljs-title"&gt;Command&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;protected&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;execute&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Console\Input\InputInterface $input,
        Console\Output\OutputInterface $output,
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;int&lt;/span&gt; &lt;/span&gt;{
        $io = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Console\Style\SymfonyStyle(
            $input,
            $output,
        );

        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

        $dayOneDirectory = $input-&amp;gt;getArgument(&lt;span class="hljs-string"&gt;'day-one-directory'&lt;/span&gt;);

        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!\is_dir($dayOneDirectory)) {
            $io-&amp;gt;error(\sprintf(
                &lt;span class="hljs-string"&gt;'DayOne directory %s does not exist'&lt;/span&gt;,
                $dayOneDirectory,
            ));

            &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;::FAILURE;
        }

        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;::SUCCESS;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the error message looks great, when you write tests and want to make assertions about the output generated by the command, you quickly run into issues.&lt;/p&gt;
&lt;p&gt;If the length of an error or success message depends on variable input, you can never be sure whether a message can fit into a single or will spread across multiple lines.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;DayOneToObsidianConverter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Integration&lt;/span&gt;\&lt;span class="hljs-title"&gt;Outside&lt;/span&gt;\&lt;span class="hljs-title"&gt;Adapter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Primary&lt;/span&gt;\&lt;span class="hljs-title"&gt;Console&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;DayOneToObsidianConverter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Outside&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;DayOneToObsidianConverter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;PHPUnit&lt;/span&gt;\&lt;span class="hljs-title"&gt;Framework&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;Console&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ConvertCommandTest&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; &lt;span class="hljs-title"&gt;Framework&lt;/span&gt;\&lt;span class="hljs-title"&gt;TestCase&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Util&lt;/span&gt;\&lt;span class="hljs-title"&gt;Helper&lt;/span&gt;;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;testExecuteFailsWhenDayOneDirectoryDoesNotExist&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;void&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

        $application = &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;::createApplication();

        $input = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Console\Input\ArrayInput([
            &lt;span class="hljs-string"&gt;'day-one-directory'&lt;/span&gt; =&amp;gt; $dayOneDirectory,
        ]);

        $output = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Console\Output\BufferedOutput();

        $exitCode = $application-&amp;gt;run(
            $input,
            $output,
        );

        &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;::assertSame(Console\Command\Command::FAILURE, $exitCode);

        $expected = \sprintf(
            &lt;span class="hljs-string"&gt;'DayOne directory %s does not exist'&lt;/span&gt;,
            $dayOneDirectory,
        );

        &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;::assertStringContainsString($expected, $output-&amp;gt;fetch());
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, the test above fails because the actual error message is wrapped across multiple lines.&lt;/p&gt;
&lt;p&gt;While Symfony allows specifying the terminal width via a &lt;a href="https://github.com/symfony/console/blob/v6.1.4/Terminal.php#L25" target="_blank"&gt;&lt;code&gt;COLUMNS&lt;/code&gt;&lt;/a&gt; environment variable, &lt;code&gt;SymfonyStyle&lt;/code&gt; does not &lt;a href="https://github.com/symfony/console/blob/v6.1.4/Style/SymfonyStyle.php#L53" target="_blank"&gt;accept values greater than 120 characters&lt;/a&gt;. Setting the &lt;code&gt;COLUMNS&lt;/code&gt; environment variable to a value larger than 120 in the context of tests will not work.&lt;/p&gt;
&lt;p&gt;You could try working around the formatting and indenting by fiddling with regular expressions, but why bother?&lt;/p&gt;
&lt;p&gt;Instead, let &lt;code&gt;SymfonyStyle&lt;/code&gt; generate the expected output!&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;DayOneToObsidianConverter&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Util&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Faker&lt;/span&gt;\&lt;span class="hljs-title"&gt;Factory&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Faker&lt;/span&gt;\&lt;span class="hljs-title"&gt;Generator&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;Console&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;trait&lt;/span&gt; Helper
{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@param&lt;/span&gt; \Closure(OutputStyle):void $closure
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-keyword"&gt;protected&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;captureConsoleOutput&lt;/span&gt;&lt;span class="hljs-params"&gt;(\Closure $closure)&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        $output = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Console\Output\BufferedOutput();

        $io = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Console\Style\SymfonyStyle(
            &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Console\Input\ArrayInput([]),
            $output,
        );

        $closure($io);

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $output-&amp;gt;fetch();
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can simplify your tests by asserting that the actual output contains the expected output.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;
         self::assertSame(Console\Command\Command::FAILURE, $exitCode);

&lt;span class="hljs-deletion"&gt;-        $expected = \sprintf(&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            'DayOne directory %s does not exist',&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            $dayOneDirectory,&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        );&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $expected = self::captureConsoleOutput(static function (Console\Style\OutputStyle $io) use ($dayOneDirectory): void {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $io-&amp;gt;error(\sprintf(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+                'DayOne directory %s does not exist',&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+                $dayOneDirectory,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            ));&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        });&lt;/span&gt;

         self::assertStringContainsString($expected, $output-&amp;gt;fetch());
     }

     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you have a better idea?&lt;/p&gt;

</content></entry><entry><title>Naming constructors in PHP</title><category term="naming"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2022/03/26/naming-constructors-in-php/"/><id>https://localheinz.com/articles/2022/03/26/naming-constructors-in-php/</id><updated>2022-03-26T19:00:00+02:00</updated><content>&lt;h1&gt;
  Naming constructors in PHP
&lt;/h1&gt;


&lt;p&gt;The chances are that you are already aware of the concept of &lt;em&gt;named constructors&lt;/em&gt;. If not, take a look at &lt;a href="https://verraes.net" target="_blank" title="Matthias Verraes"&gt;Matthias Verraes&lt;/a&gt;' excellent article &lt;a href="https://verraes.net/2014/06/named-constructors-in-php/" target="_blank" title="Named Constructors in PHP"&gt;Named Constructors in PHP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When it comes to consistently &lt;em&gt;naming&lt;/em&gt; constructors, I currently apply the following rules for different types of objects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#content-services" title="Services"&gt;services&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-exceptions" title="Exceptions"&gt;exceptions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-entities" title="Entities"&gt;entities&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-value-objects" title="Value Objects"&gt;value objects&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-services" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-services" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Services&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;When a service requires dependencies, use the default &lt;code&gt;__construct()&lt;/code&gt; method for instantiation.&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;AccountService&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        private readonly AccountEventStore $accountEventStore,
        private readonly AccountEventDispatcher $accountEventDispatcher,
    )&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why? Typically, a service has a single way of construction. Consequently, it only needs a single constructor.&lt;/p&gt;
&lt;p&gt;During yesterday's &lt;a href="https://web.archive.org/web/20230119093343/https://thephp.cc/services/office-hours" target="_blank" title="Office Hours"&gt;Office Hours&lt;/a&gt; of &lt;a href="https://thephp.cc" target="_blank" title="The PHP Consulting Company"&gt;The PHP Consulting Company&lt;/a&gt;, &lt;a target="_blank" href="https://thephp.cc/company/consultants/stefan-priebsch" target="_blank" title="Stefan Priebsch"&gt;Stefan Priebsch&lt;/a&gt; shared that he experimented with named constructors for services. Stefan marks the &lt;code&gt;__construct()&lt;/code&gt; method as &lt;code&gt;private&lt;/code&gt; and uses a named constructor &lt;code&gt;collaboratingWith()&lt;/code&gt; instead, which sounds like an idea that I might give a try.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;AccountService&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        private readonly AccountEventStore $accountEventStore,
        private readonly AccountEventDispatcher $accountEventDispatcher,
    )&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;collaboratingWith&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        AccountEventStore $accountEventStore,
        AccountEventDispatcher $accountEventDispatcher,
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(
            $accountEventStore,
            $accountEventDispatcher,
        );
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stefan also pointed out that marking the &lt;code&gt;__construct()&lt;/code&gt; method as &lt;code&gt;private&lt;/code&gt; and using a named constructor instead has the advantage that developers cannot invoke the &lt;code&gt;__construct()&lt;/code&gt; method on an already instantiated object, which is technically possible. Stefan notes that an &lt;a href="https://wiki.php.net/rfc/disallow-multiple-constructor-calls" target="_blank" title="PHP RFC: Disallow Multiple Constructor Calls"&gt;RFC proposing to disallow multiple constructor calls&lt;/a&gt; has become inactive. Hence, marking the &lt;code&gt;__construct()&lt;/code&gt; method as &lt;code&gt;private&lt;/code&gt; and using a named constructor takes away one more possibility for a PHP developer to shoot themselves in the foot.&lt;/p&gt;
&lt;p&gt;Following our conversation, Stefan has published &lt;a href="https://thephp.cc/articles/how-do-you-name-constructors" target="_blank" title="How do you name constructors?"&gt;How do you name constructors?&lt;/a&gt;, where he provides additional examples, explanations, and reasoning.&lt;/p&gt;
&lt;h2 id="content-exceptions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-exceptions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Exceptions&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;When implementing a custom exception, allow instantiation via a named constructor that suits the purpose, never override the default constructor.&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;ORM&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;CouldNotFindUser&lt;/span&gt; &lt;span class="hljs-keyword"&gt;extends&lt;/span&gt; \&lt;span class="hljs-title"&gt;RuntimeException&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;identifiedBy&lt;/span&gt;&lt;span class="hljs-params"&gt;(UserId $userId)&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(\sprintf(
            &lt;span class="hljs-string"&gt;'Could not find a user identified by "%s".'&lt;/span&gt;,
            $userId-&amp;gt;toString(),
        ));
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;withEmailAddress&lt;/span&gt;&lt;span class="hljs-params"&gt;(EmailAddress $emailAddress)&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(\sprintf(
            &lt;span class="hljs-string"&gt;'Could not find a user with email-address "%s".'&lt;/span&gt;,
            $emailAddress-&amp;gt;toString(),
        ));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why? Depending on the scenario, you may want to throw an exception of the same type, but based on different inputs. Named constructors allow doing just that. When you override the default constructor, it will be harder for cases where you are not ready to extract a named constructor yet or want to pass in an exception code, a previous exception, or both during the construction of the exception.&lt;/p&gt;
&lt;h2 id="content-entities" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-entities" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Entities&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;When an ORM entity requires other entities or values, use the default &lt;code&gt;__construct()&lt;/code&gt; method for instantiation.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In a typical project using the Doctrine ORM an entity also has only a single way of construction. I have chosen to use the default &lt;code&gt;__construct()&lt;/code&gt; method for this kind of entity.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;ORM&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;#[ORM\Mapping\Entity]&lt;/span&gt;
&lt;span class="hljs-comment"&gt;#[ORM\Mapping\Table(name="user_reset_password_token")]&lt;/span&gt;
&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;UserResetPasswordToken&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;#[ORM\Mapping\Column(&lt;/span&gt;
        length: &lt;span class="hljs-number"&gt;255&lt;/span&gt;,
        name: &lt;span class="hljs-string"&gt;'token'&lt;/span&gt;,
        nullable: &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        type: &lt;span class="hljs-string"&gt;'example_token'&lt;/span&gt;,
        unique: &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,
    )]
    &lt;span class="hljs-comment"&gt;#[ORM\Mapping\Id]&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; Token $token;

    &lt;span class="hljs-comment"&gt;#[ORM\Mapping\JoinColumn(&lt;/span&gt;
        name: &lt;span class="hljs-string"&gt;'user_id'&lt;/span&gt;,
        nullable: &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        onDelete: &lt;span class="hljs-string"&gt;'CASCADE'&lt;/span&gt;,
        referencedColumnName: &lt;span class="hljs-string"&gt;'id'&lt;/span&gt;,
        unique: &lt;span class="hljs-keyword"&gt;true&lt;/span&gt;,
    )]
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; User $user;

    &lt;span class="hljs-comment"&gt;#[ORM\Mapping\Column(&lt;/span&gt;
        name: &lt;span class="hljs-string"&gt;'expires_at'&lt;/span&gt;,
        nullable: &lt;span class="hljs-keyword"&gt;false&lt;/span&gt;,
        type: &lt;span class="hljs-string"&gt;'datetime_immutable'&lt;/span&gt;,
    )]
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; \DateTimeImmutable $expiresAt;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        private Token $token,
        private User $user,
        private \DateTimeImmutable $expiresAt,
    )&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although I am currently still working on projects using the Doctrine ORM, I have become more interested in working on projects using message-driven architectures.&lt;/p&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;For an event-sourced entity, mark the default &lt;code&gt;__construct()&lt;/code&gt; method as &lt;code&gt;private&lt;/code&gt;, add a named constructor &lt;code&gt;create()&lt;/code&gt; that initiates the lifecycle of the entity, and add a named constructor &lt;code&gt;fromEvents()&lt;/code&gt; that allows reconstituting the entity from a list of events.&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Order&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@var&lt;/span&gt; array&amp;lt;int, OrderEvent&amp;gt;
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-keyword"&gt;array&lt;/span&gt; $events = [];

    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(OrderEvent ...$events)&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;foreach&lt;/span&gt; ($events &lt;span class="hljs-keyword"&gt;as&lt;/span&gt; $event) {
            &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;apply($event);
        }
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;create&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        OrderId $orderId,
        OrderStartedAt $orderStartedAt,
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt; &lt;/span&gt;{
        $order = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;();

        $order-&amp;gt;record(OrderStarted::create(
            $orderId,
            $orderStartedAt,
        ));

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $orderId;
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;fromEvents&lt;/span&gt;&lt;span class="hljs-params"&gt;(OrderEvent ...$orderEvents)&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(...$orderEvents);
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-value-objects" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-value-objects" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Value Objects&lt;/h2&gt;
&lt;p&gt;I distinguish &lt;strike&gt;two&lt;/strike&gt; three types of value objects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#content-value-objects-wrapping-a-primitive-value" title="Value Objects wrapping a primitive value"&gt;value objects wrapping a primitive value&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-value-objects-composing-primitive-values" title="Value Objects composing primitive values"&gt;value objects composing primitive values&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-value-objects-composing-other-value-objects" title="Value Objects composing other value objects"&gt;value objects composing other value objects&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="content-value-objects-wrapping-a-primitive-value" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-value-objects-wrapping-a-primitive-value" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Value Objects wrapping a primitive value&lt;/h3&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;When a value object wraps a single primitive value, such as&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;bool&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;float&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;int&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;string&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;or an immutable object from the &lt;a href="https://www.php.net/spl" target="_blank" title="Standard PHP Library (SPL) "&gt;Standard PHP Library (SPL) &lt;/a&gt;, for example,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an instance of &lt;code&gt;DateTimeImmutable&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;mark the default &lt;code&gt;__construct()&lt;/code&gt; method as &lt;code&gt;private&lt;/code&gt; and add named constructors with corresponding counterparts for accessing the wrapped value (or a representation of it).&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;For example, to allow instantiation of a  &lt;code&gt;Name&lt;/code&gt; value object from a &lt;code&gt;string&lt;/code&gt; value, I add a constructor with the name &lt;code&gt;fromString()&lt;/code&gt; and a corresponding method &lt;code&gt;toString()&lt;/code&gt; that provides access to the wrapped &lt;code&gt;string&lt;/code&gt; value.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Name&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(private readonly string $value)&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@throws&lt;/span&gt; \InvalidArgumentException
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;fromString&lt;/span&gt;&lt;span class="hljs-params"&gt;(string $value)&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-string"&gt;''&lt;/span&gt; === \trim($value)) {
            &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; \InvalidArgumentException(&lt;span class="hljs-string"&gt;'Value can not be blank or empty.'&lt;/span&gt;);
        }

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;($value);
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;toString&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;value;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As another example, to allow instantiation of a &lt;code&gt;DateOfBirth&lt;/code&gt; value object from an instance of  &lt;code&gt;DateTimeImmutable&lt;/code&gt;, I add a named constructor &lt;code&gt;fromDateTimeImmutable()&lt;/code&gt; and a corresponding accessor &lt;code&gt;toDateTimeImmutable()&lt;/code&gt; that provides access to the wrapped &lt;code&gt;DateTimeImmutable&lt;/code&gt; instance.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;DateOfBirth&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(private readonly \DateTimeImmutable $value)&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;fromDateTimeImmutable&lt;/span&gt;&lt;span class="hljs-params"&gt;(\DateTimeImmutable $value)&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(\DateTimeImmutable::createFromFormat(
            &lt;span class="hljs-string"&gt;'!Y-m-d'&lt;/span&gt;,
            $value-&amp;gt;format(&lt;span class="hljs-string"&gt;'Y-m-d'&lt;/span&gt;),
        ));
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;toDateTimeImmutable&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: \&lt;span class="hljs-title"&gt;DateTimeImmutable&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;value;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why bother with a &lt;code&gt;toDateTimeImmutable()&lt;/code&gt; method when the field is already &lt;code&gt;readonly&lt;/code&gt; and not make the field &lt;code&gt;public&lt;/code&gt; instead?&lt;/p&gt;
&lt;p&gt;From the perspective of a user, the internal value does not matter. The only thing that matters to a user is that they can create the value object from some primitive value and obtain a primitive representation of the value object when necessary. In the example above, the &lt;code&gt;DateOfBirth&lt;/code&gt; value object could use a &lt;code&gt;string&lt;/code&gt; as internal representation, three &lt;code&gt;int&lt;/code&gt;s, or three &lt;code&gt;string&lt;/code&gt;s. If I exposed the internal representation directly by making the field &lt;code&gt;public&lt;/code&gt;, I would need to change every place that references the field(s). By encapsulating the primitive entirely, I am still open to changing the internal representation without making these changes anywhere else.&lt;/p&gt;
&lt;p&gt;When it later turns out that I want to construct a &lt;code&gt;DateOfBirth&lt;/code&gt; from a &lt;code&gt;string&lt;/code&gt; value, I can easily add a constructor with the name &lt;code&gt;fromString()&lt;/code&gt;, and if need be, a corresponding accessor &lt;code&gt;toString()&lt;/code&gt; that returns an appropriate &lt;code&gt;string&lt;/code&gt; representation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;DateOfBirth&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(private readonly \DateTimeImmutable $value)&lt;/span&gt;
    &lt;/span&gt;{
    }

    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@throws&lt;/span&gt; \InvalidArgumentException
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;fromString&lt;/span&gt;&lt;span class="hljs-params"&gt;(string $value)&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;try&lt;/span&gt; {
            $parsed = \DateTimeImmutable::createFromFormat(
                &lt;span class="hljs-string"&gt;'!Y-m-d'&lt;/span&gt;,
                $value,
            );
        } &lt;span class="hljs-keyword"&gt;catch&lt;/span&gt; (\&lt;span class="hljs-keyword"&gt;Exception&lt;/span&gt;) {
            &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; \InvalidArgumentException(\sprintf(
                &lt;span class="hljs-string"&gt;'Value "%s" does not appear to be a valid date of birth.'&lt;/span&gt;,
                $value,
            ));
        }

        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;($parsed);
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;toString&lt;/span&gt;&lt;span class="hljs-params"&gt;()&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt;
    &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;$this&lt;/span&gt;-&amp;gt;value-&amp;gt;format(&lt;span class="hljs-string"&gt;'Y-m-d'&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="content-value-objects-composing-primitive-values" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-value-objects-composing-primitive-values" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Value objects composing primitive values&lt;/h3&gt;
&lt;p&gt;I am still on the fence about whether a value object that composes more than one primitive value is a good idea. Generally, I prefer value objects to wrap a single primitive value or compose other value objects.&lt;/p&gt;
&lt;p&gt;Why? For me, value objects serve two purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;traceability&lt;/li&gt;
&lt;li&gt;enforcing invariants&lt;/li&gt;
&lt;li&gt;expressive operations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, a legacy codebase might use an &lt;code&gt;int&lt;/code&gt; or a &lt;code&gt;string&lt;/code&gt; for representing the concept of an &lt;a href="https://en.wikipedia.org/wiki/International_Bank_Account_Number" target="_blank" title="International Bank Account Number"&gt;International Bank Account Number (IBAN)&lt;/a&gt;. When I slowly replace usages of the &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;string&lt;/code&gt; value with an &lt;code&gt;Iban&lt;/code&gt; type, the concept becomes traceable throughout the codebase. Before the change, the only indication I was dealing with an IBAN was the variable name, perhaps even with a different spelling. I now have a concrete type and can easily find usages with an IDE or static code analysis. In other words, at this time, without enforcing invariants or providing expressive operations, introducing a value object already has the great benefit of traceability.&lt;/p&gt;
&lt;p&gt;Step by step, I could enforce invariants using guard clauses and add methods that allow expressive operations.&lt;/p&gt;
&lt;p&gt;If I composed multiple primitives into a value object and cared about these primitives, then I would not benefit from traceability and expressive operations anymore.&lt;/p&gt;
&lt;p&gt;For example, I could compose two &lt;code&gt;DateTimeImmutable&lt;/code&gt;s into a &lt;code&gt;Duration&lt;/code&gt; value object to model the duration of a specific event.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Duration&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        public readonly \DateTimeImmutable $start,
        public readonly \DateTimeImmutable $end,
    )&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@throws&lt;/span&gt; \InvalidArgumentException
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;create&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        \DateTimeImmutable $start,
        \DateTimeImmutable $end,
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(
            $start,
            $end,
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given proper enforcement of invariants (an event can not end before it starts), the &lt;code&gt;Duration&lt;/code&gt; object works fine, and the concept of a &lt;code&gt;Duration&lt;/code&gt; is now clearly more traceable than passing around two &lt;code&gt;DateTimeImmutable&lt;/code&gt;s. However, if I care about the concept of a &lt;code&gt;Start&lt;/code&gt; and &lt;code&gt;End&lt;/code&gt;, and when I want to distinguish between a &lt;code&gt;Start&lt;/code&gt;, an &lt;code&gt;End&lt;/code&gt;, and an ordinary instance of &lt;code&gt;DateTimeImmutable&lt;/code&gt;, and want to provide expressive operations for these values, it will make more sense to introduce additional value objects.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Duration&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        public readonly Start $start,
        public readonly End $end,
    )&lt;/span&gt; &lt;/span&gt;{
    }

    &lt;span class="hljs-comment"&gt;/**
     * &lt;span class="hljs-doctag"&gt;@throws&lt;/span&gt; \InvalidArgumentException
     */&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;create&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Start $start,
        End $end,
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!$start-&amp;gt;isBefore($end)) {
            &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
        }

        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(
            $start,
            $end,
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-value-objects-composing-other-value-objects" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-value-objects-composing-other-value-objects" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Value objects composing other value objects&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;When a value object composes other value objects, mark the default &lt;code&gt;__construct()&lt;/code&gt; method as &lt;code&gt;private&lt;/code&gt; and add a named constructor &lt;code&gt;create()&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I have found that this kind of value object only has a single way of construction. However, instead of using the default &lt;code&gt;__construct()&lt;/code&gt; method, I prefer to use a named constructor &lt;code&gt;create()&lt;/code&gt; for this kind of value object.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;final&lt;/span&gt; &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Attendee&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;__construct&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        public readonly Name $name,
        public readonly DateOfBirth $dateOfBirth,
    )&lt;/span&gt; &lt;/span&gt;{
    {
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title"&gt;create&lt;/span&gt;&lt;span class="hljs-params"&gt;(
        Name $name,
        DateOfBirth $dateOfBirth,
    )&lt;/span&gt;: &lt;span class="hljs-title"&gt;self&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-keyword"&gt;self&lt;/span&gt;(
            $name,
            $dateOfBirth,
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why &lt;code&gt;create()&lt;/code&gt; and not &lt;code&gt;fromNameAndDateOfBirth()&lt;/code&gt;? When I name the method &lt;code&gt;fromNameAndDateOfBirth()&lt;/code&gt;, and later decide to compose additional value objects, it would be consistent to reflect the change by changing the method name. However, the method name would become longer and longer, and I would like to avoid that.&lt;/p&gt;
&lt;h2 id="content-consistency" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-consistency" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Consistency&lt;/h2&gt;
&lt;p&gt;Using named constructors for value objects, even when these value objects have only a single way of construction, provides a consistent naming scheme. Consistency is key to making it easier for developers to join, understand, and contribute to a project.&lt;/p&gt;

</content></entry><entry><title>Creating releases with GitHub Actions</title><category term="github"/><category term="github-actions"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2022/01/24/creating-releases-with-github-actions/"/><id>https://localheinz.com/articles/2022/01/24/creating-releases-with-github-actions/</id><updated>2022-01-24T12:15:00+02:00</updated><content>&lt;h1&gt;
  Creating releases with GitHub Actions
&lt;/h1&gt;


&lt;p&gt;In October 2021, GitHub introduced a feature that enables maintainers to &lt;a href="https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes" target="_blank" title="GitHub: Automatically generated release notes"&gt;generate release notes for a release&lt;/a&gt; with the click of a button.&lt;/p&gt;
&lt;p&gt;By default, the automatically generated release notes contain&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a list of pull requests maintainers have merged since the last release&lt;/li&gt;
&lt;li&gt;a list of first-time contributors to that release&lt;/li&gt;
&lt;li&gt;a link to the diff, comparing the changes between this and the previous release&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, you can &lt;a href="https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes" target="_blank" title="GitHub: Configuring automatically generated release notes"&gt;configure filters and groups&lt;/a&gt; to fine-tune the content of these release notes, depending on the authors and labels attached to the pull requests.&lt;/p&gt;
&lt;p&gt;While these automatically generated release notes may not be perfect, they can be a good companion for a curated, hand-crafted &lt;code&gt;CHANGELOG.md&lt;/code&gt; as proposed by the &lt;a href="https://keepachangelog.com/en/1.0.0/" target="_blank" title="keep a changelog: Don’t let your friends dump git logs into changelogs"&gt;keepachangelog form&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But - why bother clicking buttons when you could automatically create a release with release notes any time you push a tag to GitHub? In addition to the user interface, GitHub allows maintainers to &lt;a href="https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release" target="_blank" title="GitHub REST API: Create a release"&gt;create a release via their API&lt;/a&gt;. In combination with &lt;a href="https://github.com/actions/github-script" target="_blank" title="actions/github-script"&gt;&lt;code&gt;actions/github-script&lt;/code&gt;&lt;/a&gt;, you can quickly implement a &lt;a href="https://docs.github.com/en/actions" target="_blank" title="GitHub Actions"&gt;GitHub Actions&lt;/a&gt; workflow that will create a release with generated release notes every time you push a tag!&lt;/p&gt;
&lt;h2 id="content-release-workflow" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-release-workflow" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Release workflow&lt;/h2&gt;
&lt;p&gt;Here is an example of a release workflow, placed in &lt;code&gt;.github/workflows/release.yaml&lt;/code&gt; in the root of your repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/actions&lt;/span&gt;

&lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Release"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;on:&lt;/span&gt; &lt;span class="hljs-comment"&gt;# yamllint disable-line rule:truthy&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;push:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;tags:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"**"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;jobs:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;release:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Release"&lt;/span&gt;

    &lt;span class="hljs-attr"&gt;runs-on:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ubuntu-latest"&lt;/span&gt;

    &lt;span class="hljs-attr"&gt;steps:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Determine tag"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;run:&lt;/span&gt; &lt;span class="hljs-string"&gt;"echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" &amp;gt;&amp;gt; $GITHUB_ENV"&lt;/span&gt;

      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Create release"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;uses:&lt;/span&gt; &lt;span class="hljs-string"&gt;"actions/github-script@v6"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;with:&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ secrets.GITHUB_TOKEN }}&lt;/span&gt;"&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;script:&lt;/span&gt; &lt;span class="hljs-string"&gt;|
            try {
              const response = await github.rest.repos.createRelease({
                draft: false,
                generate_release_notes: true,
                name: process.env.RELEASE_TAG,
                owner: context.repo.owner,
                prerelease: false,
                repo: context.repo.repo,
                tag_name: process.env.RELEASE_TAG,
              });
&lt;/span&gt;
              &lt;span class="hljs-string"&gt;core.exportVariable('RELEASE_ID',&lt;/span&gt; &lt;span class="hljs-string"&gt;response.data.id);&lt;/span&gt;
              &lt;span class="hljs-string"&gt;core.exportVariable('RELEASE_UPLOAD_URL',&lt;/span&gt; &lt;span class="hljs-string"&gt;response.data.upload_url);&lt;/span&gt;
            &lt;span class="hljs-string"&gt;}&lt;/span&gt; &lt;span class="hljs-string"&gt;catch&lt;/span&gt; &lt;span class="hljs-string"&gt;(error)&lt;/span&gt; &lt;span class="hljs-string"&gt;{&lt;/span&gt;
              &lt;span class="hljs-string"&gt;core.setFailed(error.message);&lt;/span&gt;
            &lt;span class="hljs-string"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a release workflow in place, run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh hljs bash" data-lang="sh"&gt;git tag -s x.y.z
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;followed by&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh hljs bash" data-lang="sh"&gt;git push --tags
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to create a release with automatically generated release notes from the terminal.&lt;/p&gt;
&lt;h2 id="content-composite-action" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-composite-action" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Composite action&lt;/h2&gt;
&lt;p&gt;Since I have started to use this workflow in all of my open-source repositories on GitHub, I have extracted this workflow into a &lt;a href="https://github.com/ergebnis/.github#github-release-create" target="_blank" title="ergebnis/.github/actions/github/release/create, a composite action to create a release"&gt;composite action in &lt;code&gt;ergebnis/.github&lt;/code&gt;&lt;/a&gt; for easier reuse.&lt;/p&gt;
&lt;p&gt;Here is what the composite action looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action&lt;/span&gt;
&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs&lt;/span&gt;
&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-run-steps-actions&lt;/span&gt;
&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/rest/reference/releases#create-a-release&lt;/span&gt;
&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push&lt;/span&gt;

&lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Create a release"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;description:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Creates a release"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;inputs:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;description:&lt;/span&gt; &lt;span class="hljs-string"&gt;"GitHub token of a user with permission to create a release"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;required:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;

&lt;span class="hljs-attr"&gt;runs:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;using:&lt;/span&gt; &lt;span class="hljs-string"&gt;"composite"&lt;/span&gt;

  &lt;span class="hljs-attr"&gt;steps:&lt;/span&gt;
    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Determine tag"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;if:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ github.event_name }}&lt;/span&gt; == 'push' &amp;amp;&amp;amp; $&lt;span class="hljs-template-variable"&gt;{{ github.ref_type }}&lt;/span&gt; == 'tag'"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;run:&lt;/span&gt; &lt;span class="hljs-string"&gt;"echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" &amp;gt;&amp;gt; $GITHUB_ENV"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;shell:&lt;/span&gt; &lt;span class="hljs-string"&gt;"bash"&lt;/span&gt;

    &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Create release"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;uses:&lt;/span&gt; &lt;span class="hljs-string"&gt;"actions/github-script@v6.3.3"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;with:&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ inputs.github-token }}&lt;/span&gt;"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;script:&lt;/span&gt; &lt;span class="hljs-string"&gt;|
          if (!process.env.RELEASE_TAG) {
            core.setFailed("The environment variable RELEASE_TAG is not defined.")
&lt;/span&gt;
            &lt;span class="hljs-string"&gt;return;&lt;/span&gt;
          &lt;span class="hljs-string"&gt;}&lt;/span&gt;

          &lt;span class="hljs-string"&gt;try&lt;/span&gt; &lt;span class="hljs-string"&gt;{&lt;/span&gt;
            &lt;span class="hljs-string"&gt;const&lt;/span&gt; &lt;span class="hljs-string"&gt;response&lt;/span&gt; &lt;span class="hljs-string"&gt;=&lt;/span&gt; &lt;span class="hljs-string"&gt;await&lt;/span&gt; &lt;span class="hljs-string"&gt;github.rest.repos.createRelease({&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;draft:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;generate_release_notes:&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;process.env.RELEASE_TAG,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;owner:&lt;/span&gt; &lt;span class="hljs-string"&gt;context.repo.owner,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;prerelease:&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;repo:&lt;/span&gt; &lt;span class="hljs-string"&gt;context.repo.repo,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;tag_name:&lt;/span&gt; &lt;span class="hljs-string"&gt;process.env.RELEASE_TAG,&lt;/span&gt;
            &lt;span class="hljs-string"&gt;});&lt;/span&gt;

            &lt;span class="hljs-string"&gt;core.exportVariable('RELEASE_ID',&lt;/span&gt; &lt;span class="hljs-string"&gt;response.data.id);&lt;/span&gt;
            &lt;span class="hljs-string"&gt;core.exportVariable('RELEASE_UPLOAD_URL',&lt;/span&gt; &lt;span class="hljs-string"&gt;response.data.upload_url);&lt;/span&gt;
          &lt;span class="hljs-string"&gt;}&lt;/span&gt; &lt;span class="hljs-string"&gt;catch&lt;/span&gt; &lt;span class="hljs-string"&gt;(error)&lt;/span&gt; &lt;span class="hljs-string"&gt;{&lt;/span&gt;
            &lt;span class="hljs-string"&gt;core.setFailed(error.message);&lt;/span&gt;
          &lt;span class="hljs-string"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-release-workflow-using-composite-action" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-release-workflow-using-composite-action" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Release workflow using composite action&lt;/h2&gt;
&lt;p&gt;Thanks to the composite action above, a workflow for creating a release on GitHub with automatically generated release notes is as simple as this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/actions&lt;/span&gt;

&lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Release"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;on:&lt;/span&gt; &lt;span class="hljs-comment"&gt;# yamllint disable-line rule:truthy&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;push:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;tags:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"**"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;jobs:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;release:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Release"&lt;/span&gt;

    &lt;span class="hljs-attr"&gt;runs-on:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ubuntu-latest"&lt;/span&gt;

    &lt;span class="hljs-attr"&gt;steps:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Create release"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;uses:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ergebnis/.github/actions/github/release/create@1.8.0"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;with:&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ secrets.GITHUB_TOKEN }}&lt;/span&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see an example of a release with release notes created by this workflow in &lt;a target="_blank" href="https://github.com/ergebnis/data-provider/releases/tag/1.3.0" title="ergebnis/data-provider:1.3.0"&gt;&lt;code&gt;ergebnis/data-provider:1.3.0&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

</content></entry><entry><title>Wrapping it up</title><category term="coding-standards"/><category term="maintenance"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2021/12/31/wrapping-it-up/"/><id>https://localheinz.com/articles/2021/12/31/wrapping-it-up/</id><updated>2021-12-31T19:30:00+02:00</updated><content>&lt;h1&gt;
  Wrapping it up
&lt;/h1&gt;


&lt;p&gt;Writing is a social activity: although often performed in solitude, all writers write to be read by an audience. If you have difficulties finding readers, you have failed - whether you write for print or web, fiction or non-fiction, or computer programs.&lt;/p&gt;
&lt;h2 id="content-line-length" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-line-length" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Line Length&lt;/h2&gt;
&lt;p&gt;Typographers of print and web agree that the hard limit for line length should be somewhere between 50 and 75 characters (including spaces). When lines are too short, readers will be too busy traveling back and forth between the beginning and end of the line. When lines are too long, readers will have difficulties finding the following line.&lt;/p&gt;
&lt;p&gt;Computer programmers usually have a different understanding.&lt;/p&gt;
&lt;p&gt;In 1920, IBM introduced rectangular punched cards with 80 columns per line. Little did they know that these 80 columns would determine the width of computer terminals for years to come.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Punched cards from IBM Deutschland" title="Punched cards from IBM Deutschland" class="figure-img img-fluid webfeedsFeaturedVisual" src="/dist/img/article/2021/12/31/wrapping-it-up/punched-cards.png?c6b293c"&gt;
  &lt;figcaption&gt;
    &lt;p&gt;
        Punched cards (Ziffernkarten) from IBM Deutschland, with 80 columns each. Feel like you need a punched card? &lt;a target="_blank" href="mailto:punched-card@localheinz.com?subject=Punched%20card" title="Send me an email"&gt;Send me an email&lt;/a&gt; with your mailing address and I will send you a punched card by snail mail.
    &lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In 2012, the &lt;a target="_blank" href="https://www.php-fig.org" title="PHP-FIG"&gt;PHP Framework Interop Group (PHP-FIG)&lt;/a&gt; accepted &lt;a target="_blank" href="https://github.com/php-fig/fig-standards/blob/ea469e35fd701468ccb48e523b2734073eb00a16/accepted/PSR-2-coding-style-guide.md#23-lines" title="PSR-2: 2.3. Lines"&gt;PSR-2&lt;/a&gt;, which recommended the following:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    There MUST NOT be a hard limit on line length.
  &lt;/p&gt;
  &lt;p&gt;
    The soft limit on line length MUST be 120 characters; automated style checkers MUST warn but MUST NOT error at the soft limit.
  &lt;/p&gt;
  &lt;p&gt;
    Lines SHOULD NOT be longer than 80 characters; lines longer than that SHOULD be split into multiple subsequent lines of no more than 80 characters each.
  &lt;/p&gt;
  &lt;p&gt;
    There MUST NOT be trailing whitespace at the end of non-blank lines.
  &lt;/p&gt;
  &lt;p&gt;
    Blank lines MAY be added to improve readability and to indicate related blocks of code.
  &lt;/p&gt;
  &lt;p&gt;
    There MUST NOT be more than one statement per line.
  &lt;/p&gt;
  &lt;div class="source"&gt;
    &lt;a target="_blank" href="https://www.php-fig.org" title="PHP-FIG"&gt;PHP Framework Interop Group (PHP-FIG)&lt;/a&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;In 2019, the PHP Framework Interop Group accepted &lt;a target="_blank" href="https://github.com/php-fig/fig-standards/blob/ea469e35fd701468ccb48e523b2734073eb00a16/accepted/PSR-12-extended-coding-style-guide.md#23-lines" title="PSR-12: 2.3. Lines"&gt;PSR-12&lt;/a&gt; as a replacement for PSR-2, which still recommends the same.&lt;/p&gt;
&lt;p&gt;The popular development platform &lt;a target="_blank" href="https://github.com" title="GitHub: Where the world builds software"&gt;GitHub&lt;/a&gt; allows&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;139 characters per line in the &lt;a target="_blank" href="https://docs.github.com/en/pull-requests/committing-changes-to-your-project/viewing-and-comparing-commits/comparing-commits" title="GitHub Docs: Comparing commits"&gt;compare branches view&lt;/a&gt; before starting to wrap lines&lt;/li&gt;
&lt;li&gt;150 characters per line in the &lt;a target="_blank" href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews" title="GitHub Docs: About pull request reviews"&gt;pull request view&lt;/a&gt; before starting horizontal scrolling&lt;/li&gt;
&lt;li&gt;154 characters per line in the &lt;a target="_blank" href="https://docs.github.com/en/repositories/working-with-files/using-files/navigating-code-on-github" title="GitHub Docs: Navigating code on GitHub"&gt;file view&lt;/a&gt; before starting to wrap lines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But hey, these are only recommendations and external constraints, and you can buy ultra-wide monitors for very little money!&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Ultra-wide monitor" class="figure-img img-fluid" src="/dist/img/article/2021/12/31/wrapping-it-up/ultra-wide-monitor.png?c6b293c" title="Ultra-wide monitor"&gt;
  &lt;figcaption&gt;
    &lt;p&gt;
      An example of an ultra-wide monitor. I have never had one, and if I did, I am not sure I would be comfortable using its entire width for code. Instead, I am a big fan of &lt;a href="/articles/2019/10/14/test-to-the-left-production-to-the-right/" target="_blank" title="Test&amp;#x20;to&amp;#x20;the&amp;#x20;left,&amp;#x20;production&amp;#x20;to&amp;#x20;the&amp;#x20;right"&gt;splitting editor windows&lt;/a&gt;.
    &lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So why bother with line lengths?&lt;/p&gt;
&lt;h2 id="content-maintenance" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-maintenance" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Maintenance&lt;/h2&gt;
&lt;p&gt;While the compiler, the most frequent reader of the code, typically does not care whether a computer program is written in a single line or not, maintainers of the computer program will.&lt;/p&gt;
&lt;p&gt;Like skimming an article, I tend to recognize patterns when reading code. If you have ever played &lt;a target="_blank" href="https://en.wikipedia.org/wiki/Minesweeper_(video_game)" title="Minesweeper"&gt;Minesweeper&lt;/a&gt; for long enough, you know the feeling: you do not have to think hard where mines hide but recognize patterns on the board and flag them.&lt;/p&gt;
&lt;p&gt;In my experience, complexity in code - the equivalent of mines in Minesweeper - tends to hide in long lines. So why not make it easier for maintainers and expose that complexity by unwrapping it into multiple lines?&lt;/p&gt;
&lt;h2 id="content-candidates" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-candidates" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Candidates&lt;/h2&gt;
&lt;p&gt;There are several candidates where authors of computer programs typically exceed line lengths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#content-annotations" title="Annotations"&gt;annotations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-attributes" title="Attributes"&gt;attributes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-array-initializations" title="Array Initializations"&gt;array initializations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-conditions" title="Conditions"&gt;conditions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-constructor-declarations" title="Constructor Declarations"&gt;constructor declarations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-constructor-invocations" title="Constructor Invocations"&gt;constructor invocations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-function-declarations" title="Function Declarations"&gt;function declarations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-function-invocations" title="Function Invocations"&gt;function invocations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-method-declarations" title="Method Declarations"&gt;method declarations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-method-invocations" title="Method Invocations"&gt;method invocations&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-annotations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-annotations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Annotations&lt;/h2&gt;
&lt;p&gt;Annotations allow developers to configure meta-data for classes, methods, fields, and functions. However, they are not a native feature of PHP and require an annotation parser, such as the widely used &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;doctrine&amp;#x2F;annotations" target="_blank" title="doctrine/annotations on GitHub"&gt;&lt;code&gt;doctrine/annotations&lt;/code&gt;&lt;/a&gt; package, to read the meta-data from the DocBlocks.&lt;/p&gt;
&lt;p&gt;For example, when using the popular &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;doctrine&amp;#x2F;orm" target="_blank" title="doctrine/orm on GitHub"&gt;&lt;code&gt;doctrine/orm&lt;/code&gt;&lt;/a&gt; package, a developer can add annotations to a class to declare it as an entity.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Entity&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;ORM&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/**
 * &lt;span class="hljs-doctag"&gt;@ORM&lt;/span&gt;\Mapping\Entity(repositoryClass="App\Repository\UserRepository")
 */&lt;/span&gt;
&lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;User&lt;/span&gt;
&lt;/span&gt;{
    &lt;span class="hljs-comment"&gt;// ...&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The annotation &lt;code&gt;Doctrine\ORM\Mapping\Entity&lt;/code&gt; refers to a &lt;a target="_blank" href="https://github.com/doctrine/orm/blob/v2.7.3/lib/Doctrine/ORM/Mapping/Entity.php" title="doctrine/orm: Doctrine\ORM\Mapping\Entity"&gt;class with the same name&lt;/a&gt;. The class declares properties, and by adding the annotation here, the annotation reader creates an instance of that class where the &lt;code&gt;$repositoryClass&lt;/code&gt; property has the value &lt;code&gt;'App\Repository\UserRepository'&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Apart from &lt;code&gt;doctrine/orm&lt;/code&gt;, there are a lot of popular packages out there that allow developers to add annotations, and a lot of these annotations can have a lot of properties. So when developers add more and more annotations with more and more properties, these annotations can become hard to read, understand, and maintain.&lt;/p&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  When an annotation has more than one property, wrap properties so that every property is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 namespace App\Entity;

 use Doctrine\ORM;

 /**
  * @ORM\Mapping\Entity(repositoryClass="App\Repository\UserRepository")
&lt;span class="hljs-deletion"&gt;- * @ORM\Mapping\Table(name="user", indexes={@ORM\Mapping\Index(name="email_idx", columns={"email"})})&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * @ORM\Mapping\Table(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *     name="user",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *     indexes={&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *         @ORM\Mapping\Index(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *             name="email_idx",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *             columns={&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *                 "email",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *             },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *         ),&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *     },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * )&lt;/span&gt;
  */
  class User
  {
      // ...

      /**
&lt;span class="hljs-deletion"&gt;-      * @ORM\Mapping\Column(name="email", type="string")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      * @ORM\Mapping\Column(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      *     name="email",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      *     type="string",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      * )&lt;/span&gt;
       */
      private string $email;

      // ...
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying the rule has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the complexity of the annotations - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;When adding a new property to an existing annotation, it will be more obvious where to add it.&lt;/li&gt;
&lt;li&gt;When adding a new value to an existing annotation, it will be more obvious where to add it.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You might find it easier to wrap and indent annotations when enabling the &lt;a target="_blank" href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.4.0/doc/rules/doctrine_annotation/doctrine_annotation_indentation.rst" title="doctrine_annotation_indentation"&gt;&lt;code&gt;doctrine_annotation_indentation&lt;/code&gt;&lt;/a&gt; fixer for &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;PHP-CS-Fixer&amp;#x2F;PHP-CS-Fixer" target="_blank" title="PHP-CS-Fixer/PHP-CS-Fixer on GitHub"&gt;&lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can quickly sort properties by name, for example, by using the &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/5919-lines-sorter" title="JetBrains Marketplace: Lines Sorter"&gt;Lines Sorter plugin&lt;/a&gt; for PhpStorm.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can quickly move the line under the cursor (or a block of selected lines) up or down by pressing &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;↑&lt;/kbd&gt; or &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;↓&lt;/kbd&gt; on macOS. Also see &lt;a target="_blank" href="https://www.jetbrains.com/idea/guide/tips/move-line/" title="IntelliJ IDEA Guide: Move Line"&gt;Move Line&lt;/a&gt; in the &lt;a target="_blank" href="https://www.jetbrains.com/idea/guide/" title="IntelliJ IDEA Guide"&gt;IntelliJ IDEA Guide&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-attributes" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-attributes" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Attributes&lt;/h2&gt;
&lt;p&gt;Like &lt;a href="#content-annotations" title="Annotations"&gt;annotations&lt;/a&gt;, &lt;a target="_blank" href="https://www.php.net/manual/en/language.attributes.overview.php" title="PHP: Attributes overview"&gt;attributes&lt;/a&gt; allow configuring meta-data on classes, properties, functions, methods, parameters, and constants. They are a native feature of PHP since their introduction with PHP 8.0.&lt;/p&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  When an attribute has more than one property, wrap properties so that every property is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;&amp;lt;?php

 declare(strict_types=1);

 namespace App\Http;

 use Symfony\Component\HttpFoundation;
 use Symfony\Component\Routing;

 final class HealthAction
 {
&lt;span class="hljs-deletion"&gt;-    #[Routing\Annotation\Route(path: '/health/', name: 'health', methods: [HttpFoundation\Request::METHOD_GET])]&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    #[Routing\Annotation\Route(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        path: '/health/',&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        name: 'health',&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        methods: [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            HttpFoundation\Request::METHOD_GET,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        ],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    )]&lt;/span&gt;
     public function __invoke(): HttpFoundation\JsonResponse
     {
         // ...
     }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying the rule has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the complexity of the attributes - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;When adding a new property to an existing attribute, it will be more obvious where to add it.&lt;/li&gt;
&lt;li&gt;When adding a new value to an existing attribute, it will be more obvious where to add it.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can quickly sort properties by name, for example, by using the &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/5919-lines-sorter" title="JetBrains Marketplace: Lines Sorter"&gt;Lines Sorter plugin&lt;/a&gt; for PhpStorm.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can quickly move the line under the cursor (or a block of selected lines) up or down by pressing &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;↑&lt;/kbd&gt; or &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;↓&lt;/kbd&gt; on macOS. Also see &lt;a target="_blank" href="https://www.jetbrains.com/idea/guide/tips/move-line/" title="IntelliJ IDEA Guide: Move Line"&gt;Move Line&lt;/a&gt; in the &lt;a target="_blank" href="https://www.jetbrains.com/idea/guide/" title="IntelliJ IDEA Guide"&gt;IntelliJ IDEA Guide&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-array-initializations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-array-initializations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Array Initializations&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  When initializing a non-empty array, wrap elements so that each element is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;&amp;lt;?php

 declare(strict_types=1);

 $foo = [];

&lt;span class="hljs-deletion"&gt;-$bar = ['baz'];&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+$bar = [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    'baz',&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+];&lt;/span&gt;

&lt;span class="hljs-deletion"&gt;-$baz = ['qux' =&amp;gt; ['quux', 'quuz'], 'corge' =&amp;gt; 'grault'];&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+$baz = [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    'qux' =&amp;gt; [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        'quux',&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        'quuz',&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    ],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    'corge' =&amp;gt; 'grault',&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying the rule has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the structure of the array - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;When an array is a list, and the order does not matter, you can easily sort elements by value.&lt;/li&gt;
&lt;li&gt;When an array is a map, and the order does not matter, you can easily sort elements by key.&lt;/li&gt;
&lt;li&gt;When adding a new element to a list or a map, it will be more obvious where to add it.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can quickly sort elements of a map by name and elements of a list by value, for example, by using the &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/5919-lines-sorter" title="JetBrains Marketplace: Lines Sorter"&gt;Lines Sorter plugin&lt;/a&gt; for PhpStorm.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 You can quickly move the line under the cursor (or a block of selected lines) up or down by pressing &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;↑&lt;/kbd&gt; or &lt;kbd&gt;Option&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;↓&lt;/kbd&gt; on macOS. Also see &lt;a target="_blank" href="https://www.jetbrains.com/idea/guide/tips/move-line/" title="IntelliJ IDEA Guide: Move Line"&gt;Move Line&lt;/a&gt; in the &lt;a target="_blank" href="https://www.jetbrains.com/idea/guide/" title="IntelliJ IDEA Guide"&gt;IntelliJ IDEA Guide&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="content-conditions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-conditions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Conditions&lt;/h3&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  When a condition has more than one predicate, wrap predicates so that each predicate is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-if (($booking-&amp;gt;getStatus() === Booking::STATUS_FIXED&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        || ($booking-&amp;gt;getStatus() === Booking::STATUS_PROMISED &amp;amp;&amp;amp; ($booking-&amp;gt;getPromisedUntil() &amp;gt; $now || ($buildingIndex &amp;amp;&amp;amp; $booking-&amp;gt;getSynchronizeExpired()))))&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    &amp;amp;&amp;amp; $allocation-&amp;gt;getCapabilityBegin() &amp;lt; $allocation-&amp;gt;getCapabilityEnd()) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+if (&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    (&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $booking-&amp;gt;getStatus() === Booking::STATUS_FIXED&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        || (&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $booking-&amp;gt;getStatus() === Booking::STATUS_PROMISED&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            &amp;amp;&amp;amp; (&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+                $booking-&amp;gt;getPromisedUntil() &amp;gt; $now&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+                || (&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+                    $buildingIndex&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+                    &amp;amp;&amp;amp; $booking-&amp;gt;getSynchronizeExpired()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+                )&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            )&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        )&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    )&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    &amp;amp;&amp;amp; $allocation-&amp;gt;getCapabilityBegin() &amp;lt; $allocation-&amp;gt;getCapabilityEnd()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+) {&lt;/span&gt;
     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying the rule has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the complexity of the condition - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;You can more easily identify opportunities for improvement and refactoring - all of which you could have easily missed if all predicates were on the same line.&lt;/li&gt;
&lt;li&gt;Are there too many predicates? Why?&lt;/li&gt;
&lt;li&gt;Are the predicates using &lt;code&gt;||&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; combined correctly? Why not?&lt;/li&gt;
&lt;li&gt;Should all cases be handled the same? Why?&lt;/li&gt;
&lt;li&gt;Are there automated tests that assert that correct behaviour for all cases? Why not?&lt;/li&gt;
&lt;li&gt;Can we reduce the cognitive load by breaking, continueing, or returning early? Why not?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-constructor-declarations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-constructor-declarations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Constructor Declarations&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  When a constructor declares more than one parameter, wrap parameters so that each parameter is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;&amp;lt;?php

 declare(strict_types=1);

 class Request
 {
     // ...

&lt;span class="hljs-deletion"&gt;-    public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function __construct(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        array $query = [],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        array $request = [],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        array $attributes = [],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        array $cookies = [],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        array $files = [],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        array $server = [],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $content = null,&lt;/span&gt;
     ) {
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying the rule has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the number of constructor parameters along with their visibilities, readonly modifiers, and default values - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;You can more easily identify opportunities for improvement and refactoring - all of which you could have easily missed if all parameters were on the same line.&lt;/li&gt;
&lt;li&gt;Does the constructor have too many parameters? Perhaps the class is doing too much?&lt;/li&gt;
&lt;li&gt;When using &lt;a target="_blank" href="https://wiki.php.net/rfc/constructor_promotion" title="RFC: Constructor Property Promotion"&gt;constructor property promotion&lt;/a&gt; on PHP 8.0, do all parameters have visibilities? Why not?&lt;/li&gt;
&lt;li&gt;When using &lt;a target="_blank" href="https://wiki.php.net/rfc/readonly_properties_v2" title="RFC: Readonly Properties"&gt;readonly properties&lt;/a&gt; on PHP 8.1, do all parameters have readonly modifiers? Why not?&lt;/li&gt;
&lt;li&gt;Do all parameters have type declarations? Why not?&lt;/li&gt;
&lt;li&gt;Does the constructor have parameters with default values? Why?&lt;/li&gt;
&lt;li&gt;Does the constructor have unused parameters? Why?&lt;/li&gt;
&lt;li&gt;Do you keep seeing the same parameters used together again and again? Perhaps they belong together in a service or a value object?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-constructor-invocations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-constructor-invocations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Constructor Invocations&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  When a constructor invocation uses more than one argument, wrap arguments so that each argument is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 abstract class AbstractBrowser
 {
     // ...

     public function request(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], string $content = null, bool $changeHistory = true): Crawler
     {
         // ...

&lt;span class="hljs-deletion"&gt;-        $this-&amp;gt;internalRequest = new Request($uri, $method, $parameters, $files, $this-&amp;gt;cookieJar-&amp;gt;allValues($uri), $server, $content);&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $this-&amp;gt;internalRequest = new Request(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $uri,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $method,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $parameters,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $files,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $this-&amp;gt;cookieJar-&amp;gt;allValues($uri),&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $server,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $content,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        );&lt;/span&gt;

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at the &lt;a href="#content-constructor-declarations" title="Constructor Declarations"&gt;constructor declaration&lt;/a&gt; from the call site, applying the rule has similar advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the number of constructor arguments - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;You can more easily identify opportunities for improvement and refactoring - all of which you could have easily missed if all arguments were on the same line.&lt;/li&gt;
&lt;li&gt;Are there too many arguments? Perhaps the object is doing too much?&lt;/li&gt;
&lt;li&gt;Are there more arguments than the constructor accepts? Why?&lt;/li&gt;
&lt;li&gt;Are there arguments that are identical to the corresponding parameter default value? Why?&lt;/li&gt;
&lt;li&gt;Do you keep seeing the same arguments used together again and again? Perhaps they belong together in a service or a value object?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-function-declarations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-function-declarations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Function Declarations&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  With exceptions, when a function declares more than one parameter, wrap parameters so that each parameter is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-function blog_get_headers($courseid = null, $groupid = null, $userid = null, $tagid = null, $tag = null, $modid = null, $entryid=null, $search = null) {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+function blog_get_headers(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $courseid = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $groupid = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $userid = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $tagid = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $tag = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $modid = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $entryid = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $search = null&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+) {&lt;/span&gt;
     // ...
 }

 // ...

 $filter = static function (string $value): bool {
     return '' !== trim($value);
 };

 // ...

 $reducer = static function (array $carry, array $item): array {
     // ...
 });

 // ...

 $sorter = static function (string $a, string $b): int {
       // ...
 };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying the rule has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the number of parameters - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;You can more easily identify opportunities for improvement and refactoring - all of which you could have easily missed if all parameters were on the same line.&lt;/li&gt;
&lt;li&gt;Does the function have too many parameters? Perhaps the function is doing too much?&lt;/li&gt;
&lt;li&gt;Do all parameters have type declarations? Why not?&lt;/li&gt;
&lt;li&gt;Does the function have parameters with default values? Why?&lt;/li&gt;
&lt;li&gt;Does the function have unused parameters? Why?&lt;/li&gt;
&lt;li&gt;Do you keep seeing the same parameters used together again and again? Perhaps they belong together in a service or a value object?&lt;/li&gt;
&lt;li&gt;Does the function have a return type declaration? Why not?&lt;/li&gt;
&lt;li&gt;Is the return type declaration nullable? Why?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-function-invocations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-function-invocations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Function Invocations&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  With exceptions, when a function invocation uses more than one argument, wrap arguments so that each argument is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 // userland function, n-ary
&lt;span class="hljs-deletion"&gt;-$blogheaders = blog_get_headers($filters['courseid'], $filters['groupid'], $filters['userid'], $filters['tagid'], $filters['tag'], $filters['cmid'], $filters['entryid'], $filters['search']);&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+$blogheaders = blog_get_headers(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['courseid'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['groupid'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['userid'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['tagid'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['tag'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['cmid'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['entryid'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    $filters['search'],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+);&lt;/span&gt;

 // native function invocation, binary
 $parts = explode(', ', $list);

 // native function invocation, binary
 $list = implode(', [
     'Gin',
     'Tonic',
     'Lemons',
 ]);

 // native function invocation, binary, using anonymous function with one argument as first argument
 $mapped = array_map(static function (string $value): string {
     // ...
 }, $values);

 // native function invocation, binary, using anonymous function with one argument as second argument
 $filtered = array_filter($values, static function (string $value): bool {
     // ...
 });

 // native function invocation, binary, using anonymous function with two arguments as as second argument
 usort($values, static function (string $a, string $b): int {
     // ...
 });

 // native function invocation, ternary
 $reduced = array_reduce(
     $values,
     static function (array $carry, array $item): array {
         // ...
     },
     []
 );

 // native function invocation, variying arity
 $message = sprintf(
     'Value should be one of "%s", got "%s" instead.',
     implode('", "', $values),
     $value
 );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at the &lt;a href="#content-function-declaration" title="Function Declarations"&gt;function declaration&lt;/a&gt; from the call site, applying the rule has similar advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the number of function arguments - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;You can more easily identify opportunities for improvement and refactoring - all of which you could have easily missed if all arguments were on the same line.&lt;/li&gt;
&lt;li&gt;Are there too many arguments? Perhaps the function is doing too much?&lt;/li&gt;
&lt;li&gt;Are there more arguments than the function accepts? Why?&lt;/li&gt;
&lt;li&gt;Are there arguments that are identical to the corresponding parameter default value? Why?&lt;/li&gt;
&lt;li&gt;Do you keep seeing the same arguments used together again and again? Perhaps they belong together in a service or a value object?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-method-declarations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-method-declarations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Method Declarations&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  When a method declares more than one parameter, wrap parameters so that each parameter is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class JWTCookieProvider
 {
     // ...

&lt;span class="hljs-deletion"&gt;-    public function createCookie(string $jwt, ?string $name = null, $expiresAt = null, ?string $sameSite = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, array $split = []): Cookie&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function createCookie(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        string $jwt,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        ?string $name = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $expiresAt = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        ?string $sameSite = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        ?string $path = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        ?string $domain = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        ?bool $secure = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        ?bool $httpOnly = null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        array $split = [],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    ): Cookie {&lt;/span&gt;
         // ...
     }

     // ...
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar to &lt;a href="#content-function-declarations" title="Function Declarations"&gt;function declarations&lt;/a&gt;, applying this rule has the following advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the number of parameters - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;You can more easily identify opportunities for improvement and refactoring - all of which you could have easily missed if all parameters were on the same line.&lt;/li&gt;
&lt;li&gt;Does the method have too many parameters? Perhaps the method is doing too much?&lt;/li&gt;
&lt;li&gt;Do all parameters have type declarations? Why not?&lt;/li&gt;
&lt;li&gt;Does the method have parameters with default values? Why?&lt;/li&gt;
&lt;li&gt;Does the method have unused parameters? Why?&lt;/li&gt;
&lt;li&gt;Do you keep seeing the same parameters used together again and again? Perhaps they belong together in a service or a value object?&lt;/li&gt;
&lt;li&gt;Does the method have a return type declaration? Why not?&lt;/li&gt;
&lt;li&gt;Is the return type declaration nullable? Why?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-method-invocations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-method-invocations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Method Invocations&lt;/h2&gt;
&lt;div class="rule"&gt;
  &lt;p&gt;
  With exceptions, when a method invocation uses more than one argument, wrap arguments so that each argument is on a new line.
&lt;/p&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class LoginSuccessHandler implements AuthenticationSuccessHandlerInterface
 {
     // ...
     public function handleAuthenticationSuccess(UserInterface $user, ?string $jwt = null): Response
     {
         // ...

&lt;span class="hljs-deletion"&gt;-        $jwtCookie = $this-&amp;gt;cookieProvider-&amp;gt;createCookie($jwt, null, $cookieLifetime, null);&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $jwtCookie = $this-&amp;gt;cookieProvider-&amp;gt;createCookie(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $jwt,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            $cookieLifetime,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            null,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        );&lt;/span&gt;

         // ...
     }
 }

 final class ListAction
 {
     // ...

     public function __invoke(): HttpFoundation\Response
     {
         // ...

         $content = $this-&amp;gt;twig-&amp;gt;render('view/article/list.html.twig', [
             'articlesGroupedByYear' =&amp;gt; $articlesGroupedByYear,
         ]);

         // ...
     }

     // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at the &lt;a href="#content-method-declarations" title="Method Declarations"&gt;method declaration&lt;/a&gt; from the call site, applying the rule has similar advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can immediately see the number of method arguments - no horizontal scrolling required.&lt;/li&gt;
&lt;li&gt;You can more easily identify opportunities for improvement and refactoring - all of which you could have easily missed if all arguments were on the same line.&lt;/li&gt;
&lt;li&gt;Are there too many arguments? Perhaps the method is doing too much?&lt;/li&gt;
&lt;li&gt;Are there more arguments than the method accepts? Why?&lt;/li&gt;
&lt;li&gt;Are there arguments that are identical to the corresponding parameter default value? Why?&lt;/li&gt;
&lt;li&gt;Do you keep seeing the same arguments used together again and again? Perhaps they belong together in a service or a value object?&lt;/li&gt;
&lt;/ul&gt;

</content></entry><entry><title>Creating Doctrine entities populated with fake data</title><category term="doctrine"/><category term="faker"/><category term="fixture"/><category term="php"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2020/07/16/creating-doctrine-entities-populated-with-fake-data/"/><id>https://localheinz.com/articles/2020/07/16/creating-doctrine-entities-populated-with-fake-data/</id><updated>2020-07-16T18:30:00+02:00</updated><content>&lt;h1&gt;
  Creating Doctrine entities populated with fake data
&lt;/h1&gt;


&lt;p&gt;Creating database fixtures in projects using &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;doctrine&amp;#x2F;orm" target="_blank" title="doctrine/orm on GitHub"&gt;&lt;code&gt;doctrine/orm&lt;/code&gt;&lt;/a&gt; can be cumbersome - but it doesn't have to be.&lt;/p&gt;
&lt;p&gt;When you create and populate entities with fake data by hand, your tests can quickly become bloated and difficult to understand. In need of a cure, you might extract helper methods or classes. The chances are that you use a third-party package for setting up test fixtures.&lt;/p&gt;
&lt;p&gt;At the time of writing, &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;doctrine&amp;#x2F;data-fixtures" target="_blank" title="doctrine/data-fixtures on GitHub"&gt;&lt;code&gt;doctrine/data-fixtures&lt;/code&gt;&lt;/a&gt; and &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;nelmio&amp;#x2F;alice" target="_blank" title="nelmio/alice on GitHub"&gt;&lt;code&gt;nelmio/alice&lt;/code&gt;&lt;/a&gt; are by far the most-downloaded packages for creating and loading fixtures for Doctrine entities, with 33 million and 11 million total downloads, respectively.&lt;/p&gt;
&lt;p&gt;I have used both of them, but there are a few things that bother me.&lt;/p&gt;
&lt;p&gt;The primary issue I have with these packages is that there is a considerable disconnect between entities created in a fixture and those used in a test. Developers can establish a connection loosely, using hard-coded values, or indirectly, using constants. I have found tests using these fixtures hard to understand and difficult to maintain.&lt;/p&gt;
&lt;h2 id="content-breerlyfactory-girl-php" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-breerlyfactory-girl-php" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;&lt;code&gt;breerly/factory-girl-php&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In 2014, a colleague recommended &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;GoodPete&amp;#x2F;factory-girl-php" target="_blank" title="GoodPete/factory-girl-php on GitHub"&gt;&lt;code&gt;breerly/factory-girl-php&lt;/code&gt;&lt;/a&gt;, a port of Ruby's &lt;a target="_blank" href="https://github.com/thoughtbot/factory_bot" title="factory_bot"&gt;&lt;code&gt;factory_bot&lt;/code&gt;&lt;/a&gt; to PHP, and I have been a big fan since.&lt;/p&gt;
&lt;p&gt;The big difference is that instead of creating fixtures, with &lt;code&gt;breerly/factory-girl-php&lt;/code&gt;, I can use a fixture factory to create entity definitions. Based on these definitions, I can then use the fixture factory to create entities. Depending on the nature of the entity definitions, the fixture factory will populate the entities with fake data. Entities can have references to other entities, and the fixture factory establishes associations to these automatically. Similar to the other packages, I can create entities for demonstration or testing purposes. However, the connection between the test code and entities is immediate.&lt;/p&gt;
&lt;p&gt;I have enjoyed working with &lt;code&gt;breerly/factory-girl-php&lt;/code&gt; so much that I started contributing to it. To simplify working with entity definitions, I built &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;factory-girl-definition" target="_blank" title="ergebnis/factory-girl-definition on GitHub"&gt;&lt;code&gt;ergebnis/factory-girl-definition&lt;/code&gt;&lt;/a&gt;, a companion that allows loading entity definitions from a directory. In March 2020, I sent an email to &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;www.linkedin.com&amp;#x2F;in&amp;#x2F;graysonkoonce&amp;#x2F;" target="_blank" title="Grayson&amp;#x20;Koonce&amp;#x20;on&amp;#x20;LinkedIn"&gt;Grayson Koonce&lt;/a&gt;, the owner of &lt;code&gt;breerly/factory-girl-php&lt;/code&gt;. I wondered if he would accept me as a collaborator.&lt;/p&gt;
&lt;h2 id="content-ergebnisfactory-bot" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-ergebnisfactory-bot" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;&lt;code&gt;ergebnis/factory-bot&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Since I never got a reply, I decided to split and started working on &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;factory-bot" target="_blank" title="ergebnis/factory-bot on GitHub"&gt;&lt;code&gt;ergebnis/factory-bot&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Splitting &lt;code&gt;ergebnis/factory-bot&lt;/code&gt; from &lt;code&gt;breerly/factory-girl-php&lt;/code&gt; allows me to work on my terms, make my own decisions, and, more importantly, will enable me to move faster.&lt;/p&gt;
&lt;p&gt;Since splitting, I have removed code that I never needed and modernized the codebase. I have integrated &lt;code&gt;ergebnis/factory-girl-definition&lt;/code&gt;, added features, and foremost, documentation and examples that help you get started.&lt;/p&gt;
&lt;p&gt;You can install &lt;code&gt;ergebnis/factory-bot&lt;/code&gt; by running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer require --dev ergebnis/factory-bot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fixture factory requires an instance of &lt;code&gt;Doctrine\ORM\EntityManagerInterface&lt;/code&gt; (for reading class metadata from Doctrine entities, and for persisting Doctrine entities when necessary) and an instance of &lt;code&gt;Faker\Generator&lt;/code&gt; for generating fake data.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;ORM&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;FactoryBot&lt;/span&gt;\&lt;span class="hljs-title"&gt;FixtureFactory&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Faker&lt;/span&gt;\&lt;span class="hljs-title"&gt;Factory&lt;/span&gt;;

$entityManager = ORM\EntityManager::create(...);
$faker = Factory::create(...);

$fixtureFactory = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; FixtureFactory(
    $entityManager,
    $faker
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the fixture factory set up, you can create definitions for Doctrine entities.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;FactoryBot&lt;/span&gt;\&lt;span class="hljs-title"&gt;Count&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;FactoryBot&lt;/span&gt;\&lt;span class="hljs-title"&gt;FieldDefinition&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;FactoryBot&lt;/span&gt;\&lt;span class="hljs-title"&gt;FixtureFactory&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;\&lt;span class="hljs-title"&gt;Entity&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/** &lt;span class="hljs-doctag"&gt;@var&lt;/span&gt; FixtureFactory $fixtureFactory */&lt;/span&gt;
$fixtureFactory-&amp;gt;define(Entity\User::class, [
    &lt;span class="hljs-string"&gt;'avatar'&lt;/span&gt; =&amp;gt; FieldDefinition::reference(Entity\Avatar::class),
    &lt;span class="hljs-string"&gt;'id'&lt;/span&gt; =&amp;gt; FieldDefinition::closure(&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-params"&gt;(Generator $faker)&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $faker-&amp;gt;uuid;
    }),
    &lt;span class="hljs-string"&gt;'location'&lt;/span&gt; =&amp;gt; FieldDefinition::optionalClosure(&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-params"&gt;(Generator $faker)&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $faker-&amp;gt;city;
    }),
    &lt;span class="hljs-string"&gt;'login'&lt;/span&gt; =&amp;gt; FieldDefinition::closure(&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-params"&gt;(Generator $faker)&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $faker-&amp;gt;userName;
    }),
]);

$fixtureFactory-&amp;gt;define(Entity\Avatar::class, [
    &lt;span class="hljs-string"&gt;'height'&lt;/span&gt; =&amp;gt; FieldDefinition::closure(&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-params"&gt;(Generator $faker)&lt;/span&gt;: &lt;span class="hljs-title"&gt;int&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $faker-&amp;gt;numberBetween(&lt;span class="hljs-number"&gt;300&lt;/span&gt;, &lt;span class="hljs-number"&gt;600&lt;/span&gt;);
    }),
    &lt;span class="hljs-string"&gt;'url'&lt;/span&gt; =&amp;gt; FieldDefinition::closure(&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-params"&gt;(Generator $faker)&lt;/span&gt;: &lt;span class="hljs-title"&gt;string&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $faker-&amp;gt;imageUrl();
    }),
    &lt;span class="hljs-string"&gt;'width'&lt;/span&gt; =&amp;gt; FieldDefinition::closure(&lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-params"&gt;(Generator $faker)&lt;/span&gt;: &lt;span class="hljs-title"&gt;int&lt;/span&gt; &lt;/span&gt;{
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; $faker-&amp;gt;numberBetween(&lt;span class="hljs-number"&gt;400&lt;/span&gt;, &lt;span class="hljs-number"&gt;900&lt;/span&gt;);
    }),
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the fixture factory aware of entity definitions, you can now create entities populated with fake data.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;FactoryBot&lt;/span&gt;\&lt;span class="hljs-title"&gt;Count&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;FactoryBot&lt;/span&gt;\&lt;span class="hljs-title"&gt;FieldDefinition&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;FactoryBot&lt;/span&gt;\&lt;span class="hljs-title"&gt;FixtureFactory&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Example&lt;/span&gt;\&lt;span class="hljs-title"&gt;Entity&lt;/span&gt;;

&lt;span class="hljs-comment"&gt;/** &lt;span class="hljs-doctag"&gt;@var&lt;/span&gt; FixtureFactory $fixtureFactory */&lt;/span&gt;
$user = $fixtureFactory-&amp;gt;createOne(Entity\User::class, [
    &lt;span class="hljs-string"&gt;'login'&lt;/span&gt; =&amp;gt; FieldDefinition::value(&lt;span class="hljs-string"&gt;'localheinz'&lt;/span&gt;),
]);

var_dump($user-&amp;gt;location()); &lt;span class="hljs-comment"&gt;// `null` or random city&lt;/span&gt;
var_dump($user-&amp;gt;login());    &lt;span class="hljs-comment"&gt;// 'localheinz'&lt;/span&gt;

$users = $fixtureFactory-&amp;gt;createMany(
    Entity\User::class,
    Count::between(&lt;span class="hljs-number"&gt;0&lt;/span&gt;, &lt;span class="hljs-number"&gt;10&lt;/span&gt;),
    [
        &lt;span class="hljs-string"&gt;'login'&lt;/span&gt; =&amp;gt; FieldDefinition::sequence(&lt;span class="hljs-string"&gt;'user-%d'&lt;/span&gt;),
    ]
);

var_dump($users); &lt;span class="hljs-comment"&gt;// array with 0-10 instances of Entity\User&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have released &lt;a target="_blank" href="https://github.com/ergebnis/factory-bot/tree/0.2.1" title="ergebnis/factory-bot:0.2.0"&gt;&lt;code&gt;ergebnis/factory-bot:0.2.0&lt;/code&gt;&lt;/a&gt; yesterday, and you can take a look at the &lt;a target="_blank" href="https://github.com/ergebnis/factory-bot/blob/main/README.md" title="ergebnis/factory-bot: Documentation"&gt;documentation&lt;/a&gt;
and &lt;a target="_blank" href="https://github.com/ergebnis/factory-bot/blob/main/example" title="ergebnis/factory-bot: Examples"&gt;examples&lt;/a&gt;, and try it out today.&lt;/p&gt;

</content></entry><entry><title>Merging pull requests with GitHub Actions</title><category term="dependabot"/><category term="github"/><category term="github-actions"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2020/06/15/merging-pull-requests-with-github-actions/"/><id>https://localheinz.com/articles/2020/06/15/merging-pull-requests-with-github-actions/</id><updated>2020-06-15T10:25:00+02:00</updated><content>&lt;h1&gt;
  Merging pull requests with GitHub Actions
&lt;/h1&gt;


&lt;p&gt;In May 2019, &lt;a target="_blank" href="https://web.archive.org/web/20210527020017/https://dependabot.com/blog/hello-github/" title="Dependabot is joining GitHub"&gt;GitHub acquired Dependabot&lt;/a&gt;, and recently &lt;a target="_blank" href="https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/" title="Keep all your packages up to date with Dependabot"&gt;GitHub announced that Dependabot is moving into GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When you follow the instructions, you will find that the migration is straightforward. However, there is one thing missing: after migrating from version 1 to version 2, Dependabot will not automatically merge pull requests anymore. That the feature is currently missing could indicate that GitHub will bring automatic merges onboard - independently from Dependabot.&lt;/p&gt;
&lt;p&gt;In the meantime, you can configure a workflow with &lt;a target="_blank" href="https://github.com/features/actions" title="GitHub Actions"&gt;GitHub Actions&lt;/a&gt; and &lt;a target="_blank" href="https://github.com/actions/github-script" title="actions/github-script"&gt;&lt;code&gt;actions/github-script&lt;/code&gt;&lt;/a&gt; that will automatically merge pull requests created by Dependabot. &lt;code&gt;actions/github-script&lt;/code&gt; uses &lt;a target="_blank" href="https://github.com/octokit/rest.js" title="octokit/rest.js"&gt;&lt;code&gt;octokit/rest.js&lt;/code&gt;&lt;/a&gt;, which is &lt;a target="_blank" href="https://octokit.github.io/rest.js/v17" title="Documentation for octokit/rest.js"&gt;well documented&lt;/a&gt;, and it is often more convenient to use than maintaining a full-blown GitHub action.&lt;/p&gt;
&lt;p&gt;Here is an example of a workflow that will run when the &lt;code&gt;Integrate&lt;/code&gt; workflow has successfully completed for a pull request opened by &lt;code&gt;dependabot[bot]&lt;/code&gt; that updates development or production dependencies for &lt;code&gt;composer&lt;/code&gt;, or updates GitHub actions.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Merge"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;on:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;workflow_run:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;types:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"completed"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;workflows:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"Integrate"&lt;/span&gt;

&lt;span class="hljs-attr"&gt;jobs:&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;merge:&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Merge"&lt;/span&gt;

    &lt;span class="hljs-attr"&gt;runs-on:&lt;/span&gt; &lt;span class="hljs-string"&gt;"ubuntu-latest"&lt;/span&gt;

    &lt;span class="hljs-attr"&gt;if:&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;gt;
      github.event.workflow_run.event == 'pull_request' &amp;amp;&amp;amp;
      github.event.workflow_run.conclusion == 'success' &amp;amp;&amp;amp;
      github.actor == 'dependabot[bot]' &amp;amp;&amp;amp; (
        startsWith(github.event.workflow_run.head_commit.message, 'composer(deps)') ||
        startsWith(github.event.workflow_run.head_commit.message, 'composer(deps-dev)') ||
        startsWith(github.event.workflow_run.head_commit.message, 'github-actions(deps)')
      )
&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;steps:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Request review from @ergebnis-bot"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;uses:&lt;/span&gt; &lt;span class="hljs-string"&gt;"actions/github-script@v5"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;with:&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ secrets.ERGEBNIS_BOT_TOKEN }}&lt;/span&gt;"&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;script:&lt;/span&gt; &lt;span class="hljs-string"&gt;|
            const pullRequest = context.payload.workflow_run.pull_requests[0]
            const repository = context.repo
&lt;/span&gt;
            &lt;span class="hljs-string"&gt;const&lt;/span&gt; &lt;span class="hljs-string"&gt;reviewers&lt;/span&gt; &lt;span class="hljs-string"&gt;=&lt;/span&gt; &lt;span class="hljs-string"&gt;[&lt;/span&gt;
              &lt;span class="hljs-string"&gt;"ergebnis-bot"&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt;
            &lt;span class="hljs-string"&gt;]&lt;/span&gt;

            &lt;span class="hljs-string"&gt;await&lt;/span&gt; &lt;span class="hljs-string"&gt;github.rest.pulls.requestReviewers({&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;owner:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.owner,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;repo:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.repo,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;pull_number:&lt;/span&gt; &lt;span class="hljs-string"&gt;pullRequest.number,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;reviewers:&lt;/span&gt; &lt;span class="hljs-string"&gt;reviewers,&lt;/span&gt;
            &lt;span class="hljs-string"&gt;})&lt;/span&gt;

      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Assign @ergebnis-bot"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;uses:&lt;/span&gt; &lt;span class="hljs-string"&gt;"actions/github-script@v5"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;with:&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ secrets.ERGEBNIS_BOT_TOKEN }}&lt;/span&gt;"&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;script:&lt;/span&gt; &lt;span class="hljs-string"&gt;|
            const pullRequest = context.payload.workflow_run.pull_requests[0]
            const repository = context.repo
&lt;/span&gt;
            &lt;span class="hljs-string"&gt;const&lt;/span&gt; &lt;span class="hljs-string"&gt;assignees&lt;/span&gt; &lt;span class="hljs-string"&gt;=&lt;/span&gt; &lt;span class="hljs-string"&gt;[&lt;/span&gt;
              &lt;span class="hljs-string"&gt;"ergebnis-bot"&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt;
            &lt;span class="hljs-string"&gt;]&lt;/span&gt;

            &lt;span class="hljs-string"&gt;await&lt;/span&gt; &lt;span class="hljs-string"&gt;github.rest.issues.addAssignees({&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;owner:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.owner,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;repo:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.repo,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;assignees:&lt;/span&gt; &lt;span class="hljs-string"&gt;assignees,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;issue_number:&lt;/span&gt; &lt;span class="hljs-string"&gt;pullRequest.number&lt;/span&gt;
            &lt;span class="hljs-string"&gt;})&lt;/span&gt;

      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Approve pull request"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;uses:&lt;/span&gt; &lt;span class="hljs-string"&gt;"actions/github-script@v5"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;with:&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ secrets.ERGEBNIS_BOT_TOKEN }}&lt;/span&gt;"&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;script:&lt;/span&gt; &lt;span class="hljs-string"&gt;|
            const pullRequest = context.payload.workflow_run.pull_requests[0]
            const repository = context.repo
&lt;/span&gt;
            &lt;span class="hljs-string"&gt;await&lt;/span&gt; &lt;span class="hljs-string"&gt;github.rest.pulls.createReview({&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;event:&lt;/span&gt; &lt;span class="hljs-string"&gt;"APPROVE"&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;owner:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.owner,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;repo:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.repo,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;pull_number:&lt;/span&gt; &lt;span class="hljs-string"&gt;pullRequest.number,&lt;/span&gt;
            &lt;span class="hljs-string"&gt;})&lt;/span&gt;

      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;name:&lt;/span&gt; &lt;span class="hljs-string"&gt;"Merge pull request"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;uses:&lt;/span&gt; &lt;span class="hljs-string"&gt;"actions/github-script@v5"&lt;/span&gt;
        &lt;span class="hljs-attr"&gt;with:&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;github-token:&lt;/span&gt; &lt;span class="hljs-string"&gt;"$&lt;span class="hljs-template-variable"&gt;{{ secrets.ERGEBNIS_BOT_TOKEN }}&lt;/span&gt;"&lt;/span&gt;
          &lt;span class="hljs-attr"&gt;script:&lt;/span&gt; &lt;span class="hljs-string"&gt;|
            const pullRequest = context.payload.workflow_run.pull_requests[0]
            const repository = context.repo
&lt;/span&gt;
            &lt;span class="hljs-string"&gt;await&lt;/span&gt; &lt;span class="hljs-string"&gt;github.rest.pulls.merge({&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;merge_method:&lt;/span&gt; &lt;span class="hljs-string"&gt;"merge"&lt;/span&gt;&lt;span class="hljs-string"&gt;,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;owner:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.owner,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;pull_number:&lt;/span&gt; &lt;span class="hljs-string"&gt;pullRequest.number,&lt;/span&gt;
              &lt;span class="hljs-attr"&gt;repo:&lt;/span&gt; &lt;span class="hljs-string"&gt;repository.repo,&lt;/span&gt;
            &lt;span class="hljs-string"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see this workflow in action in &lt;a target="_blank" href="https://github.com/ergebnis/php-package-template/blob/89f5b5d3bf085d609972f43e508d12d13be74947/.github/workflows/merge.yaml#L1-L93" title="Merge workflow in ergebnis/php-package-template"&gt;&lt;code&gt;ergebnis/php-package-template&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since the workflow references pull request titles, it works best when using it in conjunction with an explicit configuration for Dependabot.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml hljs yaml" data-lang="yaml"&gt;&lt;span class="hljs-comment"&gt;# https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates&lt;/span&gt;

&lt;span class="hljs-attr"&gt;version:&lt;/span&gt; &lt;span class="hljs-number"&gt;2&lt;/span&gt;

&lt;span class="hljs-attr"&gt;updates:&lt;/span&gt;
  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;commit-message:&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;include:&lt;/span&gt; &lt;span class="hljs-string"&gt;"scope"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;prefix:&lt;/span&gt; &lt;span class="hljs-string"&gt;"composer"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;directory:&lt;/span&gt; &lt;span class="hljs-string"&gt;"/"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"dependency"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;open-pull-requests-limit:&lt;/span&gt; &lt;span class="hljs-number"&gt;10&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;package-ecosystem:&lt;/span&gt; &lt;span class="hljs-string"&gt;"composer"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;schedule:&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;interval:&lt;/span&gt; &lt;span class="hljs-string"&gt;"daily"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;versioning-strategy:&lt;/span&gt; &lt;span class="hljs-string"&gt;"increase"&lt;/span&gt;

  &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-attr"&gt;commit-message:&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;include:&lt;/span&gt; &lt;span class="hljs-string"&gt;"scope"&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;prefix:&lt;/span&gt; &lt;span class="hljs-string"&gt;"github-actions"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;directory:&lt;/span&gt; &lt;span class="hljs-string"&gt;"/"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;labels:&lt;/span&gt;
      &lt;span class="hljs-bullet"&gt;-&lt;/span&gt; &lt;span class="hljs-string"&gt;"dependency"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;open-pull-requests-limit:&lt;/span&gt; &lt;span class="hljs-number"&gt;10&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;package-ecosystem:&lt;/span&gt; &lt;span class="hljs-string"&gt;"github-actions"&lt;/span&gt;
    &lt;span class="hljs-attr"&gt;schedule:&lt;/span&gt;
      &lt;span class="hljs-attr"&gt;interval:&lt;/span&gt; &lt;span class="hljs-string"&gt;"daily"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

</content></entry><entry><title>Rolling up database migrations with Doctrine</title><category term="doctrine"/><category term="maintenance"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2020/06/10/rolling-up-database-migrations-with-doctrine/"/><id>https://localheinz.com/articles/2020/06/10/rolling-up-database-migrations-with-doctrine/</id><updated>2020-06-10T14:10:00+02:00</updated><content>&lt;h1&gt;
  Rolling up database migrations with Doctrine
&lt;/h1&gt;


&lt;h2 id="content-introduction" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-introduction" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As a user of &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;doctrine&amp;#x2F;orm" target="_blank" title="doctrine/orm on GitHub"&gt;&lt;code&gt;doctrine/orm&lt;/code&gt;&lt;/a&gt; and &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;doctrine&amp;#x2F;migrations" target="_blank" title="doctrine/migrations on GitHub"&gt;&lt;code&gt;doctrine/migrations&lt;/code&gt;&lt;/a&gt;, it is likely that you - like me - have encountered one of the following scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have started using &lt;code&gt;doctrine/migrations&lt;/code&gt; late in a project. Unfortunately,  you can not set up a database from scratch using database migrations.&lt;/li&gt;
&lt;li&gt;You use Doctrine entities or other services in the database migrations. You have realized that this was a mistake, regret it, and you would like to get rid of these database migrations.&lt;/li&gt;
&lt;li&gt;You populate the database with data using database migrations. Again, you have realized that this was a mistake, regret it, and you would like to get rid of these database migrations.&lt;/li&gt;
&lt;li&gt;You have accumulated a lot of database migrations in a mature project. Since you run these database migrations frequently to set up databases in development, test, and staging environments, and because you get that extra kick out of things running fast, you would like to collapse them into a single database migration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If that does not describe you, for example, because you do not work on these kinds of projects or never make any mistakes, please share this article with someone who does.&lt;/p&gt;
&lt;p&gt;Are you still here? Great!&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="https://github.com/doctrine/migrations/releases/tag/v2.0.0" title="doctrine/migrations:2.0.0"&gt;&lt;code&gt;doctrine/migrations:2.0.0&lt;/code&gt;&lt;/a&gt; introduced a &lt;code&gt;DumpSchemaCommand&lt;/code&gt; and a &lt;code&gt;RollupCommand&lt;/code&gt;. The &lt;code&gt;DumpSchemaCommand&lt;/code&gt; reads the schema from our entity mapping and dumps corresponding SQL statements into a single database migration. The &lt;code&gt;RollupCommand&lt;/code&gt; verifies that only a single database migration exists, removes information about previously run database migrations from the configured table, and inserts information about this single database migration.&lt;/p&gt;
&lt;p&gt;Using these commands, we can easily roll up database migrations. In the following, I will show you how.&lt;/p&gt;
&lt;p&gt;The process consists of four steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;validate entity mapping and database&lt;/li&gt;
&lt;li&gt;remove existing migrations&lt;/li&gt;
&lt;li&gt;dump the schema into a single database migration&lt;/li&gt;
&lt;li&gt;validate the dumped database migration&lt;/li&gt;
&lt;li&gt;roll up migrations&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-commands" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-commands" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Commands&lt;/h2&gt;
&lt;p&gt;In a moment, we will run Doctrine console commands. These commands operate with options that have default values, but if our application has a setup that differs from the defaults, we need to specify values for these options.&lt;/p&gt;
&lt;h3 id="content-options" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-options" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Options&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--connection&lt;/code&gt;: The name of the Doctrine DBAL database connection; could be &lt;code&gt;default&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--env&lt;/code&gt;: The name of the environment; could be &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;, or &lt;code&gt;production&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--em&lt;/code&gt;: The name of the Doctrine ORM entity manager to use; could be &lt;code&gt;default&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="content-environments" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-environments" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Environments&lt;/h3&gt;
&lt;p&gt;We will run these commands in development and production environments. To achieve the best results, our development environment should reflect the production environment. How to create such a development environment is out of the scope of this article.&lt;/p&gt;
&lt;div class="warning"&gt;
  &lt;p&gt;❗ Make sure to use the appropriate parameters when executing the commands.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-validate-entity-mapping-and-database" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-validate-entity-mapping-and-database" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Validate entity mapping and database&lt;/h2&gt;
&lt;p&gt;As mentioned before, the &lt;code&gt;DumpSchemaCommand&lt;/code&gt; reads the schema from our entity mapping and dumps corresponding SQL statements into a single database migration. When our entity mapping and the database are not in sync, this will result in a database migration that does not reflect the actual database structure.&lt;/p&gt;
&lt;p&gt;To avoid that, we need to validate the schema and fix all errors in a development environment.&lt;/p&gt;
&lt;p&gt;We run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to show the database migration status in our development environment.&lt;/p&gt;
&lt;p&gt;When the command reports that there are new database migrations, we run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:execute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to run these database migrations in our development environment.&lt;/p&gt;
&lt;p&gt;When all database migrations have been run, we run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:schema:validate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to validate that the entity mapping is correct and in sync with the database in our development environment.&lt;/p&gt;
&lt;p&gt;When this command reports errors, we need to fix them. Fixing these errors might involve adjusting our entity mapping and creating database migrations. These fixes are useful - even when we do not want to roll up database migrations. Therefore we can apply them to the main line of the project. When we have fixed these errors, we can return to rolling up the database migrations.&lt;/p&gt;
&lt;p&gt;When this command does not report any errors, we can continue.&lt;/p&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 To avoid validation errors, we can validate that the entity mapping is correct and in sync with the database in a continuous-integration environment.&lt;/p&gt;
&lt;p&gt;When we cannot set up a database from database migrations, we validate the mapping only. Once we have rolled up the database migrations, we can set up a database from database migrations. Then we can also verify that the entity mapping is in sync with the database.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="content-remove-existing-database-migrations" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-remove-existing-database-migrations" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Remove existing database migrations&lt;/h2&gt;
&lt;p&gt;Since our entity mapping is valid and in sync with the database, we can now remove the existing database migrations from the configured migrations directory, and commit the changes.&lt;/p&gt;
&lt;h2 id="content-dump-the-schema" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-dump-the-schema" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Dump the schema&lt;/h2&gt;
&lt;p&gt;After removing the existing database migrations, we can now dump the schema from our entity mapping into a single database migration.&lt;/p&gt;
&lt;p&gt;We run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:dump-schema
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to dump the schema into a single migration in our development environment, and commit the changes.&lt;/p&gt;
&lt;h2 id="content-validate-the-dumped-migration" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-validate-the-dumped-migration" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Validate the dumped migration&lt;/h2&gt;
&lt;p&gt;We have dumped the schema into a single migration, but before we continue, let us double-check that running this migration really creates a database structure that reflects our entity mapping. We can do that by dropping and recreating the database, running the migration, and validating the schema.&lt;/p&gt;
&lt;p&gt;We run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:database:drop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to drop the database in our development environment. Then we run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:database:create
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to create the database in our development environment.&lt;/p&gt;
&lt;p&gt;We now have an empty database in our development environment, and we run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to show the database migration status. This command will show a single new database migration: the database migration we just dumped.&lt;/p&gt;
&lt;p&gt;We run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:execute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to run this database migration in our development environment.&lt;/p&gt;
&lt;p&gt;We now run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:schema:validate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to validate that the entity mapping is correct and in sync with the database in our development environment.&lt;/p&gt;
&lt;p&gt;At this point, this command should not report any errors. When it does, we need to fix them, but we should not adjust our entity mapping. However, we can modify the dumped database migration. Remember, the RollupCommand will not run when more than one database migration exists.&lt;/p&gt;
&lt;p&gt;We run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:diff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to create a database migration from the differences between our entity mapping and the database. Instead of committing the migration created from running this command, we manually merge it into the previously dumped migration and commit the resulting changes.&lt;/p&gt;
&lt;p&gt;We repeat this process until the single migration creates a database in sync with our entity mapping.&lt;/p&gt;
&lt;h2 id="content-roll-up" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-roll-up" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Roll up&lt;/h2&gt;
&lt;p&gt;We have rolled up the database migrations in our development environment, and can finally roll up the database migrations in our production environment.&lt;/p&gt;
&lt;p&gt;After deploying the changes, we run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:rollup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to roll up the migrations in our production environment.&lt;/p&gt;
&lt;p&gt;We run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;bin/console doctrine:migrations:status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to verify that there is only a single available migration and that there are more migrations that need to be executed.&lt;/p&gt;
&lt;h2 id="content-conclusion" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-conclusion" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We have now collapsed all existing database migrations into a single database migration.&lt;/p&gt;
&lt;p&gt;We can set up a database from scratch in development, testing, or continuous-integration environments now, and run commands and tests that require the presence of a database. We have also rid ourselves of database migrations that use entities or services or populate the database with data. By reducing the number of migrations, we have sped up the process of running them.&lt;/p&gt;

</content></entry><entry><title>Avoiding imports and aliases in PHP</title><category term="coding-standards"/><category term="maintenance"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2020/05/19/avoiding-imports-and-aliases-in-php/"/><id>https://localheinz.com/articles/2020/05/19/avoiding-imports-and-aliases-in-php/</id><updated>2020-05-19T12:00:00+02:00</updated><content>&lt;h1&gt;
  Avoiding imports and aliases in PHP
&lt;/h1&gt;


&lt;p&gt;&lt;a target="_blank" href="https://www.php.net/releases/5_3_0.php" title="PHP 5.3: Release notes"&gt;PHP 5.3&lt;/a&gt;, released on June 30, 2009, introduced &lt;a target="blank" href="https://www.php.net/manual/en/language.namespaces.rationale.php" title="PHP: Namespaces overview"&gt;namespaces&lt;/a&gt; as well as &lt;a target="blank" href="https://www.php.net/manual/en/language.namespaces.importing.php" title="PHP: Using namespaces"&gt;imports and aliases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Developers quickly adopted these features. Where previously a class was named &lt;code&gt;Foo_Bar_Baz_Qux&lt;/code&gt;, namespaces allow calling it &lt;code&gt;Foo\Bar\Baz\Qux&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="content-imports" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-imports" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Imports&lt;/h2&gt;
&lt;p&gt;With imports, I can import the fully-qualified class name and reference the class by its terminating class name:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Foo&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bar&lt;/span&gt;\&lt;span class="hljs-title"&gt;Baz&lt;/span&gt;\&lt;span class="hljs-title"&gt;Qux&lt;/span&gt;;

$qux = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Qux();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, I can import a namespace prefix and reference the class relative to it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Foo&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bar&lt;/span&gt;;

$qux = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Bar\Baz\Qux();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-aliases" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-aliases" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Aliases&lt;/h2&gt;
&lt;p&gt;Aliases, you guessed it, allow importing either and assigning aliases to it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Foo&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bar&lt;/span&gt; &lt;span class="hljs-title"&gt;as&lt;/span&gt; &lt;span class="hljs-title"&gt;Quux&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Foo&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bar&lt;/span&gt;\&lt;span class="hljs-title"&gt;Baz&lt;/span&gt;\&lt;span class="hljs-title"&gt;Qux&lt;/span&gt; &lt;span class="hljs-title"&gt;as&lt;/span&gt; &lt;span class="hljs-title"&gt;Quuz&lt;/span&gt;;

$qux = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Quux\Baz\Qux();
$anotherQux = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Quuz();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-problems" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-problems" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Problems&lt;/h2&gt;
&lt;p&gt;When it comes to the possibilities of how I can use these features, there are two extremes. At one end, I use fully-qualified class names everywhere. At the other end, I import every single class.&lt;/p&gt;
&lt;p&gt;Using fully-qualified class names everywhere is a bit like writing code as if today is June 29, 2009. It does not make any sense - so let us not get into it.&lt;/p&gt;
&lt;p&gt;Importing every single class is possible, but potentially leads to a long list of imports. A long list of imports takes a long time to read. For every class I add to this list, developers need to recall the context from which I introduced it. Every time I import a class, I risk conflicts.&lt;/p&gt;
&lt;p&gt;Aliases can help to avoid these conflicts. However, aliases only make the problem worse. For every alias, developers need to translate between the original class name and the aliased class name.&lt;/p&gt;
&lt;p&gt;With aliases, navigating the code becomes more difficult. In &lt;a target="_blank" href="https://www.jetbrains.com/phpstorm/" title="PhpStorm: The Lightning-Smart PHP IDE"&gt;PhpStorm&lt;/a&gt;, clicking on a symbol &lt;a target="_blank" href="https://www.jetbrains.com/help/phpstorm/navigating-through-the-source-code.html" title="PhpStorm: Source code navigation"&gt;navigates&lt;/a&gt; to where it was declared. When I click on a class name referenced in code, I navigate to the class declaration. However, when I click on an alias, I navigate to the alias declaration. I now need to click again on the class name to navigate to its declaration.&lt;/p&gt;
&lt;p&gt;Ugh.&lt;/p&gt;
&lt;h2 id="content-solution" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-solution" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Solution&lt;/h2&gt;
&lt;p&gt;The worst examples of &lt;em&gt;importitis&lt;/em&gt; and &lt;em&gt;aliasitis&lt;/em&gt; I have seen in a closed-source project included a class with 58 imports, and another class with 12 aliases. If we ignore for a moment that these classes were suffering from other problems, all of these issues could have been avoided by only importing a suitable parent namespace.&lt;/p&gt;
&lt;p&gt;Here are few examples.&lt;/p&gt;
&lt;p&gt;Take a look at the &lt;a target="_blank" href="https://github.com/symfony/demo/blob/v1.5.3/src/Controller/UserController.php" title="UserController"&gt;&lt;code&gt;UserController&lt;/code&gt;&lt;/a&gt; from the &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;symfony&amp;#x2F;demo" target="_blank" title="symfony/demo on GitHub"&gt;&lt;code&gt;symfony/demo&lt;/code&gt;&lt;/a&gt; application:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Controller&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Entity&lt;/span&gt;\&lt;span class="hljs-title"&gt;Comment&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Entity&lt;/span&gt;\&lt;span class="hljs-title"&gt;Post&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Events&lt;/span&gt;\&lt;span class="hljs-title"&gt;CommentCreatedEvent&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Form&lt;/span&gt;\&lt;span class="hljs-title"&gt;CommentType&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Repository&lt;/span&gt;\&lt;span class="hljs-title"&gt;PostRepository&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Repository&lt;/span&gt;\&lt;span class="hljs-title"&gt;TagRepository&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Sensio&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;FrameworkExtraBundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;Configuration&lt;/span&gt;\&lt;span class="hljs-title"&gt;Cache&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Sensio&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;FrameworkExtraBundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;Configuration&lt;/span&gt;\&lt;span class="hljs-title"&gt;IsGranted&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Sensio&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;FrameworkExtraBundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;Configuration&lt;/span&gt;\&lt;span class="hljs-title"&gt;ParamConverter&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;FrameworkBundle&lt;/span&gt;\&lt;span class="hljs-title"&gt;Controller&lt;/span&gt;\&lt;span class="hljs-title"&gt;AbstractController&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;EventDispatcher&lt;/span&gt;\&lt;span class="hljs-title"&gt;EventDispatcherInterface&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;HttpFoundation&lt;/span&gt;\&lt;span class="hljs-title"&gt;Request&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;HttpFoundation&lt;/span&gt;\&lt;span class="hljs-title"&gt;Response&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;Routing&lt;/span&gt;\&lt;span class="hljs-title"&gt;Annotation&lt;/span&gt;\&lt;span class="hljs-title"&gt;Route&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It has a fairly long list of imports, that can easily be reduced by importing only parent namespaces:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
index 85228f0..82723de 100644
&lt;span class="hljs-comment"&gt;--- a/src/Controller/UserController.php&lt;/span&gt;
&lt;span class="hljs-comment"&gt;+++ b/src/Controller/UserController.php&lt;/span&gt;
&lt;span class="hljs-meta"&gt;@@ -2,29 +2,31 @@&lt;/span&gt;

 namespace App\Controller;

&lt;span class="hljs-deletion"&gt;-use App\Form\Type\ChangePasswordType;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use App\Form\UserType;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Symfony\Component\HttpFoundation\Request;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Symfony\Component\HttpFoundation\Response;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Symfony\Component\Routing\Annotation\Route;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use App\Form;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Sensio\Bundle\FrameworkExtraBundle;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Symfony\Bundle\FrameworkBundle;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Symfony\Component\HttpFoundation;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Symfony\Component\Routing;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Symfony\Component\Security;&lt;/span&gt;

 /**
&lt;span class="hljs-deletion"&gt;- * @Route("/profile")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * @IsGranted("ROLE_USER")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * @Routing\Annotation\Route("/profile")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * @FrameworkExtraBundle\Configuration\IsGranted("ROLE_USER")&lt;/span&gt;
  */
&lt;span class="hljs-deletion"&gt;-class UserController extends AbstractController&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+class UserController extends FrameworkBundle\Controller\AbstractController&lt;/span&gt;
 {
     /**
&lt;span class="hljs-deletion"&gt;-     * @Route("/edit", methods="GET|POST", name="user_edit")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Routing\Annotation\Route(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     "/edit",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     methods="GET|POST",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     name="user_edit"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * )&lt;/span&gt;
      */
&lt;span class="hljs-deletion"&gt;-    public function edit(Request $request): Response&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function edit(HttpFoundation\Request $request): HttpFoundation\Response&lt;/span&gt;
     {
         $user = $this-&amp;gt;getUser();

&lt;span class="hljs-deletion"&gt;-        $form = $this-&amp;gt;createForm(UserType::class, $user);&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $form = $this-&amp;gt;createForm(Form\UserType::class, $user);&lt;/span&gt;
         $form-&amp;gt;handleRequest($request);

         if ($form-&amp;gt;isSubmitted() &amp;amp;&amp;amp; $form-&amp;gt;isValid()) {
@@ -42,13 +44,19 @@ class UserController extends AbstractController
     }

     /**
&lt;span class="hljs-deletion"&gt;-     * @Route("/change-password", methods="GET|POST", name="user_change_password")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Routing\Annotation\Route(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     "/change-password",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     methods="GET|POST",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     name="user_change_password"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * )&lt;/span&gt;
      */
&lt;span class="hljs-deletion"&gt;-    public function changePassword(Request $request, UserPasswordEncoderInterface $encoder): Response&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function changePassword(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        HttpFoundation\Request $request,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        Security\Core\Encoder\UserPasswordEncoderInterface $encoder&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    ): HttpFoundation\Response {&lt;/span&gt;
         $user = $this-&amp;gt;getUser();

&lt;span class="hljs-deletion"&gt;-        $form = $this-&amp;gt;createForm(ChangePasswordType::class);&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $form = $this-&amp;gt;createForm(Form\Type\ChangePasswordType::class);&lt;/span&gt;
         $form-&amp;gt;handleRequest($request);

         if ($form-&amp;gt;isSubmitted() &amp;amp;&amp;amp; $form-&amp;gt;isValid()) {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have shortened the list of imports and referenced classes relative to the imported namespace prefixes. We can see immediately the context from which we imported a class. Additionally, we wrapped a few lines. Cognitive load is reduced.&lt;/p&gt;
&lt;p&gt;Take a look at the &lt;a target="_blank" href="https://github.com/symfony/demo/blob/v1.5.3/src/Entity/Post.php" title="Post"&gt;&lt;code&gt;Post&lt;/code&gt;&lt;/a&gt; entity from the same application:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;App&lt;/span&gt;\&lt;span class="hljs-title"&gt;Entity&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;Common&lt;/span&gt;\&lt;span class="hljs-title"&gt;Collections&lt;/span&gt;\&lt;span class="hljs-title"&gt;ArrayCollection&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;Common&lt;/span&gt;\&lt;span class="hljs-title"&gt;Collections&lt;/span&gt;\&lt;span class="hljs-title"&gt;Collection&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;ORM&lt;/span&gt;\&lt;span class="hljs-title"&gt;Mapping&lt;/span&gt; &lt;span class="hljs-title"&gt;as&lt;/span&gt; &lt;span class="hljs-title"&gt;ORM&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Bridge&lt;/span&gt;\&lt;span class="hljs-title"&gt;Doctrine&lt;/span&gt;\&lt;span class="hljs-title"&gt;Validator&lt;/span&gt;\&lt;span class="hljs-title"&gt;Constraints&lt;/span&gt;\&lt;span class="hljs-title"&gt;UniqueEntity&lt;/span&gt;;
&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Symfony&lt;/span&gt;\&lt;span class="hljs-title"&gt;Component&lt;/span&gt;\&lt;span class="hljs-title"&gt;Validator&lt;/span&gt;\&lt;span class="hljs-title"&gt;Constraints&lt;/span&gt; &lt;span class="hljs-title"&gt;as&lt;/span&gt; &lt;span class="hljs-title"&gt;Assert&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The list of imports is not so long, but unnecessary aliases are used, and these can be avoided as well:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;diff --git a/src/Entity/Post.php b/src/Entity/Post.php
index a47a862..a2328fd 100644
&lt;span class="hljs-comment"&gt;--- a/src/Entity/Post.php&lt;/span&gt;
&lt;span class="hljs-comment"&gt;+++ b/src/Entity/Post.php&lt;/span&gt;
&lt;span class="hljs-meta"&gt;@@ -11,16 +11,19 @@&lt;/span&gt;

 namespace App\Entity;

&lt;span class="hljs-deletion"&gt;-use Doctrine\Common\Collections\ArrayCollection;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Doctrine\Common\Collections\Collection;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Doctrine\ORM\Mapping as ORM;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-use Symfony\Component\Validator\Constraints as Assert;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Doctrine\Common;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Doctrine\ORM;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Symfony\Bridge\Doctrine;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+use Symfony\Component\Validator;&lt;/span&gt;

 /**
&lt;span class="hljs-deletion"&gt;- * @ORM\Entity(repositoryClass="App\Repository\PostRepository")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * @ORM\Table(name="symfony_demo_post")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * @UniqueEntity(fields={"slug"}, errorPath="title", message="post.slug_unique")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * @ORM\Mapping\Entity(repositoryClass="App\Repository\PostRepository")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * @ORM\Mapping\Table(name="symfony_demo_post")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * @Doctrine\Validator\Constraints\UniqueEntity(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *     fields={"slug"},&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *     errorPath="title",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ *     message="post.slug_unique"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+ * )&lt;/span&gt;
  */
 class Post
 {
@@ -35,88 +38,97 @@ class Post
     /**
      * @var int
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\Id&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @ORM\GeneratedValue&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @ORM\Column(type="integer")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\Id&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\GeneratedValue&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\Column(type="integer")&lt;/span&gt;
      */
     private $id;

     /**
      * @var string
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\Column(type="string")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @Assert\NotBlank&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\Column(type="string")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Validator\Constraints\NotBlank&lt;/span&gt;
      */
     private $title;

     /**
      * @var string
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\Column(type="string")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\Column(type="string")&lt;/span&gt;
      */
     private $slug;

     /**
      * @var string
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\Column(type="string")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @Assert\NotBlank(message="post.blank_summary")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @Assert\Length(max=255)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\Column(type="string")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Validator\Constraints\NotBlank(message="post.blank_summary")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Validator\Constraints\Length(max=255)&lt;/span&gt;
      */
     private $summary;

     /**
      * @var string
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\Column(type="text")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @Assert\NotBlank(message="post.blank_content")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @Assert\Length(min=10, minMessage="post.too_short_content")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\Column(type="text")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Validator\Constraints\NotBlank(message="post.blank_content")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Validator\Constraints\Length(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     min=10,&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     minMessage="post.too_short_content"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * )&lt;/span&gt;
      */
     private $content;

     /**
      * @var \DateTime
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\Column(type="datetime")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\Column(type="datetime")&lt;/span&gt;
      */
     private $publishedAt;

     /**
      * @var User
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\ManyToOne(targetEntity="App\Entity\User")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @ORM\JoinColumn(nullable=false)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\ManyToOne(targetEntity="App\Entity\User")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\JoinColumn(nullable=false)&lt;/span&gt;
      */
     private $author;

     /**
&lt;span class="hljs-deletion"&gt;-     * @var Comment[]|ArrayCollection&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @var Comment[]|Common\Collections\ArrayCollection&lt;/span&gt;
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\OneToMany(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\OneToMany(&lt;/span&gt;
      *      targetEntity="Comment",
      *      mappedBy="post",
      *      orphanRemoval=true,
      *      cascade={"persist"}
      * )
&lt;span class="hljs-deletion"&gt;-     * @ORM\OrderBy({"publishedAt": "DESC"})&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\OrderBy({"publishedAt": "DESC"})&lt;/span&gt;
      */
     private $comments;

     /**
&lt;span class="hljs-deletion"&gt;-     * @var Tag[]|ArrayCollection&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @var Tag[]|Common\Collections\ArrayCollection&lt;/span&gt;
      *
&lt;span class="hljs-deletion"&gt;-     * @ORM\ManyToMany(targetEntity="App\Entity\Tag", cascade={"persist"})&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @ORM\JoinTable(name="symfony_demo_post_tag")&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @ORM\OrderBy({"name": "ASC"})&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @Assert\Count(max="4", maxMessage="post.too_many_tags")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\ManyToMany(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     targetEntity="App\Entity\Tag",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     cascade={"persist"}&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * )&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\JoinTable(name="symfony_demo_post_tag")&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @ORM\Mapping\OrderBy({"name": "ASC"})&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @Validator\Constraints\Count(&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     max="4",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *     maxMessage="post.too_many_tags"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * )&lt;/span&gt;
      */
     private $tags;

     public function __construct()
     {
         $this-&amp;gt;publishedAt = new \DateTime();
&lt;span class="hljs-deletion"&gt;-        $this-&amp;gt;comments = new ArrayCollection();&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        $this-&amp;gt;tags = new ArrayCollection();&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $this-&amp;gt;comments = new Common\Collections\ArrayCollection();&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        $this-&amp;gt;tags = new Common\Collections\ArrayCollection();&lt;/span&gt;
     }

     public function getId(): ?int
@@ -174,7 +186,7 @@ class Post
         $this-&amp;gt;author = $author;
     }

&lt;span class="hljs-deletion"&gt;-    public function getComments(): Collection&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function getComments(): Common\Collections\Collection&lt;/span&gt;
     {
         return $this-&amp;gt;comments;
     }
@@ -216,7 +228,7 @@ class Post
         $this-&amp;gt;tags-&amp;gt;removeElement($tag);
     }

&lt;span class="hljs-deletion"&gt;-    public function getTags(): Collection&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function getTags(): Common\Collections\Collection&lt;/span&gt;
     {
         return $this-&amp;gt;tags;
     }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, we have shortened the list of imports and referenced classes relative to the imported namespace prefixes. We can see immediately the context from which we imported a class. We removed unnecessary aliases Additionally, we wrapped a few lines. Cognitive load is reduced again.&lt;/p&gt;
&lt;h2 id="content-best-practices" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-best-practices" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Best practices&lt;/h2&gt;
&lt;p&gt;The changes I propose here do not conform with the examples found in the documentation for &lt;a target="_blank" href="https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/association-mapping.html#association-mapping" title="Doctrine: Association mapping"&gt;Doctrine&lt;/a&gt; or &lt;a target="_blank" href="https://symfony.com/doc/current/doctrine.html" title="Symfony: Databases and the Doctrine ORM"&gt;Symfony&lt;/a&gt;, which might be a problem when you intend to contribute to the core of Doctrine or Symfony or related projects.&lt;/p&gt;
&lt;p&gt;When you work on other projects, you are free to do whatever you want.&lt;/p&gt;
&lt;p&gt;Decide for yourself. Find out what works best for you.&lt;/p&gt;

</content></entry><entry><title>Quickly switching between PCOV and Xdebug</title><category term="homebrew"/><category term="macos"/><category term="pcov"/><category term="php"/><category term="xdebug"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2020/05/16/quickly-switching-between-pcov-and-xdebug/"/><id>https://localheinz.com/articles/2020/05/16/quickly-switching-between-pcov-and-xdebug/</id><updated>2020-05-16T12:15:00+02:00</updated><content>&lt;h1&gt;
  Quickly switching between PCOV and Xdebug
&lt;/h1&gt;


&lt;p&gt;There are at least two PHP extensions I need during the development of a PHP application or package: PCOV and Xdebug.&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="https://github.com/krakjoe/pcov" title="pcov"&gt;PCOV&lt;/a&gt; is &lt;a target="_blank" href="https://web.archive.org/web/20230605085915/https://blog.krakjoe.ninja/2019/01/running-for-coverage.html" title="Joe Watkins: Running for Coverage"&gt;relatively new&lt;/a&gt;. Built by by &lt;a target="_blank" href="https://github.com/krakjoe" title="Joe Watkins"&gt;Joe Watkins&lt;/a&gt;, it provides fast code coverage for use with &lt;code&gt;phpunit/phpunit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="https://xdebug.org" title="Xdebug"&gt;Xdebug&lt;/a&gt; has been around for &lt;a target="_blank" href="https://derickrethans.nl/xdebug-10.html" title="Derick Rethans: 10 years of Xdebug and Xdebug 2.2.0 released"&gt;a while&lt;/a&gt;. Built by &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;derickr" target="_blank" title="Derick&amp;#x20;Rethans&amp;#x20;on&amp;#x20;GitHub"&gt;Derick Rethans&lt;/a&gt;, it is a development tool that can not only be used for collecting code coverage, but also provides a single-step debugger for use with IDEs, is equipped with a profiler,  upgrades &lt;code&gt;var_dump()&lt;/code&gt;, and more. Since it does more things differently, it is slower at collecting code coverage.&lt;/p&gt;
&lt;p&gt;Both are great tools and have their place. When I want to collect code coverage with recent versions of &lt;code&gt;phpunit/phpunit&lt;/code&gt;, I use PCOV. When I work on a project with older versions of &lt;code&gt;phpunit/phpunit&lt;/code&gt;, I use Xdebug to collect code coverage. When I want to debug production code while running tests in PhpStorm, I use Xdebug.&lt;/p&gt;
&lt;p&gt;There are drawbacks, however. PCOV does not play well with Xdebug, so I only want one of them to be enabled at a time. Xdebug adds significant overhead when running other tasks in PHP, so I prefer to disable Xdebug when I do not need it.&lt;/p&gt;
&lt;p&gt;Before I can enable or disable PCOV and Xdebug, I need to install and configure them. I am going to demonstrate the installation of both on PHP 7.4.&lt;/p&gt;
&lt;h2 id="content-installation-of-pcov" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-installation-of-pcov" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Installation of PCOV&lt;/h2&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;pecl install pcov
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and take note of last few lines that are shown when the installation process is finished and the location of &lt;code&gt;pcov.so&lt;/code&gt; is revealed (we need it in a moment):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;Build process completed successfully
Installing '/usr/local/Cellar/php/7.4.6/pecl/20190902/pcov.so'
install ok: channel://pecl.php.net/pcov-1.0.6
Extension pcov enabled in php.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-configuration-of-pcov" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-configuration-of-pcov" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Configuration of PCOV&lt;/h2&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;php --ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to show the names of configuration files loaded by PHP:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;Configuration File (php.ini) Path: /usr/local/etc/php/7.4
Loaded Configuration File:         /usr/local/etc/php/7.4/php.ini
Scan for additional .ini files in: /usr/local/etc/php/7.4/conf.d
Additional .ini files parsed:      /usr/local/etc/php/7.4/conf.d/ext-opcache.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open &lt;code&gt;/usr/local/etc/php/7.4/php.ini&lt;/code&gt; and remove&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;&lt;span class="hljs-deletion"&gt;- extension="pcov.so"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a file &lt;code&gt;/usr/local/etc/php/7.4/conf.d/ext-pcov.ini&lt;/code&gt;, but use the exact location of &lt;code&gt;pcov.so&lt;/code&gt; we noted earlier:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ini hljs ini" data-lang="ini"&gt;&lt;span class="hljs-attr"&gt;extension&lt;/span&gt;=&lt;span class="hljs-string"&gt;"/usr/local/Cellar/php/7.4.5_2/pecl/20190902/pcov.so"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;php --ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;again and observe that &lt;code&gt;ext-pcov.ini&lt;/code&gt; has been loaded:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;Configuration File (php.ini) Path: /usr/local/etc/php/7.4
Loaded Configuration File:         /usr/local/etc/php/7.4/php.ini
Scan for additional .ini files in: /usr/local/etc/php/7.4/conf.d
Additional .ini files parsed:      /usr/local/etc/php/7.4/conf.d/ext-opcache.ini,
/usr/local/etc/php/7.4/conf.d/ext-pcov.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-installation-of-xdebug" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-installation-of-xdebug" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Installation of Xdebug&lt;/h2&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;pecl install xdebug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and take note of the location of &lt;code&gt;xdebug.so&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;Build process completed successfully
Installing '/usr/local/Cellar/php/7.4.6/pecl/20190902/xdebug.so'
install ok: channel://pecl.php.net/xdebug-2.9.5
Extension xdebug enabled in php.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-configuration-of-xdebug" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-configuration-of-xdebug" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Configuration of Xdebug&lt;/h2&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;php --ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to show the names of configuration files loaded by PHP:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;Configuration File (php.ini) Path: /usr/local/etc/php/7.4
Loaded Configuration File:         /usr/local/etc/php/7.4/php.ini
Scan for additional .ini files in: /usr/local/etc/php/7.4/conf.d
Additional .ini files parsed:      /usr/local/etc/php/7.4/conf.d/ext-opcache.ini,
/usr/local/etc/php/7.4/conf.d/ext-pcov.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open &lt;code&gt;/usr/local/etc/php/7.4/php.ini&lt;/code&gt; and remove&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;&lt;span class="hljs-deletion"&gt;- zend_extension="xdebug.so"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a file &lt;code&gt;/usr/local/etc/php/7.4/conf.d/ext-xdebug.ini&lt;/code&gt;, but use the exact location of &lt;code&gt;xdebug.so&lt;/code&gt; we noted earlier:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ini hljs ini" data-lang="ini"&gt;&lt;span class="hljs-attr"&gt;zend_extension&lt;/span&gt;=&lt;span class="hljs-string"&gt;"/usr/local/Cellar/php/7.4.6/pecl/20190902/xdebug.so"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;php --ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;again and observe that &lt;code&gt;ext-xdebug.ini&lt;/code&gt; has been loaded:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;Configuration File (php.ini) Path: /usr/local/etc/php/7.4
Loaded Configuration File:         /usr/local/etc/php/7.4/php.ini
Scan for additional .ini files in: /usr/local/etc/php/7.4/conf.d
Additional .ini files parsed:      /usr/local/etc/php/7.4/conf.d/ext-opcache.ini,
/usr/local/etc/php/7.4/conf.d/ext-pcov.ini,
/usr/local/etc/php/7.4/conf.d/ext-xdebug.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-disabling-and-enabling-pcov-and-xdebug" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-disabling-and-enabling-pcov-and-xdebug" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Disabling and enabling PCOV and Xdebug&lt;/h2&gt;
&lt;p&gt;Now that we have installed PCOV and Xdebug, we need a way for disabling and enabling them.&lt;/p&gt;
&lt;p&gt;The easiest way to disable an extension is to prevent PHP from loading the corresponding configuration file. Since PHP is looking for files ending with a &lt;code&gt;.ini&lt;/code&gt; extension, renaming the file seems smart.&lt;/p&gt;
&lt;p&gt;In &lt;a href="/articles/2020/05/05/switching-php-versions-when-using-homebrew/" target="_blank" title="Switching&amp;#x20;PHP&amp;#x20;versions&amp;#x20;when&amp;#x20;using&amp;#x20;Homebrew"&gt;Switching PHP versions when using Homebrew&lt;/a&gt;, I collect PHP versions previously installed with Homebrew.&lt;/p&gt;
&lt;p&gt;Again, I would like to have a command that works regardless of which version of PHP I currently use. I would also prefer to use a short command, especially when I am going to run them often. To achieve this, I have added the following to my &lt;code&gt;.zhsrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash hljs bash" data-lang="bash"&gt;&lt;span class="hljs-comment"&gt;# Toggle extension&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;toggle&lt;/span&gt;&lt;/span&gt;()
{
    name=&lt;span class="hljs-variable"&gt;$1&lt;/span&gt;
    displayName=&lt;span class="hljs-variable"&gt;${2:-$1}&lt;/span&gt;

    &lt;span class="hljs-keyword"&gt;for&lt;/span&gt; phpVersion &lt;span class="hljs-keyword"&gt;in&lt;/span&gt; &lt;span class="hljs-variable"&gt;${installedPhpVersions[*]}&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;do&lt;/span&gt;
        config=&lt;span class="hljs-string"&gt;"/usr/local/etc/php/&lt;span class="hljs-variable"&gt;${phpVersion}&lt;/span&gt;/conf.d/ext-&lt;span class="hljs-variable"&gt;${name}&lt;/span&gt;.ini"&lt;/span&gt;

        &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; [[ -f &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${config}&lt;/span&gt;"&lt;/span&gt; ]]; &lt;span class="hljs-keyword"&gt;then&lt;/span&gt;
            mv &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${config}&lt;/span&gt;"&lt;/span&gt; &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${config}&lt;/span&gt;.bak"&lt;/span&gt;
        &lt;span class="hljs-keyword"&gt;elif&lt;/span&gt; [[ -f &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${config}&lt;/span&gt;.bak"&lt;/span&gt; ]]; &lt;span class="hljs-keyword"&gt;then&lt;/span&gt;
            mv &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${config}&lt;/span&gt;.bak"&lt;/span&gt; &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${config}&lt;/span&gt;"&lt;/span&gt;
        &lt;span class="hljs-keyword"&gt;fi&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;done&lt;/span&gt;

    php -v

    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; [[ $(php -m | grep &lt;span class="hljs-variable"&gt;${name}&lt;/span&gt;) ]]; &lt;span class="hljs-keyword"&gt;then&lt;/span&gt;
        &lt;span class="hljs-built_in"&gt;echo&lt;/span&gt; &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${displayName}&lt;/span&gt; extension has been enabled."&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;else&lt;/span&gt;
        &lt;span class="hljs-built_in"&gt;echo&lt;/span&gt; &lt;span class="hljs-string"&gt;"&lt;span class="hljs-variable"&gt;${displayName}&lt;/span&gt; extension has been disabled."&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;fi&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function expects a single argument, the name of an extension, and optionally a second argument, the &lt;a target="_blank" href="https://twitter.com/derickr/status/1184465999971700736" title="Derick Rethans: What's the correct spelling and capitalisation of Xdebug?"&gt;display name&lt;/a&gt;. The function iterates over all installed PHP versions, and either adds a &lt;code&gt;.bak&lt;/code&gt; extension to the name of a matching configuration file, or removes it - effectively disabling or enabling the corresponding extension.&lt;/p&gt;
&lt;p&gt;Now I could run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;toggle pcov
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;toggle xdebug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but to be honest, that is still too much work. I have added the following to &lt;code&gt;.zshrc&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash hljs bash" data-lang="bash"&gt;&lt;span class="hljs-comment"&gt;# Toggle PCOV&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;p&lt;/span&gt;&lt;/span&gt; () {
    toggle pcov PCOV
}

&lt;span class="hljs-comment"&gt;# Toggle Xdebug&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;x&lt;/span&gt;&lt;/span&gt; () {
    toggle xdebug Xdebug
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to these small functions, I can run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to quickly disable or enable PCOV and Xdebug.&lt;/p&gt;

</content></entry><entry><title>Using Makefiles in projects where I can not use them</title><category term="maintenance"/><category term="makefile"/><category term="productivity"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2020/05/07/using-makefiles-in-projects-where-i-can-not-use-them/"/><id>https://localheinz.com/articles/2020/05/07/using-makefiles-in-projects-where-i-can-not-use-them/</id><updated>2020-05-07T09:45:00+02:00</updated><content>&lt;h1&gt;
  Using Makefiles in projects where I can not use them
&lt;/h1&gt;


&lt;p&gt;In &lt;a href="/articles/2018/01/24/makefile-for-lazy-developers/" target="_blank" title="Makefile&amp;#x20;for&amp;#x20;lazy&amp;#x20;developers"&gt;Makefile for lazy developers&lt;/a&gt;, I have shared how I use &lt;code&gt;Makefile&lt;/code&gt;s to save time running frequent tasks.&lt;/p&gt;
&lt;p&gt;As a maintainer of a project, I can use any tool I like to get the job done, and of course, I use Makefiles in all of these projects.&lt;/p&gt;
&lt;p&gt;As a collaborator of a project, I do not have that luxury. I depend on the owners of a project. Some projects already use an alternative, perhaps &lt;a target="_blank" href="https://getcomposer.org/doc/articles/scripts.md" title="Composer Scripts"&gt;composer scripts&lt;/a&gt; or &lt;a target="_blank" href="https://www.phing.info" title="PHing"&gt;PHing&lt;/a&gt;. Occasionally I have suggested the use of &lt;code&gt;Makefile&lt;/code&gt;s. Often the owners have stated that they prefer not to use &lt;code&gt;Makefile&lt;/code&gt;s. Rarely the owners already have a &lt;code&gt;Makefile&lt;/code&gt; but object to modifications.&lt;/p&gt;
&lt;p&gt;I respect that. However, I can still use &lt;code&gt;Makefile&lt;/code&gt;s the way I like in every single project, and as a matter of fact, I do.&lt;/p&gt;
&lt;p&gt;In &lt;a href="/articles/2019/10/25/project-notes/" title="Project notes"&gt;Project notes&lt;/a&gt;, I have shared how I keep files in a &lt;code&gt;.notes&lt;/code&gt; directory within the root directory of a project - without the need to check them into version control. This directory is where I put a &lt;code&gt;Makefile&lt;/code&gt; when necessary.&lt;/p&gt;
&lt;p&gt;I can now run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;make -f .note/Makefile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to run the first target of the &lt;code&gt;Makefile&lt;/code&gt;, but I find that this is too much work.&lt;/p&gt;
&lt;p&gt;Instead, I have adjusted the alias I previously used from&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;alias m="make"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;alias m="if [[ -f .note/Makefile ]]; then make -f .note/Makefile; else make; fi"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in every single project and still use &lt;code&gt;Makefile&lt;/code&gt;s the way I prefer.&lt;/p&gt;

</content></entry><entry><title>Switching PHP versions when using Homebrew</title><category term="homebrew"/><category term="macos"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2020/05/05/switching-php-versions-when-using-homebrew/"/><id>https://localheinz.com/articles/2020/05/05/switching-php-versions-when-using-homebrew/</id><updated>2020-05-05T13:20:00+02:00</updated><content>&lt;h1&gt;
  Switching PHP versions when using Homebrew
&lt;/h1&gt;


&lt;p&gt;&lt;a href="https://brew.sh" target="_blank" title="Homebrew"&gt;Homebrew&lt;/a&gt; is an excellent option for installing one or more PHP versions on your Mac.&lt;/p&gt;
&lt;p&gt;But how can you switch PHP versions when you have installed more than one version of PHP with Homebrew?&lt;/p&gt;
&lt;p&gt;Which do you prefer - &lt;a href="#content-slowly-switching-php-versions" title=""&gt;slowly&lt;/a&gt; or &lt;a href="#content-quickly-switching-php-versions" title=""&gt;quickly&lt;/a&gt;?&lt;/p&gt;
&lt;h2 id="content-scenario" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-scenario" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Scenario&lt;/h2&gt;
&lt;p&gt;You have installed PHP 7.4, 8.0, 8.1, and 8.2 with Homebrew. You have no idea which PHP version is currently linked, but you want to use PHP 8.2.&lt;/p&gt;
&lt;h2 id="content-slowly-switching-php-versions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-slowly-switching-php-versions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Slowly switching PHP versions&lt;/h2&gt;
&lt;p&gt;First, you need to find out which PHP version is currently linked.&lt;/p&gt;
&lt;p&gt;Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;php -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;PHP 7.4.33 (cli) (built: Jan 21 2023 06:43:54) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.33, Copyright (c), by Zend Technologies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second, since you know that PHP 7.4 is currently linked, you need to unlink PHP 7.4.&lt;/p&gt;
&lt;p&gt;Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;brew unlink php@7.4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;Unlinking /opt/homebrew/Cellar/php@7.4/7.4.33_1... 25 symlinks removed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Third, you need to link PHP 8.2, the PHP version you want to use.&lt;/p&gt;
&lt;p&gt;Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;brew link php@8.2 --force --overwrite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;Linking /opt/homebrew/Cellar/php/8.2.1_1... 24 symlinks created.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fourth, you want to double-check that PHP 8.2 is actually linked now.&lt;/p&gt;
&lt;p&gt;Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;php -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;PHP 8.2.1 (cli) (built: Jan 12 2023 03:48:24) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.1, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.1, Copyright (c), by Zend Technologies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perfect, you can start working with PHP 8.2 after running four (!) commands.&lt;/p&gt;
&lt;h2 id="content-quickly-switching-php-versions" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-quickly-switching-php-versions" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Quickly switching PHP versions&lt;/h2&gt;
&lt;p&gt;How about running a single command instead of four commands?&lt;/p&gt;
&lt;p&gt;How about running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;8.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to switch from any (!) PHP version to PHP 8.2? Let me show you how you can achieve that in two steps.&lt;/p&gt;
&lt;p&gt;First, you need to install a modern version of &lt;a href="https://man7.org/linux/man-pages/man1/grep.1.html" target="_blank" title="Linux manual page: grep"&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/a&gt; with Homebrew.&lt;/p&gt;
&lt;p&gt;Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;grep --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;on macOS Ventura yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;grep (BSD grep, GNU compatible) 2.6.0-FreeBSD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, that version does not support Perl-compatible regular expressions (PCREs) that you will use in the next step.&lt;/p&gt;
&lt;p&gt;Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;brew install grep
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;==&amp;gt; Fetching grep
==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/grep/manifests/3.8_1
Already downloaded: /Users/am/Library/Caches/Homebrew/downloads/a2ee255269e00fca81c021b40c338155577736b42bab878651de2922798bd235--grep-3.8_1.bottle_manifest.json
==&amp;gt; Downloading https://ghcr.io/v2/homebrew/core/grep/blobs/sha256:d2450448352fb2c389634cab3dec581882f6fc0f02a79489b4ba9c603b8f780b
Already downloaded: /Users/am/Library/Caches/Homebrew/downloads/aefda17db919b6aed862b1ae9d2deb2d07197fef50b34f0bcacf179108b8fd12--grep--3.8_1.arm64_ventura.bottle.tar.gz
==&amp;gt; Pouring grep--3.8_1.arm64_ventura.bottle.tar.gz
==&amp;gt; Caveats
All commands have been installed with the prefix "g".
If you need to use these commands with their normal names, you
can add a "gnubin" directory to your PATH from your bashrc like:
  PATH="/opt/homebrew/opt/grep/libexec/gnubin:$PATH"
==&amp;gt; Summary
🍺  /opt/homebrew/Cellar/grep/3.8_1: 19 files, 1MB
==&amp;gt; Running `brew cleanup grep`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="tip"&gt;
  &lt;p&gt;💡 Note that the command &lt;code&gt;grep&lt;/code&gt; has been installed with the prefix &lt;code&gt;g&lt;/code&gt; to allow you to use &lt;code&gt;grep&lt;/code&gt; as installed with macOS if necessary.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Second, after careful inspection, add the following shell script to &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;&lt;span class="hljs-meta"&gt;#&lt;/span&gt;&lt;span class="bash"&gt; determine versions of PHP installed with HomeBrew&lt;/span&gt;
installedPhpVersions=($(brew ls --versions | ggrep -E 'php(@.*)?\s' | ggrep -oP '(?&amp;lt;=\s)\d\.\d' | uniq | sort))
&lt;span class="hljs-meta"&gt;
#&lt;/span&gt;&lt;span class="bash"&gt; create &lt;span class="hljs-built_in"&gt;alias&lt;/span&gt; &lt;span class="hljs-keyword"&gt;for&lt;/span&gt; every version of PHP installed with HomeBrew&lt;/span&gt;
for phpVersion in ${installedPhpVersions[*]}; do
    value="{"

    for otherPhpVersion in ${installedPhpVersions[*]}; do
        if [ "${otherPhpVersion}" = "${phpVersion}" ]; then
            continue;
        fi

        value="${value} brew unlink php@${otherPhpVersion};"
    done

    value="${value} brew link php@${phpVersion} --force --overwrite; } &amp;amp;&amp;gt; /dev/null &amp;amp;&amp;amp; php -v"

    alias "${phpVersion}"="${value}"
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script will first determine the versions of PHP you have installed with Homebrew.&lt;/p&gt;
&lt;p&gt;The script will then create aliases for each PHP version you have installed with Homebrew. For each PHP version, the corresponding alias will unlink all other PHP versions, link the PHP version, and finally show the version information.&lt;/p&gt;
&lt;p&gt;By adding the script to &lt;code&gt;.zshrc&lt;/code&gt;, the script will run every time you open a new terminal. This means that the list of aliases will stay current at all times, depending on the versions of PHP that you have currently installed with Homebrew.&lt;/p&gt;
&lt;p&gt;When you have PHP 7.4, 8.0, 8.1, and 8.2 installed with Homebrew, added the script to &lt;code&gt;.zshrc&lt;/code&gt;, and opened a new terminal, running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;alias | grep php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;7.4='{ brew unlink php@8.0; brew unlink php@8.1; brew unlink php@8.2; brew link php@7.4 --force --overwrite; } &amp;amp;&amp;gt; /dev/null &amp;amp;&amp;amp; php -v'
8.0='{ brew unlink php@7.4; brew unlink php@8.1; brew unlink php@8.2; brew link php@8.0 --force --overwrite; } &amp;amp;&amp;gt; /dev/null &amp;amp;&amp;amp; php -v'
8.1='{ brew unlink php@7.4; brew unlink php@8.0; brew unlink php@8.2; brew link php@8.1 --force --overwrite; } &amp;amp;&amp;gt; /dev/null &amp;amp;&amp;amp; php -v'
8.2='{ brew unlink php@7.4; brew unlink php@8.0; brew unlink php@8.1; brew link php@8.2 --force --overwrite; } &amp;amp;&amp;gt; /dev/null &amp;amp;&amp;amp; php -v'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As promised, Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;8.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;switches to PHP 8.2 and yields&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;PHP 8.2.1 (cli) (built: Jan 12 2023 03:48:24) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.1, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.1, Copyright (c), by Zend Technologies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Have you found a better option for switching PHP versions when using Homebrew?&lt;/p&gt;

</content></entry><entry><title>From @localheinz to @ergebnis</title><category term="open-source"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2019/12/10/from-localheinz-to-ergebnis/"/><id>https://localheinz.com/articles/2019/12/10/from-localheinz-to-ergebnis/</id><updated>2019-12-10T16:40:00+01:00</updated><content>&lt;h1&gt;
  From @localheinz to @ergebnis
&lt;/h1&gt;


&lt;p&gt;With 2019 in review and 2020 coming closer, I am sorting out a few things related to open-source projects I am maintaining.&lt;/p&gt;
&lt;h2 id="content-move" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-move" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Move&lt;/h2&gt;
&lt;p&gt;I have decided to move the open-source PHP projects I am currently maintaining on my personal account &lt;a target="_blank" href="https://github.com/localheinz" title="@localheinz on GitHub"&gt;&lt;code&gt;@localheinz&lt;/code&gt;&lt;/a&gt; to the organization &lt;a target="_blank" href="https://github.com/ergebnis" title="@ergebnis on GitHub"&gt;&lt;code&gt;@ergebnis&lt;/code&gt;&lt;/a&gt; on &lt;a target="_blank" href="https://github.com" title="GitHub"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following projects have been moved so far:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/classy" title="ergebnis/classy"&gt;&lt;code&gt;ergebnis/classy&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/clock" title="ergebnis/clock"&gt;&lt;code&gt;ergebnis/clock&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/composer-normalize" title="ergebnis/composer-normalize"&gt;&lt;code&gt;ergebnis/composer-normalize&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/composer-normalize-action" title="ergebnis/composer-normalize-action"&gt;&lt;code&gt;ergebnis/composer-normalize-action&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/factory-girl-definition" title="ergebnis/factory-girl-definition"&gt;&lt;code&gt;ergebnis/factory-girl-definition&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/factory-muffin-definition" title="ergebnis/factory-muffin-definition"&gt;&lt;code&gt;ergebnis/factory-muffin-definition&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/faker-provider" title="ergebnis/faker-provider"&gt;&lt;code&gt;ergebnis/faker-provider&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/github-changelog" title="ergebnis/github-changelog"&gt;&lt;code&gt;ergebnis/github-changelog&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/http-method" title="ergebnis/http-method"&gt;&lt;code&gt;ergebnis/http-method&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/json-normalizer" title="ergebnis/json-normalizer"&gt;&lt;code&gt;ergebnis/json-normalizer&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/json-printer" title="ergebnis/json-printer"&gt;&lt;code&gt;ergebnis/json-printer&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/php-cs-fixer-config" title="ergebnis/php-cs-fixer-config"&gt;&lt;code&gt;ergebnis/php-cs-fixer-config&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/php-library-template" title="ergebnis/php-library-template"&gt;&lt;code&gt;ergebnis/php-library-template&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/ergebnis/phpunit-framework-constraint" title="ergebnis/phpunit-framework-constraint"&gt;&lt;code&gt;ergebnis/phpunit-framework-constraint&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-why" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-why" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Why&lt;/h2&gt;
&lt;p&gt;At the moment, I have ~350 public repositories under my personal account. Most of these repositories are &lt;em&gt;forks&lt;/em&gt; of projects I have contributed to in the past, and only a few of them are &lt;em&gt;sources&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;By moving the &lt;em&gt;source&lt;/em&gt; repositories to a separate organization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;they become more visible to me and can be more easily be discovered by others&lt;/li&gt;
&lt;li&gt;they become less attached to my person and maintenance can be more easily transferred to collaborators when necessary&lt;/li&gt;
&lt;li&gt;adding and managing maintainers or collaborators becomes a lot easier&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are using any of the packages provided by these repositories: thank you! I am happy that I - standing on the shoulders of giants - have built something useful for others.&lt;/p&gt;
&lt;p&gt;In any case, take a look at &lt;code&gt;CHANGELOG.md&lt;/code&gt;; I have provided instructions for updating. In most cases, the only things that have changed are vendor prefixes (from &lt;code&gt;localheinz&lt;/code&gt; to &lt;code&gt;ergebnis&lt;/code&gt;) and namespaces (from &lt;code&gt;Localheinz&lt;/code&gt; to &lt;code&gt;Ergebnis&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="content-how" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-how" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;How&lt;/h2&gt;
&lt;p&gt;Perhaps you have been considering to do something similar?&lt;/p&gt;
&lt;p&gt;Here is an example of how I moved &lt;code&gt;localheinz/test-util&lt;/code&gt; to &lt;code&gt;ergebnis/test-util&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;a target="_blank" href="https://help.github.com/en/github/administering-a-repository/transferring-a-repository" title="GitHub Help: Transferring a repository"&gt;transfer ownership&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;rename references &lt;code&gt;localheinz/test-util&lt;/code&gt; to &lt;code&gt;ergebnis/test-util&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rename namespace &lt;code&gt;Localheinz\Test\Util&lt;/code&gt; to &lt;code&gt;Ergebnis\Test\Util&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;provide details on how to update in &lt;a target="_blank" href="https://github.com/ergebnis/test-util/blob/master/CHANGELOG.md#090" title="Changelog for ergebnis/test-util"&gt;&lt;code&gt;CHANGELOG.md&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;submit package &lt;code&gt;ergebnis/test-util&lt;/code&gt; to &lt;a target="_blank" href="https://packagist.org/packages/submit" title="Packagist: Submit package"&gt;Packagist&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ensure that &lt;a target="_blank" href="https://packagist.org/about#how-to-update-packages" title="Packagist: How to update packages"&gt;integration with Packagist&lt;/a&gt; works&lt;/li&gt;
&lt;li&gt;abandon package &lt;a target="_blank" href="https://packagist.org/packages/localheinz/test-util" title="localheinz/test-util on Packagist"&gt;&lt;code&gt;localheinz/test-util&lt;/code&gt;&lt;/a&gt; on Packagist and suggest &lt;code&gt;ergebnis/test-util&lt;/code&gt; as a replacement&lt;/li&gt;
&lt;li&gt;tag and push a new major release of &lt;code&gt;ergebnis/test-util&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;update packages previously using &lt;code&gt;localheinz/test-util&lt;/code&gt; to using &lt;code&gt;ergebnis/test-util&lt;/code&gt;, for example, in &lt;a target="_blank" href="https://github.com/ergebnis/phpstan-rules/pull/154" title="Enhancement: Use ergebnis/test-util instead of localheinz/test-util"&gt;&lt;code&gt;ergebnis/phpstan-rules&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;keep fingers crossed 🤞&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="content-archive" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-archive" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Archive&lt;/h2&gt;
&lt;p&gt;At the same time, I am archiving a few projects that I have never brought to a stage that makes sense keeping them around:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/localheinz/composer-json-normalizer" title="localheinz/composer-json-normalizer"&gt;&lt;code&gt;localheinz/composer-json-normalizer&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/localheinz/composer-require-checker-action" title="localheinz/composer-require-checker-action"&gt;&lt;code&gt;localheinz/composer-require-checker-action&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/localheinz/data-structure" title="localheinz/data-structure"&gt;&lt;code&gt;localheinz/data-structure&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/localheinz/github-pulse" title="localheinz/github-pulse"&gt;&lt;code&gt;localheinz/github-pulse&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/localheinz/specification" title="localheinz/specification"&gt;&lt;code&gt;localheinz/specification&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a target="_blank" href="https://github.com/localheinz/token" title="localheinz/token"&gt;&lt;code&gt;localheinz/token&lt;/code&gt;&lt;/a&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you find the transition from &lt;a target="_blank" href="https://github.com/localheinz" title="@localheinz on GitHub"&gt;&lt;code&gt;@localheinz&lt;/code&gt;&lt;/a&gt; to &lt;a target="_blank" href="https://github.com/ergebnis" title="@ergebnis on GitHub"&gt;&lt;code&gt;@ergebnis&lt;/code&gt;&lt;/a&gt; difficult, please accept my apologies.&lt;/p&gt;
&lt;p&gt;If you need any help, let me know, and I will see what I can do.&lt;/p&gt;

</content></entry><entry><title>The grass could be a lot greener on both sides of the fence</title><category term="community"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2019/12/03/the-grass-could-be-a-lot-greener-on-both-sides-of-the-fence/"/><id>https://localheinz.com/articles/2019/12/03/the-grass-could-be-a-lot-greener-on-both-sides-of-the-fence/</id><updated>2019-12-03T12:00:00+02:00</updated><content>&lt;h1&gt;
  The grass could be a lot greener on both sides of the fence
&lt;/h1&gt;


&lt;p&gt;It is the end of 2019, and the invitation to write an article for 24 Days in December is an excellent opportunity to reflect on what has happened this year - and maybe even look back a bit further.&lt;/p&gt;
&lt;h2 id="content-past" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-past" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Past&lt;/h2&gt;
&lt;p&gt;In 1990 I wrote my first line of code on an Atari Portfolio, and at the time, I would have never guessed that one day I would be building and maintaining software for a living.&lt;/p&gt;
&lt;p&gt;In 1999 I got paid the first time for building a website with static HTML.&lt;/p&gt;
&lt;p&gt;In 2001 I took up an internship in a startup in Berlin, and there I would write my first line of PHP code. In 2007, shortly before dropping out of business school, I started working full-time with PHP in another startup. In the years to follow, I worked for several startups, always with PHP.&lt;/p&gt;
&lt;p&gt;In 2012 I found out that PHP developers meet up monthly to give and listen to talks about their experiences at so-called user groups, and I first attended the &lt;a target="_blank" href="https://www.bephpug.de" title="BEPHPUG"&gt;Berlin PHP user group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From writing my first line of PHP code in 2001 to attending my first user group in 2012, it took me eleven years to realize that I am part of an actual community! A community of people who not necessarily work in the same companies but a community of people who share their experiences; people who are eager to learn from and eventually help each other to become better developers!&lt;/p&gt;
&lt;p&gt;In the months and years to follow, a lot of things changed for me. Most of all, seeing that there is a community out there, and that it is easy to become a part of it, made me want to become a better developer.&lt;/p&gt;
&lt;p&gt;Soon I started following other developers on Twitter. I made my first contributions to open-source software. I attended my first PHP conference. Attending the user groups more or less regularly, I got to know other developers, some of whom introduced me to other people in the PHP community. I took note of books recommended by speakers, and added them to an ever-growing reading list. I overcame my fear of writing tests. Eventually, I started writing tests first. I continued to make contributions to open-source software. I began working for a company with an actual build pipeline and a process that I still admire today. I overcame framework fanboy-ism and got hired over Twitter to work remotely for a company in New York. I attended conferences more regularly. Seeing a lot of developers over and over again, I found it rather easy to get in touch with them -  and was surprised that they are very approachable.  I became acknowledged with more and more developer tools -  tools that made my life as a PHP developer a lot easier. I started contributing to these tools, and even published a few small open-source libraries that have users other than me. I came around to writing articles, and at the moment, I’m struggling with writing this article here.&lt;/p&gt;
&lt;p&gt;Probably none of this would have happened if I had never attended the meetings of the PHP user group - none of it. Up until the moment when I first joined the user group meeting, I was entirely concerned with &lt;i&gt;making things run&lt;/i&gt;. By going to these meetings, I had opened a new chapter and began wondering how to &lt;i&gt;make things right&lt;/i&gt;.&lt;/p&gt;
&lt;h2 id="content-present" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-present" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Present&lt;/h2&gt;
&lt;p&gt;In the last five years, the focus of my work has shifted from creating to maintaining and modernizing legacy applications. Thanks to excellent tooling experiences made, dealing with legacy applications is not a hard problem.&lt;/p&gt;
&lt;p&gt;What I have come to realize in 2019, but have heard numerous time before, is, that people are the hard problems - just as Jerry Weinberg, who passed away in August 2018, put it in &lt;a target="_blank" href="https://leanpub.com/b/secretsofconsulting" title="The Secrets of Consulting"&gt;The Secrets of Consulting&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    No matter how it looks at first, it’s always a people problem.
  &lt;/p&gt;
  &lt;div class="source"&gt;
    &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;twitter.com&amp;#x2F;JerryWeinberg" target="_blank" title="Gerald&amp;#x20;M.&amp;#x20;Weinberg&amp;#x20;on&amp;#x20;Twitter"&gt;Gerald M. Weinberg&lt;/a&gt;, The Secrets of Consulting
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;Setting up a build pipeline, putting developer tools in place, refining a process, making it harder for developers to ship faulty code - this is all excellent.&lt;/p&gt;
&lt;p&gt;Working &lt;em&gt;on code&lt;/em&gt;, however, has only short-term effects: as soon as the project is over, developers are left alone with a code base they hardly understand. When developers have only learned to follow the rules enforced by automated systems, but have not learned why they have been put in place, these automated systems become an annoyance rather than a crutch.&lt;/p&gt;
&lt;p&gt;Working &lt;em&gt;with developers&lt;/em&gt;, on the other hand, can have long-term effects: by asking developers questions instead of giving them directions, by letting developers fail and helping them up again, by showing them alternative ways of developing software, and by encouraging them to question the status quo, they will eventually become better versions of themselves.&lt;/p&gt;
&lt;h2 id="content-future" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-future" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Future&lt;/h2&gt;
&lt;p&gt;Perhaps you are working on a legacy code base, with colleagues who do not care so much. Perhaps you are looking for a new job already. Of course, the grass always looks greener on the other side - but is it?&lt;/p&gt;
&lt;p&gt;For some of your colleagues, being a software developer is just a 9-to-5 job. Others have already so much on their plate, maybe they really cannot and will not be able to do much more. However, there will always be interested developers who only need a little nudge or a pointer.&lt;/p&gt;
&lt;p&gt;You could be the person reaching out, go ahead and invite them to the next &lt;a target="_blank" href="https://php.ug" title="PHP user group"&gt;user group&lt;/a&gt;.&lt;/p&gt;
&lt;div class="note"&gt;
  &lt;p&gt;This article was originally published on &lt;a target="_blank" href="https://24daysindecember.net/2019/12/03/the-grass-could-be-a-lot-greener-on-both-sides-of-the-fence/" title="24 Days in December"&gt;24 Days in December&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

</content></entry><entry><title>Project notes</title><category term="git"/><category term="phpstorm"/><category term="productivity"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2019/10/25/project-notes/"/><id>https://localheinz.com/articles/2019/10/25/project-notes/</id><updated>2019-10-25T07:55:00+02:00</updated><content>&lt;h1&gt;
  Project notes
&lt;/h1&gt;


&lt;p&gt;In almost every project I work on, I take notes in Markdown files, keep track of small lists of things that need to be done, or create small scripts - and most of these are not important enough to find their way into a journal, an issue tracker, or version control.&lt;/p&gt;
&lt;p&gt;I have observed developers who keep these files untracked within or in a separate directory outside of the project. I keep these files within the project. This way, they are always in reach.&lt;/p&gt;
&lt;p&gt;In 2014, &lt;a target="_blank" href="https://blog.jetbrains.com/idea/2014/09/intellij-idea-14-eap-138-2210-brings-scratch-files-and-better-mercurial-integration/" title="Jetbrains: IntelliJ IDEA 14 EAP 138.2210 Brings Scratch Files and Better Mercurial Integration"&gt;Jetbrains introduced&lt;/a&gt; the &lt;a target="_blank" href="https://www.jetbrains.com/help/phpstorm/scratches.html" title="PhpStorm: Scratch Files"&gt;Scratch Files&lt;/a&gt; feature.&lt;/p&gt;
&lt;p&gt;However, there's something better that you might have been using already and does not depend on features provided by your IDE: a &lt;a target="_blank" href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_core_excludesfile" title="Git Configuration: core.excludesfile"&gt;global &lt;code&gt;.gitignore&lt;/code&gt; file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;touch ~/.gitignore_global
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to create a &lt;code&gt;.gitignore_global&lt;/code&gt; file in your home directory.&lt;/p&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;git config --global core.excludesfile ~/.gitignore_global
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to configure &lt;code&gt;git&lt;/code&gt; to use &lt;code&gt;.gitignore_global&lt;/code&gt; as a global exludes file.&lt;/p&gt;
&lt;p&gt;Add the following&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; .idea/
&lt;span class="hljs-addition"&gt;+.note/&lt;/span&gt;
 .DS_Store
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to to &lt;code&gt;.gitignore_global&lt;/code&gt; to ignore a &lt;code&gt;.note/&lt;/code&gt; directory in any project that is under version control with &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, for a project I'm currently working on, my &lt;code&gt;.note/&lt;/code&gt; directory currently looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;.note
├── XYZ-123         # sub-directory for issue XYZ-123
│   ├── commits.md  # list of commit hashes kept as a backup
│   ├── test.php    # small script for prototyping
│   └── todo.md     # list of todos for a ticket in Markdown
├── todo.md         # list of general todos in Markdown
└── XYZ-234.md      # notes regarding issue XYZ-234
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hope it helps!&lt;/p&gt;

</content></entry><entry><title>Test to the left, production to the right</title><category term="phpstorm"/><category term="productivity"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2019/10/14/test-to-the-left-production-to-the-right/"/><id>https://localheinz.com/articles/2019/10/14/test-to-the-left-production-to-the-right/</id><updated>2019-10-14T06:40:00+02:00</updated><content>&lt;h1&gt;
  Test to the left, production to the right
&lt;/h1&gt;


&lt;p&gt;Not only do I speak and read languages where texts are written and read from left-to-right and top-to-bottom, but I also work with programming languages where programs are written in the same order.&lt;/p&gt;
&lt;p&gt;I'm also a proponent of Test-Driven Development (TDD), a practice that suggests following &lt;a target="_blank" href="http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd" title="Uncle Bob: The three rules of TDD"&gt;three simple rules&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;
      You are not allowed to write any production code unless it is to make a failing unit test pass.
    &lt;/li&gt;
    &lt;li&gt;
      You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
    &lt;/li&gt;
    &lt;li&gt;
      You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
    &lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;
    &amp;mdash; &lt;a href="http&amp;#x3A;&amp;#x2F;&amp;#x2F;cleancoder.com" target="_blank" title="Robert&amp;#x20;C.&amp;#x20;Martin"&gt;Robert C. Martin&lt;/a&gt;, The Three Rules of TDD
  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Consequently, it only feels natural to me to split the editor screen vertically, with&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the more important test code on the left side&lt;/li&gt;
&lt;li&gt;the less important production code on the right side&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
  &lt;img alt="Test code to the left, production code to the right" src="/dist/img/article/2019/10/14/test-code-to-the-left-production-code-to-the-right/split-vertically.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;In JetBrains IDEs this can be achieved by right-clicking on the editor tab and then selecting &lt;strong&gt;Split Vertically&lt;/strong&gt; (find out more at &lt;a target="_blank" href="https://www.jetbrains.com/help/phpstorm/using-code-editor.html#split_screen" title="Editor Basics: Split Screen"&gt;Editor Basics: Split Screen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How do you organize yourself working with test and production code?&lt;/p&gt;

</content></entry><entry><title>Running tests for PHPUnit in PhpStorm</title><category term="phpstorm"/><category term="phpunit"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2019/04/27/running-tests-for-phpunit-in-phpstorm/"/><id>https://localheinz.com/articles/2019/04/27/running-tests-for-phpunit-in-phpstorm/</id><updated>2019-04-27T11:01:00+01:00</updated><content>&lt;h1&gt;
  Running tests for PHPUnit in PhpStorm
&lt;/h1&gt;


&lt;p&gt;Have you been working on &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;sebastianbergmann&amp;#x2F;phpunit" target="_blank" title="sebastianbergmann/phpunit on GitHub"&gt;&lt;code&gt;phpunit/phpunit&lt;/code&gt;&lt;/a&gt;? Are you a user of &lt;a target="_blank" href="https://www.jetbrains.com/phpstorm/" title="PhpStorm"&gt;PhpStorm&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;I have and I am, and until yesterday I have been struggling with setting up &lt;code&gt;phpunit/phpunit&lt;/code&gt; as test framework for PhpStorm so I can run tests for &lt;code&gt;phpunit/phpunit&lt;/code&gt; from within PhpStorm for &lt;code&gt;phpunit/phpunit&lt;/code&gt; itself.&lt;/p&gt;
&lt;h2 id="content-use-composer-autoloader" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-use-composer-autoloader" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Use Composer autoloader&lt;/h2&gt;
&lt;p&gt;In any project that requires &lt;code&gt;phpunit/phpunit&lt;/code&gt; as a development dependency, the setup is fairly simple: pick the &lt;strong&gt;Use Composer autoloader&lt;/strong&gt; option and reference the path to &lt;code&gt;vendor/autoload.php&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, for &lt;code&gt;phpunit/phpunit&lt;/code&gt; this will not work, as it does not have a dependency on itself:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Use composer autoloader" src="/dist/img/article/2019/04/27/running-tests-for-phpunit-in-phpstorm/01-use-composer-autoloader.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;h2 id="content-path-to-phpunitphar-with-downloaded-phpunitphar" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-path-to-phpunitphar-with-downloaded-phpunitphar" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Path to phpunit.phar (with downloaded phpunit.phar)&lt;/h2&gt;
&lt;p&gt;Ok, then, let's try the option &lt;strong&gt;Path to phpunit.phar&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Looks like we need to download &lt;code&gt;phpunit.phar&lt;/code&gt; here, so let's head over to &lt;a target="_blank" href="https://phpunit.de/getting-started/phpunit-8.html" title="Getting Started with PHPUnit 8"&gt;Getting Started with PHPUnit 8&lt;/a&gt; and find out how to download phpunit.phar`.&lt;/p&gt;
&lt;p&gt;Personally, I ran&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;wget -O ~/Sites/phpunit.phar https://phar.phpunit.de/phpunit.phar
chmod +x ~/Sites/phpunit.phar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I pick the &lt;strong&gt;Path to phpunit.phar&lt;/strong&gt; option and specify the path to the previously downloaded &lt;code&gt;phpunit.phar&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Path to phpunit.phar (with downloaded phpunit.phar)" src="/dist/img/article/2019/04/27/running-tests-for-phpunit-in-phpstorm/02-path-to-phpunit-phar-with-downloaded-phar.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;Looks like that worked fine, now let's run the tests by right-clicking on &lt;code&gt;phpunit.xml&lt;/code&gt;, and selecting the option &lt;strong&gt;Run 'phpunit.xml'&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Great, the tests run! But they fail with&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-plaintext hljs plaintext" data-lang="plaintext"&gt;Testing started at 11:25 ...
/usr/local/Cellar/php@7.2/7.2.17_1/bin/php /Users/am/Sites/phpunit --configuration /Users/am/Sites/sebastianbergmann/phpunit/phpunit.xml --teamcity
PHPUnit 8.1.3 by Sebastian Bergmann and contributors.

Runtime:       PHP 7.2.17 with Xdebug 2.7.1
Configuration: /Users/am/Sites/sebastianbergmann/phpunit/phpunit.xml


TypeError : Return value of PHPUnit\Framework\Constraint\Count::getCountOf() must be of the type integer or null, none returned
 phar:///Users/am/Sites/phpunit/phpunit/Framework/Constraint/Count.php:86
 phar:///Users/am/Sites/phpunit/phpunit/Framework/Constraint/Count.php:44
 phar:///Users/am/Sites/phpunit/phpunit/Framework/Constraint/Constraint.php:45
 /Users/am/Sites/sebastianbergmann/phpunit/tests/unit/Framework/Constraint/CountTest.php:164
 phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:1172
 phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:854
 phar:///Users/am/Sites/phpunit/phpunit/Framework/TestResult.php:685
 phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:808
 phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
 phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
 phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
 phar:///Users/am/Sites/phpunit/phpunit/TextUI/TestRunner.php:601
 phar:///Users/am/Sites/phpunit/phpunit/TextUI/Command.php:207
 phar:///Users/am/Sites/phpunit/phpunit/TextUI/Command.php:163

PHP Fatal error:  Class Mock_Exception_19a384fc contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (ExceptionWithThrowable::getAdditionalInformation) in phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php(608) : eval()'d code on line 1
PHP Stack trace:
PHP   1. {main}() /Users/am/Sites/phpunit:0
PHP   2. PHPUnit\TextUI\Command::main() /Users/am/Sites/phpunit:619
PHP   3. PHPUnit\TextUI\Command-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/TextUI/Command.php:163
PHP   4. PHPUnit\TextUI\TestRunner-&amp;gt;doRun() phar:///Users/am/Sites/phpunit/phpunit/TextUI/Command.php:207
PHP   5. PHPUnit\Framework\TestSuite-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/TextUI/TestRunner.php:601
PHP   6. PHPUnit\Framework\TestSuite-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
PHP   7. PHPUnit\Framework\TestSuite-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
PHP   8. GeneratorTest-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
PHP   9. PHPUnit\Framework\TestResult-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:808
PHP  10. GeneratorTest-&amp;gt;runBare() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestResult.php:685
PHP  11. GeneratorTest-&amp;gt;runTest() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:854
PHP  12. GeneratorTest-&amp;gt;testMockingOfThrowable() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:1172
PHP  13. PHPUnit\Framework\MockObject\Generator-&amp;gt;getMock() /Users/am/Sites/sebastianbergmann/phpunit/tests/unit/Framework/MockObject/GeneratorTest.php:190
PHP  14. PHPUnit\Framework\MockObject\Generator-&amp;gt;getObject() phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php:196
PHP  15. PHPUnit\Framework\MockObject\Generator-&amp;gt;evalClass() phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php:562
PHP  16. eval() phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php:608

Fatal error: Class Mock_Exception_19a384fc contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (ExceptionWithThrowable::getAdditionalInformation) in phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php(608) : eval()'d code on line 1

Call Stack:
    0.0057     543992   1. {main}() /Users/am/Sites/phpunit:0
    0.1437    9827856   2. PHPUnit\TextUI\Command::main() /Users/am/Sites/phpunit:619
    0.1437    9827968   3. PHPUnit\TextUI\Command-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/TextUI/Command.php:163
    0.4764   23119992   4. PHPUnit\TextUI\TestRunner-&amp;gt;doRun() phar:///Users/am/Sites/phpunit/phpunit/TextUI/Command.php:207
    0.7250   23869248   5. PHPUnit\Framework\TestSuite-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/TextUI/TestRunner.php:601
    0.7466   23869440   6. PHPUnit\Framework\TestSuite-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
    3.7839   25022328   7. PHPUnit\Framework\TestSuite-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
    3.8201   25324600   8. GeneratorTest-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestSuite.php:761
    3.8201   25324600   9. PHPUnit\Framework\TestResult-&amp;gt;run() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:808
    3.8202   25324600  10. GeneratorTest-&amp;gt;runBare() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestResult.php:685
    3.8204   25341232  11. GeneratorTest-&amp;gt;runTest() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:854
    3.8204   25341288  12. GeneratorTest-&amp;gt;testMockingOfThrowable() phar:///Users/am/Sites/phpunit/phpunit/Framework/TestCase.php:1172
    3.8204   25341288  13. PHPUnit\Framework\MockObject\Generator-&amp;gt;getMock() /Users/am/Sites/sebastianbergmann/phpunit/tests/unit/Framework/MockObject/GeneratorTest.php:190
    3.8209   25344136  14. PHPUnit\Framework\MockObject\Generator-&amp;gt;getObject() phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php:196
    3.8209   25344136  15. PHPUnit\Framework\MockObject\Generator-&amp;gt;evalClass() phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php:562
    3.8211   25350656  16. eval('class Mock_Exception_19a384fc extends Exception implements ExceptionWithThrowable, PHPUnit\Framework\MockObject\MockObject
{
    private $__phpunit_invocationMocker;
    private $__phpunit_originalObject;
    private $__phpunit_configurable = [];
    private $__phpunit_returnValueGeneration = true;


    public function expects(\PHPUnit\Framework\MockObject\Matcher\Invocation $matcher): \PHPUnit\Framework\MockObject\Builder\InvocationMocker
    {
        return $this-&amp;gt;__phpunit_getInvocationMocker()-&amp;gt;expects($matcher);
    }

    public function method()
    {
        $any     = new \PHPUnit\Framework\MockObject\Matcher\AnyInvokedCount;
        $expects = $this-&amp;gt;expects($any);

        return call_user_func_array([$expects, 'method'], func_get_args());
    }

    public function __phpunit_setOriginalObject($originalObject): void
    {
        $this-&amp;gt;__phpunit_originalObject = $originalObject;
    }

    public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void
    {
        $this-&amp;gt;__phpunit_returnValueGeneration = $returnValueGeneration;
    }

    public function __phpunit_getInvocationMocker(): \PHPUnit\Framework\MockObject\InvocationMocker
    {
        if ($this-&amp;gt;__phpunit_invocationMocker === null) {
            $this-&amp;gt;__phpunit_invocationMocker = new \PHPUnit\Framework\MockObject\InvocationMocker($this-&amp;gt;__phpunit_configurable, $this-&amp;gt;__phpunit_returnValueGeneration);
        }

        return $this-&amp;gt;__phpunit_invocationMocker;
    }

    public function __phpunit_hasMatchers(): bool
    {
        return $this-&amp;gt;__phpunit_getInvocationMocker()-&amp;gt;hasMatchers();
    }

    public function __phpunit_verify(bool $unsetInvocationMocker = true): void
    {
        $this-&amp;gt;__phpunit_getInvocationMocker()-&amp;gt;verify();

        if ($unsetInvocationMocker) {
            $this-&amp;gt;__phpunit_invocationMocker = null;
        }
    }
}
') phar:///Users/am/Sites/phpunit/phpunit/Framework/MockObject/Generator.php:608


Process finished with exit code 255
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks like we have some issues here because we are using a &lt;em&gt;different&lt;/em&gt; version of &lt;code&gt;phpunit/phpunit&lt;/code&gt; for testing the version of &lt;code&gt;phpunit/phpunit&lt;/code&gt; we have currently checked out.&lt;/p&gt;
&lt;h2 id="content-path-to-phpunitphar-with-phpunit-binary" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-path-to-phpunitphar-with-phpunit-binary" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Path to phpunit.phar (with phpunit binary)&lt;/h2&gt;
&lt;p&gt;While not obvious, there's a rather simple solution that was suggested by &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;nicolashohm" target="_blank" title="Nicolas&amp;#x20;Hohm&amp;#x20;on&amp;#x20;GitHub"&gt;Nicolas Hohm&lt;/a&gt;: pick the &lt;strong&gt;Path to phpunit.phar&lt;/strong&gt; option and specify the path the &lt;code&gt;phpunit&lt;/code&gt; binary in the root of the project!&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Path to phpunit.phar (with phpunit binary)" src="/dist/img/article/2019/04/27/running-tests-for-phpunit-in-phpstorm/03-path-to-phpunit-phar-with-phpunit-binary.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;Running the tests by right-clicking on &lt;code&gt;phpunit.xml&lt;/code&gt;, and selecting the option &lt;strong&gt;Run 'phpunit.xml'&lt;/strong&gt; now works like a charm!&lt;/p&gt;
&lt;p&gt;Even better, when enabling &lt;a target="_blank" href="https://xdebug.org" title="Xdebug"&gt;Xdebug&lt;/a&gt; and selecting the option &lt;strong&gt;Run 'phpunit.xml' with Coverage&lt;/strong&gt;, we can now easily identify areas of the code base which are obviously lacking tests - something we have been doing yesterday and today here at &lt;a target="_blank" href="https://www.flyeralarm.com/de/" title="FLYERALARM"&gt;FLYERALARAM&lt;/a&gt;, where the 5th &lt;a target="_blank" href="https://phpunit.de/code-sprints/index.html" title="PHPUnit Code Sprint"&gt;PHPUnit Code Sprint&lt;/a&gt; has been taking place.&lt;/p&gt;
&lt;p&gt;Hope it helps!&lt;/p&gt;

</content></entry><entry><title>Accessing a system-wide terminal via hotkey on macOS</title><category term="macos"/><category term="productivity"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/"/><id>https://localheinz.com/articles/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/</id><updated>2018-10-02T11:05:00+01:00</updated><content>&lt;h1&gt;
  Accessing a system-wide terminal via hotkey on macOS
&lt;/h1&gt;


&lt;p&gt;When pairing with other developers, I oftentimes notice them spending more time than necessary on things that are neither interesting nor should take a lot of time.&lt;/p&gt;
&lt;p&gt;One of these things is opening a terminal window so commands can be entered.&lt;/p&gt;
&lt;p&gt;Very often I have observed developers move around windows on the screen in search for a previously opened terminal. While it is possible on macOS to switch between applications using &lt;kbd&gt;⌥&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt; or &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;⌥&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt;, there are certainly faster and better ways of reaching a terminal. While it is also possible to &lt;a target="_blank" href="https://www.jetbrains.com/help/idea/working-with-system-console.html" title="JetBrains: Working with Embedded Local Terminal"&gt;open an internal terminal window in any JetBrains IDE&lt;/a&gt; using &lt;kbd&gt;⌥&lt;/kbd&gt; + &lt;kbd&gt;F12&lt;/kbd&gt;, these windows are usually too small to show a lot of information, and at the same time they take away space that is better suited for displaying code.&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="https://totalterminal.binaryage.com" title="TotalTerminal, a system-wide terminal available on a hot-key"&gt;TotalTerminal&lt;/a&gt; provided &lt;i&gt;a system-wide terminal available on a hot-key&lt;/i&gt; - until OS X El Capitan was released in September 2015. Because of a lack of compatibility out of the box and a lack of interest by the original maintainers, development was stopped. Instead, the maintainers suggested to switch to &lt;a target="_blank" href="https://www.iterm2.com" title="iTerm2"&gt;iTerm2&lt;/a&gt;, which offers similar functionality.&lt;/p&gt;
&lt;p&gt;If &lt;a target="_blank" href="https://www.iterm2.com/documentation-hotkey.html" title="iTerm Documentation: Hotkeys"&gt;iTerm's documentation for hotkeys&lt;/a&gt; doesn't suffice, here's a step-by-step guide for setting up a full-screen, system-wide terminal accessible via hotkey:&lt;/p&gt;
&lt;h2 id="content-basic-setup" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-basic-setup" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Basic Setup&lt;/h2&gt;
&lt;p&gt;Download &lt;a href="https://www.iterm2.com/downloads.html"&gt;iTerm2&lt;/a&gt;, move &lt;code&gt;iTerm.app&lt;/code&gt; from &lt;code&gt;Downloads&lt;/code&gt; to &lt;code&gt;Applications&lt;/code&gt;, and open iTerm.&lt;/p&gt;
&lt;p&gt;In iTerm, press &lt;kbd&gt;⌘&lt;/kbd&gt; + &lt;kbd&gt;,&lt;/kbd&gt; to open preferences.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="iTerm preferences" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/01-open-preferences.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;Go the &lt;strong&gt;Keys&lt;/strong&gt; tab, and click on the &lt;strong&gt;Create a Dedicated Hotkey Window...&lt;/strong&gt; button in the lower left.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Go to Keys tab" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/02-go-to-keys-tab.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;In the panel that opens up, check the &lt;strong&gt;Double-tab key&lt;/strong&gt; checkbox, and press &lt;strong&gt;OK&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Check the Double-tab Key checkbox" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/03-check-double-tab-key-checkbox.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;Congratulations, your hotkey window has now been configured and can be toggled on and off by double-pressing the &lt;kbd&gt;Control&lt;/kbd&gt; key!&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="In action with default settings" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/04-in-action-with-default-settings.gif?c6b293c"&gt;
&lt;/figure&gt;
&lt;h2 id="content-customization" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-customization" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Customization&lt;/h2&gt;
&lt;p&gt;In order to open terminal windows or tabs using the previously used location, go to the &lt;strong&gt;Profiles&lt;/strong&gt; tab, select the &lt;strong&gt;Hotkey Window&lt;/strong&gt; profile, select the &lt;strong&gt;General&lt;/strong&gt; tab, and in the &lt;strong&gt;Working Directory&lt;/strong&gt; section, click on &lt;strong&gt;Reuse previous session's directory&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Click on Reuse previous session's directory" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/05-hotkey-window-general-settings.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;In order to change the font, go to the &lt;strong&gt;Profiles&lt;/strong&gt; tab, select the &lt;strong&gt;Hotkey Window&lt;/strong&gt; profile, select the &lt;strong&gt;Text&lt;/strong&gt; tab, and and in the &lt;strong&gt;Font&lt;/strong&gt; section, click on &lt;strong&gt;Change Font&lt;/strong&gt; to select your favorite font. Personally, I use &lt;a target="_blank" href="https://www.typography.com/fonts/operator/styles/"&gt;Operator Mono&lt;/a&gt; by &lt;a target="_blank" href="https://www.typography.com" title="Hoefler &amp; Co."&gt;Hoefler &amp;amp; Co.&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Click on Change Font" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/06-hotkey-window-text-settings-font.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;In order to reduce distraction and increase the size of the hotkey window, go to the &lt;strong&gt;Profiles&lt;/strong&gt; tab, select the &lt;strong&gt;Hotkey Window&lt;/strong&gt; profile, select the &lt;strong&gt;Windows&lt;/strong&gt; tab, and in the &lt;strong&gt;Window Appearance&lt;/strong&gt; section, reduce &lt;strong&gt;Transparency&lt;/strong&gt; to opaque, then in the &lt;strong&gt;Settings for New Windows&lt;/strong&gt; section, click on the &lt;strong&gt;Style&lt;/strong&gt; options, and select &lt;strong&gt;Fullscreen&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="Reduce transparency and set style to fullscreen" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/07-hotkey-window-window-settings-transparency-and-style.png?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;Congratulations, your hotkey window is now full-screen!&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="In action without transparency and in fullscreen" src="/dist/img/article/2018/10/02/accessing-a-system-wide-terminal-via-hotkey-on-macos/08-in-action-opaque-and-fullscreen.gif?c6b293c"&gt;
&lt;/figure&gt;
&lt;p&gt;Hope it helps!&lt;/p&gt;
&lt;p&gt;If you think iTerm makes you more productive, please consider &lt;a target="_blank" href="https://www.iterm2.com/donate.html"&gt;donating&lt;/a&gt;.&lt;/p&gt;

</content></entry><entry><title>Cost and value of DocBlocks</title><category term="docblock"/><category term="maintenance"/><category term="php"/><category term="php-cs-fixer"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2018/05/06/cost-and-value-of-docblocks/"/><id>https://localheinz.com/articles/2018/05/06/cost-and-value-of-docblocks/</id><updated>2018-05-06T07:25:00+02:00</updated><content>&lt;h1&gt;
  Cost and value of DocBlocks
&lt;/h1&gt;


&lt;p&gt;Over the years I have added, updated, and removed a lot of DocBlocks. I have also suggested to add, update, or remove DocBlocks many times, as well as explained why I believe a DocBlock makes sense in one case, and doesn't in another. In order to have a document I can refer to if I need to explain my reasoning again, I'm writing this article - maybe it's of use for you as well.&lt;/p&gt;
&lt;p&gt;As a user of products that run software, the products I like the most are those that can be used and explored without needing a lot of documentation. As a user of open-source software I have been surprised by excellent and up-to-date documentation, and disappointed by outdated or even entirely missing documentation. As a contributor to open-source software I have witnessed the struggles and challenges of projects to keep documentation up-to-date. As a software engineer working on closed-source software I understand that there are different documentation needs for open- and closed-source software.&lt;/p&gt;
&lt;p&gt;Regardless of whether software is open- or closed-source software, its maintenance, and the maintenance of its documentation, incurs costs. Since resources are limited, it's in the interest of everyone involved to focus on activities that provide value.&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="https://docs.phpdoc.org/3.0/guide/getting-started/what-is-a-docblock.html#what-is-a-docblock" title="phpDocumentor: What is a DocBlock?"&gt;DocBlocks&lt;/a&gt; can provide value as documentation that lives inline with the code: it can easily be understood by developers, and interpreted by tools. The information present in DocBlocks can guide developers on the what, how and why of a piece of software. However, the value of DocBlocks diminishes quickly when they only repeat information that is already present on the structural elements they intend to document. Worse, when not kept up-to-date, they can become misleading.&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-dnt="true"&gt;&lt;p lang="fr" dir="ltr"&gt;Code comments &lt;a href="http://t.co/2KDRdfFE9u"&gt;pic.twitter.com/2KDRdfFE9u&lt;/a&gt;&lt;/p&gt;&amp;mdash; Michael Koziarski (@nzkoz) &lt;a href="https://twitter.com/nzkoz/status/538892801941848064?ref_src=twsrc%5Etfw"&gt;November 30, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;Personally, when it comes to DocBlocks, I follow two rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add a DocBlock when it adds value.&lt;/li&gt;
&lt;li&gt;Remove a DocBlock when it does not add value.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DocBlocks can be applied to a number of structural elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#content-file" title="Files"&gt;files&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-class" title="Classes"&gt;classes (also: interfaces and traits)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;constants&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-property" title="Properties"&gt;properties&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-method" title="Methods"&gt;methods (also: functions)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="#content-variable" title="Variables"&gt;variables&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content-file" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-file" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;File&lt;/h2&gt;
&lt;p&gt;Frequently found in open-source software, file-level DocBlocks document copyright information, refer to a license document, and link to the source repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-comment"&gt;/**
 * Copyright (c) 2017 Andreas Möller.
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 *
 * &lt;span class="hljs-doctag"&gt;@see&lt;/span&gt; https://github.com/ergebnis/test-util
 */&lt;/span&gt;

 &lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Test&lt;/span&gt;\&lt;span class="hljs-title"&gt;Util&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far I have not found a use for file-level DocBlocks in closed-source software.&lt;/p&gt;
&lt;p&gt;However, I have worked on a closed-source project where dependencies had actually been checked in into version control. When introducing &lt;a target="_blank" href="https://getcomposer.org" title="composer"&gt;composer&lt;/a&gt; and replacing the checked-in dependencies with requirements, file-level DocBlocks proved to be extremely useful to identify some of these dependencies.&lt;/p&gt;
&lt;p&gt;When using &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;PHP-CS-Fixer&amp;#x2F;PHP-CS-Fixer" target="_blank" title="PHP-CS-Fixer/PHP-CS-Fixer on GitHub"&gt;&lt;code&gt;friendsofphp/php-cs-fixer&lt;/code&gt;&lt;/a&gt;, file-level DocBlocks similar to above can be easily added or replaced with a configuration similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$header = &lt;span class="hljs-string"&gt;&amp;lt;&amp;lt;&amp;lt;EOF
Copyright (c) 2017 Andreas Möller

For the full copyright and license information, please view
the LICENSE file that was distributed with this source code.

@see https://github.com/ergebnis/test-util
EOF;&lt;/span&gt;

$config = PhpCsFixer\Config::create()-&amp;gt;setRules([
    &lt;span class="hljs-string"&gt;'header_comment'&lt;/span&gt; =&amp;gt; [
        &lt;span class="hljs-string"&gt;'commentType'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;'PHPDoc'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'header'&lt;/span&gt; =&amp;gt; $header,
        &lt;span class="hljs-string"&gt;'location'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;'after_declare_strict'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'separate'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;'both'&lt;/span&gt;,
    ],
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;File-level DocBlocks can easily be removed with a configuration similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$config = PhpCsFixer\Config::create()-&amp;gt;setRules([
    &lt;span class="hljs-string"&gt;'header_comment'&lt;/span&gt; =&amp;gt; [
        &lt;span class="hljs-string"&gt;'header'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;''&lt;/span&gt;,
    ],
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="content-class" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-class" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Class&lt;/h2&gt;
&lt;p&gt;I have rarely found a need for class-level DocBlocks, and in particular, class-level DocBlocks such as&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-/**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * Represents a user in the system.&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- */&lt;/span&gt;
 final class User
 {
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;do not add any value - so I remove them.&lt;/p&gt;
&lt;p&gt;Oftentimes, a need for a description of a class is a &lt;a target="_blank" href="https://wiki.c2.com/?CodeSmell" title="Code Smell"&gt;smell&lt;/a&gt;. Sometimes the only thing that is required to enhance clarity is a &lt;a target="_blank" href="https://wiki.c2.com/?IntentionRevealingNames" title="Intention-revealing Names"&gt;name that reveals intent&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="content-property" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-property" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Property&lt;/h2&gt;
&lt;p&gt;Depending on how properties are initialized (for example, via constructor injection), some IDEs are capable of inferring their type - either from &lt;a href="#method"&gt;method-level&lt;/a&gt; DocBlocks or type declarations on constructors, or from initializations of properties within constructors. Adding DocBlocks for properties with &lt;a target="_blank" href="https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/var.html" title="phpDocumentor: @var"&gt;&lt;code&gt;@var&lt;/code&gt;&lt;/a&gt; tags assists with auto-completion when using an IDE, but also helps when reading the code elsewhere, so I always add DocBlocks to properties.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class User
 {
&lt;span class="hljs-addition"&gt;+    /**&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @var UserId&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     */&lt;/span&gt;
     private $id;

&lt;span class="hljs-addition"&gt;+    /**&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @var Login&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     */&lt;/span&gt;
     private $login;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is worth mentioning that there have been a few RFCs related to typed properties&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a target="_blank" href="https://wiki.php.net/rfc/property_type_hints" title="PHP RFC: Property type-hints"&gt;PHP RFC: Property type-hints&lt;/a&gt; (2015-07-19, in draft)&lt;/li&gt;
&lt;li&gt;
&lt;a target="_blank" href="https://wiki.php.net/rfc/typed-properties" title="PHP RFC: Typed Properties"&gt;PHP RFC: Typed Properties&lt;/a&gt; (2016-03-16, declined)&lt;/li&gt;
&lt;li&gt;
&lt;a target="_blank" href="https://wiki.php.net/rfc/typed_properties_v2" title="PHP RFC: Typed Properties 2.0"&gt;PHP RFC: Typed Properties 2.0&lt;/a&gt; (2018-06-15, accepted)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;an on September 27, 2018, the &lt;a target="_blank" href="https://wiki.php.net/rfc/typed_properties_v2" title="PHP RFC: Typed Properties 2.0"&gt;PHP RFC: Typed Properties 2.0&lt;/a&gt; was accepted!&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-dnt="true"&gt;&lt;p lang="en" dir="ltr"&gt;🎉 Typed properties have been accepted for PHP 7.4 🎉&lt;a href="https://t.co/64kgmIw7TQ"&gt;https://t.co/64kgmIw7TQ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Nikita Popov (@nikita_ppv) &lt;a href="https://twitter.com/nikita_ppv/status/1044947172771418112?ref_src=twsrc%5Etfw"&gt;September 26, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;That is, starting with PHP 7.4, a lot of DocBlocks on properties can be removed.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class User
 {
&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @var UserId&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     private $id;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     private UserId $id;&lt;/span&gt;

&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @var Login&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     private $login;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     private Login $login;&lt;/span&gt;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Descriptions on property-level DocBlocks such as&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class User
 {
     /**
&lt;span class="hljs-deletion"&gt;-     * The identifier of the user.&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     *&lt;/span&gt;
      * @var UserId
      */
     private $id;

     /**
&lt;span class="hljs-deletion"&gt;-     * The login of the user.&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     *&lt;/span&gt;
      * @var Login
      */
     private $login;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;do not add any value - so I remove them.&lt;/p&gt;
&lt;p&gt;Again, a need for a description of a property is a smell.&lt;/p&gt;
&lt;h2 id="content-method" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-method" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Method&lt;/h2&gt;
&lt;p&gt;Method-level DocBlocks which only repeat information that is entirely present in type and return type declarations, such as&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 class User
 {
     /**
      * @var UserId
      */
     private $id;

     /**
      * @var Login
      */
     private $login;

&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @param UserId $id&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @param Login  $login&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
     public function __construct(
         UserId $id,
         Login $login
     ) {
         $this-&amp;gt;id = $id;
         $this-&amp;gt;login = $login;
     }

&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @return UserId&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
     public function id(): UserId
     {
         return $this-&amp;gt;id;
     }

&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @return Login&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
     public function login(): Login
     {
         return $this-&amp;gt;login;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;do not add any value - so I remove them.&lt;/p&gt;
&lt;p&gt;Again, oftentimes a need for a description of a method is a smell. Consider renaming instead to clarify the intent.&lt;/p&gt;
&lt;p&gt;With the introduction of &lt;a target="_blank" href="https://php.net/manual/en/migration70.new-features.php#migration70.new-features.scalar-type-declarations" title="PHP 7.0 New Features: Scalar type declarations"&gt;scalar&lt;/a&gt; and &lt;a target="_blank" href="https://php.net/manual/en/migration70.new-features.php#migration70.new-features.return-type-declarations" title="PHP 7.0 New Features: Return type declarations"&gt;return type declarations&lt;/a&gt; in PHP 7.0, as well as with the introduction of &lt;a target="_blank" href="https://php.net/manual/en/migration71.new-features.php#migration71.new-features.nullable-types" title="PHP 7.1 New Features: Nullable types"&gt;nullable type and return declarations&lt;/a&gt; and &lt;a target="_blank" href="https://php.net/manual/en/migration71.new-features.php#migration71.new-features.void-functions" title="PHP 7.1 New Features: Void functions"&gt;void return type declarations&lt;/a&gt; in PHP 7.1, a lot of DocBlocks can easily be replaced by corresponding type declarations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Login
 {
     /**
      * @var string
      */
     private $value;

&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @param string $value&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    public function __construct($value)&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function __construct(string $value)&lt;/span&gt;
     {
         $this-&amp;gt;value = $value;
     }

&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @return string&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    public function __toString()&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function __toString(): string&lt;/span&gt;
     {
         return $this-&amp;gt;value;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, oftentimes not all of the required information is present in type and return type declarations. For example, when a method throws exceptions&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 final class Login
 {
     /**
      * @var string
      */
     private $value;

&lt;span class="hljs-addition"&gt;+    /**&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @param string $value&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     *&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @throws \InvalidArgumentException&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     */&lt;/span&gt;
     public function __construct(string $value)
     {
         if ('' &lt;span class="hljs-comment"&gt;=== trim($login)) {&lt;/span&gt;
             throw new \InvalidArgumentException('Value cannot be an empty string');
         }

         $this-&amp;gt;value = $value;
     }

     public function __toString(): string
     {
         return $this-&amp;gt;value;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or when a method returns an array (of objects or scalars)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 class UserRepository
 {
&lt;span class="hljs-addition"&gt;+    /**&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @return array&amp;lt;int, User&amp;gt;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     */&lt;/span&gt;
     public function all(): array
     {
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then DocBlocks add value - so I add them.&lt;/p&gt;
&lt;p&gt;When an interface is extracted (or an implementation of an interface added) and the method on the interface already has all of the information&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;&lt;span class="hljs-addition"&gt;+&amp;lt;?php&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+declare(strict_types=1);&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+interface UserRepository&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+{&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    /**&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     * @return array&amp;lt;int, User&amp;gt;&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+     */&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    public function all(): array&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then a DocBlock on the corresponding method in the implementations such as&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-class UserRepository&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+final class DoctrineUserRepository implements UserRepository&lt;/span&gt;
 {
&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @return array&amp;lt;int, User&amp;gt;&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
     public function all(): array
     {
         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;doesn't add any value - so I remove them.&lt;/p&gt;
&lt;p&gt;Similarly, when an abstract class is extracted (or extending a class) and implementing an abstract method (overriding a method) the method on the interface already has all of the information, then a DocBlock on the corresponding method in the child class doesn't add any value - so I remove them as well.&lt;/p&gt;
&lt;h2 id="content-variable" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-variable" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Variable&lt;/h2&gt;
&lt;p&gt;Sometimes return types of methods are not clear&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 use PHPUnit\Framework;

 final class DoctrineUserRepositoryTest extends Framework\TestCase
 {
     public function testByLoginReturnsUser(): void
     {
         // ...

&lt;span class="hljs-addition"&gt;+        /** @var User $user */&lt;/span&gt;
         $user = $this-&amp;gt;fixtureFactory-&amp;gt;get(User::class, [
             'login' =&amp;gt; $login,
         ]);

         // ...
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and a clarifying inline DocComment adds value - so I add them.&lt;/p&gt;
&lt;h2 id="content-generated-code" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-generated-code" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Generated Code&lt;/h2&gt;
&lt;p&gt;When creating files and classes in IDEs, they often add unnecessary DocBlocks. However, DocBlocks such as&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

  declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-/**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * Created by PhpStorm.&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * User: localheinz&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * Date: 05.05.18&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * Time: 22:18.&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- */&lt;/span&gt;

 final class User
 {
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

&lt;span class="hljs-deletion"&gt;-/**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * Class User&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- */&lt;/span&gt;
 final class User
 {
&lt;span class="hljs-deletion"&gt;-    /**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * User constructor.&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     *&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @param Uuid   $id&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     * @param string $login&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-     */&lt;/span&gt;
     public function __construct(Uuid $id, string $login)
     {
         $this-&amp;gt;id = $id;
         $this-&amp;gt;login = $login;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;do not add any value - so I remove them.&lt;/p&gt;
&lt;p&gt;When using &lt;a target="_blank" href="https://www.jetbrains.com/phpstorm/" title="PhpStorm"&gt;PhpStorm&lt;/a&gt;, the &lt;a target="_blank" href="https://www.jetbrains.com/help/phpstorm/using-file-and-code-templates.html" title="PhpStorm Help: File and Code Templates"&gt;file and code templates&lt;/a&gt; used when creating files and classy constructs can be easily configured (and unnecessary comments removed).&lt;/p&gt;
&lt;p&gt;When creating code with command-line tools, for example, when creating database migrations using &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;doctrine&amp;#x2F;migrations" target="_blank" title="doctrine/migrations on GitHub"&gt;&lt;code&gt;doctrine/migrations&lt;/code&gt;&lt;/a&gt;, the generated code often contains helpful comments for getting started, such as&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt; &amp;lt;?php

 declare(strict_types=1);

 use Doctrine\DBAL\Migrations\AbstractMigration;
 use Doctrine\DBAL\Schema\Schema;

&lt;span class="hljs-deletion"&gt;-/**&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- * Auto-generated Migration: Please modify to your needs!&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;- */&lt;/span&gt;
 class Version20180426054008 extends AbstractMigration
 {
     public function up(Schema $schema)
     {
&lt;span class="hljs-deletion"&gt;-        // this up() migration is auto-generated, please modify it to your needs&lt;/span&gt;
     }

     public function down(Schema $schema)
     {
&lt;span class="hljs-deletion"&gt;-        // this down() migration is auto-generated, please modify it to your needs&lt;/span&gt;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but other than that, these DocBlocks (and additional comments) add no value - so I remove them.&lt;/p&gt;
&lt;p&gt;When code is generated from external sources, or its structure is dictated by external sources which we do not control, for example, when generating &lt;a target="_blank" href="https://martinfowler.com/eaaCatalog/dataTransferObject.html" title="P of EAA Catalog: Data Transfer Object"&gt;Data Transfer Objects&lt;/a&gt; or similar from API definitions or documentation, then DocBlocks with descriptions may add value. Especially when there is control over the mechanisms that generate the code, then including descriptions adds little maintenance cost.&lt;/p&gt;
&lt;p&gt;If you have different opinions, that is fine - just make sure you don't blindly add DocBlocks everywhere, but weigh costs against value.&lt;/p&gt;
&lt;p&gt;Hope it helps!&lt;/p&gt;

</content></entry><entry><title>Code coverage is a meaningless metric</title><category term="code-coverage"/><category term="php"/><category term="testing"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2018/02/01/code-coverage-is-a-meaningless-metric/"/><id>https://localheinz.com/articles/2018/02/01/code-coverage-is-a-meaningless-metric/</id><updated>2018-02-01T14:25:00+01:00</updated><content>&lt;h1&gt;
  Code coverage is a meaningless metric
&lt;/h1&gt;


&lt;p&gt;Oftentimes people ask: How much code coverage is sufficient? While I believe that this is a legitimate question to ask, especially when  just getting started with testing, there is a better, a counter-question: Do you want to do Test-Driven Development or not?&lt;/p&gt;
&lt;p&gt;This question has three possible answers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yes&lt;/li&gt;
&lt;li&gt;No&lt;/li&gt;
&lt;li&gt;What is Test-Driven Development?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test-Driven Development (TDD), is a practice that suggests following &lt;a target="_blank" href="http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd" title="Uncle Bob: The three rules of TDD"&gt;three simple rules&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;
      You are not allowed to write any production code unless it is to make a failing unit test pass.
    &lt;/li&gt;
    &lt;li&gt;
      You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
    &lt;/li&gt;
    &lt;li&gt;
      You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
    &lt;/li&gt;
  &lt;/ol&gt;
  &lt;div class="source"&gt;
    &lt;a href="http&amp;#x3A;&amp;#x2F;&amp;#x2F;cleancoder.com" target="_blank" title="Robert&amp;#x20;C.&amp;#x20;Martin"&gt;Robert C. Martin&lt;/a&gt;, The Three Rules of TDD
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;If developers follow TDD, not a single line of production code is written without a failing test first. Consequently, the resulting code coverage will &lt;strong&gt;always be 100%&lt;/strong&gt;, and code coverage becomes a meaningless metric.&lt;/p&gt;
&lt;p&gt;If developers do not follow TDD, they likely write production code, and either never or only occasionally write tests afterwards. Consequently, code coverage will &lt;strong&gt;vary between 0% and 100%&lt;/strong&gt;. Code coverage will decrease when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tests are deleted&lt;/li&gt;
&lt;li&gt;untested code is added&lt;/li&gt;
&lt;li&gt;tested code is reformatted or refactored (the same problem is solved with fewer lines of code)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and it will increase when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tests are added&lt;/li&gt;
&lt;li&gt;untested code is removed&lt;/li&gt;
&lt;li&gt;tested code is reformatted or refactored (the same problem is solved with more lines of code)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While you can achieve 100% code coverage when writing tests after the fact, how confident will you be that the tests are meaningful? How confident will you be that the code works as expected in production?&lt;/p&gt;
&lt;p&gt;For example, I recently &lt;a target="_blank" href="https://github.com/ergebnis/composer-normalize/pull/38" title="Enhancement: Add --dry-run option"&gt;added a &lt;code&gt;--dry-run&lt;/code&gt; option&lt;/a&gt; to &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;composer-normalize" target="_blank" title="ergebnis/composer-normalize on GitHub"&gt;&lt;code&gt;ergebnis/composer-normalize&lt;/code&gt;&lt;/a&gt;. Since a &lt;a target="_blank" href="https://github.com/sebastianbergmann/diff/blob/2.0.1/src/Differ.php" title="Sebastian\Diff\Differ"&gt;collaborator&lt;/a&gt; from a 3rd-party package is marked as &lt;code&gt;final&lt;/code&gt; and does not implement an interface, I did not have a possibility to create a test double for it. If you look closely, the code I added to output a diff in &lt;code&gt;--dry-run&lt;/code&gt; mode is largely untested. Still, I have 100% code coverage.&lt;/p&gt;
&lt;p&gt;While the &lt;strong&gt;changes&lt;/strong&gt; in code coverage might tell you something (what exactly, you have to find out for yourself), do you still think code coverage itself is a meaningful metric?&lt;/p&gt;
&lt;p&gt;Personally, I have decided to follow the three rules of TDD. Following these rules simplifies a lot of things for me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There's no need to discuss what to test - every component is equally important and will be thoroughly tested.&lt;/li&gt;
&lt;li&gt;There's no need to discuss when to test - tests are always written first.&lt;/li&gt;
&lt;li&gt;There's no need to discuss how much code coverage is sufficient - it will always be 100%.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You might have a different point of view, and if that works for you and your clients, fine.&lt;/p&gt;
&lt;p&gt;However, if you are still asking how much coverage you should aim for, here is a tweet by &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;Ocramius" target="_blank" title="Marco&amp;#x20;Pivetta&amp;#x20;on&amp;#x20;GitHub"&gt;Marco Pivetta&lt;/a&gt; for you:&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-dnt="true"&gt;&lt;p lang="en" dir="ltr"&gt;Broke production because  I didn&amp;#39;t bother covering &amp;gt;90%, and affected code path was in the 10%.&lt;br&gt;&lt;br&gt;To those that say testing everything isn&amp;#39;t worth doing: STFU.&lt;/p&gt;&amp;mdash; @ocramius@mastodon.social 🇺🇦🍥 (@Ocramius) &lt;a href="https://twitter.com/Ocramius/status/958737872613462017?ref_src=twsrc%5Etfw"&gt;January 31, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;If you are still hesitant, the hardest part with testing is &lt;a target="_blank" href="https://phpunit.de/getting-started/phpunit-8.html" title="Getting Started with PHPUnit 7"&gt;getting started&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Good luck!&lt;/p&gt;

</content></entry><entry><title>Makefile for lazy developers</title><category term="maintenance"/><category term="makefile"/><category term="productivity"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2018/01/24/makefile-for-lazy-developers/"/><id>https://localheinz.com/articles/2018/01/24/makefile-for-lazy-developers/</id><updated>2018-01-24T09:30:00+01:00</updated><content>&lt;h1&gt;
  Makefile for lazy developers
&lt;/h1&gt;


&lt;p&gt;Whatever the size of the software project, I believe in, subscribe to, and promote &lt;a target="_blank" href="https://www.martinfowler.com/articles/continuousIntegration.html" title="Continuous Integration"&gt;Continuous Integration&lt;/a&gt;. Personally, I rely on &lt;a target="_blank" href="https://github.com/features/actions" title="GitHub Actions"&gt;GitHub Actions&lt;/a&gt; as an automated build system. Even this blog here is built with, and eventually deployed into production using GitHub Actions.&lt;/p&gt;
&lt;p&gt;Regardless of whether an automated build system can be set up and used for a project or not, I prefer to be able to run build steps locally. This prevents stress-testing the automated build system and taking away resources from other developers. Also, it gives me more confidence before committing and pushing changes upstream.&lt;/p&gt;
&lt;p&gt;The first time I heard of an automated build tool was when I was working on a crowd investment project in 2011. Back then a developer set up &lt;a target="_blank" href="https://capistranorb.com" title="Capistrano"&gt;Capistrano&lt;/a&gt;, and I didn't understand a thing. Then had used &lt;a target="_blank" href="https://www.phing.info" title="PHing Is Not GNU make"&gt;Phing&lt;/a&gt; for a while. For a couple of years now I have been using &lt;a target="_blank" href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html" title="make"&gt;&lt;code&gt;make&lt;/code&gt;&lt;/a&gt;, after having been introduced to it when working on a project in 2014. While it has its limitations, it's short and simple, and most of all, it get's the job done.&lt;/p&gt;
&lt;p&gt;In most of my projects I use a &lt;code&gt;Makefile&lt;/code&gt; similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-makefile hljs makefile" data-lang="makefile"&gt;&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: it&lt;/span&gt;
&lt;span class="hljs-section"&gt;it: coding-standards static-code-analysis tests&lt;/span&gt;

&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: code-coverage&lt;/span&gt;
&lt;span class="hljs-section"&gt;code-coverage: vendor&lt;/span&gt;
	vendor/bin/phpunit --configuration=test/Unit/phpunit.xml --coverage-text

&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: coding-standards&lt;/span&gt;
&lt;span class="hljs-section"&gt;coding-standards: vendor&lt;/span&gt;
	vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --show-progress=dots --verbose

&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: mutation-tests&lt;/span&gt;
&lt;span class="hljs-section"&gt;mutation-tests: vendor&lt;/span&gt;
	mkdir -p .build/infection
	vendor/bin/infection --configuration=infection.json

&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: static-code-analysis&lt;/span&gt;
&lt;span class="hljs-section"&gt;static-code-analysis: vendor&lt;/span&gt;
	mkdir -p .build/psalm
	vendor/bin/psalm --config=psalm.xml --clear-cache
	vendor/bin/psalm --config=psalm.xml --show-info=false --stats --threads=10

&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: static-code-analysis-baseline&lt;/span&gt;
&lt;span class="hljs-section"&gt;static-code-analysis-baseline: vendor&lt;/span&gt;
	mkdir -p .build/psalm
	vendor/bin/psalm --config=psalm.xml --clear-cache
	vendor/bin/psalm --config=psalm.xml --set-baseline=psalm-baseline.xml

&lt;span class="hljs-meta"&gt;&lt;span class="hljs-meta-keyword"&gt;.PHONY&lt;/span&gt;: tests&lt;/span&gt;
&lt;span class="hljs-section"&gt;tests: vendor&lt;/span&gt;
	vendor/bin/phpunit --configuration=test/Unit/phpunit.xml
	vendor/bin/phpunit --configuration=test/Integration/phpunit.xml

&lt;span class="hljs-section"&gt;vendor: composer.json composer.lock&lt;/span&gt;
	composer validate
	composer install
	composer normalize
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, I have defined a few &lt;a target="_blank" href="https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html" title="phony targets"&gt;phony targets&lt;/a&gt;, which may run any number of commands, or depend on so-called &lt;em&gt;prerequisites&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For example, running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;make tests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will first execute the &lt;code&gt;vendor&lt;/code&gt; target, followed by running unit and integration tests. Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;make coding-standards
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will first execute the &lt;code&gt;vendor&lt;/code&gt; target, followed by running &lt;code&gt;php-cs-fixer&lt;/code&gt;. You get the idea.&lt;/p&gt;
&lt;p&gt;A note on the &lt;code&gt;vendor&lt;/code&gt; target: I previously used to have a &lt;code&gt;composer&lt;/code&gt; target. After posting this article on &lt;a target="_blank" href="https://www.reddit.com/r/PHP/comments/7sltmh/makefile_for_lazy_developers/" title="Reddit"&gt;Reddit&lt;/a&gt;, &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;nicwortel" target="_blank" title="Nic&amp;#x20;Wortel&amp;#x20;on&amp;#x20;GitHub"&gt;Nic Wortel&lt;/a&gt; provided an &lt;a target="_blank" href="https://www.reddit.com/r/PHP/comments/7sltmh/makefile_for_lazy_developers/dt5rba0/" title="Reddit: Comment on 'Makefile for lazy developers'"&gt;excellent suggestion&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I use the following recipe in my Makefiles:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-makefile hljs makefile" data-lang="makefile"&gt;&lt;span class="hljs-section"&gt;vendor: composer.json composer.lock&lt;/span&gt;
	composer install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will compare the timestamp of the &lt;code&gt;vendor&lt;/code&gt; directory with those of &lt;code&gt;composer.json&lt;/code&gt; and &lt;code&gt;composer.lock&lt;/code&gt;, and will only execute the recipe if either of the composer files is newer than the &lt;code&gt;vendor&lt;/code&gt; directory, or if the vendor directory is missing.&lt;/p&gt;
  &lt;div class="source"&gt;
    &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;nicwortel" target="_blank" title="Nic&amp;#x20;Wortel&amp;#x20;on&amp;#x20;GitHub"&gt;Nic Wortel&lt;/a&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;Excellent, this saves even more time!&lt;/p&gt;
&lt;p&gt;Finally, I have added an &lt;code&gt;it&lt;/code&gt; target. Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;make it
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will execute all of the targets I consider important enough to be run before I commit and push changes upstream.&lt;/p&gt;
&lt;p&gt;Now, you might have noticed that while I have a penchant for &lt;a href="/articles/2018/01/15/normalizing-composer.json/" target="_blank" title="Normalizing&amp;#x20;composer.json"&gt;keeping things sorted&lt;/a&gt;, I have intentionally defined &lt;code&gt;it&lt;/code&gt; as the first target. There's a good reason to it: unless a target has been specified explicitly, the first target defined in the &lt;code&gt;Makefile&lt;/code&gt; will be executed. That is, by making &lt;code&gt;it&lt;/code&gt; the first target, I can run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;make
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to execute all of the important targets.&lt;/p&gt;
&lt;p&gt;Nonetheless, as I have gotten into the habit of running these tasks many, many (dozens? hundreds?) of times throughout the day, it's still a bit too much typing, right? Therefore, I have created an &lt;a target="_blank" href="https://tldp.org/LDP/abs/html/aliases.html" title="alias"&gt;alias&lt;/a&gt; in my shell configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash hljs bash" data-lang="bash"&gt;&lt;span class="hljs-comment"&gt;# Makefile&lt;/span&gt;
&lt;span class="hljs-built_in"&gt;alias&lt;/span&gt; m=&lt;span class="hljs-string"&gt;"make"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;achieves the same now. I believe that's the closest thing to &lt;a target="_blank" href="https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-steps-to-better-code/" title="The Joel Test: 12 Steps to Better Code"&gt;making a build in one step&lt;/a&gt;, with only two key strokes.&lt;/p&gt;
&lt;p&gt;Hope it helps to increase your productivity!&lt;/p&gt;

</content></entry><entry><title>Normalizing composer.json</title><category term="coding-standards"/><category term="composer"/><category term="json"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2018/01/15/normalizing-composer.json/"/><id>https://localheinz.com/articles/2018/01/15/normalizing-composer.json/</id><updated>2018-01-15T08:30:00+01:00</updated><content>&lt;h1&gt;
  Normalizing composer.json
&lt;/h1&gt;


&lt;p&gt;If you are using &lt;a target="_blank" href="https://getcomposer.org" title="composer"&gt;composer&lt;/a&gt;, you have probably modified &lt;code&gt;composer.json&lt;/code&gt; at least once to keep things nice and tidy.&lt;/p&gt;
&lt;p&gt;In fact, when I started using composer in late summer 2012, I of course edited &lt;code&gt;composer.json&lt;/code&gt; manually, even when I wanted to add or update dependencies. I soon learned that one should run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer require foo/bar:~x.y.z
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead, as this prevents pulling in undesired updates. I did that, but in order to keep the list of dependencies sorted, I actually had to do it twice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;require package&lt;/li&gt;
&lt;li&gt;move package in &lt;code&gt;require&lt;/code&gt; or &lt;code&gt;require-dev&lt;/code&gt; section so packages are sorted&lt;/li&gt;
&lt;li&gt;run command again (to keep the lock file updated, as the order of dependencies matters for the calculation of the hash)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's a bit crazy, right? And just to keep things nice and tidy!&lt;/p&gt;
&lt;p&gt;Then I came across a tweet by &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;HenrikJoreteg" target="_blank" title="Henrik&amp;#x20;Joreteg&amp;#x20;on&amp;#x20;GitHub"&gt;Henrik Joreteg&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-dnt="true"&gt;&lt;p lang="en" dir="ltr"&gt;Favorite little OCD module that I use *all the time* fixpack: &lt;a href="https://t.co/amXS7th05U"&gt;https://t.co/amXS7th05U&lt;/a&gt;&lt;br&gt;&lt;br&gt;Alphabetizes dependencies, and cleans up package.json&lt;/p&gt;&amp;mdash; Henrik Joreteg (@HenrikJoreteg) &lt;a href="https://twitter.com/HenrikJoreteg/status/448366378836180993?ref_src=twsrc%5Etfw"&gt;March 25, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;Nice, I wasn't alone with liking things nice and tidy!&lt;/p&gt;
&lt;p&gt;At the time I was contracting for a company here in Berlin. One of the best parts working there was that a lot of respected members of the PHP community stopped by the office oftentimes, for example, &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;beberlei" target="_blank" title="Benjamin&amp;#x20;Eberlei&amp;#x20;on&amp;#x20;GitHub"&gt;Benjamin Eberlei&lt;/a&gt;, &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;dzuelke" target="_blank" title="David&amp;#x20;Zuelke&amp;#x20;on&amp;#x20;GitHub"&gt;David Zuelke&lt;/a&gt;, and &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;naderman" target="_blank" title="Nils&amp;#x20;Adermann&amp;#x20;on&amp;#x20;GitHub"&gt;Nils Adermann&lt;/a&gt;. Nils (whom you probably know as one of the maintainers of composer, together with &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;seldaek" target="_blank" title="Jordi&amp;#x20;Boggiano&amp;#x20;on&amp;#x20;GitHub"&gt;Jordi Boggiano&lt;/a&gt;) even regularly worked out of the office. Hesitant at first, I approached him about this issue, and asked him whether they would consider a pull request. I think he liked it, so I was quite happy and figured it should be ok to do something about it.&lt;/p&gt;
&lt;p&gt;Nonetheless, it took me until December 2014 until I eventually opened a &lt;a target="_blank" href="https://github.com/composer/composer/pull/3549" title="Enhancement: Add sort-packages option which allows sorting of packages"&gt;pull request&lt;/a&gt; to add the
&lt;a target="_blank" href="https://getcomposer.org/doc/03-cli.md#require" title="Composer Command Line Interface: require"&gt;&lt;code&gt;--sort-packages&lt;/code&gt;&lt;/a&gt; option to composer.&lt;/p&gt;
&lt;p&gt;Since then it has been possible to run, for example,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer require --dev --sort-packages phpunit/phpunit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to require a package and keep packages sorted (if you already have that package required, you can run the command to trigger sorting).&lt;/p&gt;
&lt;p&gt;With a bit of &lt;a target="_blank" href="https://github.com/composer/composer/pull/3872" title="Enhancement: Sort packages by importance, then alphabetically"&gt;tweaking&lt;/a&gt; not only packages and platform requirements would be sorted, but platform
requirements listed &lt;em&gt;before&lt;/em&gt; packages. Thanks to &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;hanovruslan" target="_blank" title="Hanov&amp;#x20;Ruslan&amp;#x20;on&amp;#x20;GitHub"&gt;Hanov Ruslan&lt;/a&gt;, this behaviour
can be &lt;a target="_blank" href="https://github.com/composer/composer/pull/4716" title="Added sort-packages into config"&gt;configured&lt;/a&gt;, making it unnecessary to use the option on the command line.&lt;/p&gt;
&lt;p&gt;Not only have a lot of people blogged about the feature, but a quick &lt;a target="_blank" href="https://github.com/search?utf8=✓&amp;q=sort-packages+filename%3A%22composer.json%22&amp;type=Code" title="Searcg for sort-packages in composer.json"&gt;search on GitHub&lt;/a&gt; reveals that the configuration option is used at least &lt;strong&gt;327,598&lt;/strong&gt; times! Excellent!&lt;/p&gt;
&lt;p&gt;Now, how about taking it a step further? Today I present to you &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;composer-normalize" target="_blank" title="ergebnis/composer-normalize on GitHub"&gt;&lt;code&gt;ergebnis/composer-normalize&lt;/code&gt;&lt;/a&gt;, a composer plugin for tidying up &lt;em&gt;all of&lt;/em&gt; &lt;code&gt;composer.json&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer global require ergebnis/composer-normalize
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to install the composer plugin globally.&lt;/p&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer normalize
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to normalize &lt;code&gt;composer.json&lt;/code&gt; in the working directory or run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer normalize ~/Sites/foo/bar/composer.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to normalize &lt;code&gt;composer.json&lt;/code&gt; in the &lt;code&gt;~/Sites/foo/bar&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;NormalizeCommand&lt;/code&gt; provided by the NormalizePlugin within this package will&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;determine whether a &lt;code&gt;composer.json&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;determine whether a &lt;code&gt;composer.lock&lt;/code&gt; exists, and if so, whether it is up to date&lt;/li&gt;
&lt;li&gt;use the &lt;code&gt;ComposerJsonNormalizer&lt;/code&gt; to normalize the content of &lt;code&gt;composer.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;format the normalized content (either as sniffed, or as specified using the &lt;code&gt;--indent-size&lt;/code&gt; and &lt;code&gt;--indent-style&lt;/code&gt; options)&lt;/li&gt;
&lt;li&gt;write the normalized and formatted content of composer.json back to the file&lt;/li&gt;
&lt;li&gt;update the hash in &lt;code&gt;composer.lock&lt;/code&gt; if it exists and if an update is necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Currently, the following normalizations will be applied&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;restructure &lt;code&gt;composer.json&lt;/code&gt; according to the underlying &lt;a target="_blank" href="https://getcomposer.org/schema.json" title="JSON Schema for composer.json"&gt;JSON schema&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;sort entries in the &lt;code&gt;bin&lt;/code&gt; section (by value)&lt;/li&gt;
&lt;li&gt;sort entries in the &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;extra&lt;/code&gt;, and &lt;code&gt;scripts-descriptions&lt;/code&gt; sections (by key)&lt;/li&gt;
&lt;li&gt;sort entries in the &lt;code&gt;conflict&lt;/code&gt;, &lt;code&gt;provide&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;require&lt;/code&gt;, &lt;code&gt;require-dev&lt;/code&gt;, and &lt;code&gt;suggest&lt;/code&gt; sections (as you are used to from the &lt;a target="_blank" href="https://getcomposer.org/doc/06-config.md#sort-packages" title="sort-packages"&gt;&lt;code&gt;sort-packages&lt;/code&gt;&lt;/a&gt; configuration)&lt;/li&gt;
&lt;li&gt;normalize version constraints in &lt;code&gt;conflict&lt;/code&gt;, &lt;code&gt;provide&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;require&lt;/code&gt;, and &lt;code&gt;require-dev&lt;/code&gt; sections&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command accepts the following argument&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;file&lt;/code&gt;: Path to &lt;code&gt;composer.json&lt;/code&gt; file (optional, defaults to &lt;code&gt;composer.json&lt;/code&gt; in working directory)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command comes with the following options&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--dry-run&lt;/code&gt;: Show the results of normalizing, but do not modify any files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--indent-size&lt;/code&gt;: Indent size (an integer greater than 0); should be used with the &lt;code&gt;--indent-style&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--indent-style&lt;/code&gt;: Indent style (one of &amp;quot;space&amp;quot;, &amp;quot;tab&amp;quot;); should be used with the &lt;code&gt;--indent-size&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-update-lock&lt;/code&gt;: Do not update lock file if it exists&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example of running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer normalize
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;on &lt;code&gt;composer/composer&lt;/code&gt; itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;diff --git a/composer.json b/composer.json
index dd672c3b..330f73d0 100644
&lt;span class="hljs-comment"&gt;--- a/composer.json&lt;/span&gt;
&lt;span class="hljs-comment"&gt;+++ b/composer.json&lt;/span&gt;
&lt;span class="hljs-meta"&gt;@@ -1,9 +1,13 @@&lt;/span&gt;
 {
     "name": "composer/composer",
&lt;span class="hljs-addition"&gt;+    "type": "library",&lt;/span&gt;
     "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
&lt;span class="hljs-deletion"&gt;-    "keywords": ["package", "dependency", "autoload"],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "keywords": [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "package",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "dependency",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "autoload"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    ],&lt;/span&gt;
     "homepage": "https://getcomposer.org/",
&lt;span class="hljs-deletion"&gt;-    "type": "library",&lt;/span&gt;
     "license": "MIT",
     "authors": [
         {
&lt;span class="hljs-meta"&gt;@@ -17,52 +21,58 @@&lt;/span&gt;
             "homepage": "https://seld.be"
         }
     ],
&lt;span class="hljs-deletion"&gt;-    "support": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "irc": "irc://irc.freenode.org/composer",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "issues": "https://github.com/composer/composer/issues"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
     "require": {
         "php": "^5.3.2 || ^7.0",
&lt;span class="hljs-deletion"&gt;-        "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",&lt;/span&gt;
         "composer/ca-bundle": "^1.0",
         "composer/semver": "^1.0",
         "composer/spdx-licenses": "^1.2",
&lt;span class="hljs-addition"&gt;+        "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "psr/log": "^1.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "seld/cli-prompt": "^1.0",&lt;/span&gt;
         "seld/jsonlint": "^1.4",
&lt;span class="hljs-addition"&gt;+        "seld/phar-utils": "^1.0",&lt;/span&gt;
         "symfony/console": "^2.7 || ^3.0 || ^4.0",
&lt;span class="hljs-deletion"&gt;-        "symfony/finder": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "symfony/process": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
         "symfony/filesystem": "^2.7 || ^3.0 || ^4.0",
&lt;span class="hljs-deletion"&gt;-        "seld/phar-utils": "^1.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "seld/cli-prompt": "^1.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "psr/log": "^1.0"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "symfony/finder": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "symfony/process": "^2.7 || ^3.0 || ^4.0"&lt;/span&gt;
     },
     "require-dev": {
         "phpunit/phpunit": "^4.8.35 || ^5.7",
         "phpunit/phpunit-mock-objects": "^2.3 || ^3.0"
     },
&lt;span class="hljs-addition"&gt;+    "suggest": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "ext-zip": "Enabling the zip extension allows you to unzip archives",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "ext-zlib": "Allow gzip compression of HTTP requests"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    },&lt;/span&gt;
     "config": {
         "platform": {
             "php": "5.3.9"
         }
     },
&lt;span class="hljs-deletion"&gt;-    "suggest": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "ext-zip": "Enabling the zip extension allows you to unzip archives",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "ext-zlib": "Allow gzip compression of HTTP requests",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "extra": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "branch-alias": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            "dev-master": "1.7-dev"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        }&lt;/span&gt;
     },
     "autoload": {
&lt;span class="hljs-deletion"&gt;-        "psr-4": { "Composer\\": "src/Composer" }&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "psr-4": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            "Composer\\": "src/Composer"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        }&lt;/span&gt;
     },
     "autoload-dev": {
&lt;span class="hljs-deletion"&gt;-        "psr-4": { "Composer\\Test\\": "tests/Composer/Test" }&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "bin": ["bin/composer"],&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "extra": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "branch-alias": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "dev-master": "1.7-dev"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "psr-4": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+            "Composer\\Test\\": "tests/Composer/Test"&lt;/span&gt;
         }
     },
&lt;span class="hljs-addition"&gt;+    "bin": [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "bin/composer"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    ],&lt;/span&gt;
     "scripts": {
         "test": "phpunit"
&lt;span class="hljs-addition"&gt;+    },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "support": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "issues": "https://github.com/composer/composer/issues",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+        "irc": "irc://irc.freenode.org/composer"&lt;/span&gt;
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to change the indentation, you can use the &lt;code&gt;--indent-size&lt;/code&gt; and &lt;code&gt;--indent-style&lt;/code&gt; options. Here's an example of running&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer normalize --indent-size=2 --indent-style=space
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;on &lt;code&gt;composer/composer&lt;/code&gt; itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-diff hljs diff" data-lang="diff"&gt;diff --git a/composer.json b/composer.json
index dd672c3b..071c99c6 100644
&lt;span class="hljs-comment"&gt;--- a/composer.json&lt;/span&gt;
&lt;span class="hljs-comment"&gt;+++ b/composer.json&lt;/span&gt;
&lt;span class="hljs-meta"&gt;@@ -1,68 +1,78 @@&lt;/span&gt;
 {
&lt;span class="hljs-deletion"&gt;-    "name": "composer/composer",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "keywords": ["package", "dependency", "autoload"],&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "homepage": "https://getcomposer.org/",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "type": "library",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "license": "MIT",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "authors": [&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "name": "Nils Adermann",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "email": "naderman@naderman.de",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "homepage": "https://www.naderman.de"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "name": "Jordi Boggiano",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "email": "j.boggiano@seld.be",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "homepage": "https://seld.be"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        }&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    ],&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "support": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "irc": "irc://irc.freenode.org/composer",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "issues": "https://github.com/composer/composer/issues"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "name": "composer/composer",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "type": "library",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "keywords": [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "package",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "dependency",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "autoload"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  ],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "homepage": "https://getcomposer.org/",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "license": "MIT",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "authors": [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "name": "Nils Adermann",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "email": "naderman@naderman.de",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "homepage": "https://www.naderman.de"&lt;/span&gt;
     },
&lt;span class="hljs-deletion"&gt;-    "require": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "php": "^5.3.2 || ^7.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "composer/ca-bundle": "^1.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "composer/semver": "^1.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "composer/spdx-licenses": "^1.2",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "seld/jsonlint": "^1.4",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "symfony/console": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "symfony/finder": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "symfony/process": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "symfony/filesystem": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "seld/phar-utils": "^1.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "seld/cli-prompt": "^1.0",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "psr/log": "^1.0"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "require-dev": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "phpunit/phpunit": "^4.8.35 || ^5.7",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "phpunit/phpunit-mock-objects": "^2.3 || ^3.0"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "config": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "platform": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "php": "5.3.9"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        }&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "suggest": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "ext-zip": "Enabling the zip extension allows you to unzip archives",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "ext-zlib": "Allow gzip compression of HTTP requests",&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "autoload": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "psr-4": { "Composer\\": "src/Composer" }&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "autoload-dev": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "psr-4": { "Composer\\Test\\": "tests/Composer/Test" }&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "bin": ["bin/composer"],&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "extra": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "branch-alias": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-            "dev-master": "1.7-dev"&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        }&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    },&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-    "scripts": {&lt;/span&gt;
&lt;span class="hljs-deletion"&gt;-        "test": "phpunit"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "name": "Jordi Boggiano",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "email": "j.boggiano@seld.be",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "homepage": "https://seld.be"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    }&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  ],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "require": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "php": "^5.3.2 || ^7.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "composer/ca-bundle": "^1.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "composer/semver": "^1.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "composer/spdx-licenses": "^1.2",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "psr/log": "^1.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "seld/cli-prompt": "^1.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "seld/jsonlint": "^1.4",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "seld/phar-utils": "^1.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "symfony/console": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "symfony/filesystem": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "symfony/finder": "^2.7 || ^3.0 || ^4.0",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "symfony/process": "^2.7 || ^3.0 || ^4.0"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "require-dev": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "phpunit/phpunit": "^4.8.35 || ^5.7",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "phpunit/phpunit-mock-objects": "^2.3 || ^3.0"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "suggest": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "ext-zip": "Enabling the zip extension allows you to unzip archives",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "ext-zlib": "Allow gzip compression of HTTP requests"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "config": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "platform": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "php": "5.3.9"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    }&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "extra": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "branch-alias": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "dev-master": "1.7-dev"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    }&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "autoload": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "psr-4": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "Composer\\": "src/Composer"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    }&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "autoload-dev": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "psr-4": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+      "Composer\\Test\\": "tests/Composer/Test"&lt;/span&gt;
     }
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "bin": [&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "bin/composer"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  ],&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "scripts": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "test": "phpunit"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  },&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  "support": {&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "issues": "https://github.com/composer/composer/issues",&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+    "irc": "irc://irc.freenode.org/composer"&lt;/span&gt;
&lt;span class="hljs-addition"&gt;+  }&lt;/span&gt;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hope you like it!&lt;/p&gt;

</content></entry><entry><title>Pretty-printing JSON with custom indentation</title><category term="indentation"/><category term="json"/><category term="php"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2018/01/04/pretty-printing-json-with-custom-indentation/"/><id>https://localheinz.com/articles/2018/01/04/pretty-printing-json-with-custom-indentation/</id><updated>2018-01-04T11:25:00+01:00</updated><content>&lt;h1&gt;
  Pretty-printing JSON with custom indentation
&lt;/h1&gt;


&lt;p&gt;&lt;a href="https://www.php.net/ChangeLog-5.php#5.4.0" target="_blank" title="PHP 5.4.0 Changelog"&gt;PHP 5.4&lt;/a&gt; added the &lt;code&gt;JSON_PRETTY_PRINT&lt;/code&gt;, &lt;code&gt;JSON_UNESCAPED_SLASHES&lt;/code&gt;, and &lt;code&gt;JSON_UNESCAPED_UNICODE&lt;/code&gt; constants for use with the &lt;a href="https://www.php.net/manual/en/function.json-encode.php" target="_blank" title="PHP Manual: json_encode()"&gt;&lt;code&gt;json_encode()&lt;/code&gt;&lt;/a&gt; function.&lt;/p&gt;
&lt;p&gt;For example, you can use the &lt;code&gt;JSON_PRETTY_PRINT&lt;/code&gt; constant to encode a value to a JSON string with four spaces of indentation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

$data = [
    &lt;span class="hljs-string"&gt;'name'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;'Andreas Möller'&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;'emoji'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;'🤓'&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;'urls'&lt;/span&gt; =&amp;gt; [
        &lt;span class="hljs-string"&gt;'https://localheinz.com'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'https://github.com/localheinz'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'https://twitter.com/localheinz'&lt;/span&gt;,
    ],
];

&lt;span class="hljs-keyword"&gt;echo&lt;/span&gt; json_encode(
    $data,
    JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-json hljs json" data-lang="json"&gt;{
    &lt;span class="hljs-attr"&gt;"name"&lt;/span&gt;:&lt;span class="hljs-string"&gt;"Andreas Möller"&lt;/span&gt;,
    &lt;span class="hljs-attr"&gt;"emoji"&lt;/span&gt;:&lt;span class="hljs-string"&gt;"🤓"&lt;/span&gt;,
    &lt;span class="hljs-attr"&gt;"urls"&lt;/span&gt;:[
        &lt;span class="hljs-string"&gt;"https://localheinz.com"&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;"https://github.com/localheinz"&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;"https://twitter.com/localheinz"&lt;/span&gt;
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But what if you need to indent a JSON string with two spaces or tabs?&lt;/p&gt;
&lt;p&gt;In June 2022, only 7 out of 21 people voted for a related &lt;a href="https://wiki.php.net/rfc/json_encode_indentation" target="_blank" title="PHP RFC: json_encode indentation"&gt;&lt;code&gt;json_encode&lt;/code&gt; indentation RFC&lt;/a&gt;, which proposed adding a parameter to the json_encode() that would allow configuring the number of spaces to use for indenting the resulting JSON string.&lt;/p&gt;
&lt;p&gt;If you need to indent a JSON string with an indentation other than four spaces, you need to solve the problem in PHP code.&lt;/p&gt;
&lt;p&gt;But fear not - based on source code I found in &lt;a target="_blank" href="https://github.com/composer/composer/blob/1.6.0/src/Composer/Json/JsonFormatter.php" title="Composer\Json\JsonFormatter"&gt;&lt;code&gt;composer/composer&lt;/code&gt;&lt;/a&gt;, linking back to an &lt;a target="_blank" href="https://www.daveperrett.com/articles/2008/03/11/format-json-with-php/" title="Format JSON With PHP"&gt;article&lt;/a&gt; by &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;www.daveperrett.com" target="_blank" title="Dave&amp;#x20;Perrett"&gt;Dave Perrett&lt;/a&gt; from 2008, I have built the PHP package &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;ergebnis&amp;#x2F;json-printer" target="_blank" title="ergebnis/json-printer on GitHub"&gt;&lt;code&gt;ergebnis/json-printer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To install the PHP package, run&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell hljs shell" data-lang="shell"&gt;composer require ergebnis/json-printer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After adjusting the example from earlier using the printer, the script produces a JSON string indented with two spaces.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-php hljs php" data-lang="php"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="hljs-keyword"&gt;declare&lt;/span&gt;(strict_types=&lt;span class="hljs-number"&gt;1&lt;/span&gt;);

&lt;span class="hljs-keyword"&gt;use&lt;/span&gt; &lt;span class="hljs-title"&gt;Ergebnis&lt;/span&gt;\&lt;span class="hljs-title"&gt;Json&lt;/span&gt;\&lt;span class="hljs-title"&gt;Printer&lt;/span&gt;;

$data = [
    &lt;span class="hljs-string"&gt;'name'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;'Andreas Möller'&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;'emoji'&lt;/span&gt; =&amp;gt; &lt;span class="hljs-string"&gt;'🤓'&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;'urls'&lt;/span&gt; =&amp;gt; [
        &lt;span class="hljs-string"&gt;'https://localheinz.com'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'https://github.com/localheinz'&lt;/span&gt;,
        &lt;span class="hljs-string"&gt;'https://twitter.com/localheinz'&lt;/span&gt;,
    ],
];

$json = json_encode(
    $data,
    JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);

$printer = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Printer\Printer();

&lt;span class="hljs-keyword"&gt;echo&lt;/span&gt; $printer-&amp;gt;print(
    $json,
    str_repeat(&lt;span class="hljs-string"&gt;' '&lt;/span&gt;, &lt;span class="hljs-number"&gt;2&lt;/span&gt;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-json hljs json" data-lang="json"&gt;{
  &lt;span class="hljs-attr"&gt;"name"&lt;/span&gt;:&lt;span class="hljs-string"&gt;"Andreas Möller"&lt;/span&gt;,
  &lt;span class="hljs-attr"&gt;"emoji"&lt;/span&gt;:&lt;span class="hljs-string"&gt;"🤓"&lt;/span&gt;,
  &lt;span class="hljs-attr"&gt;"urls"&lt;/span&gt;:[
    &lt;span class="hljs-string"&gt;"https://localheinz.com"&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;"https://github.com/localheinz"&lt;/span&gt;,
    &lt;span class="hljs-string"&gt;"https://twitter.com/localheinz"&lt;/span&gt;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope it helps - I definitely need this for &lt;a href="/articles/2018/01/15/normalizing-composer.json/" target="_blank" title="Normalizing&amp;#x20;composer.json"&gt;normalizing composer.json&lt;/a&gt;!&lt;/p&gt;

</content></entry><entry><title>Essential PhpStorm plugins</title><category term="php"/><category term="phpstorm"/><author><name>Andreas Möller</name><uri>https://localheinz.com/</uri><email>am@localheinz.com</email></author><link href="https://localheinz.com/articles/2017/10/27/essential-phpstorm-plugins/"/><id>https://localheinz.com/articles/2017/10/27/essential-phpstorm-plugins/</id><updated>2017-10-27T09:30:00+01:00</updated><content>&lt;h1&gt;
  Essential PhpStorm plugins
&lt;/h1&gt;


&lt;p&gt;
  If you are like me and have been programming for a bit, you probably have worked with several editors and IDEs. Over the years, I have tried a bunch of them - with varying degrees of successes. Some of them were just the right tool to get started, others were recommended by friends and developers along the way. Most of them were quickly outgrown by the projects I worked on at the time.
&lt;/p&gt;
&lt;p&gt;
  So I kept looking for alternatives.
&lt;/p&gt;
&lt;h2 id="content-phpstorm" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-phpstorm" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;PhpStorm&lt;/h2&gt;
&lt;p&gt;In January 2012 I attended a meeting of &lt;a target="_blank" href="https://www.bephpug.de/"&gt;BEPHPUG&lt;/a&gt;, the Berlin PHP user group. At that meeting, a few IDEs and editors (some of which I had worked with before) were demonstrated. &lt;a href="https&amp;#x3A;&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;bashofmann" target="_blank" title="Bastian&amp;#x20;Hofmann&amp;#x20;on&amp;#x20;GitHub"&gt;Bastian Hofmann&lt;/a&gt; gave a demonstration of &lt;a target="_blank" href="https://www.jetbrains.com/phpstorm/"&gt;PhpStorm&lt;/a&gt;, and I was amazed! Directly after the meeting I went home, installed it, and have been using it ever since. As advertised, it's lightning fast and smart, and I can't image I could be any happier or more productive with any other IDE!&lt;/p&gt;
&lt;h2 id="content-plugins" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-plugins" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Plugins&lt;/h2&gt;
&lt;p&gt;
  Nonetheless, while PhpStorm already &lt;em&gt;is&lt;/em&gt; the perfect development environment, there are plugins that make it even better. I have tried a few, and here's a list of what I see as essential PhpStorm plugins:
&lt;/p&gt;
&lt;ul&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/9525--env-files-support" title=".env files support"&gt;.env files support&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/adelf" title="Adel Fayzrakhmanov"&gt;Adel Fayzrakhmanov&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7495--ignore" title=".ignore"&gt;.ignore&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/hsz" title="Jakub Chrzanowski"&gt;Jakub Chrzanowski&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/6834-apache-config--htaccess-support" title="Apache config (.htaccess) support"&gt;Apache config (.htaccess) support&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/neuro159" title="Alexey Gopachenko"&gt;Alexey Gopachenko&lt;/a&gt;,                   &lt;a target="_blank" href="https://github.com/MaXal" title="Maxim Kolmakov"&gt;Maxim Kolmakov&lt;/a&gt; and                   &lt;a target="_blank" href="https://github.com/SvetlanaZem" title="Svetlana Zemlyanskaya"&gt;Svetlana Zemlyanskaya&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/4230-bashsupport" title="BashSupport"&gt;BashSupport&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/jansorg" title="Joachim Ansorg"&gt;Joachim Ansorg&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7160-camelcase" title="CamelCase"&gt;CamelCase&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/johannespfeiffer" title="Johannes Pfeiffer"&gt;Johannes Pfeiffer&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/14896-code-with-me" title="Code With Me"&gt;Code With Me&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://www.jetbrains.com" title="JetBrains s.r.o"&gt;JetBrains s.r.o&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7275-codeglance" title="CodeGlance"&gt;CodeGlance&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/Vektah" title="Adam Scarr"&gt;Adam Scarr&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7499-gittoolbox" title="GitToolBox"&gt;GitToolBox&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/zielu" title="Lukasz Zielinski"&gt;Lukasz Zielinski&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/9792-key-promoter-x" title="Key Promoter X"&gt;Key Promoter X&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/halirutan" title="Patrick Scheibe"&gt;Patrick Scheibe&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/5919-lines-sorter" title="Lines Sorter"&gt;Lines Sorter&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/syllant" title="Sylvain Francois"&gt;Sylvain Francois&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/9333-makefile-support" title="Makefile support"&gt;Makefile support&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/kropp" title="Victor Kropp"&gt;Victor Kropp&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced" title="Markdown Navigator Enhanced"&gt;Markdown Navigator Enhanced&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/vsch" title="Vladimir Schneider"&gt;Vladimir Schneider&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7060-neon-support" title="NEON support"&gt;NEON support&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/juzna" title="Jan Dolecek"&gt;Jan Dolecek&lt;/a&gt; and                   &lt;a target="_blank" href="https://github.com/matej21" title="David Matějka"&gt;David Matějka&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7320-php-annotations" title="PHP Annotations"&gt;PHP Annotations&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/Haehnchen" title="Daniel Espendiller"&gt;Daniel Espendiller&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/12754-phpstan--psalm--generics" title="PHPStan / Psalm / Generics"&gt;PHPStan / Psalm / Generics&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/Haehnchen" title="Daniel Espendiller"&gt;Daniel Espendiller&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/9674-phpunit-enhancement" title="PHPUnit Enhancement"&gt;PHPUnit Enhancement&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/Haehnchen" title="Daniel Espendiller"&gt;Daniel Espendiller&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7622-php-inspections-ea-extended-" title="Php Inspections (EA Extended)"&gt;Php Inspections (EA Extended)&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/kalessil" title="Vladimir Reznichenko"&gt;Vladimir Reznichenko&lt;/a&gt;,                   &lt;a target="_blank" href="https://github.com/funivan" title="Ivan Scherbak"&gt;Ivan Scherbak&lt;/a&gt; and                   &lt;a target="_blank" href="https://github.com/rentalhost" title="David Rodrigues"&gt;David Rodrigues&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/10215-php-inspections-ea-ultimate-" title="Php Inspections (EA Ultimate)"&gt;Php Inspections (EA Ultimate)&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/kalessil" title="Vladimir Reznichenko"&gt;Vladimir Reznichenko&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/2917-regexptester" title="RegexpTester"&gt;RegexpTester&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/sevdokimov" title="Sergey Evdokimov"&gt;Sergey Evdokimov&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/2162-string-manipulation" title="String Manipulation"&gt;String Manipulation&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/krasa" title="Vojtěch Krása"&gt;Vojtěch Krása&lt;/a&gt;                  &lt;/li&gt;
      &lt;li&gt;
      &lt;a target="_blank" href="https://plugins.jetbrains.com/plugin/7219-symfony-plugin" title="Symfony Plugin"&gt;Symfony Plugin&lt;/a&gt;
              by
                  &lt;a target="_blank" href="https://github.com/Haehnchen" title="Daniel Espendiller"&gt;Daniel Espendiller&lt;/a&gt;                  &lt;/li&gt;
  &lt;/ul&gt;
&lt;p&gt;
  You can find more plugins from within the IDE or by browsing the &lt;a target="_blank" href="https://plugins.jetbrains.com/phpstorm" title="JetBrains PhpStorm Plugin Repository"&gt;JetBrains PhpStorm Plugin Repository&lt;/a&gt;.
&lt;/p&gt;
&lt;h2 id="content-not-using-phpstorm-yet" class="group flex items-center whitespace-pre-wrap -ml-4 pl-4"&gt;&lt;a href="#content-not-using-phpstorm-yet" class="absolute hidden md:inline-flex -ml-6 flex items-center opacity-0 border-0 group-hover:opacity-100 text-base text-fun-blue-700 hover:text-fun-blue-800 visited:text-fun-blue-700 hover:no-underline" aria-hidden="true" title="Permalink"&gt;&lt;i class="fa-solid fa-fw fa-link"&gt;&lt;/i&gt;&lt;/a&gt;Not using PhpStorm yet?&lt;/h2&gt;
&lt;p&gt;
  If you haven't been using PhpStorm before, I highly recommend giving it a try. If you are a student, you can even use PhpStorm or any JetBrains IDE &lt;a target="_blank" href="https://www.jetbrains.com/student/"&gt;free of charge&lt;/a&gt;. JetBrains also offer a free 30-day-trial, so you can find out if PhpStorm will work for you without any strings attached. If you need some more time, you can also participate in the &lt;a target="_blank" href="https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Early+Access+Program"&gt;PhpStorm Early Access Program&lt;/a&gt;, which allows you to use the early releases of upcoming versions in exchange for living with (and ideally reporting) an occasional bug or two.
&lt;/p&gt;
&lt;p&gt;
  Don't waste any more time with mediocre editors or IDEs!
&lt;/p&gt;
&lt;figure&gt;
  &lt;a target="_blank" href="https://www.jetbrains.com/phpstorm/" title="PhpStorm"&gt;
    &lt;img alt="Boxshot of PhpStorm" class="webfeedsFeaturedVisual" src="/dist/img/article/2017/10/27/essential-phpstorm-plugins/boxshot.png?c6b293c"&gt;
  &lt;/a&gt;
&lt;/figure&gt;

</content></entry></feed>
