<?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>Víctor Dorado</title>
	<atom:link href="https://www.victordorado.es/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.victordorado.es/</link>
	<description></description>
	<lastBuildDate>Tue, 05 May 2026 07:43:07 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>Advanced Motorhome Truma Control Panel: A Custom ESP32 CYD Journey</title>
		<link>https://www.victordorado.es/2026/05/05/advanced-motorhome-truma-control-panel-a-custom-esp32-cyd-journey/</link>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Mon, 04 May 2026 22:59:02 +0000</pubDate>
				<category><![CDATA[Electronics]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2406</guid>

					<description><![CDATA[<p>If you own a motorhome with a Truma Combi heater, you’ve probably shared my frustration: You&#8217;d like to turn on your boiler 20 minutes before you come back to the van, so you can take a shower immediately, but the standard CPPlus controller doesn&#8217;t allow internet connectivity out of the box. Sure, you can buy [&#8230;]</p>
<p>The post <a href="https://www.victordorado.es/2026/05/05/advanced-motorhome-truma-control-panel-a-custom-esp32-cyd-journey/">Advanced Motorhome Truma Control Panel: A Custom ESP32 CYD Journey</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>If you own a motorhome with a Truma Combi heater, you’ve probably shared my frustration: <strong>You&#8217;d like to turn on your boiler 20 minutes before you come back to the van, so you can take a shower immediately</strong>, but the standard CPPlus controller doesn&#8217;t allow internet connectivity out of the box. Sure, you can buy an iNet Box, but it requires a yearly subscription and—here is the catch—your CPPlus unit must be &#8220;iNet Ready.&#8221; Mine wasn&#8217;t.</p>



<p><strong>Instead of paying for a subscription and upgrading my hardware to a closed ecosystem,</strong> <strong>I decided to build my own solution</strong>. Inspired by the <a href="https://github.com/olivluca/TruMinus">olivluca/truminus</a> and <a href="https://github.com/olivluca/TrumaDisplay">olivluca/TrumaDisplay</a> projects, my original plan was to build an iNet Box simulator that would sit in parallel on the Truma TIN bus. Even though my CPPlus didn&#8217;t say &#8220;iNet Ready,&#8221; it was a brand-new 2025 heater, so I gambled on it being compatible. Spoiler alert: <strong>I was wrong.</strong></p>



<p><strong>I&#8217;ve developed the core of the project using Claude Code and Kimi K2.6</strong>. It’s been an incredible experience <strong>acting as the orchestrator</strong>, focusing on the <strong>UX and QA</strong>, while letting the AI handle the heavy lifting of implementation details.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Phase 1: The &#8220;iNet&#8221; Gamble</h3>



<p>My first approach was conservative. I didn&#8217;t want to remove the original CPPlus, so I planned to build a &#8220;Man-in-the-Middle&#8221; device. Using an <strong>ESP32 C3 Supermini</strong>, I tried to emulate an iNet Box to sit alongside the original controller on the <strong>TIN-Bus</strong>.</p>



<p>I had high hopes. My heater was a brand-new 2025 model, and I assumed Truma would have made it compatible by default. I spent time trial-and-error-coding the emulation layer, but the heater simply wouldn&#8217;t recognize the device. The lesson? If the screen doesn&#8217;t say &#8220;iNet Ready,&#8221; it really isn&#8217;t.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="576" src="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260422_170036702-1024x576.jpg" alt="The first prototype with the ESP32 C3 and wires everywhere" class="wp-image-2414" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260422_170036702-1024x576.jpg 1024w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260422_170036702-300x169.jpg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260422_170036702-768x432.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260422_170036702-1536x864.jpg 1536w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260422_170036702.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>The first prototype with the ESP32 C3 and wires everywhere</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Phase 2: Pivoting to the CYD (Cheap Yellow Display)</h3>



<p>Since I couldn&#8217;t &#8220;talk&#8221; to the CPPlus, I decided to <strong>replace</strong> the CPPlus. I moved the project to the <strong>ESP32-2432S028</strong>, popularly known as the <strong>CYD (Cheap Yellow Display)</strong>. This gave me a 2.8-inch touchscreen and some GPIOs.</p>



<p>To make it fit the motorhome&#8217;s aesthetic, I designed a custom PCB the exact same size as the screen. This &#8220;sandwich&#8221; design meant the unit would be deep but have a small footprint on the wall, making it much easier to mount.</p>



<p><strong>The Software Grind:</strong> I spent hours at my desk iterating on the UI (AI doesn&#8217;t hit the bullseye with C++ and embedded hardware as quick as with other more common technologies like PHP, Javascript&#8230; etc.</p>



<p>I wanted a professional feel, with intuitive menus and a built-in web server. The biggest headache was the ESP32&#8217;s memory. Between the graphic library (LVGL/TFT_eSPI), the Wi-Fi stack, and the web server, I kept hitting &#8220;Out of Memory&#8221; crashes. It took many some nights of code optimization and AI-assisted debugging to get the loop running smoothly.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="576" src="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_111309671-1024x576.jpg" alt="" class="wp-image-2415" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_111309671-1024x576.jpg 1024w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_111309671-300x169.jpg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_111309671-768x432.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_111309671-1536x864.jpg 1536w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_111309671.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>The CYD display showing the main menu</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Phase 3: Field Testing &amp; Reverse Engineering</h3>



<p>Once the UI was stable, it was time to move from the desk to the van. This is where things got real. I used <strong>WomoLIN</strong> logs as a baseline, but since my heater was so new, the commands didn&#8217;t match perfectly, so I had to perform <strong>Live Sniffing</strong>. I connected the CYD in parallel with the original Truma panel and watched the hex codes fly by.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="576" src="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_175439038-1024x576.jpg" alt="" class="wp-image-2416" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_175439038-1024x576.jpg 1024w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_175439038-300x169.jpg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_175439038-768x432.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_175439038-1536x864.jpg 1536w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260423_175439038.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>Working inside the van, wires connected to the Truma unit</strong></p>



<p><strong>The &#8220;Cold Sweat&#8221; Moment:</strong> During one of these tests, the heater suddenly locked up. A red light started flashing: <strong>Error E545</strong>. I searched every manual—nothing. The heater was unresponsive. I had to literally dismantle the furniture to reach the Combi unit&#8217;s core. After a few tense minutes of panic, I found the physical reset button. A long press later, the &#8220;clac-clac&#8221; of the diesel pump returned. I’ve never been so happy to hear a clicking noise in my life.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260427_151926896-1024x576.jpg" alt="" class="wp-image-2428" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260427_151926896-1024x576.jpg 1024w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260427_151926896-300x169.jpg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260427_151926896-768x432.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260427_151926896-1536x864.jpg 1536w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260427_151926896.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>The Truma unit</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Phase 4: Adding the &#8220;Pro&#8221; Features (Victron &amp; Ultimatron)</h3>



<p>Since I had a powerful screen, I figured: why stop at the heater? I wanted a &#8220;Command Center.&#8221;</p>



<ol start="1" class="wp-block-list">
<li><strong>Victron SmartSolar MPPT:</strong> I connected to it via bluetooth based on <a href="https://github.com/chrisj7903/Read-Victron-advertised-data">chrisj7903/Read-Victron-advertised-data</a> project. I had to fix some decryption issues because my newer MPPT used a slightly different frame format than the open-source libraries I found.</li>



<li><strong>Ultimatron LiFePo4:</strong> I integrated the battery status via Bluetooth/Serial based on the <a href="https://github.com/sergkh/node-ultimatron-battery">sergkh/node-ultimatron-battery</a> project.</li>
</ol>



<p>Now, in one single glance, I could see my room temperature, manage my heater and boiler, and check my solar harvest and my battery percentage.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://www.victordorado.es/wp-content/uploads/2026/05/WhatsApp-Image-2026-05-01-at-20.43.13-1024x576.jpeg" alt="" class="wp-image-2417" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/WhatsApp-Image-2026-05-01-at-20.43.13-1024x576.jpeg 1024w, https://www.victordorado.es/wp-content/uploads/2026/05/WhatsApp-Image-2026-05-01-at-20.43.13-300x169.jpeg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/WhatsApp-Image-2026-05-01-at-20.43.13-768x432.jpeg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/WhatsApp-Image-2026-05-01-at-20.43.13-1536x864.jpeg 1536w, https://www.victordorado.es/wp-content/uploads/2026/05/WhatsApp-Image-2026-05-01-at-20.43.13.jpeg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>Screen showing the Solar and Battery stats</strong></p>



<p>I also dedicated significant time to fine-tuning the web server to mirror the physical display&#8217;s state. By implementing a <strong>real-time WebSocket (WS) connection</strong>, I ensured that any change on the web interface is instantly reflected on the screen and vice versa, creating a seamless, <strong>zero-latency synchronization</strong></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="959" height="451" src="https://www.victordorado.es/wp-content/uploads/2026/05/image.png" alt="" class="wp-image-2429" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/image.png 959w, https://www.victordorado.es/wp-content/uploads/2026/05/image-300x141.png 300w, https://www.victordorado.es/wp-content/uploads/2026/05/image-768x361.png 768w" sizes="(max-width: 959px) 100vw, 959px" /></figure>



<p><strong>Landscape view of the web app</strong></p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="437" height="756" src="https://www.victordorado.es/wp-content/uploads/2026/05/image-1.png" alt="" class="wp-image-2430" style="width:347px;height:auto" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/image-1.png 437w, https://www.victordorado.es/wp-content/uploads/2026/05/image-1-173x300.png 173w" sizes="(max-width: 437px) 100vw, 437px" /></figure>



<p><strong>Portrait view of the web app</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Phase 5: 3D Case Design &amp; Installation</h3>



<p>The final hurdle was physical. In my van, the auxiliary drop-down bed partially covers the original control area. I had to mount the new panel as low as possible so I could still see the temperature while lying in bed.</p>



<p>I used <strong>Shapr3D</strong> to design a bespoke enclosure. I wanted it to look factory-spec, so I added a recessed Truma logo. A friend printed it for me on a multi-color Bambu Lab printer, and the result was incredible. To hide the old screw holes in the wood, I used a matching wood-effect vinyl—the integration is seamless.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1022" height="750" src="https://www.victordorado.es/wp-content/uploads/2026/05/image.jpg" alt="" class="wp-image-2419" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/image.jpg 1022w, https://www.victordorado.es/wp-content/uploads/2026/05/image-300x220.jpg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/image-768x564.jpg 768w" sizes="(max-width: 1022px) 100vw, 1022px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="655" src="https://www.victordorado.es/wp-content/uploads/2026/05/image-1-1024x655.jpg" alt="" class="wp-image-2421" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/image-1-1024x655.jpg 1024w, https://www.victordorado.es/wp-content/uploads/2026/05/image-1-300x192.jpg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/image-1-768x491.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/image-1.jpg 1080w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="995" height="1024" src="https://www.victordorado.es/wp-content/uploads/2026/05/image-2-995x1024.jpg" alt="" class="wp-image-2422" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/image-2-995x1024.jpg 995w, https://www.victordorado.es/wp-content/uploads/2026/05/image-2-291x300.jpg 291w, https://www.victordorado.es/wp-content/uploads/2026/05/image-2-768x791.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/image-2.jpg 1080w" sizes="(max-width: 995px) 100vw, 995px" /></figure>



<h3 class="wp-block-heading">Wood Machining &amp; Panel Adaptation</h3>



<p>The physical integration was a delicate part of the project. In my motorhome, the auxiliary drop-down bed partially blocks the original control area when lowered. I had to study the dimensions carefully to reposition the new system as low as possible without losing visibility or functionality.</p>



<p>After taking precision measurements, I proceeded to machine the original wooden panel. This required immense patience; it was crucial not to splinter the plywood while creating the exact cutouts for the CYD screen and the internal electronics. Ensuring everything was aligned and firm, while taking advantage of the empty space behind the panel, was a major milestone</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_145547150-1024x576.jpg" alt="" class="wp-image-2426" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_145547150-1024x576.jpg 1024w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_145547150-300x169.jpg 300w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_145547150-768x432.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_145547150-1536x864.jpg 1536w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_145547150.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_172450393-576x1024.jpg" alt="" class="wp-image-2427" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_172450393-576x1024.jpg 576w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_172450393-169x300.jpg 169w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_172450393-768x1365.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_172450393-864x1536.jpg 864w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260430_172450393.jpg 1080w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260501_135144471-576x1024.jpg" alt="" class="wp-image-2425" srcset="https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260501_135144471-576x1024.jpg 576w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260501_135144471-169x300.jpg 169w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260501_135144471-768x1365.jpg 768w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260501_135144471-864x1536.jpg 864w, https://www.victordorado.es/wp-content/uploads/2026/05/PXL_20260501_135144471.jpg 1080w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>
</div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



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



<p><strong>Developing this controller was a significant technical challenge</strong>. Reversing the proprietary TIN-bus protocol and resolving critical errors, such as the E545, required nerves of steel and extensive debugging.</p>



<p>The result is a system that outperforms the original hardware in responsiveness and telemetry. It now offers global remote access and a data granularity that exceeds the official iNet Box&#8217;s capabilities. Future iterations may include water level monitoring, but the current focus is the system&#8217;s deployment in a live environment.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
</blockquote>
<p>The post <a href="https://www.victordorado.es/2026/05/05/advanced-motorhome-truma-control-panel-a-custom-esp32-cyd-journey/">Advanced Motorhome Truma Control Panel: A Custom ESP32 CYD Journey</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Supercharging RAG Information Retrieval with N8N and LightRAG</title>
		<link>https://www.victordorado.es/2025/08/25/supercharging-rag-information-retrieval-with-n8n-and-lightrag/</link>
					<comments>https://www.victordorado.es/2025/08/25/supercharging-rag-information-retrieval-with-n8n-and-lightrag/#respond</comments>
		
		<dc:creator><![CDATA[vidorado]]></dc:creator>
		<pubDate>Mon, 25 Aug 2025 09:35:17 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[N8N]]></category>
		<category><![CDATA[RAG]]></category>
		<guid isPermaLink="false">https://www.victordorado.es/?p=2372</guid>

					<description><![CDATA[<p>LightRAG takes your AI to the next level: it merges the power of LLMs with semantic graphs and structured data to generate accurate, contextual, and trustworthy answers.</p>
<p>The post <a href="https://www.victordorado.es/2025/08/25/supercharging-rag-information-retrieval-with-n8n-and-lightrag/">Supercharging RAG Information Retrieval with N8N and LightRAG</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Introduction</h2>



<p>Large Language Models (LLMs) have proven remarkably effective at handling general-purpose queries, but <strong>they struggle when precision, domain-specificity, or continuously evolving knowledge are required</strong>. Out-of-the-box, their responses are often too shallow or prone to hallucination, which makes them unsuitable for production-grade information retrieval tasks.</p>



<p><strong>Retrieval-Augmented Generation (RAG) was introduced</strong> as a solution to this limitation, bridging LLMs with external knowledge bases. Yet, most “naive” RAG implementations remain constrained: <strong>they typically rely on basic vector similarity search</strong> and flat document retrieval, lacking deeper semantic reasoning, graph-based relationships, or robust orchestration capabilities. As a result, <strong>these pipelines often fail to deliver the context-rich, reliable answers</strong> demanded by real-world applications in domains such as legal or healthcare.</p>



<p>This is where <strong>LightRAG</strong> comes into play. LightRAG enriches retrieval by <strong>leveraging structured knowledge representations and semantic graphs</strong>, enabling more accurate and context-aware responses.</p>



<p>However, <strong>LightRAG is a single-query tool, and does not support chat history</strong> or other components within workflows. N8N, in turn, provides a powerful workflow automation layer, making it possible to seamlessly integrate enhanced RAG pipelines into operational systems and complex data flows. This is achieved through <strong>AI agents</strong>, which can leverage LightRAG alongside other tools to deliver more tailored responses and actions.</p>



<p>In this post, I’ll walk through <strong>my experience combining LightRAG with N8N</strong> to move beyond naive RAG and build a more robust, production-ready approach to information retrieval.</p>



<h2 class="wp-block-heading">How LightRAG works</h2>



<p>First, a RAG system must process documents. Instead of retrieving isolated text chunks from them, like standard RAG solutions, <strong>LightRAG also constructs a</strong> <strong>knowledge graph</strong> where entities, concepts, and their relationships are explicitly represented. This allows the system to <strong>capture semantic connections that naive RAG pipelines typically miss.</strong></p>



<figure class="wp-block-image size-large" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><img loading="lazy" decoding="async" width="1024" height="324" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-2-1024x324.png" alt="" class="wp-image-2377" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-2-1024x324.png 1024w, https://www.victordorado.es/wp-content/uploads/2025/08/image-2-300x95.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-2-768x243.png 768w, https://www.victordorado.es/wp-content/uploads/2025/08/image-2.png 1190w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>At retrieval time, when a query is received, it pulls the most similar passages like standard RAG solutions do, but <strong style="font-weight: bold;">LightRAG also performs a graph-based retrieval process</strong> <strong>to identify the most relevant nodes and relationships</strong>.</p>



<p>Finally, <strong>the retrieved graph substructure and the most relevant text chunks are fed into the LLM</strong>, which can leverage<strong> both the textual evidence and the relational context</strong>. The result is a response that is not only <strong>grounded in the source documents</strong> but also <strong>informed by semantic structure</strong> and contextual relevance, enabling higher-quality answers for complex, domain-specific use cases.</p>



<div class="wp-block-stackable-heading stk-block-heading stk-block-heading--v2 stk-block stk-021b19e" id="thats-awesome-now-show-me-the-code" data-block-id="021b19e"><h2 class="stk-block-heading__text">That&#8217;s awesome, now show me the code!</h2></div>



<p>We will set up our workbench with <a href="https://docs.docker.com/compose/">Docker Compose</a>, a tool for defining and running multi-container applications. Docker Compose uses a a <code>docker-compose.yml</code> file that specifies all the configuration required by the containers.</p>



<p>First, we define <strong>the PostgreSQL service</strong>—a database server with basic vector storage capabilities. The Docker Hub image <a href="https://hub.docker.com/r/shangor/postgres-for-rag">shangor/postgres-for-rag</a> is well-suited to our needs, as it comes with both the pgvector and Apache AGE extensions preinstalled. This image uses fixed defaults <code>rag/rag/rag</code> for the database name, user, and password, and cannot be configured via environment variables.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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-color:#d8dee9ff;--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.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:
  postgres:
    container_name: postgres
    image: shangor/postgres-for-rag:v1.0
    container_name: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/16/main
    ports:
      - "5432:5432"
    environment: {}
    restart: unless-stopped
volumes:
  postgres_data:</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">postgres</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">postgres</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">shangor/postgres-for-rag:v1.0</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">postgres</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: #A3BE8C">postgres_data:/var/lib/postgresql/16/main</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: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5432:5432</span><span style="color: #ECEFF4">&quot;</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">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">unless-stopped</span></span>
<span class="line"><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">postgres_data</span><span style="color: #ECEFF4">:</span></span></code></pre></div>



<p>Then, we add <strong>the LightRAG service</strong>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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-color:#d8dee9ff;--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.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:
  postgres:
   ...
  lightrag:
    container_name: lightrag
    image: ghcr.io/hkuds/lightrag:latest
    ports:
      - "9621:9621"
    volumes:
      - lightrag_data:/app/data
    env_file:
      - .lightrag-env
    restart: unless-stopped
volumes:
  postgres_data:
  lightrag_data:</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">postgres</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #B48EAD">...</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">lightrag</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">lightrag</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">ghcr.io/hkuds/lightrag:latest</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: #ECEFF4">&quot;</span><span style="color: #A3BE8C">9621:9621</span><span style="color: #ECEFF4">&quot;</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: #A3BE8C">lightrag_data:/app/data</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">env_file</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">.lightrag-env</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">unless-stopped</span></span>
<span class="line"><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">postgres_data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">lightrag_data</span><span style="color: #ECEFF4">:</span></span></code></pre></div>



<p>We will keep all the configuration in <strong>a separate <code>.lightrag-env</code> file</strong>. Since it is quite large, placing it directly in the <code>docker-compose.yml</code> would bloat the file. There are many environment variables available, but here I will only show the ones I have configured.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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-color:#d8dee9ff;--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">.lightrag-env</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>### Reranker Configuration
RERANK_BINDING=cohere
RERANK_MODEL=rerank-v3.5
RERANK_BINDING_HOST=https://api.cohere.com/v2/rerank
RERANK_BINDING_API_KEY=YOUR_API_KEY

### LLM Configuration
LLM_BINDING=openai
LLM_MODEL=gpt-4.1-mini
LLM_BINDING_HOST=https://api.openai.com/v1
LLM_BINDING_API_KEY=YOUR_API_KEY

### Embedding Configuration
EMBEDDING_BINDING=openai
EMBEDDING_MODEL=text-embedding-3-small
EMBEDDING_DIM=1536
EMBEDDING_BINDING_HOST=https://api.openai.com/v1
EMBEDDING_BINDING_API_KEY=YOUR_API_KEY

### Storage Configuration
LIGHTRAG_KV_STORAGE=PGKVStorage
LIGHTRAG_DOC_STATUS_STORAGE=PGDocStatusStorage
LIGHTRAG_GRAPH_STORAGE=PGGraphStorage
LIGHTRAG_VECTOR_STORAGE=PGVectorStorage

### PostgreSQL Configuration
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_USER=rag
POSTGRES_PASSWORD=rag
POSTGRES_DATABASE=rag
POSTGRES_MAX_CONNECTIONS=12</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: #616E88">### Reranker Configuration</span></span>
<span class="line"><span style="color: #D8DEE9">RERANK_BINDING</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">cohere</span></span>
<span class="line"><span style="color: #D8DEE9">RERANK_MODEL</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">rerank-v3.5</span></span>
<span class="line"><span style="color: #D8DEE9">RERANK_BINDING_HOST</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">https://api.cohere.com/v2/rerank</span></span>
<span class="line"><span style="color: #D8DEE9">RERANK_BINDING_API_KEY</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">YOUR_API_KEY</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">### LLM Configuration</span></span>
<span class="line"><span style="color: #D8DEE9">LLM_BINDING</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">openai</span></span>
<span class="line"><span style="color: #D8DEE9">LLM_MODEL</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">gpt-4.1-mini</span></span>
<span class="line"><span style="color: #D8DEE9">LLM_BINDING_HOST</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">https://api.openai.com/v1</span></span>
<span class="line"><span style="color: #D8DEE9">LLM_BINDING_API_KEY</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">YOUR_API_KEY</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">### Embedding Configuration</span></span>
<span class="line"><span style="color: #D8DEE9">EMBEDDING_BINDING</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">openai</span></span>
<span class="line"><span style="color: #D8DEE9">EMBEDDING_MODEL</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">text-embedding-3-small</span></span>
<span class="line"><span style="color: #D8DEE9">EMBEDDING_DIM</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">1536</span></span>
<span class="line"><span style="color: #D8DEE9">EMBEDDING_BINDING_HOST</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">https://api.openai.com/v1</span></span>
<span class="line"><span style="color: #D8DEE9">EMBEDDING_BINDING_API_KEY</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">YOUR_API_KEY</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">### Storage Configuration</span></span>
<span class="line"><span style="color: #D8DEE9">LIGHTRAG_KV_STORAGE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">PGKVStorage</span></span>
<span class="line"><span style="color: #D8DEE9">LIGHTRAG_DOC_STATUS_STORAGE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">PGDocStatusStorage</span></span>
<span class="line"><span style="color: #D8DEE9">LIGHTRAG_GRAPH_STORAGE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">PGGraphStorage</span></span>
<span class="line"><span style="color: #D8DEE9">LIGHTRAG_VECTOR_STORAGE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">PGVectorStorage</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">### PostgreSQL Configuration</span></span>
<span class="line"><span style="color: #D8DEE9">POSTGRES_HOST</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">postgres</span></span>
<span class="line"><span style="color: #D8DEE9">POSTGRES_PORT</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">5432</span></span>
<span class="line"><span style="color: #D8DEE9">POSTGRES_USER</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">rag</span></span>
<span class="line"><span style="color: #D8DEE9">POSTGRES_PASSWORD</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">rag</span></span>
<span class="line"><span style="color: #D8DEE9">POSTGRES_DATABASE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">rag</span></span>
<span class="line"><span style="color: #D8DEE9">POSTGRES_MAX_CONNECTIONS</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">12</span></span></code></pre></div>



<p>For testing purposes, I’ve used OpenAI, which is inexpensive and easy to set up. If you have access to a capable GPU, you can also run an Ollama service as an alternative.</p>



<p><strong>Using a reranker is crucial</strong>, as it greatly enhances the quality of responses provided by LightRAG. In my case, <strong>I chose Cohere</strong>, which offers a simple free plan, perfectly sufficient for our testing needs.</p>



<p>Finally, we must add the N8N service. With N8N, we will implement the AI agent that leverages LightRAG as one of its tools.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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-color:#d8dee9ff;--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.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:
  postgres:
   ...
  lightrag:
   ...
  n8n:
    image: n8nio/n8n:latest
    ports:
      - "5678:5678"
    env_file:
      - .n8n-env
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres
    restart: unless-stopped
volumes:
  postgres_data:
  lightrag_data:
  n8n_data:</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">postgres</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #B48EAD">...</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">lightrag</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #B48EAD">...</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">n8n</span><span style="color: #ECEFF4">:</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">n8nio/n8n:latest</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: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5678:5678</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">env_file</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">.n8n-env</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: #A3BE8C">n8n_data:/home/node/.n8n</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">depends_on</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">postgres</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">unless-stopped</span></span>
<span class="line"><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">postgres_data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">lightrag_data</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">n8n_data</span><span style="color: #ECEFF4">:</span></span></code></pre></div>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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-color:#d8dee9ff;--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">.n8n-env</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>DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=rag
DB_POSTGRESDB_USER=rag
DB_POSTGRESDB_PASSWORD=rag

N8N_HOST=http://localhost
N8N_PORT=5678
N8N_PROTOCOL=http
N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true</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: #D8DEE9">DB_TYPE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">postgresdb</span></span>
<span class="line"><span style="color: #D8DEE9">DB_POSTGRESDB_HOST</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">postgres</span></span>
<span class="line"><span style="color: #D8DEE9">DB_POSTGRESDB_PORT</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">5432</span></span>
<span class="line"><span style="color: #D8DEE9">DB_POSTGRESDB_DATABASE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">rag</span></span>
<span class="line"><span style="color: #D8DEE9">DB_POSTGRESDB_USER</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">rag</span></span>
<span class="line"><span style="color: #D8DEE9">DB_POSTGRESDB_PASSWORD</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">rag</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">N8N_HOST</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">http://localhost</span></span>
<span class="line"><span style="color: #D8DEE9">N8N_PORT</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">5678</span></span>
<span class="line"><span style="color: #D8DEE9">N8N_PROTOCOL</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">http</span></span>
<span class="line"><span style="color: #D8DEE9">N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE</span><span style="color: #81A1C1">=</span><span style="color: #81A1C1">true</span></span></code></pre></div>



<p>Now we can run <code>docker compose up</code> to deploy our stack. We will have LightRAG available at <code>http://localhost:9621</code></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="281" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-3-1024x281.png" alt="" class="wp-image-2380" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-3-1024x281.png 1024w, https://www.victordorado.es/wp-content/uploads/2025/08/image-3-300x82.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-3-768x210.png 768w, https://www.victordorado.es/wp-content/uploads/2025/08/image-3.png 1095w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In the screen above, I’ve already uploaded a document of my city’s mobility regulation. It covers cycle lanes, pedestrian areas, traffic, and how they interact within the urban environment. After waiting for the processing to be done (~15 minutes for a 130 page document), we can check out the knowledge graph and even make queries.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1007" height="461" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-4.jpg" alt="" class="wp-image-2381" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-4.jpg 1007w, https://www.victordorado.es/wp-content/uploads/2025/08/image-4-300x137.jpg 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-4-768x352.jpg 768w" sizes="(max-width: 1007px) 100vw, 1007px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="902" height="576" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-4.png" alt="" class="wp-image-2382" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-4.png 902w, https://www.victordorado.es/wp-content/uploads/2025/08/image-4-300x192.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-4-768x490.png 768w" sizes="(max-width: 902px) 100vw, 902px" /></figure>



<p>We obtained a fairly good response, along with the references used by the algorithm:</p>



<ul class="wp-block-list">
<li><strong>[KG]</strong> &#8211; References in the Knowledge Graph: either single entities or relations between two entities.</li>



<li><strong>[DG]</strong> &#8211; Text chunks retrieved from a document.</li>
</ul>



<p>We can also play with different parameters: Query mode (global, local, hybrid&#8230; etc.),  response format, KG Top K, Chunk Top K, whether to enable or not reranking&#8230; refer to <a href="https://github.com/HKUDS/LightRAG?tab=readme-ov-file#query-param">the LightRAG documentation</a> for more info on these topics.</p>



<h2 class="wp-block-heading">Introducing N8N</h2>



<p>LightRAG can handle simple queries, but <strong>it lacks chat history</strong>, a <strong>system prompt</strong> (although it seems to have a “User Prompt” setting, it didn’t work for me), and other tools needed to properly manage a complete automation pipeline.</p>



<p>With N8N, we can implement the ideal <strong>orchestration mechanisms</strong> to fully unleash LightRAG’s search capabilities. We will set up <strong>a basic AI agent that includes a “LightRAG Tool”,</strong> which it can use to perform searches while generating more thoughtful responses that are also aligned with a custom system prompt.</p>



<h2 class="wp-block-heading">Installing the LightRAG Tool</h2>



<p>There is not an official node for interacting with LightRAG. Fortunately, there is a contributed one: <a href="https://www.npmjs.com/package/n8n-nodes-lightrag">n8n-nodes-lightrag</a>, that will allow us connecting an AI agent with our LightRAG instance.</p>



<p>Go to settings, in the bottom left corner, under the three points button:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="305" height="294" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-5.png" alt="" class="wp-image-2384" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-5.png 305w, https://www.victordorado.es/wp-content/uploads/2025/08/image-5-300x289.png 300w" sizes="(max-width: 305px) 100vw, 305px" /></figure>



<p>In the next screen, go to &#8220;Community Nodes&#8221; and install it:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="488" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-6-1024x488.png" alt="" class="wp-image-2385" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-6-1024x488.png 1024w, https://www.victordorado.es/wp-content/uploads/2025/08/image-6-300x143.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-6-768x366.png 768w, https://www.victordorado.es/wp-content/uploads/2025/08/image-6.png 1056w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Creating our AI Agent</h2>



<p>Now it&#8217;s finally time to create our AI Agent. We will use an OpenAI Model with <code>gpt-4.1-mini</code> configured, a simple memory node, and our LightRAG Tool</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="661" height="500" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-7.png" alt="" class="wp-image-2386" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-7.png 661w, https://www.victordorado.es/wp-content/uploads/2025/08/image-7-300x227.png 300w" sizes="(max-width: 661px) 100vw, 661px" /></figure>



<p>For our system prompt, we will use simple instructions:</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(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><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>You are an information assistant on Urban Mobility in Logroño, designed to answer user questions based on municipal regulations.

Retrieve relevant information and provide a concise, precise, and informative response to the user’s question. To do so, always use the "Light Rag Query" tool to fetch information from the company’s corpus, which is hosted on a LightRAG server. You should ask the tool any questions you deem necessary, including multiple queries if needed.

This tool will always return an answer along with the references used, for example:

------------------------------
Question:
Tell me in which cases wearing a helmet is required.

Answer:
Wearing a helmet is mandatory in several cases according to the regulations… etc.
References
&#91;KG&#93; certified helmet
&#91;KG&#93; certified helmet – motorcycles
&#91;DC&#93; MOBILITY-ORDINANCE-2019.pdf
------------------------------

A &#91;KG&#93; reference means it was obtained from the Knowledge Graph and can be either:

 - &#91;KG&#93; entity: A single entity
 - &#91;KG&#93; entity1 – entity2: A relationship between entity1 and entity2

A &#91;DC&#93; reference means it was obtained from text chunks in documents:

 - &#91;DC&#93; document name

However, you should not provide the references used unless explicitly asked for them.

If the answer cannot be found using the "Light Rag Query" tool, respond with:
"I cannot find the answer in the available resources."</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: #d8dee9ff">You are an information assistant on Urban Mobility in Logroño, designed to answer user questions based on municipal regulations.</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">Retrieve relevant information and provide a concise, precise, and informative response to the user’s question. To do so, always use the &quot;Light Rag Query&quot; tool to fetch information from the company’s corpus, which is hosted on a LightRAG server. You should ask the tool any questions you deem necessary, including multiple queries if needed.</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">This tool will always return an answer along with the references used, for example:</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">Question:</span></span>
<span class="line"><span style="color: #d8dee9ff">Tell me in which cases wearing a helmet is required.</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">Answer:</span></span>
<span class="line"><span style="color: #d8dee9ff">Wearing a helmet is mandatory in several cases according to the regulations… etc.</span></span>
<span class="line"><span style="color: #d8dee9ff">References</span></span>
<span class="line"><span style="color: #d8dee9ff">&#91;KG&#93; certified helmet</span></span>
<span class="line"><span style="color: #d8dee9ff">&#91;KG&#93; certified helmet – motorcycles</span></span>
<span class="line"><span style="color: #d8dee9ff">&#91;DC&#93; MOBILITY-ORDINANCE-2019.pdf</span></span>
<span class="line"><span style="color: #d8dee9ff">------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">A &#91;KG&#93; reference means it was obtained from the Knowledge Graph and can be either:</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff"> - &#91;KG&#93; entity: A single entity</span></span>
<span class="line"><span style="color: #d8dee9ff"> - &#91;KG&#93; entity1 – entity2: A relationship between entity1 and entity2</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">A &#91;DC&#93; reference means it was obtained from text chunks in documents:</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff"> - &#91;DC&#93; document name</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">However, you should not provide the references used unless explicitly asked for them.</span></span>
<span class="line"><span style="color: #d8dee9ff"></span></span>
<span class="line"><span style="color: #d8dee9ff">If the answer cannot be found using the &quot;Light Rag Query&quot; tool, respond with:</span></span>
<span class="line"><span style="color: #d8dee9ff">&quot;I cannot find the answer in the available resources.&quot;</span></span></code></pre></div>



<p>We must also configure the LightRAG Tool. First, configure the URL where N8N can access the lightRAG server. As we have a Docker internal network, every container is seen by  the others by its name, in our case &#8220;lightrag&#8221;.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="704" height="444" src="https://www.victordorado.es/wp-content/uploads/2025/08/Captura-desde-2025-08-25-10-45-27.png" alt="" class="wp-image-2387" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/Captura-desde-2025-08-25-10-45-27.png 704w, https://www.victordorado.es/wp-content/uploads/2025/08/Captura-desde-2025-08-25-10-45-27-300x189.png 300w" sizes="(max-width: 704px) 100vw, 704px" /></figure>



<p>Then, configure the query to be provided by the model, the &#8220;mix&#8221; strategy and enable reranking:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="513" height="665" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-8.png" alt="" class="wp-image-2388" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-8.png 513w, https://www.victordorado.es/wp-content/uploads/2025/08/image-8-231x300.png 231w" sizes="(max-width: 513px) 100vw, 513px" /></figure>



<p>You can try out the different modes to observe their differences. For more details, refer to <a href="https://github.com/HKUDS/LightRAG?tab=readme-ov-file#query-param">the LightRAG documentation</a>.</p>



<h2 class="wp-block-heading">Evaluating our agent</h2>



<p>Finally, we can open a chat session with our agent and ask any question. For example:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
</blockquote>



<p>Our AI Agent will start processing the request:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="649" height="493" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-9.png" alt="" class="wp-image-2389" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-9.png 649w, https://www.victordorado.es/wp-content/uploads/2025/08/image-9-300x228.png 300w" sizes="(max-width: 649px) 100vw, 649px" /></figure>



<p>We inmediately receive a response, and we can check out the execution process, as it is logged.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="770" height="432" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-10.png" alt="" class="wp-image-2390" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-10.png 770w, https://www.victordorado.es/wp-content/uploads/2025/08/image-10-300x168.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-10-768x431.png 768w" sizes="(max-width: 770px) 100vw, 770px" /></figure>



<p>This is the query that our agent sent to LightRAG, and its response:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="552" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-12-1024x552.png" alt="" class="wp-image-2392" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-12-1024x552.png 1024w, https://www.victordorado.es/wp-content/uploads/2025/08/image-12-300x162.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-12-768x414.png 768w, https://www.victordorado.es/wp-content/uploads/2025/08/image-12.png 1213w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>We can see that the response is quite similar to the one provided by LightRAG, but this was a relatively simple question. We could instruct our agent to query LightRAG more effectively, respond in a more informal tone, consult other information sources, or even perform actions.</p>



<p>Let&#8217;s try a more complicated question:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="475" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-13-1024x475.png" alt="" class="wp-image-2394" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-13-1024x475.png 1024w, https://www.victordorado.es/wp-content/uploads/2025/08/image-13-300x139.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-13-768x356.png 768w, https://www.victordorado.es/wp-content/uploads/2025/08/image-13.png 1059w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="456" src="https://www.victordorado.es/wp-content/uploads/2025/08/image-14-1024x456.png" alt="" class="wp-image-2395" srcset="https://www.victordorado.es/wp-content/uploads/2025/08/image-14-1024x456.png 1024w, https://www.victordorado.es/wp-content/uploads/2025/08/image-14-300x134.png 300w, https://www.victordorado.es/wp-content/uploads/2025/08/image-14-768x342.png 768w, https://www.victordorado.es/wp-content/uploads/2025/08/image-14.png 1243w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>We can observe how the agent synthesizes the information. It adds another layer of abstraction, allowing it to emphasize certain aspects of the information provided by LightRAG.</p>



<p>However, this example still doesn’t add much beyond simple LightRAG queries. But keep in mind that, with N8N, the possibilities are vast: for instance, we could equip the agent with a weather tool that checks today’s conditions and advises the user on any precautions they should take.</p>



<div class="wp-block-stackable-heading stk-block-heading stk-block-heading--v2 stk-block stk-3919067" id="conclusion" data-block-id="3919067"><h2 class="stk-block-heading__text">Conclusion</h2></div>



<p><strong>LightRAG represents a significant advancement</strong> in the field of Retrieval-Augmented Generation. It outperforms traditional or “naive” RAG solutions by combining <strong>efficient knowledge retrieval with contextual reasoning</strong>, all while maintaining a moderate token cost. This makes it a powerful tool for building intelligent, scalable, and cost-effective information systems.</p>



<p></p>
<p>The post <a href="https://www.victordorado.es/2025/08/25/supercharging-rag-information-retrieval-with-n8n-and-lightrag/">Supercharging RAG Information Retrieval with N8N and LightRAG</a> appeared first on <a href="https://www.victordorado.es">Víctor Dorado</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.victordorado.es/2025/08/25/supercharging-rag-information-retrieval-with-n8n-and-lightrag/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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>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>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>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><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>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><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><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>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>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>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>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 loading="lazy" 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>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>(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>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>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>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>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>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><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>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><a href="https://github.com/aws/amazon-ec2-metadata-mock">Amazon EC2 Metadata Mock</a></p>



<p>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><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><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>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>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>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>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>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><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>This way, the credentials provider accessed will be our EC2 Metadata Mock server.</p>



<p><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>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>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>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>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>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><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>And go to configure its settings at <code>/admin/config/media/s3fs</code>. </p>



<figure class="wp-block-image size-large"><img loading="lazy" 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>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>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>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 loading="lazy" 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>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>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>From now on, <strong>any new images added to articles will be stored in the new LocalStack <code>sample-bucket</code></strong></p>



<p><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><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>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>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><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>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><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>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><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>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>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>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>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><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>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>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>(DrupalPractice is a more exhaustive standard that checks not only the coding style used, but also some best practices for Drupal development).</p>



<p><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>PHPStorm has friendly configuration dialogs for some popular quality tools, and one of them is PHPCS.</p>



<p><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>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><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>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>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>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>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>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>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>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>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>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>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><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>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>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><strong>These are all the customizations that have been made:</strong></p>



<p>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>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>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>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>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>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>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>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>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>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>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><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></p>



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



<p><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><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>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>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><strong>Mind the serverName because we will need it later</strong>, when configuring a server in PHPStorm.</p>



<p>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>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>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>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>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><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>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>After that, to be sure everything is ok,<strong> I recommend checking and configuring the Xdebug installation</strong> under PHP -&gt; Debug:</p>



<p>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>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><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>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>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><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><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><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><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><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>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>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><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>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><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>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><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>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"><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>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-f580226a54e7d6d59301f475afd43b12"><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>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>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>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>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>Here are some examples:</p>



<p><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><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>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><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>(<code>current(), past() and future()</code> are possible by using a unique date range field for all entities):</p>



<p><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>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>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-03627597 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>
