<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Drupal Archives - Víctor Dorado</title>
	<atom:link href="https://www.victordorado.es/category/drupal/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.victordorado.es/category/drupal/</link>
	<description></description>
	<lastBuildDate>Tue, 05 May 2026 07:33:40 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
	<item>
		<title>Tests de Kernel en Drupal: cuando los tests unitarios no son suficiente</title>
		<link>https://www.victordorado.es/2025/03/06/tests-de-kernel-en-drupal-cuando-los-tests-unitarios-no-son-suficiente/</link>
					<comments>https://www.victordorado.es/2025/03/06/tests-de-kernel-en-drupal-cuando-los-tests-unitarios-no-son-suficiente/#respond</comments>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Thu, 06 Mar 2025 14:23:51 +0000</pubDate>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2361</guid>

					<description><![CDATA[<p>En este vídeo veremos en qué ocasiones los tests unitarios se vuelven demasiado complicados, debido al gran número de dependencias que debemos simular, o a su complejidad. Los tests de kernel vienen a solucionar el problema, a costa de una pérdida de velocidad.</p>
<p>The post <a href="https://www.victordorado.es/2025/03/06/tests-de-kernel-en-drupal-cuando-los-tests-unitarios-no-son-suficiente/">Tests de Kernel en Drupal: cuando los tests unitarios no son suficiente</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">En este vídeo veremos <strong>en qué ocasiones los tests unitarios se vuelven demasiado complicados, debido al gran número de dependencias que debemos simular, o a su complejidad</strong>. Los tests de kernel vienen a solucionar el problema, a costa de una pérdida de velocidad.</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Videocurso Testing en Drupal - Parte 3:  Tests de Kernel" width="1290" height="726" src="https://www.youtube.com/embed/eVG5cUPgmmc?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
<p>The post <a href="https://www.victordorado.es/2025/03/06/tests-de-kernel-en-drupal-cuando-los-tests-unitarios-no-son-suficiente/">Tests de Kernel en Drupal: cuando los tests unitarios no son suficiente</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.victordorado.es/2025/03/06/tests-de-kernel-en-drupal-cuando-los-tests-unitarios-no-son-suficiente/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Tests Unitarios en Drupal. Parte 2: Sujetos de test con dependencias</title>
		<link>https://www.victordorado.es/2025/03/02/tests-unitarios-en-drupal-parte-2-sujetos-de-test-con-dependencias/</link>
					<comments>https://www.victordorado.es/2025/03/02/tests-unitarios-en-drupal-parte-2-sujetos-de-test-con-dependencias/#respond</comments>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Sun, 02 Mar 2025 19:44:44 +0000</pubDate>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[PHPStorm]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2353</guid>

					<description><![CDATA[<p>En esta segunda parte veremos tests un poco más avanzados, donde es necesario simular (mockear) las dependencias de nuestra clase sujeto. Además, podremos ver un caso típico de testing por intervalos, donde debemos comprobar que cada intervalo produce la salida correcta.</p>
<p>The post <a href="https://www.victordorado.es/2025/03/02/tests-unitarios-en-drupal-parte-2-sujetos-de-test-con-dependencias/">Tests Unitarios en Drupal. Parte 2: Sujetos de test con dependencias</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">En esta segunda parte veremos<strong> tests un poco más avanzados, donde es necesario simular (mockear) las dependencias</strong> de nuestra clase sujeto.</p>



<p class="wp-block-paragraph">Además, <strong>podremos ver un caso típico de testing por intervalos</strong>, donde debemos comprobar que cada intervalo produce la salida correcta.</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Videocurso Testing en Drupal - Parte 2: Tests Unitarios con Dependencias" width="1290" height="726" src="https://www.youtube.com/embed/Vbcpnh0lU4I?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
<p>The post <a href="https://www.victordorado.es/2025/03/02/tests-unitarios-en-drupal-parte-2-sujetos-de-test-con-dependencias/">Tests Unitarios en Drupal. Parte 2: Sujetos de test con dependencias</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.victordorado.es/2025/03/02/tests-unitarios-en-drupal-parte-2-sujetos-de-test-con-dependencias/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Tests unitarios en Drupal con PHPUnit y PHPStorm</title>
		<link>https://www.victordorado.es/2025/02/28/tests-unitarios-en-drupal-con-phpunit-y-phpstorm/</link>
					<comments>https://www.victordorado.es/2025/02/28/tests-unitarios-en-drupal-con-phpunit-y-phpstorm/#respond</comments>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Fri, 28 Feb 2025 02:26:39 +0000</pubDate>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[PHPStorm]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2349</guid>

					<description><![CDATA[<p>En este vídeo veremos, de forma muy resumida, cómo se puede crear un test unitario en Drupal. Crearemos un servicio muy simple y un test que compruebe que el servicio se comporta como esperamos.</p>
<p>The post <a href="https://www.victordorado.es/2025/02/28/tests-unitarios-en-drupal-con-phpunit-y-phpstorm/">Tests unitarios en Drupal con PHPUnit y PHPStorm</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>En esta ocasión, esta publicación estará en español,</strong> ya que aún no tengo la soltura para grabar un vídeo en inglés 😁. No obstante, esto es un comienzo para, algún día, dar ese paso.</p>



<p class="wp-block-paragraph">En este vídeo <strong>veremos, de forma muy resumida, cómo se puede crear un test unitario en Drupal.</strong> Crearemos un servicio muy simple y un test que compruebe que el servicio se comporta como esperamos. Además, podremos apreciar, en primera persona, las <strong>ventajas de programar con TDD </strong>(Test Driven Development).</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Videocurso Testing en Drupal - Parte 1: Tests Unitarios" width="1290" height="726" src="https://www.youtube.com/embed/JDM8cLCbdW4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
<p>The post <a href="https://www.victordorado.es/2025/02/28/tests-unitarios-en-drupal-con-phpunit-y-phpstorm/">Tests unitarios en Drupal con PHPUnit y PHPStorm</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.victordorado.es/2025/02/28/tests-unitarios-en-drupal-con-phpunit-y-phpstorm/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Bring the AWS Cloud to Your Desktop with LocalStack™, DDEV and Drupal</title>
		<link>https://www.victordorado.es/2024/12/19/bring-the-aws-cloud-to-your-desktop-with-localstack-and-drupal/</link>
					<comments>https://www.victordorado.es/2024/12/19/bring-the-aws-cloud-to-your-desktop-with-localstack-and-drupal/#comments</comments>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Thu, 19 Dec 2024 19:07:56 +0000</pubDate>
				<category><![CDATA[DDEV]]></category>
		<category><![CDATA[Drupal]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2323</guid>

					<description><![CDATA[<p>Developing applications to run on AWS can become much simpler with a local setup. We will perform a complete S3 local service installation with Drupal and DDEV</p>
<p>The post <a href="https://www.victordorado.es/2024/12/19/bring-the-aws-cloud-to-your-desktop-with-localstack-and-drupal/">Bring the AWS Cloud to Your Desktop with LocalStack™, DDEV and Drupal</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>Developing applications to run on AWS can become much simpler with a local setup</strong>. Imagine working without needing an internet connection or incurring additional costs. As LocalStack states on its website:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>&#8220;Develop and test your AWS applications locally to reduce development time and increase product velocity. Reduce unnecessary AWS spend and remove the complexity and risk of maintaining AWS dev accounts.&#8221;</em></p>
</blockquote>



<p class="wp-block-paragraph">LocalStack™ offers both a community and a pro version, allowing users to access the community version for free. In this guide, we’ll walk you through configuring a local instance of Simple Storage Service (S3) hosted in a DDEV container.</p>



<h2 class="wp-block-heading">Installing Drupal with DDEV</h2>



<p class="wp-block-paragraph">From the <a href="https://ddev.readthedocs.io/en/stable/users/quickstart/#drupal">DDEV quickstart guide</a>, we can follow these steps to set up a new Drupal 11 project:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>mkdir my-drupal11-site &amp;&amp; cd my-drupal11-site \
ddev config --project-type=drupal11 --docroot=web \
ddev composer create drupal/recommended-project:^11.0.0 \
ddev composer require drush/drush \
ddev drush site:install --account-name=admin --account-pass=admin -y \
ddev launch</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my-drupal11-site</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my-drupal11-site</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">ddev </span><span style="color: #A3BE8C">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--project-type=drupal11</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--docroot=web</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">ddev </span><span style="color: #A3BE8C">composer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">create</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">drupal/recommended-project:^11.0.0</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">ddev </span><span style="color: #A3BE8C">composer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">require</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">drush/drush</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">ddev </span><span style="color: #A3BE8C">drush</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">site:install</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--account-name=admin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--account-pass=admin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-y</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">ddev </span><span style="color: #A3BE8C">launch</span></span></code></pre></div>



<h3 class="wp-block-heading">Important:</h3>



<p class="wp-block-paragraph">At the time of writing this guide, the <strong>S3 Filesystem</strong> module requires <code>drupal/core:^11.0.0</code>. This means it is not compatible with Drupal 11.1, which is currently the latest core version. To address this, we’ve specified the <code>^11.0.0</code> version constraint in Composer, ensuring the installation of the latest 11.0.x version.</p>



<p class="wp-block-paragraph">With these steps, <strong>you should now have a DDEV project running a clean Drupal installation with Drush.</strong></p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="641" src="https://www.victordorado.es/wp-content/uploads/2024/12/image-1-1024x641.png" alt="" class="wp-image-2333" srcset="https://www.victordorado.es/wp-content/uploads/2024/12/image-1-1024x641.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/12/image-1-300x188.png 300w, https://www.victordorado.es/wp-content/uploads/2024/12/image-1-768x480.png 768w, https://www.victordorado.es/wp-content/uploads/2024/12/image-1.png 1226w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Adding the LocalStack service to DDEV</h2>



<p class="wp-block-paragraph">Next, we need to create a file named <code>docker-compose.localstack.yml</code> in the root of the <code>.ddev</code> directory with the following content:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0">docker-compose.localstack.yml</span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>services:
  localstack:
    container_name: ddev-${DDEV_SITENAME}-localstack
    image: localstack/localstack
    restart: always
    labels:
      com.ddev.site-name: ${DDEV_SITENAME}
      com.ddev.approot: $DDEV_APPROOT
    environment:
      # LocalStack config: https://docs.localstack.cloud/references/configuration/
      - VIRTUAL_HOST=$DDEV_HOSTNAME
      - DEBUG=0
    ports:
      - 4566:4566
    volumes:
      - "./localstack/volume:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

  web:
    links:
      - localstack</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">services</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">localstack</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ddev-${DDEV_SITENAME}-localstack</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">localstack/localstack</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">always</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">labels</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">com.ddev.site-name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">${DDEV_SITENAME}</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">com.ddev.approot</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$DDEV_APPROOT</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #ECEFF4">      </span><span style="color: #616E88"># LocalStack config: https://docs.localstack.cloud/references/configuration/</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">VIRTUAL_HOST=$DDEV_HOSTNAME</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">DEBUG=0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">4566:4566</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./localstack/volume:/var/lib/localstack</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/var/run/docker.sock:/var/run/docker.sock</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">web</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">links</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">localstack</span></span></code></pre></div>



<p class="wp-block-paragraph">(based on <a href="https://docs.localstack.cloud/getting-started/installation/#docker-compose">https://docs.localstack.cloud/getting-started/installation/#docker-compose</a>)</p>



<p class="wp-block-paragraph">Then, we must<strong> restart the DDEV project:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ddev restart</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">restart</span></span></code></pre></div>



<p class="wp-block-paragraph">And we will be able to <strong>open a ssh connection to our new localstack container:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -ti ddev-drupal11-localstack-localstack bash</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-ti</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ddev-drupal11-localstack-localstack</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">bash</span></span></code></pre></div>



<p class="wp-block-paragraph">Following the instructions from the <a href="https://docs.localstack.cloud/user-guide/aws/s3/">LocalStack S3 User Guide</a>, we will now proceed to create a bucket:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>awslocal s3api create-bucket --bucket sample-bucket
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">awslocal</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">s3api</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">create-bucket</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--bucket</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">sample-bucket</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">And we will get a positive feedback response:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>{
    "Location": "/sample-bucket"
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&quot;Location&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/sample-bucket</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">At this point, <strong>we now have a local AWS S3 instance up and running inside a container</strong>, accessible as <code>localstack</code> from the other DDEV project containers.</p>



<h3 class="wp-block-heading">Important Caveat:</h3>



<p class="wp-block-paragraph"><strong>The community version of the LocalStack container does not include persistence</strong>. This feature is only available in the paid LocalStack Pro version. As a result, when using the community edition, all buckets and their content will be lost each time the <code>localstack</code> container is restarted. This limitation means we can only perform very basic tests with this setup.</p>



<h2 class="wp-block-heading">Configuring EC2 Metadata Mock</h2>



<p class="wp-block-paragraph">Many AWS SDKs rely on a critical component available in every EC2 instance: a metadata server accessible at <code>169.254.169.254</code>. Since this server won&#8217;t be available from our DDEV web container, we need to mock it using a tool provided by Amazon.</p>



<p class="wp-block-paragraph"><a href="https://github.com/aws/amazon-ec2-metadata-mock">Amazon EC2 Metadata Mock</a></p>



<p class="wp-block-paragraph">Fortunately, <strong>this tool is available as a Docker image</strong>, which allows us to create another container within our DDEV project to host it. To set this up, create a file named <code>docker-compose.ec2-metadata-mock.yml</code> inside the <code>.ddev</code> directory with the following content:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0">docker-compose.ec2-metadata-mock.yml</span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>services:
  ec2-metadata-mock:
    container_name: ddev-${DDEV_SITENAME}-ec2-metadata-mock
    image: public.ecr.aws/aws-ec2/amazon-ec2-metadata-mock:v1.12.0
    restart: always
    labels:
      com.ddev.site-name: ${DDEV_SITENAME}
      com.ddev.approot: $DDEV_APPROOT
    environment: []
    ports:
      - 1338:1338
    volumes: []

  web:
    links:
      - ec2-metadata-mock</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">services</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">ec2-metadata-mock</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ddev-${DDEV_SITENAME}-ec2-metadata-mock</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">public.ecr.aws/aws-ec2/amazon-ec2-metadata-mock:v1.12.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">always</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">labels</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">com.ddev.site-name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">${DDEV_SITENAME}</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">com.ddev.approot</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$DDEV_APPROOT</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">1338:1338</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">web</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">links</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ec2-metadata-mock</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>The tool listens on port <code>1338</code> instead of the standard port <code>80</code></strong>, but we’ll address this difference later. More details will follow in the next sections.</p>



<p class="wp-block-paragraph"><strong>Restart DDEV</strong> to deploy the new container:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ddev restart</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">restart</span></span></code></pre></div>



<p class="wp-block-paragraph">After restarting, <strong>the EC2 Metadata Mock will be running on port <code>1338</code> and accessible as <code>ec2-metadata-mock</code> from other containers</strong> within the DDEV project.</p>



<p class="wp-block-paragraph">We now can make a simple request from our web container to http://ec2-metadata-mock:1338/</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ddev ssh

curl http://ec2-metadata-mock:1338/latest/meta-data/iam/security-credentials
baskinc-role

curl http://ec2-metadata-mock:1338/latest/meta-data/iam/security-credentials/baskinc-role
{
  "Code": "Success",
  "LastUpdated": "2020-04-02T18:50:40Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "12345678901",
  "SecretAccessKey": "v/12345678901",
  "Token": "TEST92test48TEST+y6RpoTEST92test48TEST/8oWVAiBqTEsT5Ky7ty2tEStxC1T==",
  "Expiration": "2020-04-02T00:49:51Z"
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ssh</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">curl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">http://ec2-metadata-mock:1338/latest/meta-data/iam/security-credentials</span></span>
<span class="line"><span style="color: #88C0D0">baskinc-role</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">curl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">http://ec2-metadata-mock:1338/latest/meta-data/iam/security-credentials/baskinc-role</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;Code&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Success</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;LastUpdated&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2020-04-02T18:50:40Z</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;Type&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">AWS-HMAC</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;AccessKeyId&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">12345678901</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;SecretAccessKey&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">v/12345678901</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;Token&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">TEST92test48TEST+y6RpoTEST92test48TEST/8oWVAiBqTEsT5Ky7ty2tEStxC1T==</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">&quot;Expiration&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2020-04-02T00:49:51Z</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">The <code>Expiration</code> value will trigger a warning later when validating the configuration in Drupal. However, this warning can safely be ignored.</p>



<p class="wp-block-paragraph">For more information on making requests to the EC2 Metadata Mock, refer to the official documentation at <a href="https://github.com/aws/amazon-ec2-metadata-mock/tree/main?tab=readme-ov-file#making-a-request">https://github.com/aws/amazon-ec2-metadata-mock/tree/main?tab=readme-ov-file#making-a-request</a>.</p>



<h2 class="wp-block-heading">Patch the aws-sdk-php Composer package</h2>



<p class="wp-block-paragraph">The simplest way to make the AWS SDK recognize a custom EC2 Metadata Service location is by directly <strong>modifying its hardcoded IP address:</strong></p>



<p class="wp-block-paragraph"><strong>Change the value of the class constant </strong><code>Aws\Credentials\InstanceProfileProvider::<em>DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT</em></code> to <code>http://ec2-metadata-mock:1338</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0">PHP</span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;?php
namespace Aws\Credentials;

use ...

/**
 * Credential provider that provides credentials from the EC2 metadata service.
 */
class InstanceProfileProvider
{
    ...
    const DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT = 'http://ec2-metadata-mock:1338';</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> Aws</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Credentials</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> ...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * Credential provider that provides credentials from the EC2 metadata service.</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #8FBCBB">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">InstanceProfileProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ...</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT</span><span style="color: #D8DEE9FF"> = &#39;</span><span style="color: #8FBCBB">http</span><span style="color: #D8DEE9FF">:</span><span style="color: #616E88">//ec2-metadata-mock:1338&#39;;</span></span></code></pre></div>



<p class="wp-block-paragraph">This way, the credentials provider accessed will be our EC2 Metadata Mock server.</p>



<p class="wp-block-paragraph"><strong>You can make a composer patch to retain this configuration.</strong> See <a href="https://github.com/cweagans/composer-patches">https://github.com/cweagans/composer-patches</a> for more information.</p>



<h2 class="wp-block-heading">Edit /etc/hosts file in the host computer</h2>



<p class="wp-block-paragraph">From the web container, <strong>the LocalStack container is accessible as <code>localstack</code> via <code>http://localstack:4566</code></strong>. This setup enables internal communication between Drupal and LocalStack to function seamlessly.</p>



<p class="wp-block-paragraph">However, <strong>we also need access to the bucket from the host computer (our PC), </strong>as the web browser navigating the Drupal site will be running locally and needs to access the files stored in the bucket.</p>



<p class="wp-block-paragraph">So, <strong>add a new hostname to your localhost definition line inside your  /etc/hosts file</strong>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-blur-enabled" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0">/etc/hosts</span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>127.0.0.1 localhost localstack
127.0.1.1 MY-PC

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line cbp-no-blur"><span style="color: #88C0D0">127.0.0.1</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">localhost</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">localstack</span></span>
<span class="line"><span style="color: #88C0D0">127.0.1.1</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">MY-PC</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># The following lines are desirable for IPv6 capable hosts</span></span>
<span class="line"><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF">:1 </span><span style="color: #A3BE8C">ip6-localhost</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ip6-loopback</span></span>
<span class="line"><span style="color: #88C0D0">fe00::0</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ip6-localnet</span></span>
<span class="line"><span style="color: #88C0D0">ff00::0</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ip6-mcastprefix</span></span>
<span class="line"><span style="color: #88C0D0">ff02::1</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ip6-allnodes</span></span>
<span class="line"><span style="color: #88C0D0">ff02::2</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ip6-allrouters</span></span></code></pre></div>



<p class="wp-block-paragraph">And that’s it! <strong>Both your web browser and the DDEV web container can now access the bucket</strong> via <code>http://localstack:4566/sample-bucket</code>.</p>



<p class="wp-block-paragraph">Remember, <strong>we selected the &#8220;Use path-style endpoint&#8221; option in the S3 FileSystem configuration</strong>, which ensures compatibility with this setup.</p>



<h2 class="wp-block-heading">Configuring Drupal</h2>



<p class="wp-block-paragraph"><strong>Install the module &#8220;S3 Filesystem&#8221;.</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ddev composer require drupal/s3fs \
ddev drush en s3fs</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">composer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">require</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">drupal/s3fs</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">ddev </span><span style="color: #A3BE8C">drush</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">en</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">s3fs</span></span></code></pre></div>



<p class="wp-block-paragraph">And go to configure its settings at <code>/admin/config/media/s3fs</code>. </p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="641" src="https://www.victordorado.es/wp-content/uploads/2024/12/image-2-1024x641.png" alt="" class="wp-image-2334" srcset="https://www.victordorado.es/wp-content/uploads/2024/12/image-2-1024x641.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/12/image-2-300x188.png 300w, https://www.victordorado.es/wp-content/uploads/2024/12/image-2-768x480.png 768w, https://www.victordorado.es/wp-content/uploads/2024/12/image-2.png 1226w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">We will only have to fill these settings:</p>



<ul class="wp-block-list">
<li><strong>S3 Bucket Name:</strong> sample-bucket</li>



<li><strong>Use a Custom Host:</strong> Checked</li>



<li><strong>Hostname:</strong> http://localstack:4566</li>



<li><strong>Use path-style endpoint:</strong> Checked</li>
</ul>



<p class="wp-block-paragraph">Now, you can go to the &#8220;Actions&#8221; tab or visit <code>/admin/config/media/s3fs/actions</code> and click on the &#8220;Validate configuration&#8221; button.</p>



<p class="wp-block-paragraph">Despite the warning about the EC2 Metadata Mock credentials&#8217; expiration date (set to the year 2020), <strong>the settings will still be validated correctly</strong>. This means that your Drupal configuration will function as expected, even with this warning present.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="959" height="188" src="https://www.victordorado.es/wp-content/uploads/2024/12/image-4.png" alt="" class="wp-image-2341" srcset="https://www.victordorado.es/wp-content/uploads/2024/12/image-4.png 959w, https://www.victordorado.es/wp-content/uploads/2024/12/image-4-300x59.png 300w, https://www.victordorado.es/wp-content/uploads/2024/12/image-4-768x151.png 768w" sizes="(max-width: 959px) 100vw, 959px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="954" height="143" src="https://www.victordorado.es/wp-content/uploads/2024/12/image-5.png" alt="" class="wp-image-2342" srcset="https://www.victordorado.es/wp-content/uploads/2024/12/image-5.png 954w, https://www.victordorado.es/wp-content/uploads/2024/12/image-5-300x45.png 300w, https://www.victordorado.es/wp-content/uploads/2024/12/image-5-768x115.png 768w" sizes="(max-width: 954px) 100vw, 954px" /></figure>



<h2 class="wp-block-heading">Make an image field use the new S3 storage</h2>



<p class="wp-block-paragraph">Go to <code>/admin/structure/types/manage/article/fields/node.article.field_image</code> so <strong>we can configure the image field in the default &#8220;Article&#8221; content type</strong>:</p>



<p class="wp-block-paragraph">Then, <strong>change the Field Storage to &#8220;S3 File System&#8221; and click on &#8220;Save settings&#8221;.</strong></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1019" height="654" src="https://www.victordorado.es/wp-content/uploads/2024/12/image-3.png" alt="" class="wp-image-2339" srcset="https://www.victordorado.es/wp-content/uploads/2024/12/image-3.png 1019w, https://www.victordorado.es/wp-content/uploads/2024/12/image-3-300x193.png 300w, https://www.victordorado.es/wp-content/uploads/2024/12/image-3-768x493.png 768w" sizes="(max-width: 1019px) 100vw, 1019px" /></figure>



<p class="wp-block-paragraph">From now on, <strong>any new images added to articles will be stored in the new LocalStack <code>sample-bucket</code></strong></p>



<p class="wp-block-paragraph"><strong>Let&#8217;s now create a new Article with an image:</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="791" src="https://www.victordorado.es/wp-content/uploads/2024/12/image-6-1024x791.png" alt="" class="wp-image-2343" srcset="https://www.victordorado.es/wp-content/uploads/2024/12/image-6-1024x791.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/12/image-6-300x232.png 300w, https://www.victordorado.es/wp-content/uploads/2024/12/image-6-768x594.png 768w, https://www.victordorado.es/wp-content/uploads/2024/12/image-6.png 1255w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>We can view the image thanks to the previously configured entry in <code>/etc/hosts</code> for the <code>localstack</code> hostname</strong>. This ensures that the image’s <code>src</code> attribute, pointing to:</p>



<pre class="wp-block-code"><code>https:&#47;&#47;&#91;ddev-project-name].ddev.site/s3/files/styles/wide/s3/2024-12/image.webp?itok=_NXZWAN7</code></pre>



<p class="wp-block-paragraph">Which is redirected with a 302 to:</p>



<pre class="wp-block-code"><code>http:&#47;&#47;localstack:4566/sample-bucket/styles/wide/s3/2024-12/localstack.png.webp?itok=_NXZWAN7</code></pre>



<p class="wp-block-paragraph">On the other hand, we can also see that <strong>the image and its image styles are effectively stored in the bucket</strong>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#39404f;color:#c8d0e0">Bash</span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -ti ddev-drupal11-localstack-localstack bash

awslocal s3api list-objects --bucket sample-bucket

{
    "Contents": &#91;
        {
            "Key": "2024-12/localstack.png",
            "LastModified": "2024-12-19T18:26:47.000Z",
            "ETag": "\"d7cbf11f95e7d277c8989d2e8a11267f\"",
            "Size": 11510,
            "StorageClass": "STANDARD",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        },
        {
            "Key": "styles/thumbnail/s3/2024-12/localstack.png.webp",
            "LastModified": "2024-12-19T18:26:47.000Z",
            "ETag": "\"34ab21d0998bd749a7cfe82085ca6f76\"",
            "Size": 780,
            "StorageClass": "STANDARD",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        },
        {
            "Key": "styles/wide/s3/2024-12/localstack.png.webp",
            "LastModified": "2024-12-19T18:26:55.000Z",
            "ETag": "\"4f4f644fc0a98604de82e58b720af7cb\"",
            "Size": 3212,
            "StorageClass": "STANDARD",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        }
    &#93;,
    "RequestCharged": null,
    "Prefix": ""
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-ti</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ddev-drupal11-localstack-localstack</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">bash</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">awslocal</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">s3api</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">list-objects</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--bucket</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">sample-bucket</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&quot;Contents&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> &#91;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Key&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2024-12/localstack.png</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;LastModified&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2024-12-19T18:26:47.000Z</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;ETag&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">d7cbf11f95e7d277c8989d2e8a11267f</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Size&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">11510</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;StorageClass&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">STANDARD</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Owner&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">&quot;DisplayName&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">webfile</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">&quot;ID&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Key&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">styles/thumbnail/s3/2024-12/localstack.png.webp</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;LastModified&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2024-12-19T18:26:47.000Z</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;ETag&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">34ab21d0998bd749a7cfe82085ca6f76</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Size&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">780</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;StorageClass&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">STANDARD</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Owner&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">&quot;DisplayName&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">webfile</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">&quot;ID&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        },</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Key&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">styles/wide/s3/2024-12/localstack.png.webp</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;LastModified&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2024-12-19T18:26:55.000Z</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;ETag&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">4f4f644fc0a98604de82e58b720af7cb</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Size&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3212</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;StorageClass&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">STANDARD</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">&quot;Owner&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">&quot;DisplayName&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">webfile</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">&quot;ID&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        }</span></span>
<span class="line"><span style="color: #D8DEE9FF">    &#93;,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&quot;RequestCharged&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">null,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">&quot;Prefix&quot;</span><span style="color: #88C0D0">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre></div>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph"><strong>Having a local implementation of AWS services is incredibly helpful for development purposes</strong>. This applies not only to the S3 service but also to the wide range of complex services supported by LocalStack. Using LocalStack can prevent unnecessary expenses—potentially significant ones—caused by mistakes during development.</p>



<p class="wp-block-paragraph">In this guide, we focused solely on the S3 service and demonstrated the simplest configuration method. However, <strong>with a more advanced understanding of Docker Compose, it’s possible to design and implement a fully-featured local AWS cloud infrastructure </strong>to meet even the most complex development needs.</p>
<p>The post <a href="https://www.victordorado.es/2024/12/19/bring-the-aws-cloud-to-your-desktop-with-localstack-and-drupal/">Bring the AWS Cloud to Your Desktop with LocalStack™, DDEV and Drupal</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.victordorado.es/2024/12/19/bring-the-aws-cloud-to-your-desktop-with-localstack-and-drupal/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Improve your code quality from PHPStorm with PHP_CodeSniffer</title>
		<link>https://www.victordorado.es/2024/06/28/improve-your-code-quality-with-php-codesniffer-phpstan-eslint-stylelint-phpstorm-integration/</link>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Fri, 28 Jun 2024 20:01:01 +0000</pubDate>
				<category><![CDATA[DDEV]]></category>
		<category><![CDATA[Drupal]]></category>
		<category><![CDATA[PHPStorm]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2284</guid>

					<description><![CDATA[<p>Learn how to set up some quality tools with PHPStorm and DDEV: PHP_CodeSniffer, PHPStan, and others</p>
<p>The post <a href="https://www.victordorado.es/2024/06/28/improve-your-code-quality-with-php-codesniffer-phpstan-eslint-stylelint-phpstorm-integration/">Improve your code quality from PHPStorm with PHP_CodeSniffer</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>Following a consistent code style in a project is crucial for making it easier for developers to read each other&#8217;s code</strong>. When the code feels familiar, cognitive effort can be redirected to more important tasks.</p>



<p class="wp-block-paragraph">However, <strong>adhering to agreed-upon rules isn&#8217;t always easy.</strong> <strong>With many conventions to remember, we need tools to assist us</strong>. This is where our quality tools come in. They provide a powerful toolkit to ensure that our coding standards and style agreements are met. Furthermore, we can configure PHPStorm to use these tools, creating an integrated in-editor coding experience.</p>



<p class="wp-block-paragraph"><strong>In this article we will be working on a Drupal project, but the process is applicable to any project type</strong>, as long as we will be using standard npm/yarn and Composer package management to get the tools.</p>



<h2 class="wp-block-heading">Installing PHP_CodeSniffer</h2>



<p class="wp-block-paragraph">We will install it with Composer, as usual for many other PHP packages. We will also use a handy tool to ease the installation process.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="#ddev composer require --dev squizlabs/php_codesniffer dealerdirect/phpcodesniffer-composer-installer drupal/coder" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">#</span><span style="color: #D8DEE9">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">composer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">require</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9">dev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">squizlabs</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">php_codesniffer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dealerdirect</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">phpcodesniffer</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">composer</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">installer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">drupal</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">coder</span></span></code></pre></div>



<p class="wp-block-paragraph">After that, as the documentation states (<a href="https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer">https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer</a>) we must ensure the following config is present in our composer.json:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
    &quot;config&quot;: {
        &quot;allow-plugins&quot;: {
            &quot;dealerdirect/phpcodesniffer-composer-installer&quot;: true
        }
    }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">config</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">: </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">allow-plugins</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">dealerdirect/phpcodesniffer-composer-installer</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">If we are using Composer &lt; 2.2 it won&#8217;t and we will have to add it with the following command:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">composer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">allow</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">plugins</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">dealerdirect</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">phpcodesniffer</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">composer</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">installer</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span></code></pre></div>



<p class="wp-block-paragraph">After that, we will let the magic happen:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# composer install" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">composer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">install</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Now, we can execute the phpcs command inside our DDEV web container</strong>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev exec phpcs --version
Registering sniffs in the PEAR standard... DONE (28 sniffs registered)
PHP_CodeSniffer version 3.10.1 (stable) by Squiz and PHPCSStandards" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">phpcs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9">version</span></span>
<span class="line"><span style="color: #D8DEE9">Registering</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">sniffs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">the</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">PEAR</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">standard</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DONE</span><span style="color: #D8DEE9FF"> (</span><span style="color: #B48EAD">28</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">sniffs</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">registered</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9">PHP_CodeSniffer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">version</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3.10</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">stable</span><span style="color: #D8DEE9FF">) </span><span style="color: #D8DEE9">by</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Squiz</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">and</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">PHPCSStandards</span></span></code></pre></div>



<p class="wp-block-paragraph">Note that PHP_CodeSniffer says something about a &#8220;standard&#8221;. <strong>PHP_CodeSniffer checks our code against one of various possible &#8220;standards&#8221;</strong>, and there are some of them bundled by default with it. The default standard that will be used is the &#8220;PEAR&#8221; one.</p>



<p class="wp-block-paragraph">But, for a Drupal project (a website, a theme, a contrib module&#8230; etc.), <strong>we must use a special crafted standard called &#8220;Drupal&#8221;</strong>, that has all the Drupal community-agreed coding standards. Thanks to our previously installed <code>dealerdirect/phpcodesniffer-composer-installer</code>, we can see that the standards &#8220;Drupal&#8221; and &#8220;DrupalPractice&#8221; are installed:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev exec phpcs -i
The installed coding standards are MySource, PEAR, PSR1, PSR2, PSR12, Squiz, Zend, Drupal, DrupalPractice, VariableAnalysis and SlevomatCodingStandard" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">phpcs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">i</span></span>
<span class="line"><span style="color: #D8DEE9">The</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">installed</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">coding</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">standards</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">are</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">MySource</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">PEAR</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">PSR1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">PSR2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">PSR12</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Squiz</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Zend</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Drupal</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">DrupalPractice</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">VariableAnalysis</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">and</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">SlevomatCodingStandard</span></span></code></pre></div>



<p class="wp-block-paragraph">(DrupalPractice is a more exhaustive standard that checks not only the coding style used, but also some best practices for Drupal development).</p>



<p class="wp-block-paragraph"><strong>In this article, we will focus in the use of PHPCS through the UI, that is what will make a more &#8220;realtime&#8221; development experience</strong>. Using phpcs from the command line is harder, and is often only used by cli scripts, like in CI environment QA tests, or s Git <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">precommit hook</a> in a local development environment.</p>



<h2 class="wp-block-heading">Integrating PHP_CodeSniffer with PHPStorm</h2>



<p class="wp-block-paragraph">PHPStorm has friendly configuration dialogs for some popular quality tools, and one of them is PHPCS.</p>



<p class="wp-block-paragraph"><strong>Is highly recommended executing PHPCS inside our DDEV container</strong>, <strong>in order to keep consistency with our project&#8217;s PHP version and configuration</strong> instead of relying on our own machine config. To do so, first we must have a PHPStorm &#8220;DDEV&#8221; PHP interpreter (check my previous article <a href="https://www.victordorado.es/2024/03/18/phpunit-up-and-running-with-drupal-ddev-and-phpstorm/">PHPUnit up and running with Drupal, DDEV and PHPStorm</a> to know how to accomplish it).</p>



<p class="wp-block-paragraph">Then, go to Settings, under the &#8220;PHP &#8211; Quality Tools&#8221; section, and open the first accordion, setting its configuration and options as follows:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="993" height="312" src="https://www.victordorado.es/wp-content/uploads/2024/06/image-4.png" alt="" class="wp-image-2286" srcset="https://www.victordorado.es/wp-content/uploads/2024/06/image-4.png 993w, https://www.victordorado.es/wp-content/uploads/2024/06/image-4-300x94.png 300w, https://www.victordorado.es/wp-content/uploads/2024/06/image-4-768x241.png 768w" sizes="(max-width: 993px) 100vw, 993px" /></figure>



<p class="wp-block-paragraph"><strong>Notice that Drupal Core uses a different more relaxed standard</strong>. If you are going to develop for core you should set the coding standard to &#8220;Custom&#8221; and point to your project&#8217;s <code>&lt;drupal-root>/core/phpcs.xml.dist</code></p>



<p class="wp-block-paragraph">You can also configure some additional inspection settings in the &#8220;Editor &#8211; Inspections&#8221; menu:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="422" src="https://www.victordorado.es/wp-content/uploads/2024/06/image-5-1024x422.png" alt="" class="wp-image-2287" srcset="https://www.victordorado.es/wp-content/uploads/2024/06/image-5-1024x422.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/06/image-5-300x124.png 300w, https://www.victordorado.es/wp-content/uploads/2024/06/image-5-768x316.png 768w, https://www.victordorado.es/wp-content/uploads/2024/06/image-5.png 1158w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Once configured, edit any file and <strong>PHPStorm will automatically execute PHPCS, showing the warnings and errors</strong> in that file.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="946" height="408" src="https://www.victordorado.es/wp-content/uploads/2024/06/image-6.png" alt="" class="wp-image-2288" srcset="https://www.victordorado.es/wp-content/uploads/2024/06/image-6.png 946w, https://www.victordorado.es/wp-content/uploads/2024/06/image-6-300x129.png 300w, https://www.victordorado.es/wp-content/uploads/2024/06/image-6-768x331.png 768w" sizes="(max-width: 946px) 100vw, 946px" /></figure>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Maintaining a consistent code style in our projects is made easier with these supportive tools. Including them in every developer&#8217;s toolkit is highly beneficial.</p>
<p>The post <a href="https://www.victordorado.es/2024/06/28/improve-your-code-quality-with-php-codesniffer-phpstan-eslint-stylelint-phpstorm-integration/">Improve your code quality from PHPStorm with PHP_CodeSniffer</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PHPUnit up and running with Drupal, DDEV and PHPStorm</title>
		<link>https://www.victordorado.es/2024/03/18/phpunit-up-and-running-with-drupal-ddev-and-phpstorm/</link>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Mon, 18 Mar 2024 00:01:03 +0000</pubDate>
				<category><![CDATA[DDEV]]></category>
		<category><![CDATA[Drupal]]></category>
		<category><![CDATA[PHPStorm]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2229</guid>

					<description><![CDATA[<p>Getting PHPStorm, DDEV, Drupal and PHPUnit to work together is not a trivial task. Read this guide to get up and running!</p>
<p>The post <a href="https://www.victordorado.es/2024/03/18/phpunit-up-and-running-with-drupal-ddev-and-phpstorm/">PHPUnit up and running with Drupal, DDEV and PHPStorm</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">There are some articles out there about this topic: like the official <a href="https://ddev.readthedocs.io/en/latest/users/install/phpstorm/#enabling-phpunit" data-type="link" data-id="https://ddev.readthedocs.io/en/latest/users/install/phpstorm/#enabling-phpunit" target="_blank" rel="noreferrer noopener">DDEV docs PHPStorm &#8211; Enabling PHPUnit</a> or Matt Glaman&#8217;s <a href="https://mglaman.dev/blog/running-drupals-phpunit-test-suites-ddev" data-type="link" data-id="https://mglaman.dev/blog/running-drupals-phpunit-test-suites-ddev" target="_blank" rel="noreferrer noopener">Running Drupal&#8217;s PHPUnit test suites on DDEV</a>, <strong>but</strong> <strong>I haven&#8217;t found it so easy to figure it out. So, I&#8217;m writing this guide</strong>, although knowing that one day it will become obsolete, like it always have been with guides and new versions of things 🙂</p>



<h2 class="wp-block-heading">First step: enabling DDEV integration plugin for PHPStorm</h2>



<p class="wp-block-paragraph">DDEV has a PHPStorm plugin that eases a lot working with DDEV. <strong>One of the great things DDEV&#8217;s plugin does is creating a CLI interpreter</strong> that executes PHP code inside the web container. This will be the cornerstone of our setup.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="675" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-7-1024x675.png" alt="" class="wp-image-2231" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-7-1024x675.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-7-300x198.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-7-768x507.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-7.png 1128w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Setting DDEV as the project&#8217;s CLI interpreter and the Test framework interpreter</h2>



<p class="wp-block-paragraph">Under Settings -&gt; PHP section, we can now select the DDEV CLI interpreter:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="183" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-9-1024x183.png" alt="" class="wp-image-2232" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-9-1024x183.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-9-300x54.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-9-768x137.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-9.png 1129w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">If you don&#8217;t see the interpreter, check that you have a Docker connection added in Build, Execution, Deployment -&gt; Docker:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="685" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-10-1024x685.png" alt="" class="wp-image-2233" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-10-1024x685.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-10-300x201.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-10-768x514.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-10.png 1113w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">And check that &#8220;Use Compose V2&#8221; is enabled under the Tools subsection:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="685" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-11-1024x685.png" alt="" class="wp-image-2234" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-11-1024x685.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-11-300x201.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-11-768x514.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-11.png 1113w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Now, we must go to PHP -&gt; Test Frameworks and add a new one that uses the DDEV CLI interpreter:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="546" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-12-1024x546.png" alt="" class="wp-image-2235" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-12-1024x546.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-12-300x160.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-12-768x409.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-12.png 1394w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">We must also set a custom Default configuration file named phpunit.xml and located in our project&#8217;s root directory. DDEV&#8217;s CLI interpreter runs inside the web docker container, so we must set the path as it&#8217;s seen inside the container: <code>/var/www/html/phpunit.xml</code>. (this file doesn&#8217;t exist yet, we will create it later).</p>



<p class="wp-block-paragraph"><strong>Now, we have to tell PHPStorm to run this project&#8217;s tests with this new test framework</strong>. Go to Run -&gt; Edit configurations menu and clear any configuration you may find here.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="727" height="352" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-14.png" alt="" class="wp-image-2237" style="object-fit:cover" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-14.png 727w, https://www.victordorado.es/wp-content/uploads/2024/03/image-14-300x145.png 300w" sizes="(max-width: 727px) 100vw, 727px" /></figure>



<p class="wp-block-paragraph">Then, click on Edit configuration templates&#8230; -&gt; PHPUnit and set DDEV as the Command Line Interpreter:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1007" height="681" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-13.png" alt="" class="wp-image-2236" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-13.png 1007w, https://www.victordorado.es/wp-content/uploads/2024/03/image-13-300x203.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-13-768x519.png 768w" sizes="(max-width: 1007px) 100vw, 1007px" /></figure>



<h2 class="wp-block-heading">Creating project&#8217;s PHPUnit configuration file</h2>



<p class="wp-block-paragraph">Copy core&#8217;s PHPUnit configuration file <code>/web/core/phpunit.xml.dist</code> to <code>/phpunit.xml</code>, and edit it as follows:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;

&lt;phpunit xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         bootstrap=&quot;web/core/tests/bootstrap.php&quot;
         colors=&quot;true&quot;
         beStrictAboutTestsThatDoNotTestAnything=&quot;true&quot;
         beStrictAboutOutputDuringTests=&quot;true&quot;
         beStrictAboutChangesToGlobalState=&quot;true&quot;
         failOnWarning=&quot;true&quot;
         printerClass=&quot;\Drupal\Tests\Listeners\HtmlOutputPrinter&quot;
         cacheResult=&quot;false&quot;
         xsi:noNamespaceSchemaLocation=&quot;https://schema.phpunit.de/9.3/phpunit.xsd&quot;&gt;
  &lt;php&gt;
    &lt;ini name=&quot;error_reporting&quot; value=&quot;32767&quot;/&gt; &lt;!-- 32767 = E_ALL. --&gt;
    &lt;ini name=&quot;memory_limit&quot; value=&quot;-1&quot;/&gt;
    &lt;env name=&quot;SIMPLETEST_BASE_URL&quot; value=&quot;http://my-project.ddev.site&quot;/&gt;
    &lt;env name=&quot;SIMPLETEST_DB&quot; value=&quot;mysql://db:db@db:3306/test&quot;/&gt;
    &lt;env name=&quot;BROWSERTEST_OUTPUT_DIRECTORY&quot; value=&quot;&quot;/&gt;
&lt;!--    &lt;env name=&quot;SYMFONY_DEPRECATIONS_HELPER&quot; value=&quot;weak&quot;/&gt;--&gt;
  &lt;/php&gt;

  &lt;testsuites&gt;
    &lt;testsuite name=&quot;unit&quot;&gt;
      &lt;directory&gt;web/modules/custom/*/tests/src/Unit/&lt;/directory&gt;
    &lt;/testsuite&gt;
    &lt;testsuite name=&quot;kernel&quot;&gt;
      &lt;directory&gt;web/modules/custom/*/tests/src/Kernel/&lt;/directory&gt;
    &lt;/testsuite&gt;
    &lt;testsuite name=&quot;functional&quot;&gt;
      &lt;directory&gt;web/modules/custom/*/tests/src/Functional/&lt;/directory&gt;
    &lt;/testsuite&gt;
    &lt;testsuite name=&quot;functional-javascript&quot;&gt;
      &lt;directory&gt;web/modules/custom/*/tests/src/FunctionalJavascript/&lt;/directory&gt;
    &lt;/testsuite&gt;
  &lt;/testsuites&gt;

  &lt;listeners&gt;
    &lt;listener class=&quot;\Drupal\Tests\Listeners\DrupalListener&quot;&gt;
    &lt;/listener&gt;
    &lt;!-- The Symfony deprecation listener has to come after the Drupal listener --&gt;
    &lt;listener class=&quot;Symfony\Bridge\PhpUnit\SymfonyTestsListener&quot;&gt;
    &lt;/listener&gt;
  &lt;/listeners&gt;

  &lt;coverage&gt;
    &lt;include&gt;
      &lt;directory suffix=&quot;.php&quot;&gt;web/modules/custom/*/src&lt;/directory&gt;
    &lt;/include&gt;
  &lt;/coverage&gt;

&lt;/phpunit&gt;" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #5E81AC">xml</span><span style="color: #8FBCBB"> version</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">1.0</span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB"> encoding</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">UTF-8</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">?&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">&lt;phpunit</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">xmlns</span><span style="color: #ECEFF4">:</span><span style="color: #8FBCBB">xsi</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">http://www.w3.org/2001/XMLSchema-instance</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">bootstrap</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">web/core/tests/bootstrap.php</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">colors</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">true</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">beStrictAboutTestsThatDoNotTestAnything</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">true</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">beStrictAboutOutputDuringTests</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">true</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">beStrictAboutChangesToGlobalState</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">true</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">failOnWarning</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">true</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">printerClass</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">\Drupal\Tests\Listeners\HtmlOutputPrinter</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">cacheResult</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">false</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #8FBCBB">xsi</span><span style="color: #ECEFF4">:</span><span style="color: #8FBCBB">noNamespaceSchemaLocation</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">https://schema.phpunit.de/9.3/phpunit.xsd</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;php&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;ini</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">error_reporting</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">value</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">32767</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">/&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">&lt;!-- 32767 = E_ALL. --&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;ini</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">memory_limit</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">value</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">-1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;env</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SIMPLETEST_BASE_URL</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">value</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">http://my-project.ddev.site</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;env</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SIMPLETEST_DB</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">value</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">mysql://db:db@db:3306/test</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;env</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">BROWSERTEST_OUTPUT_DIRECTORY</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">value</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #616E88">&lt;!--    &lt;env name=&quot;SYMFONY_DEPRECATIONS_HELPER&quot; value=&quot;weak&quot;/&gt;--&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;/php&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;testsuites&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;testsuite</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">unit</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">&lt;directory&gt;</span><span style="color: #D8DEE9FF">web/modules/custom/*/tests/src/Unit/</span><span style="color: #81A1C1">&lt;/directory&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/testsuite&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;testsuite</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">kernel</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">&lt;directory&gt;</span><span style="color: #D8DEE9FF">web/modules/custom/*/tests/src/Kernel/</span><span style="color: #81A1C1">&lt;/directory&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/testsuite&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;testsuite</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">functional</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">&lt;directory&gt;</span><span style="color: #D8DEE9FF">web/modules/custom/*/tests/src/Functional/</span><span style="color: #81A1C1">&lt;/directory&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/testsuite&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;testsuite</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">functional-javascript</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">&lt;directory&gt;</span><span style="color: #D8DEE9FF">web/modules/custom/*/tests/src/FunctionalJavascript/</span><span style="color: #81A1C1">&lt;/directory&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/testsuite&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;/testsuites&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;listeners&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;listener</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">class</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">\Drupal\Tests\Listeners\DrupalListener</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/listener&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">&lt;!-- The Symfony deprecation listener has to come after the Drupal listener --&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;listener</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">class</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Symfony\Bridge\PhpUnit\SymfonyTestsListener</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/listener&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;/listeners&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;coverage&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;include&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">&lt;directory</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">suffix</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">.php</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">web/modules/custom/*/src</span><span style="color: #81A1C1">&lt;/directory&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/include&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;/coverage&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">&lt;/phpunit&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>These are all the customizations that have been made:</strong></p>



<p class="wp-block-paragraph">1. Adjust the boostrap path to core&#8217;s bootstrap</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="bootstrap=&quot;web/core/tests/bootstrap.php&quot;" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">bootstrap=&quot;web/core/tests/bootstrap.php&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">2. Set site URL and database connection details (for Kernel, Functional&#8230; etc. tests). Note that PHPUnit will be executed inside the web container, so we must set &#8220;db&#8221; and port 3306 as our database host, as it is seen from the web container. We also set the testing database name to &#8220;test&#8221;, that is the convention.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;env name=&quot;SIMPLETEST_BASE_URL&quot; value=&quot;https://my-project.ddev.site&quot;/&gt;
&lt;env name=&quot;SIMPLETEST_DB&quot; value=&quot;mysql://db:db@db:3306/test&quot;/&gt;" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">&lt;env</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SIMPLETEST_BASE_URL</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">value</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">https://my-project.ddev.site</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;env</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SIMPLETEST_DB</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">value</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">mysql://db:db@db:3306/test</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">/&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">3. Finally, we adjust all test suites to only execute tests from our custom modules by default, if we execute phpunit without file parameters nor filters.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;testsuite name=&quot;unit&quot;&gt;
  &lt;directory&gt;web/modules/custom/*/tests/src/Unit/&lt;/directory&gt;
&lt;/testsuite&gt;
&lt;testsuite name=&quot;kernel&quot;&gt;
  &lt;directory&gt;web/modules/custom/*/tests/src/Kernel/&lt;/directory&gt;
&lt;/testsuite&gt;
..." style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">&lt;testsuite</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">unit</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;directory&gt;</span><span style="color: #D8DEE9FF">web/modules/custom/*/tests/src/Unit/</span><span style="color: #81A1C1">&lt;/directory&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/testsuite&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;testsuite</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #D8DEE9FF">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">kernel</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;directory&gt;</span><span style="color: #D8DEE9FF">web/modules/custom/*/tests/src/Kernel/</span><span style="color: #81A1C1">&lt;/directory&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/testsuite&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">...</span></span></code></pre></div>



<h2 class="wp-block-heading">Installing PHPUnit</h2>



<p class="wp-block-paragraph">As simple as installing these packages with Composer</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev composer require --dev \
  phpunit/phpunit \
  phpspec/prophecy-phpunit \
  symfony/phpunit-bridge \
  dmore/chrome-mink-driver \
  behat/mink-browserkit-driver \
  behat/mink-selenium2-driver \
  mikey179/vfsstream" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># ddev composer require --dev \</span></span>
<span class="line"><span style="color: #D8DEE9FF">  phpunit/phpunit \</span></span>
<span class="line"><span style="color: #D8DEE9FF">  phpspec/prophecy-phpunit \</span></span>
<span class="line"><span style="color: #D8DEE9FF">  symfony/phpunit-bridge \</span></span>
<span class="line"><span style="color: #D8DEE9FF">  dmore/chrome-mink-driver \</span></span>
<span class="line"><span style="color: #D8DEE9FF">  behat/mink-browserkit-driver \</span></span>
<span class="line"><span style="color: #D8DEE9FF">  behat/mink-selenium2-driver \</span></span>
<span class="line"><span style="color: #D8DEE9FF">  mikey179/vfsstream</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>phpunit/phpunit</strong> &#8211; The PHPUnit core package.</li>



<li><strong>phpspec/prophecy-phpunit</strong> &#8211; The Prophecy mocking system. In a lot of tests it is used instead of PHPUnit Mock Object Framework, the PHPUnit standard.</li>



<li><strong>symfony/phpunit-bridge</strong> &#8211; Bridges the gap between Symfony and PHPUnit.</li>



<li><strong>dmore/chrome-mink-driver</strong> &#8211; Required for some Kernel nad Functional tests.</li>



<li><strong>behat/mink-browserkit-driver</strong> &#8211; Required for some Functional tests.</li>



<li><strong>mikey179/vfsstream</strong> &#8211; Some tests make use of this virtual filesystem mocker.</li>
</ul>



<h2 class="wp-block-heading">Configuring DDEV Selenium Standalone Chrome</h2>



<p class="wp-block-paragraph">This config is necessary to execute Javascript and FunctionalJavascript Drupal tests. PHPUnit needs a headless Chromium browser to control and to send commands to. That&#8217;s what this DDEV addon provides.</p>



<p class="wp-block-paragraph">Follow the instructions in <a href="https://github.com/ddev/ddev-selenium-standalone-chrome">https://github.com/ddev/ddev-selenium-standalone-chrome</a>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev get ddev/ddev-selenium-standalone-chrome" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">get</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ddev</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">ddev</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">selenium</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">standalone</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">chrome</span></span></code></pre></div>



<p class="wp-block-paragraph">Then, update your <code>.ddev/config.selenium-standalone-chrome.yaml</code> and set the local domain for your DDEV project, making sure you set it as <code>http://</code> and not <code>https://</code> (I got an &#8220;invalid cookie domain&#8221; error when I did).</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="#ddev-generated  &lt;- REMOVE THIS LINE

...

- SIMPLETEST_BASE_URL=http://my-project.ddev.site

...

- DRUPAL_TEST_BASE_URL=http://my-project.ddev.site

...

- DTT_BASE_URL=http://my-project.ddev.site" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF">#</span><span style="color: #D8DEE9">ddev</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">generated</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">&lt;-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">REMOVE</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">THIS</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">LINE</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">SIMPLETEST_BASE_URL</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">http</span><span style="color: #ECEFF4">:</span><span style="color: #616E88">//my-project.ddev.site</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">DRUPAL_TEST_BASE_URL</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">http</span><span style="color: #ECEFF4">:</span><span style="color: #616E88">//my-project.ddev.site</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">DTT_BASE_URL</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">http</span><span style="color: #ECEFF4">:</span><span style="color: #616E88">//my-project.ddev.site</span></span></code></pre></div>



<p class="wp-block-paragraph">Then, restart DDEV:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev restart" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">restart</span></span></code></pre></div>



<h2 class="wp-block-heading">Executing tests from the command line</h2>



<p class="wp-block-paragraph">If we want being able to execute our tests easily from outside the web container, we must create the file <code>/.ddev/commands/web/phpunit</code> with this content:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="#!/bin/bash

phpunit &quot;$@&quot;" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">#!/bin/bash</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">phpunit</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$@</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">And now, outside the web container, we can do:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev phpunit
# ddev phpunit --testsuite &lt;name&gt;" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># ddev phpunit</span></span>
<span class="line"><span style="color: #D8DEE9FF"># ddev phpunit --testsuite </span><span style="color: #81A1C1">&lt;name&gt;</span></span></code></pre></div>



<h2 class="wp-block-heading">Executing tests from PHPStorm</h2>



<p class="wp-block-paragraph">From PHPStorm&#8217;s interface, <strong>we can go to any test case class and use the GUI to run its tests</strong>, either all at once or one by one, simply clicking on the run icons next to the class and test method definitions:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="806" height="269" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-16.png" alt="" class="wp-image-2241" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-16.png 806w, https://www.victordorado.es/wp-content/uploads/2024/03/image-16-300x100.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-16-768x256.png 768w" sizes="(max-width: 806px) 100vw, 806px" /></figure>



<p class="wp-block-paragraph"><strong>We can even right click on any directory in the project&#8217;s explorer and click on &#8220;Run (tests PHPUnit)&#8221; and all tests in that directory will be executed.</strong> This is very convenient when we want to debug only a test method or execute only one type of tests inside a module, for example.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="162" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-15-1024x162.png" alt="" class="wp-image-2239" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-15-1024x162.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-15-300x47.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-15-768x121.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-15-1536x243.png 1536w, https://www.victordorado.es/wp-content/uploads/2024/03/image-15.png 1729w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph"><strong>Getting PHPStorm, DDEV, Drupal and PHPUnit to work together is not a trivial task</strong>. Although DDEV&#8217;s PHPStorm plugin eases a bit the task, we have yet to wire all the things correctly.</p>
<p>The post <a href="https://www.victordorado.es/2024/03/18/phpunit-up-and-running-with-drupal-ddev-and-phpstorm/">PHPUnit up and running with Drupal, DDEV and PHPStorm</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Debug your Drupal code with PHPStorm, DDEV and Xdebug</title>
		<link>https://www.victordorado.es/2024/03/17/debug-your-drupal-code-with-phpstorm-ddev-and-xdebug/</link>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Sun, 17 Mar 2024 17:37:16 +0000</pubDate>
				<category><![CDATA[DDEV]]></category>
		<category><![CDATA[Drupal]]></category>
		<category><![CDATA[PHPStorm]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2186</guid>

					<description><![CDATA[<p>Debugging goes next level when it's done with a tool like Xdebug and through a debugging GUI like PHPStorm has.</p>
<p>The post <a href="https://www.victordorado.es/2024/03/17/debug-your-drupal-code-with-phpstorm-ddev-and-xdebug/">Debug your Drupal code with PHPStorm, DDEV and Xdebug</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>How would have been our lives without debuggers?</strong> We would have been spreading <code>print_r</code>, <code>echo</code> and the like everywhere. Our world got better with tools like Krumo or (in the case of Drupal) with <code>dpm()</code>, but nothing like a real [IDE + debugger] couple like [PHPStorm + Xdebug].</p>



<p class="wp-block-paragraph">If you also add DDEV and its docker containers to the mix, it&#8217;s not easy to get all pieces working, without a proper recipe. This article aims to be that recipe.</p>



<h2 class="wp-block-heading">Preparing DDEV&#8217;s web container</h2>



<p class="wp-block-paragraph">DDEV automatically adds two environment variables to its <code>.ddev-docker-compose-base.yaml</code> file:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="PHP_IDE_CONFIG=serverName=${DDEV_SITENAME}.${DDEV_TLD}
DRUSH_ALLOW_XDEBUG=1" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">PHP_IDE_CONFIG</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9">serverName</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9">$</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9">DDEV_SITENAME</span><span style="color: #ECEFF4">}.</span><span style="color: #D8DEE9">$</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9">DDEV_TLD</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9">DRUSH_ALLOW_XDEBUG</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">1</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Mind the serverName because we will need it later</strong>, when configuring a server in PHPStorm.</p>



<p class="wp-block-paragraph">Now, we must config the Xdebug PHP extension. Create a new directory <code>.ddev/php/</code> and create a file called <code>xdebug.ini</code> inside it, with this content.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="xdebug.client_port = 9000
xdebug.idekey = PHPSTORM
xdebug.mode = develop,debug
xdebug.start_with_request = yes" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">xdebug</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">client_port</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">9000</span></span>
<span class="line"><span style="color: #D8DEE9">xdebug</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">idekey</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">PHPSTORM</span></span>
<span class="line"><span style="color: #D8DEE9">xdebug</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">mode</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">develop</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9">debug</span></span>
<span class="line"><span style="color: #D8DEE9">xdebug</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">start_with_request</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">yes</span></span></code></pre></div>



<p class="wp-block-paragraph">I like to set <code>xdebug.start_with_request=yes</code> and turn on and off the web contaniner&#8217;s xdebug PHP extension with <code>ddev xdebug on</code> and <code>ddev xdebug off</code>. You could set <code>xdebug.start_with_request=no</code> and play with a browser switch like <a href="https://chromewebstore.google.com/detail/xdebug-helper/eadndfjplgieldjbigjakmdgkmoaaaoc" data-type="link" data-id="https://chromewebstore.google.com/detail/xdebug-helper/eadndfjplgieldjbigjakmdgkmoaaaoc" target="_blank" rel="noreferrer noopener">Xdebug Helper</a>, but this won&#8217;t have effect on drush commands.</p>



<p class="wp-block-paragraph">After that, we have to restart DDEV&#8217;s web container:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev restart" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">restart</span></span></code></pre></div>



<h2 class="wp-block-heading">Configuring PHPStorm</h2>



<p class="wp-block-paragraph">Now, we must create a new PHP server under Settings -&gt; PHP -&gt; Servers. Recall the server name we saw in DDEV&#8217;s config: it was <code>${DDEV_SITENAME}.${DDEV_TLD}</code>, that translates to <code>my-project.ddev.site</code> if your DDEV project is named <code>my-project</code>.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="740" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-1024x740.png" alt="" class="wp-image-2187" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-1024x740.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-300x217.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-768x555.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image.png 1037w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">We need to set path mappings for the webserver docroot directory. A path mapping &#8220;maps&#8221; a real directory path in your filesystem with a directory inside the DDEV docker web container. In our case, <strong>a DDEV drupal project has its web server&#8217;s document root in <code>/var/www/html/web</code>, so we have to set this mapping</strong>:</p>



<ul class="wp-block-list">
<li><code>/path/to/your/project<strong>/web</strong> -&gt; /var/www/html<strong>/web</strong></code></li>
</ul>



<p class="wp-block-paragraph"><strong>However, I recommend also adding this mapping </strong>to be able to debug vendor code.</p>



<ul class="wp-block-list">
<li><code>/path/to/your/project<strong>/vendor</strong> -&gt; /var/www/html<strong>/vendor</strong></code></li>
</ul>



<p class="wp-block-paragraph">With these mappings, PHPStorm now knows how to map between the same file as it&#8217;s seen inside/outside DDEV&#8217;s web container.</p>



<p class="wp-block-paragraph">After that, to be sure everything is ok,<strong> I recommend checking and configuring the Xdebug installation</strong> under PHP -&gt; Debug:</p>



<p class="wp-block-paragraph">First, click on the &#8220;Validate&#8221; link:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="916" height="279" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-1.png" alt="" class="wp-image-2188" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-1.png 916w, https://www.victordorado.es/wp-content/uploads/2024/03/image-1-300x91.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-1-768x234.png 768w" sizes="(max-width: 916px) 100vw, 916px" /></figure>



<p class="wp-block-paragraph">In the next screen configure the docroot directory as it&#8217;s seen by PHPStorm (your real machine path to the web directory inside your project directory) and the URL where the project&#8217;s docroot will be accesible. Then, click on &#8220;Validate&#8221;.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="861" height="690" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-2.png" alt="" class="wp-image-2189" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-2.png 861w, https://www.victordorado.es/wp-content/uploads/2024/03/image-2-300x240.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-2-768x615.png 768w" sizes="(max-width: 861px) 100vw, 861px" /></figure>



<p class="wp-block-paragraph"><strong>I strongly recommend unchecking these two &#8220;Force break&#8221; checkboxes</strong>, because we don&#8217;t want the execution to be paused if we don&#8217;t set or don&#8217;t reach a breakpoint. I remember myself waiting a drush command to finish unaware that the execution was paused, because of this.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="908" height="744" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-6.png" alt="" class="wp-image-2193" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-6.png 908w, https://www.victordorado.es/wp-content/uploads/2024/03/image-6-300x246.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-6-768x629.png 768w" sizes="(max-width: 908px) 100vw, 908px" /></figure>



<p class="wp-block-paragraph">So far, we have configured DDEV and PHPStorm to &#8220;connect&#8221; between them and our next step will be to set a breakpoint. For example, we can break on the <code>handle()</code> call that <code>Drupal\Core\DrupalKernel</code> class does on every page load.</p>



<p class="wp-block-paragraph">Click next to the line number and a red dot will appear. This is our breakpoint. The PHP execution will stop here until we let it continue.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="437" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-4-1024x437.png" alt="" class="wp-image-2191" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-4-1024x437.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-4-300x128.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-4-768x328.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-4.png 1404w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>Remember to enable xdebug PHP extension on DDEV&#8217;s </strong>web container with</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="# ddev xdebug on" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9FF"># </span><span style="color: #D8DEE9">ddev</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">xdebug</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">on</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>And to tell PHPStorm to listen</strong> to incoming debug connections with the <img decoding="async" style="width:21px;height:auto;" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-8.png" alt="Listen icon"> icon.</p>



<p class="wp-block-paragraph"><strong>Now, reload any page</strong> in your browser and you will view a system notification letting you know that PHPStorm requires your attention. Go for it and you will see the breakpoint&#8217;s line highlighted and <strong>a panel where you can explore all the variables in it&#8217;s scope.</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="470" src="https://www.victordorado.es/wp-content/uploads/2024/03/image-5-1024x470.png" alt="" class="wp-image-2192" srcset="https://www.victordorado.es/wp-content/uploads/2024/03/image-5-1024x470.png 1024w, https://www.victordorado.es/wp-content/uploads/2024/03/image-5-300x138.png 300w, https://www.victordorado.es/wp-content/uploads/2024/03/image-5-768x352.png 768w, https://www.victordorado.es/wp-content/uploads/2024/03/image-5.png 1426w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>You can browse the variables panel and deep dive into the tree structures. This is VERY useful</strong>, specially when debugging a framework like Drupal that has many nesting levels on its variables.</p>



<p class="wp-block-paragraph"><strong>You can also select any piece of code, right click on it and click on &#8220;Evaluate expression&#8221;</strong> and you could test it inside that paused execution state. You can even write directly your own expressions.</p>



<p class="wp-block-paragraph">You can continue the execution step-by-step (step over, F8), even traveling inside method calls (step into, F7), or continue the execution until the next breakpoint (or the same breakpoint if it&#8217;s reached again)</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Debugging goes next level when it&#8217;s done with a tool like Xdebug and through a debugging GUI like PHPStorm has.</p>
<p>The post <a href="https://www.victordorado.es/2024/03/17/debug-your-drupal-code-with-phpstorm-ddev-and-xdebug/">Debug your Drupal code with PHPStorm, DDEV and Xdebug</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Query objects, the brand new form of getting your typed entities</title>
		<link>https://www.victordorado.es/2024/03/14/query-objects-the-brand-new-form-of-getting-your-typed-entities/</link>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Thu, 14 Mar 2024 16:37:55 +0000</pubDate>
				<category><![CDATA[Drupal]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2091</guid>

					<description><![CDATA[<p>The use of query objects is a great advance in clarity and expressiveness of the code. Read the details.</p>
<p>The post <a href="https://www.victordorado.es/2024/03/14/query-objects-the-brand-new-form-of-getting-your-typed-entities/">Query objects, the brand new form of getting your typed entities</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>I&#8217;ve been working for a while with wrapped entities</strong>, following the principles of this Lullabot article: <a href="https://www.lullabot.com/articles/maintainable-code-drupal-wrapped-entities" target="_blank" rel="noreferrer noopener">Maintainable Code in Drupal: Wrapped Entities</a>, but using a custom solution and not the Typed Entity module.</p>



<p class="wp-block-paragraph">I have to say that, now that the project has evolved and I can have an overview of it, <strong>I think we could not have implemented all the complexity of the business logic without this design pattern</strong>. In my opinion, it is a fantastic way to organize the code, although it has some problems that must be taken into account.</p>



<h2 class="wp-block-heading">Why wrapped entities and not bundle classes?</h2>



<p class="wp-block-paragraph"><a href="https://mateuaguilo.com/" data-type="link" data-id="https://mateuaguilo.com/" target="_blank" rel="noreferrer noopener">Mateu Aguiló</a> explains it perfectly in the Typed Entity module page: <strong>Bundle classes may seem attractive at first, but they have disadvantages</strong>: they carry on all the complexity of <code>EntityInterface</code>, making it hard to do tests with them.</p>



<p class="wp-block-paragraph">However, <strong>wrapped entities use the <a href="https://en.wikipedia.org/wiki/Facade_pattern" data-type="link" data-id="https://en.wikipedia.org/wiki/Facade_pattern" target="_blank" rel="noreferrer noopener">facade pattern</a>, which hides all the complexity</strong> of EntityInterface, leaving a clean object that is much easier to test and mock.</p>



<h2 class="wp-block-heading">Repositories? Active Record pattern?</h2>



<p class="wp-block-paragraph"><strong>It is very convenient to use the Active Record pattern</strong> this way:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="$owner = $book-&gt;owner();" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">owner</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">book</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">owner</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<p class="wp-block-paragraph">But, if not implemented well, <strong>this pattern has its drawbacks in terms of testing the code</strong>. Fortunately, there are some solutions, and one of them is to pass as a dependency the repositories that the entity will use to get the related entities. This has a drawback: <strong>if the Book entity is related with many other entity types, then we will have a lot of dependencies</strong> (one for each of the repositories that allow obtaining those entities)</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="$bookRepository = \Drupal::service('model.book.repository');
$authorRepository = \Drupal::service('model.author.repository');
$libraryRepository =\Drupal::service('model.library.repository');

$book = new Book(
  $bookRepository,
  $authorRepository,
  $libraryRepository
);

$author = $book-&gt;author();
$library = $book-&gt;library();" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">bookRepository</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Drupal</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">service</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">model.book.repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">authorRepository</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Drupal</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">service</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">model.author.repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">libraryRepository</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Drupal</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">service</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">model.library.repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">book</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Book</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">bookRepository</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">authorRepository</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">libraryRepository</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">author</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">book</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">author</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">library</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">book</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">library</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<p class="has-medium-font-size wp-block-paragraph"><strong>Typed Entity module solves this problem using a &#8220;Repository Manager&#8221;</strong>, which becomes a single dependency and is in charge of wrapping an entity with its wrapped entity facade object.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="class WrappedEntityBase {
  ...
  
  public function wrapReference(string $field_name): ?WrappedEntityInterface {
    $target_entity = $this-&gt;getEntity()-&gt;{$field_name}-&gt;entity;
    return $this-&gt;repositoryManager()-&gt;wrap($target_entity);
  }
  
  public function owner(): ?WrappedEntityInterface {
    $owner_key = $this-&gt;getEntity()-&gt;getEntityType()-&gt;getKey('owner');
    if (!$owner_key) {
      return NULL;
    }
    return $this-&gt;wrapReference($owner_key);
  }
  
  ...
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">WrappedEntityBase</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">...</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">wrapReference</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">field_name</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">WrappedEntityInterface</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">target_entity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getEntity</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;{$</span><span style="color: #D8DEE9">field_name</span><span style="color: #81A1C1">}-&gt;</span><span style="color: #D8DEE9">entity</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">repositoryManager</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">wrap</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">target_entity</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">owner</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">WrappedEntityInterface</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">owner_key</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getEntity</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getEntityType</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getKey</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">owner</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!$</span><span style="color: #D8DEE9">owner_key</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">NULL;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">wrapReference</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">owner_key</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">...</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">We can see an example of this in the <code>WrappedEntityBase</code> base class, that has an <code>owner()</code> method that makes use of the repository manager to obtain the entity. This, in turn, uses a <code>wrapReference()</code> utility method that wraps an entity related through any <code>EntityReferenceField</code>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="has-palette-color-3-color has-palette-color-6-background-color has-text-color has-background has-link-color has-medium-font-size wp-elements-1 wp-block-paragraph"><strong>Note:</strong> At the time of writing, in the Typed Entity module, the repository manager is not an injected dependency and is being obtained through a .module function. I think it could be because most of the module tests are Kernel tests, and this way things are simpler.</p>
</blockquote>



<p class="wp-block-paragraph">Although the <code>owner()</code> function example is a bit confusing (because it uses the <code>EntityType</code> definition element to get the field name), <strong>in our custom entities we can do something as simple as this:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="class Book extends WrappedEntityInterface {
  
  public const FIELD_LIBRARY = 'field_library'
  
  public function library(): ?WrappedEntityInterface {
    return $this-&gt;wrapReference($self::FIELD_LIBRARY);
  }

}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Book</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">WrappedEntityInterface</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> FIELD_LIBRARY </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">field_library</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">library</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">WrappedEntityInterface</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">wrapReference</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">self</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">FIELD_LIBRARY</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">Advanced use of repositories and Query Objects</h2>



<p class="wp-block-paragraph">If we want to get our wrapped entities from our repositories using a query analogous to how we would do it with the Entity Query API, <strong>we start to see an old known repositories problem: the repository interface starts to be cluttered with many findBySomething() methods.</strong></p>



<p class="wp-block-paragraph">To avoid this, in my &#8220;custom adventure&#8221; (without using Typed Entity) <strong>I was able to implement the Query Object pattern, so that a &#8220;query object&#8221; is prepared and then passed as a parameter to a repository.</strong> This is a much smoother way of working, as it can be seen in the following example:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// Given this library wrapped entity (it does not matter now how it's created).
$library = ...

// And a featured book.
$featuredBook = ...

// We can get all the other books from the library
$otherBooks = $this-&gt;bookRepository-&gt;get(
  BookQuery::new()
    -&gt;relatedWith($library)
    -&gt;notEquals($featuredBook)
    -&gt;sort('DESC', Book::FIELD_NAME_DATE)
    -&gt;range(0, 10)
);" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// Given this library wrapped entity (it does not matter now how it&#39;s created).</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">library</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// And a featured book.</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">featuredBook</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// We can get all the other books from the library</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">otherBooks</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">bookRepository</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">BookQuery</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">new</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">relatedWith</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">library</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">notEquals</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">featuredBook</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">sort</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DESC</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Book</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">FIELD_NAME_DATE</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">range</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<p class="wp-block-paragraph">The <code>QueryBase</code> class (from which <code>BookQuery</code> extends) has several utility methods that can be used to communicate very expressively with repositories, giving them precise selection, sorting and paging instructions.</p>



<p class="wp-block-paragraph">Here are some examples:</p>



<p class="wp-block-paragraph"><strong>Identity:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="idIn(array $entityIds)
idNotIn(array $entityIds)" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">idIn</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">entityIds</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #88C0D0">idNotIn</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">entityIds</span><span style="color: #ECEFF4">)</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Relations</strong>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="relatedWith(WrappedEntity $entity)" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">relatedWith</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">WrappedEntity </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">entity</span><span style="color: #ECEFF4">)</span></span></code></pre></div>



<p class="wp-block-paragraph">Perhaps this method is the most curious, since <strong>it uses the entity&#8217;s field definitions to find automatitically the entity reference fields that relate to the entities</strong> of the concrete WrappedEntity child passed as parameter.</p>



<p class="wp-block-paragraph"><strong>Dates:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="current()
past()
future()
fromDate(DrupalDateTime $date)
toDate(DrupalDateTime $date)
betweenDates(DrupalDateTime $from, DrupalDateTime $to)" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">current</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #88C0D0">past</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #88C0D0">future</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #88C0D0">fromDate</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">DrupalDateTime </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">date</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #88C0D0">toDate</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">DrupalDateTime </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">date</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #88C0D0">betweenDates</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">DrupalDateTime </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">from</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> DrupalDateTime </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">to</span><span style="color: #ECEFF4">)</span></span></code></pre></div>



<p class="wp-block-paragraph">(<code>current(), past() and future()</code> are possible by using a unique date range field for all entities):</p>



<p class="wp-block-paragraph"><strong>Result control:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="sort(string $direction = 'ASC', ?string $fieldName = NULL)
range(?int $start = NULL, ?int $length = NULL)" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">sort</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">direction</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">ASC</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">?string</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fieldName</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">NULL</span><span style="color: #88C0D0">)</span></span>
<span class="line"><span style="color: #88C0D0">range</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">?int</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">start</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">NULL</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">?int</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">length</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">NULL</span><span style="color: #88C0D0">)</span></span></code></pre></div>



<p class="wp-block-paragraph">We aren&#8217;t limited to these base query object utility methods<strong>. We can extend the BaseQueryObject with a BookQueryObject that could have custom entity specific conditions</strong>.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="$bestSellers = $this-&gt;bookRepository-&gt;get(
  BookQuery::new()
    -&gt;purchasedAtLeastNtimes(100)
);" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">bestSellers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">bookRepository</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">BookQuery</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">new</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">purchasedAtLeastNtimes</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">100</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">As you can see, <strong>the use of query objects is a great advance in clarity and expressiveness of the code</strong>, although it does not solve all cases. For those special cases, we can always continue using a getBySomething() in the repository.</p>



<div class="wp-block-buttons has-custom-font-size has-medium-font-size is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-c41db87e wp-block-buttons-is-layout-flex"></div>



<div class="wp-block-stackable-button-group stk-block-button-group stk-block stk-242c9c9" data-block-id="242c9c9"><div class="stk-row stk-inner-blocks has-text-align-center stk-block-content stk-button-group">
<div class="wp-block-stackable-button stk-block-button has-text-align-center stk-block stk-e19e1de" data-block-id="e19e1de"><style>.stk-e19e1de{min-height:17px !important;align-items:center !important;padding-top:0px !important;padding-right:0px !important;padding-bottom:0px !important;padding-left:0px !important;margin-top:0px !important;display:flex !important}.stk-e19e1de .stk-button{padding-top:11px !important;padding-right:34px !important;padding-bottom:11px !important;padding-left:34px !important}.stk-e19e1de .stk-button .stk--inner-svg svg:last-child{height:43px !important;width:43px !important}</style><a class="stk-link stk-button stk--hover-effect-darken" href="/wp-content/uploads/2024/03/wrapped-entity-repositories-and-query-objects-example.zip" target="_blank" rel="noreferrer noopener"><span class="stk--svg-wrapper"><div class="stk--inner-svg"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" aria-hidden="true" width="32" height="32"><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM96 48c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm0 64c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm0 64c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm-6.3 71.8c3.7-14 16.4-23.8 30.9-23.8h14.8c14.5 0 27.2 9.7 30.9 23.8l23.5 88.2c1.4 5.4 2.1 10.9 2.1 16.4c0 35.2-28.8 63.7-64 63.7s-64-28.5-64-63.7c0-5.5 .7-11.1 2.1-16.4l23.5-88.2zM112 336c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H112z"></path></svg></div></span><span class="stk-button__inner-text">Download sample code<br>(zip file &#8211; 6.7kB)</span></a></div>
</div></div>
<p>The post <a href="https://www.victordorado.es/2024/03/14/query-objects-the-brand-new-form-of-getting-your-typed-entities/">Query objects, the brand new form of getting your typed entities</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
