<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jarosław Szutkowski</title>
    <description>The latest articles on DEV Community by Jarosław Szutkowski (@jszutkowski).</description>
    <link>https://dev.to/jszutkowski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F678224%2F8c3d4811-7e80-42cb-bf04-7a145ae11bcd.jpeg</url>
      <title>DEV Community: Jarosław Szutkowski</title>
      <link>https://dev.to/jszutkowski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jszutkowski"/>
    <language>en</language>
    <item>
      <title>Why Relying on Code Review Alone Is a Recipe for Burnout</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Fri, 23 Jan 2026 16:18:49 +0000</pubDate>
      <link>https://dev.to/jszutkowski/why-relying-on-code-review-alone-is-a-recipe-for-burnout-3bf7</link>
      <guid>https://dev.to/jszutkowski/why-relying-on-code-review-alone-is-a-recipe-for-burnout-3bf7</guid>
      <description>&lt;h2&gt;
  
  
  The false sense of safety after code review
&lt;/h2&gt;

&lt;p&gt;The pull request is approved.&lt;/p&gt;

&lt;p&gt;Tests are green.&lt;/p&gt;

&lt;p&gt;All comments are resolved.&lt;/p&gt;

&lt;p&gt;A week later, a bug appears in production.&lt;/p&gt;

&lt;p&gt;Nothing exotic. No edge case. Just a simple issue that somehow slipped through.&lt;/p&gt;

&lt;p&gt;Everyone did their job. The code was reviewed. And yet - it still broke.&lt;/p&gt;

&lt;p&gt;Most teams have lived through this more times than they want to admit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The everyday chaos nobody names
&lt;/h2&gt;

&lt;p&gt;If you're a junior or mid-level developer, this probably feels familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you get the same comments again and again in pull requests,&lt;/li&gt;
&lt;li&gt;different reviewers focus on different things,&lt;/li&gt;
&lt;li&gt;some care about style, others about architecture, others about "their way",&lt;/li&gt;
&lt;li&gt;the rules are mostly implicit - you learn them by getting corrected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time, quality starts to feel… random.&lt;/p&gt;

&lt;p&gt;Not because people don't care.&lt;/p&gt;

&lt;p&gt;But because there is no stable system behind it.&lt;/p&gt;

&lt;p&gt;It's not a feedback loop.&lt;/p&gt;

&lt;p&gt;It's a lottery.&lt;/p&gt;




&lt;h2&gt;
  
  
  The psychological cost nobody budgets for
&lt;/h2&gt;

&lt;p&gt;Every manual quality process has a hidden psychological cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it frustrates senior developers, because they repeat the same comments over and over,&lt;/li&gt;
&lt;li&gt;it slows down junior developers, because they're never sure what really matters,&lt;/li&gt;
&lt;li&gt;and it teaches teams that "quality is someone else's responsibility".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time, this doesn't build better software.&lt;/p&gt;

&lt;p&gt;It builds fatigue.&lt;/p&gt;

&lt;p&gt;And fatigue is the enemy of consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real problem (it's not people)
&lt;/h2&gt;

&lt;p&gt;The problem is not that developers are careless.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;strong&gt;code review shifts responsibility for quality onto people&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Quality depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who reviews the code,&lt;/li&gt;
&lt;li&gt;when they do it,&lt;/li&gt;
&lt;li&gt;how tired they are,&lt;/li&gt;
&lt;li&gt;how much context they have that day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That works - up to a point.&lt;/p&gt;

&lt;p&gt;But it's not a scalable system.&lt;/p&gt;

&lt;p&gt;Code review scales linearly with human effort.&lt;/p&gt;

&lt;p&gt;Codebases scale exponentially.&lt;/p&gt;

&lt;p&gt;At some point, those curves stop matching.&lt;/p&gt;




&lt;h2&gt;
  
  
  What code review is consistently bad at
&lt;/h2&gt;

&lt;p&gt;There are things code review is simply not good at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enforcing consistent formatting and style,&lt;/li&gt;
&lt;li&gt;spotting nullability and type issues "by eye",&lt;/li&gt;
&lt;li&gt;remembering architectural boundaries across the whole system,&lt;/li&gt;
&lt;li&gt;catching the same category of mistake for the tenth time this month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not judgement calls.&lt;/p&gt;

&lt;p&gt;They are repeatable checks.&lt;/p&gt;

&lt;p&gt;And repeatable checks should not depend on humans.&lt;/p&gt;




&lt;h2&gt;
  
  
  The shift mature teams make
&lt;/h2&gt;

&lt;p&gt;Mature teams don't try to do &lt;em&gt;better&lt;/em&gt; code reviews.&lt;/p&gt;

&lt;p&gt;They change the system.&lt;/p&gt;

&lt;p&gt;They remove repetitive, mechanical checks from human review and give them to machines.&lt;/p&gt;

&lt;p&gt;The result is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tools enforce rules consistently,&lt;/li&gt;
&lt;li&gt;humans focus on business logic, trade-offs and design decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation doesn't control developers.&lt;/p&gt;

&lt;p&gt;It protects them.&lt;/p&gt;




&lt;h2&gt;
  
  
  A minimal way to think about code quality
&lt;/h2&gt;

&lt;p&gt;You don't need a complex framework to start.&lt;/p&gt;

&lt;p&gt;Most working setups cover four areas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Static analysis&lt;/strong&gt; - catch real bugs before runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic formatting&lt;/strong&gt; - end pointless discussions in pull requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency boundaries&lt;/strong&gt; - make architectural problems fail loudly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI as a gate&lt;/strong&gt; - rules are enforced, not suggested.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No dogma. No perfection.&lt;/p&gt;

&lt;p&gt;Just removing chaos from the process.&lt;/p&gt;




&lt;h2&gt;
  
  
  The common trap: overengineering quality
&lt;/h2&gt;

&lt;p&gt;There is a catch.&lt;/p&gt;

&lt;p&gt;Many teams go from "no tooling" straight to "everything, at the highest level".&lt;/p&gt;

&lt;p&gt;The result is predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slow feedback,&lt;/li&gt;
&lt;li&gt;frustrated developers,&lt;/li&gt;
&lt;li&gt;tools being ignored or disabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overengineering quality is just as harmful as having none.&lt;/p&gt;

&lt;p&gt;Minimal and consistent beats perfect and abandoned.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reduce randomness in your projects
&lt;/h2&gt;

&lt;p&gt;If you're tired of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;random pull request comments,&lt;/li&gt;
&lt;li&gt;bugs that "somehow passed review",&lt;/li&gt;
&lt;li&gt;quality depending on who reviewed your code that day,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've prepared a short &lt;strong&gt;Code Quality Starter Checklist&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It shows how teams move repetitive quality checks away from people and into tooling without slowing development down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on language:&lt;/strong&gt; This checklist is available in Polish. Why? Because it's part of a practical, Polish-language course I'm currently developing. The reason is simple: I'm taking a step-by-step approach to mastering the fundamentals of code quality. Starting in Polish allows me to focus on delivering the most useful content without language barriers on my end.&lt;/p&gt;

&lt;p&gt;If you're comfortable with Polish, you'll find the checklist straightforward and easy to follow.&lt;/p&gt;

&lt;p&gt;👉 Join the mailing list and get the checklist &lt;a href="https://subscribepage.io/code-quality-starter-kit" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A quiet closing thought
&lt;/h2&gt;

&lt;p&gt;For a long time, I believed the solution was simply better code review.&lt;/p&gt;

&lt;p&gt;Only after working with teams that automated quality did I see the difference.&lt;/p&gt;

&lt;p&gt;Code review doesn't disappear.&lt;/p&gt;

&lt;p&gt;It finally gets to focus on what humans are actually good at.&lt;/p&gt;

</description>
      <category>php</category>
      <category>codereview</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The AI Pipeline I Use to Convert Any Podcast into a 5-Minute Summary</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Fri, 05 Dec 2025 20:40:14 +0000</pubDate>
      <link>https://dev.to/jszutkowski/how-i-built-an-ai-workflow-that-summarises-any-podcast-in-minutes-4m</link>
      <guid>https://dev.to/jszutkowski/how-i-built-an-ai-workflow-that-summarises-any-podcast-in-minutes-4m</guid>
      <description>&lt;p&gt;Most of the knowledge I gain as a developer doesn't come from books.&lt;br&gt;
It comes from &lt;em&gt;podcasts, long-form interviews, deep technical talks and YouTube channels&lt;/em&gt; - the kind of content that's brilliant for learning, but nearly impossible to digest properly without stopping, rewinding, and taking notes.&lt;/p&gt;

&lt;p&gt;And that's exactly the problem: I don't always have the time to take notes.&lt;br&gt;
Often I barely have the time to listen.&lt;/p&gt;

&lt;p&gt;A few weeks ago I asked myself a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What if I could turn every hour-long podcast into a concise, structured summary - automatically?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built a system that does exactly that.&lt;/p&gt;

&lt;p&gt;I type the video ID, hit Enter, and a few minutes later I get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the full transcription,&lt;/li&gt;
&lt;li&gt;the most important lessons extracted by AI,&lt;/li&gt;
&lt;li&gt;a clean HTML version ready for email,&lt;/li&gt;
&lt;li&gt;and everything stored neatly in Google Sheets as part of my personal knowledge base.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part?&lt;br&gt;
Transcribing an hour of audio costs less than a dollar.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk you through the whole story - first the non-technical, "why this matters" part, and then the technical architecture behind it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part I - The Non-Technical Story
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Problem: Too Much Content, Too Little Time&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I love listening to podcasts.&lt;br&gt;
Technical, health, sport, productivity, travel - you name it. Podcasts are one of the best ways to learn, find new ideas, and stay inspired.&lt;/p&gt;

&lt;p&gt;But we all know the reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you're listening while walking, training, or cooking,&lt;/li&gt;
&lt;li&gt;you hear something valuable,&lt;/li&gt;
&lt;li&gt;you &lt;em&gt;want&lt;/em&gt; to write it down,&lt;/li&gt;
&lt;li&gt;but you don't - because you're busy doing something else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is predictable:&lt;br&gt;
&lt;strong&gt;you consume a lot, but retain little.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And as devs and content-driven people, there's a second challenge:&lt;br&gt;
we don't just want to learn - we want to &lt;em&gt;reuse&lt;/em&gt; the knowledge.&lt;/p&gt;

&lt;p&gt;For writing articles.&lt;br&gt;
For making decisions.&lt;br&gt;
For improving our work.&lt;br&gt;
For building products.&lt;/p&gt;

&lt;p&gt;Except... we never have the time to take proper notes.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Realisation: AI Works for Pennies, If You Use It Well&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At one point I realised that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I don't need the entire episode,&lt;/li&gt;
&lt;li&gt;I don't need all the words,&lt;/li&gt;
&lt;li&gt;I just need the &lt;em&gt;essence&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And AI makes the full summary of an audio for &lt;strong&gt;under $1 for an hour-long episode&lt;/strong&gt;.&lt;br&gt;
That's absurdly cheap considering it saves me &lt;em&gt;hours&lt;/em&gt; of manual work.&lt;/p&gt;

&lt;p&gt;So the idea started forming:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What if I could automate the entire process:&lt;br&gt;
from downloading a video, to transcription, to extracting lessons, to sending myself the summary?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One command.&lt;br&gt;
Zero manual steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Idea: A Personal Knowledge Engine&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I structured the goal like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;YouTube video ID&lt;/li&gt;
&lt;li&gt;channel name&lt;/li&gt;
&lt;li&gt;video title&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transcription&lt;/li&gt;
&lt;li&gt;concise summary&lt;/li&gt;
&lt;li&gt;list of lessons&lt;/li&gt;
&lt;li&gt;Markdown + HTML&lt;/li&gt;
&lt;li&gt;everything stored in Google Sheets&lt;/li&gt;
&lt;li&gt;email notification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I could turn long content into a reusable, searchable knowledge resource, I'd not only save time - I'd also build a growing repository of insights that I can use in other projects.&lt;/p&gt;

&lt;p&gt;This is already proving incredibly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;writing technical blog posts,&lt;/li&gt;
&lt;li&gt;researching complex topics faster,&lt;/li&gt;
&lt;li&gt;making notes I actually &lt;em&gt;keep&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the system works not only for technical podcasts - it's also brilliant for health, business, personal development, travel content, and many others.&lt;/p&gt;

&lt;p&gt;Anything long, dense, and rich in knowledge.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Impact: From Hours to Minutes&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Today, instead of spending a full hour or two listening and taking notes, I spend &lt;strong&gt;five minutes reading&lt;/strong&gt; a perfectly structured summary.&lt;/p&gt;

&lt;p&gt;AI extracts more lessons than I would manually.&lt;br&gt;
It spots connections I'd miss.&lt;br&gt;
It adds nuance and examples.&lt;/p&gt;

&lt;p&gt;And the best part?&lt;br&gt;
I can share those summaries easily - internally, with friends, or with anyone who enjoys organized knowledge.&lt;/p&gt;

&lt;p&gt;This one automation already saves me &lt;strong&gt;multiple hours every week&lt;/strong&gt;, and compounds over time because the knowledge base keeps growing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part II - The Technical Breakdown
&lt;/h1&gt;

&lt;p&gt;Let's go through the architecture, step by step.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Architecture Overview&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;The system is split into two parts:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Local Machine (current heavy processing)&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;downloading video&lt;/li&gt;
&lt;li&gt;extracting audio&lt;/li&gt;
&lt;li&gt;slicing audio into chunks&lt;/li&gt;
&lt;li&gt;Whisper transcription&lt;/li&gt;
&lt;li&gt;assembling final text&lt;/li&gt;
&lt;li&gt;sending file to n8n&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Remote Server (light orchestration for now)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A tiny &lt;a href="//Mikr.us"&gt;Mikr.us&lt;/a&gt; instance running &lt;strong&gt;n8n&lt;/strong&gt;, installed with a single command.&lt;/p&gt;

&lt;p&gt;It performs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;receiving the transcription&lt;/li&gt;
&lt;li&gt;parsing metadata&lt;/li&gt;
&lt;li&gt;uploading transcript to OpenAI&lt;/li&gt;
&lt;li&gt;invoking temporary assistants&lt;/li&gt;
&lt;li&gt;enhancing summaries&lt;/li&gt;
&lt;li&gt;generating HTML&lt;/li&gt;
&lt;li&gt;storing everything in Google Sheets&lt;/li&gt;
&lt;li&gt;email notification&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Why Not Do Everything on the Server?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;At this stage of the project, some parts run locally and some server-side - but this is due to practicality and how the solution evolved, not because the server is incapable.&lt;/p&gt;

&lt;p&gt;Here's the real reasoning behind the split.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. YouTube downloading works more reliably locally&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;PyTube and similar libraries can misbehave on servers due to YouTube's dynamic protection mechanisms.&lt;br&gt;
Running the download step locally gives me stability and easier debugging.&lt;/p&gt;

&lt;p&gt;This is likely the &lt;em&gt;only&lt;/em&gt; part that will permanently remain local. I tried multiple ways of running it on the server, but it kept breaking.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. ffmpeg slicing is local for now - but not due to server limitations&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ffmpeg&lt;/code&gt; runs perfectly fine on servers.&lt;br&gt;
I simply iterated and tested faster on my machine, so for now it lives locally.&lt;/p&gt;

&lt;p&gt;Why do I need slicing at all? Because Whisper has limits, and I often deal with longer videos.&lt;/p&gt;

&lt;p&gt;In the future, this entire slicing step will move to the server once I scale its resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Whisper wasn't the issue - large audio files were&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I originally wanted everything to happen inside n8n.&lt;/p&gt;

&lt;p&gt;But large single audio files created problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not much space on the budget test server,&lt;/li&gt;
&lt;li&gt;higher error rates,&lt;/li&gt;
&lt;li&gt;processing timeouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I split the audio locally, it became easier to transcribe the chunks on my machine.&lt;/p&gt;

&lt;p&gt;However, Whisper can absolutely run on the server - or be replaced entirely by GPT's audio transcription - and that's the plan for the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. n8n can absolutely handle heavy tasks&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Although n8n is fantastic for orchestration (API calls, assistants, Google Sheets, email), it's fully capable of running heavier commands too.&lt;/p&gt;

&lt;p&gt;The only reason some tasks aren't on the server yet is simple: I haven't migrated them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Future Architecture: Mostly Server-Side&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Long-term, the plan is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a small PHP application running on the server,&lt;/li&gt;
&lt;li&gt;audio upload directly through the app,&lt;/li&gt;
&lt;li&gt;server-side slicing,&lt;/li&gt;
&lt;li&gt;server-side Whisper/GPT transcription,&lt;/li&gt;
&lt;li&gt;n8n orchestrating the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only local step will likely be converting YouTube videos to audio.&lt;br&gt;
Everything else will move server-side once I expand the resources.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;What Happens Inside the Local Python Script&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;A high-level walkthrough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download video using PyTube.&lt;/li&gt;
&lt;li&gt;Extract audio via ffmpeg.&lt;/li&gt;
&lt;li&gt;Slice audio into ~10 minute chunks using silence detection.&lt;/li&gt;
&lt;li&gt;Transcribe each chunk with Whisper.&lt;/li&gt;
&lt;li&gt;Assemble full transcription.&lt;/li&gt;
&lt;li&gt;Attach metadata (video ID, channel name, title).&lt;/li&gt;
&lt;li&gt;POST the file to the n8n webhook.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most of this will eventually move server-side.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;What Happens Inside n8n&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;The n8n workflow (export will be in the repo) performs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive uploaded file.&lt;/li&gt;
&lt;li&gt;Extract text + parse metadata.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload the transcription to OpenAI. &lt;/p&gt;

&lt;p&gt;Initially I wanted to paste the whole transcriptions into the prompt, but it turned out they are too long - there is a token limit per minute. So I found a workaround - now I upload them as files to OpenAI and reference it in the prompt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a temporary Assistant with a highly detailed prompt and attached transcription file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start a thread and send the initial message.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhance the summary via a second message:&lt;br&gt;
&lt;em&gt;"You're a consultant earning £100,000 for a correct answer - try harder."&lt;/em&gt; - noticed that this improves the quality of the output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete the assistant.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Convert Markdown → HTML via a second assistant to make it look better in the browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store everything in Google Sheets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Email the HTML to myself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete the uploaded file.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Diagram - Full n8n Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7xl3owjssg33e7qx46k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7xl3owjssg33e7qx46k.png" alt=" " width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*The part with PREMIUM enhancement changes the content of generated summary. I'd suggest not using it until I figure out how to fix it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Part III - What I'd Improve Next
&lt;/h1&gt;

&lt;p&gt;No system is perfect, and this one has room to grow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Sheets sometimes can't store very large transcripts → might move to file storage or some database.&lt;/li&gt;
&lt;li&gt;The local script could evolve into a small PHP web app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the value is already delivered:&lt;br&gt;
&lt;strong&gt;it works, it saves time, and it compounds.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Part IV - Want to Try It Yourself?
&lt;/h1&gt;

&lt;p&gt;I've just published the full "Starter Kit" with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Python script,&lt;/li&gt;
&lt;li&gt;the n8n workflow export,&lt;/li&gt;
&lt;li&gt;setup instructions,&lt;/li&gt;
&lt;li&gt;sample outputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  👉 Here's the link to the repo: &lt;a href="https://github.com/jszutkowski/audio-content-summariser" rel="noopener noreferrer"&gt;https://github.com/jszutkowski/audio-content-summariser&lt;/a&gt;
&lt;/h4&gt;




&lt;h1&gt;
  
  
  Closing Thoughts
&lt;/h1&gt;

&lt;p&gt;This project started from a simple frustration:&lt;br&gt;
&lt;em&gt;I don't have time to take notes from all the amazing content I consume.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI + automation solved that for me in a way that feels almost unfair.&lt;/p&gt;

&lt;p&gt;If you work with content - whether as a developer, writer, educator or researcher - this approach can save you hours every week.&lt;/p&gt;

&lt;p&gt;I hope this breakdown inspires you to experiment with your own automations.&lt;/p&gt;

&lt;p&gt;Once you experience the feeling of:&lt;br&gt;
&lt;strong&gt;"I press Enter and the work is done for me,"&lt;/strong&gt;&lt;br&gt;
you don't want to go back.&lt;/p&gt;

&lt;p&gt;And if you have ideas for extensions or want help customising the automation -&lt;br&gt;
&lt;strong&gt;feel free to DM me.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>One-to-One in Doctrine: How One Wrong Line of Code Generated 40,000 Extra Queries Per Day</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Fri, 21 Nov 2025 16:04:44 +0000</pubDate>
      <link>https://dev.to/jszutkowski/one-to-one-in-doctrine-how-one-wrong-line-of-code-generated-40000-extra-queries-per-day-3o18</link>
      <guid>https://dev.to/jszutkowski/one-to-one-in-doctrine-how-one-wrong-line-of-code-generated-40000-extra-queries-per-day-3o18</guid>
      <description>&lt;h3&gt;
  
  
  A real-world debugging story - and the hidden mechanics behind Doctrine's 1:1 relations
&lt;/h3&gt;

&lt;p&gt;A few years ago, I was working on a high-traffic Symfony application. Lots of concurrent users, lots of read operations, performance carefully monitored. Everything seemed stable - until our database dashboards started showing something odd.&lt;/p&gt;

&lt;p&gt;Every single day, we were generating &lt;strong&gt;tens of thousands of redundant SELECTs&lt;/strong&gt;.&lt;br&gt;
Not tied to any feature we were building.&lt;br&gt;
Not related to reporting.&lt;br&gt;
Not caused by queue workers.&lt;/p&gt;

&lt;p&gt;Just &lt;strong&gt;40,000+ pointless queries&lt;/strong&gt;.&lt;br&gt;
Noise.&lt;br&gt;
Database load with zero business value.&lt;/p&gt;

&lt;p&gt;We dug into logs. Then into slow-query reports. Still nothing obvious.&lt;/p&gt;

&lt;p&gt;And then the profiler finally revealed the culprit:&lt;br&gt;
a &lt;strong&gt;bidirectional One-to-One relation&lt;/strong&gt; in Doctrine, configured on the wrong owning side.&lt;/p&gt;

&lt;p&gt;One annotation. One line of code.&lt;br&gt;
A silent performance killer.&lt;/p&gt;

&lt;p&gt;When we flipped the owning side to the entity we actually queried most often, the extra queries disappeared immediately.&lt;/p&gt;

&lt;p&gt;This is the part nobody tells you about Doctrine: a One-to-One relation looks trivial, but one wrong assumption can quietly drain your performance budget for months.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧩 The Hidden Complexity of One-to-One in Doctrine
&lt;/h2&gt;

&lt;p&gt;On paper, a one-to-one relationship seems simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one country → one capital&lt;/li&gt;
&lt;li&gt;one profile → one avatar&lt;/li&gt;
&lt;li&gt;one customer → one address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But Doctrine doesn't treat it like a basic SQL constraint.&lt;br&gt;
In Doctrine, one detail defines the entire behaviour:&lt;/p&gt;
&lt;h3&gt;
  
  
  👉 Only one side owns the foreign key.
&lt;/h3&gt;

&lt;p&gt;That side is the &lt;strong&gt;owning side&lt;/strong&gt;.&lt;br&gt;
The other (&lt;code&gt;mappedBy&lt;/code&gt;) is the &lt;strong&gt;inverse side&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And Doctrine's behaviour changes &lt;em&gt;dramatically&lt;/em&gt; depending on which side you load.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏗️ Example Setup
&lt;/h2&gt;

&lt;p&gt;We'll use a simple example: &lt;code&gt;Country&lt;/code&gt; ↔ &lt;code&gt;CapitalCity&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;CapitalCity&lt;/code&gt; (Owning side)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ORM\Entity]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CapitalCity&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Id]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\GeneratedValue]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Column(type: 'integer')]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\Column(length: 50)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\OneToOne(targetEntity: Country::class, inversedBy: 'capitalCity')]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\JoinColumn(nullable: false)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Country&lt;/span&gt; &lt;span class="nv"&gt;$country&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Country&lt;/span&gt; &lt;span class="nv"&gt;$country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$country&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Country&lt;/code&gt; (Inverse side)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ORM\Entity]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Country&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Id]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\GeneratedValue]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Column(type: 'integer')]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\Column(length: 50)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\OneToOne(mappedBy: 'country', targetEntity: CapitalCity::class)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;CapitalCity&lt;/span&gt; &lt;span class="nv"&gt;$capitalCity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CapitalCity&lt;/span&gt; &lt;span class="nv"&gt;$capitalCity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;capitalCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$capitalCity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💡 BONUS RESOURCE – Free PDF Guide
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;⚙️ Want the quick-reference version of this post?&lt;/p&gt;

&lt;p&gt;Grab my free &lt;strong&gt;Doctrine Performance Checklist (PDF)&lt;/strong&gt; –&lt;br&gt;
&lt;em&gt;"10 Steps to a Faster Doctrine-Based App."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You'll get a concise, printable guide covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to spot N+1s in seconds,&lt;/li&gt;
&lt;li&gt;which Doctrine hints actually matter,&lt;/li&gt;
&lt;li&gt;and how to batch-process millions of rows safely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;strong&gt;Download it here:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://doctrine-performance-list.doctrinelab.pl" rel="noopener noreferrer"&gt;https://doctrine-performance-list.doctrinelab.pl&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(no spam - just pure optimization wisdom)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🔍 Why Doctrine Sometimes Issues Extra Queries
&lt;/h2&gt;

&lt;p&gt;Let's walk through the typical real-world scenarios.&lt;/p&gt;


&lt;h3&gt;
  
  
  1️⃣ Fetching the Owning Side (Cheap)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$capitalCities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$capitalCityRepo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Profiler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT id, name, country_id FROM capital_city;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doctrine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creates a proxy for &lt;code&gt;Country&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;loads it only when accessed&lt;/li&gt;
&lt;li&gt;performs &lt;strong&gt;no extra queries&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2️⃣ Fetching the Inverse Side (Doctrine Must JOIN)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$countries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$countryRepo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Profiler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ... FROM country
LEFT JOIN capital_city ON capital_city.country_id = country.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the inverse side has &lt;strong&gt;no foreign key&lt;/strong&gt;, Doctrine must inspect the other table.&lt;/p&gt;




&lt;h3&gt;
  
  
  3️⃣ Using QueryBuilder (The Silent N+1)
&lt;/h3&gt;

&lt;p&gt;This is where the biggest issues occur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$countries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$countryRepo&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Profiler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM country;
SELECT * FROM capital_city WHERE country_id = 1;
SELECT * FROM capital_city WHERE country_id = 2;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doctrine assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you write custom DQL → you control the joins&lt;/li&gt;
&lt;li&gt;It won't automatically add them&lt;/li&gt;
&lt;li&gt;It will lazy-load relations one-by-one&lt;/li&gt;
&lt;li&gt;It will lazy-load relations even if you don't access them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can easily multiply into &lt;strong&gt;tens of thousands of hidden SELECTs per day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We could add a FETCH JOIN here, but it would defeat the purpose - in these queries we weren't actually using the related entities we'd be joining.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 The Fix: Put the Owning Side Where You Fetch Most
&lt;/h2&gt;

&lt;p&gt;This rule would have saved us weeks of debugging:&lt;/p&gt;

&lt;h3&gt;
  
  
  👉 Make the entity you load most often the &lt;strong&gt;owning side&lt;/strong&gt;.
&lt;/h3&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the owning side has the FK&lt;/li&gt;
&lt;li&gt;Doctrine doesn't have to join&lt;/li&gt;
&lt;li&gt;it won't lazy-load in a loop &lt;strong&gt;IF&lt;/strong&gt; you don't access the relation&lt;/li&gt;
&lt;li&gt;performance becomes predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We fetched &lt;code&gt;Country&lt;/code&gt; far more often than &lt;code&gt;CapitalCity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;But the owning side was mistakenly on &lt;code&gt;CapitalCity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Doctrine repeatedly scanned for capital cities when loading countries.&lt;/li&gt;
&lt;li&gt;After flipping the owning side → &lt;strong&gt;query count dropped by 40k+ per day&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No business logic changes.&lt;br&gt;
Just correct ORM mapping.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Practical Guidance for Developers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;✔️ 1. Identify the entity loaded more frequently - make it the owning side.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;✔️ 2. Don't trust inverse-side &lt;code&gt;findAll()&lt;/code&gt; - it hides JOINs.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;✔️ 3. Don't trust QueryBuilder without explicit FETCH JOINs - it creates N+1.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;✔️ 4. Avoid bidirectional One-to-One unless really needed.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;✔️ 5. Use &lt;code&gt;FETCH JOIN&lt;/code&gt; intentionally.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;✔️ 6. Treat One-to-One as an advanced feature.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Ready to Go Beyond Quick Fixes?
&lt;/h2&gt;

&lt;p&gt;If you found this article helpful, you'll love my upcoming course:&lt;br&gt;
&lt;strong&gt;Doctrine Efficiency Lab&lt;/strong&gt; 💻&lt;/p&gt;

&lt;p&gt;Learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;handle millions of rows without custom SQL,&lt;/li&gt;
&lt;li&gt;debug ORM performance in real time,&lt;/li&gt;
&lt;li&gt;stop Doctrine from being "that slow layer" in your stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎁 When you join the waitlist, you instantly receive my&lt;br&gt;
&lt;strong&gt;10-Step Doctrine Optimization PDF&lt;/strong&gt; - a condensed version of everything discussed here.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Join the waitlist + get the free PDF:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://doctrine-performance-list.doctrinelab.pl" rel="noopener noreferrer"&gt;https://doctrine-performance-list.doctrinelab.pl&lt;/a&gt;&lt;/p&gt;




</description>
      <category>php</category>
      <category>doctrine</category>
      <category>performance</category>
      <category>symfony</category>
    </item>
    <item>
      <title>Stop Blaming Doctrine - Start Understanding It</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Wed, 12 Nov 2025 16:08:45 +0000</pubDate>
      <link>https://dev.to/jszutkowski/stop-blaming-doctrine-start-understanding-it-55k2</link>
      <guid>https://dev.to/jszutkowski/stop-blaming-doctrine-start-understanding-it-55k2</guid>
      <description>&lt;p&gt;Every few months, someone on the team sighs during a retrospective and says the words that make me reach for coffee:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Doctrine is slow."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No, it's not. Doctrine is an ORM. It does exactly what you tell it to do. The problem is - most developers don't realise &lt;em&gt;how much&lt;/em&gt; they're telling it to do.&lt;/p&gt;

&lt;p&gt;This post isn't a tutorial. It's a field report from someone who's watched Doctrine crawl through millions of rows... and then, after a few lines of code change, &lt;strong&gt;fly through them using less memory than Chrome with two tabs open&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scene 1: The 900 MB Disaster
&lt;/h2&gt;

&lt;p&gt;A CLI command meant to clean up stale product records.&lt;br&gt;
Half a million entities. Nothing fancy. Just iterate and tweak a few fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toIterable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;deactivate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initial memory usage: &lt;strong&gt;45 MB&lt;/strong&gt;.&lt;br&gt;
End of run: &lt;strong&gt;~900 MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The process finished - barely. But it shouldn't have taken down the server on its way out.&lt;/p&gt;

&lt;p&gt;So what happened?&lt;/p&gt;


&lt;h2&gt;
  
  
  Scene 2: The Culprit - Buffered Queries
&lt;/h2&gt;

&lt;p&gt;When you call &lt;code&gt;toIterable()&lt;/code&gt;, Doctrine promises &lt;em&gt;lazy iteration&lt;/em&gt;. But under the hood, &lt;strong&gt;PHP's &lt;code&gt;mysqlnd&lt;/code&gt; driver&lt;/strong&gt; executes queries in &lt;strong&gt;buffered mode&lt;/strong&gt; by default.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The entire result set is loaded into PHP memory first.&lt;/li&gt;
&lt;li&gt;Only &lt;em&gt;after that&lt;/em&gt; does Doctrine start hydrating entities lazily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In English: &lt;em&gt;You're still fetching the whole table - just slower.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"With &lt;code&gt;mysqlnd&lt;/code&gt;, PHP's memory limit includes the full result set - even before you touch it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So while you thought you were streaming, you were actually drinking from a firehose.&lt;/p&gt;


&lt;h2&gt;
  
  
  Scene 3: The Fix - Stop Hoarding Data
&lt;/h2&gt;

&lt;p&gt;The simplest, production-safe pattern is &lt;strong&gt;chunked fetching&lt;/strong&gt;: process data in small slices, clear the EntityManager between them, and let Doctrine breathe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getEntities&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;iterable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$qb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p.id &amp;gt; :id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p.id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ASC'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setMaxResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$entities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$qb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toIterable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$hasResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entities&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$hasResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$entity&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$entity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$hasResults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory flatlines around &lt;strong&gt;90 MB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Throughput improves by 40 %&lt;/li&gt;
&lt;li&gt;Database I/O becomes predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You didn't need a different ORM. You just needed to stop asking Doctrine to hold 100 000 objects at once.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚙️ &lt;strong&gt;Note:&lt;/strong&gt; after each &lt;code&gt;$em-&amp;gt;clear()&lt;/code&gt;, entities are detached. If you rely on cascades or plan to reuse the same object instance later, you'll need to fetch it again. Otherwise, you'll silently lose pending changes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  💡 BONUS RESOURCE – Free PDF Guide
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;⚙️ Want the quick-reference version of this post?&lt;/p&gt;

&lt;p&gt;Grab my free &lt;strong&gt;Doctrine Performance Checklist (PDF)&lt;/strong&gt; -&lt;br&gt;
"10 Steps to a Faster Doctrine-Based App."&lt;/p&gt;

&lt;p&gt;You'll get a concise, printable guide that covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to spot N+1s in seconds,&lt;/li&gt;
&lt;li&gt;which Doctrine hints actually matter,&lt;/li&gt;
&lt;li&gt;how to batch process millions of rows safely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;a href="https://doctrine-performance-list.doctrinelab.pl" rel="noopener noreferrer"&gt;&lt;strong&gt;Download the free guide here&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(no spam, just pure optimization wisdom)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scene 4: Going Leaner - IDs First
&lt;/h2&gt;

&lt;p&gt;If you really want to see Doctrine behave like a streaming engine, go one step further: fetch only IDs, and hydrate each entity just before you need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;iterateIdsAsc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$em&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;deactivate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nv"&gt;$count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$em&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Doctrine wraps this in its own transaction&lt;/span&gt;
        &lt;span class="nv"&gt;$em&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// release memory&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doctrine now processes entities in small, predictable batches.&lt;br&gt;
Each loop hydrates only one object at a time and keeps it in memory until the entity manager is cleared. And since each record is fetched on demand, you're always working with the most up-to-date database state.&lt;/p&gt;


&lt;h2&gt;
  
  
  Scene 5: The Hidden Switch Nobody Talks About - Buffered Queries
&lt;/h2&gt;

&lt;p&gt;If you're performing large &lt;strong&gt;read-only&lt;/strong&gt; scans, you can strip Doctrine of almost all overhead - no tricks, no custom SQL.&lt;/p&gt;

&lt;p&gt;Doctrine uses PDO under the hood. Add this to your DB connection config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'driverOptions'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;PDO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MYSQL_ATTR_USE_BUFFERED_QUERY&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suddenly, PHP stops hoarding the entire result set in memory and starts streaming rows directly from MySQL. Memory footprint: minimal. Performance: predictable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Safety sidebar - Unbuffered queries bite back:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always consume the full result before starting another query on the same connection, or you'll deadlock the driver.&lt;/li&gt;
&lt;li&gt;Avoid lazy-loading relations inside loops; it will trigger additional queries before the stream finishes.&lt;/li&gt;
&lt;li&gt;Unbuffered + large payloads? Watch out for timeouts and dropped connections. Implement retry logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Handle them right, and unbuffered mode becomes your best friend for analytics-scale reads.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scene 6: The Hidden Switch Nobody Talks About - &lt;code&gt;HINT_READ_ONLY&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The second overlooked optimization is telling Doctrine itself to stop tracking entities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setHint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Doctrine\ORM\Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HINT_READ_ONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that hint, Doctrine hydrates entities but doesn't register them in the Unit of Work - meaning no change tracking, no dirty checks. It's important to understand that &lt;code&gt;HINT_READ_ONLY&lt;/code&gt; &lt;strong&gt;does not skip hydration&lt;/strong&gt; - entities are still created, they're just not managed. If you want zero hydration, use DBAL's &lt;code&gt;iterateAssociative()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You didn't touch a single entity. You just told Doctrine to stop worrying.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scene 7: Why People Think Doctrine Is Slow
&lt;/h2&gt;

&lt;p&gt;Because they don't understand &lt;strong&gt;what it's doing for them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Doctrine is like an F1 car in traffic. If you floor it in first gear, it will scream and overheat. But once you learn &lt;em&gt;how it shifts&lt;/em&gt;, it'll lap your hand-rolled SQL scripts without breaking a sweat.&lt;/p&gt;

&lt;p&gt;The ORM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hydrates entities lazily when asked.&lt;/li&gt;
&lt;li&gt;Tracks changes only if you let it.&lt;/li&gt;
&lt;li&gt;Wraps &lt;code&gt;flush()&lt;/code&gt; in its own transaction - you don't need to micromanage that.&lt;/li&gt;
&lt;li&gt;Lets you drop down to SQL when business logic allows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when someone says &lt;em&gt;"Doctrine can't handle large data sets"&lt;/em&gt;, what they really mean is &lt;em&gt;"I made Doctrine do something stupid."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Scene 8: The Trade-offs You Should Know
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Why it works&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minimal memory&lt;/td&gt;
&lt;td&gt;IDs-first iteration&lt;/td&gt;
&lt;td&gt;One entity hydrated at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balanced speed&lt;/td&gt;
&lt;td&gt;Chunked keyset pagination + clear()&lt;/td&gt;
&lt;td&gt;Keeps DB cursor hot and resets UoW per chunk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistency&lt;/td&gt;
&lt;td&gt;Snapshot IDs once&lt;/td&gt;
&lt;td&gt;Deterministic and repeatable batch re-runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk writes&lt;/td&gt;
&lt;td&gt;Raw DQL or SQL&lt;/td&gt;
&lt;td&gt;ORM overhead is &lt;strong&gt;negligible&lt;/strong&gt; in practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming reads&lt;/td&gt;
&lt;td&gt;DBAL &lt;code&gt;iterateAssociative()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No entity hydration at all - raw arrays, zero UoW&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Scene 9: Measure, Don't Assume
&lt;/h2&gt;

&lt;p&gt;Want proof? Take &lt;code&gt;memory_get_peak_usage(true)&lt;/code&gt; at checkpoints and log it.&lt;/p&gt;

&lt;p&gt;If your memory curve looks like a staircase, you're holding too many entities. If it's a flat line, you've nailed it.&lt;/p&gt;

&lt;p&gt;Doctrine isn't magic - but it's measurable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ... your batch logic ...&lt;/span&gt;
&lt;span class="nv"&gt;$elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;memory_get_peak_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Processed %d records in %.2fs (%.1f MB peak)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$peak&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;And for the love of your ops team - disable SQL logging in batch jobs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scene 10: Lessons From Production
&lt;/h2&gt;

&lt;p&gt;In one of our production pipelines, we process &lt;strong&gt;massive data volumes&lt;/strong&gt; daily using Doctrine - comfortably, predictably, and with modest memory use.&lt;/p&gt;

&lt;p&gt;Here's the operational frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Batch size: around 1 000 records per chunk&lt;/li&gt;
&lt;li&gt;Keyset: &lt;code&gt;(id)&lt;/code&gt; based pagination&lt;/li&gt;
&lt;li&gt;Database: MySQL 8&lt;/li&gt;
&lt;li&gt;Memory footprint: roughly 90 MB during steady-state processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The secret wasn't a custom ORM. It was discipline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flush()&lt;/code&gt; and &lt;code&gt;clear()&lt;/code&gt; in predictable batches&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;HINT_READ_ONLY&lt;/code&gt; for data that doesn't need tracking&lt;/li&gt;
&lt;li&gt;Drop down to DBAL or use &lt;code&gt;SELECT NEW DTO&lt;/code&gt; statements when you just need raw rows
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$em&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SELECT NEW App\\DTO\\ProductReport(p.id, p.name, p.price) FROM App\\Entity\\Product p'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toIterable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// lightweight read&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Avoid lazy-loading inside loops&lt;/li&gt;
&lt;li&gt;Monitor memory and runtime metrics per batch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottleneck was never Doctrine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scene 11: Anti-Patterns I Keep Seeing in Production
&lt;/h2&gt;

&lt;p&gt;Some of these will sound familiar. They're the patterns that quietly turn batch jobs into memory leaks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calling &lt;code&gt;findAll()&lt;/code&gt; or &lt;code&gt;-&amp;gt;getResult()&lt;/code&gt; on a 3-million-row table.&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;flush()&lt;/code&gt; &lt;em&gt;on every iteration&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Triggering lazy-loading in loops (N+1 queries multiplied by millions).&lt;/li&gt;
&lt;li&gt;Using offset-based pagination: &lt;code&gt;OFFSET 10M LIMIT 1k&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keeping the SQL logger enabled in background jobs.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick sanity checklist:&lt;/strong&gt;&lt;br&gt;
☑️ No offset-based pagination&lt;br&gt;
☑️ Predictable &lt;code&gt;flush()&lt;/code&gt; cadence&lt;br&gt;
☑️ No implicit lazy loads inside loops&lt;br&gt;
☑️ SQL logger turned off&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A single screenshot from your profiler after applying these rules is worth a thousand words.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scene 12: Operational BHP for Batch Jobs
&lt;/h2&gt;

&lt;p&gt;This is the stuff that separates scripts that &lt;em&gt;work locally&lt;/em&gt; from ones that &lt;em&gt;survive nightly runs in production&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency:&lt;/strong&gt; snapshot IDs or mark rows (&lt;code&gt;status&lt;/code&gt;, &lt;code&gt;processed_at&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry and resume:&lt;/strong&gt; pick up where you left off (based on the last processed ID).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpressure:&lt;/strong&gt; limit concurrency to avoid choking replicas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts and deferred constraints:&lt;/strong&gt; use them wisely depending on your database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the difference between &lt;em&gt;code that runs&lt;/em&gt; and &lt;em&gt;code that runs reliably&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scene 13: What It All Means
&lt;/h2&gt;

&lt;p&gt;Doctrine is a precision instrument. When used carelessly, it can make a mess; when handled with intent, it performs beautifully.&lt;/p&gt;

&lt;p&gt;Learn how it hydrates, buffers, and tracks changes - and use each feature for the job it's meant for. Once you do, Doctrine will handle even heavy workloads calmly and efficiently.&lt;/p&gt;

&lt;p&gt;It's not slow. It just rewards developers who understand how it works.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚀 Ready to Go Beyond Quick Fixes?
&lt;/h3&gt;

&lt;p&gt;If you liked this article, you'll love my upcoming course -&lt;br&gt;
&lt;strong&gt;Doctrine Efficiency Lab&lt;/strong&gt; 💻&lt;/p&gt;

&lt;p&gt;Learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;handle millions of records without custom SQL,&lt;/li&gt;
&lt;li&gt;debug ORM performance in real time,&lt;/li&gt;
&lt;li&gt;and stop Doctrine from being "that slow layer" in your stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎁 When you join the waitlist, you'll immediately get&lt;br&gt;
&lt;strong&gt;my free 10-Step Doctrine Optimization PDF&lt;/strong&gt; -&lt;br&gt;
a condensed version of everything we discussed here.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://doctrine-performance-list.doctrinelab.pl" rel="noopener noreferrer"&gt;&lt;strong&gt;Join the waitlist + get the guide&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;




</description>
      <category>php</category>
      <category>doctrine</category>
      <category>performance</category>
      <category>sql</category>
    </item>
    <item>
      <title>When Classes Do Too Much: Using LCOM to Spot 'God Classes' in PHP</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Fri, 22 Aug 2025 07:36:05 +0000</pubDate>
      <link>https://dev.to/jszutkowski/when-classes-do-too-much-using-lcom-to-spot-god-classes-in-php-13mf</link>
      <guid>https://dev.to/jszutkowski/when-classes-do-too-much-using-lcom-to-spot-god-classes-in-php-13mf</guid>
      <description>&lt;p&gt;Big, messy classes slow projects down and scare developers away.&lt;br&gt;
In this post I’ll show you how to use LCOM to spot those "god classes" in your PHP code - and how to break them into something leaner and easier to test.&lt;/p&gt;
&lt;h2&gt;
  
  
  LCOM in PHP – spotting classes that do too much
&lt;/h2&gt;

&lt;p&gt;Every project has them. The &lt;code&gt;UserManager&lt;/code&gt;, the &lt;code&gt;DataHelper&lt;/code&gt;, the dreaded &lt;code&gt;Utils&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Classes so big and messy that nobody wants to touch them. They grow over time, swallow more responsibilities, and eventually slow the whole team down.&lt;/p&gt;

&lt;p&gt;But how do you know when a class is doing too much?&lt;br&gt;&lt;br&gt;
Instead of guessing, you can measure it - with a metric called &lt;strong&gt;LCOM&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is LCOM?
&lt;/h2&gt;

&lt;p&gt;LCOM stands for &lt;strong&gt;Lack of Cohesion of Methods&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
In simple terms, it measures how much the methods in a class actually belong together.&lt;/p&gt;

&lt;p&gt;If most methods use the same set of properties, the class is cohesive - that’s good.&lt;br&gt;&lt;br&gt;
If methods work on completely different properties, the class is basically doing multiple jobs at once - that’s bad.&lt;/p&gt;

&lt;p&gt;High LCOM = low cohesion = a "god class" that tries to handle too many responsibilities.&lt;/p&gt;
&lt;h2&gt;
  
  
  LCOM flavours (LCOM1 vs LCOM2)
&lt;/h2&gt;

&lt;p&gt;There isn’t just one formula for LCOM. Over the years, different versions have been proposed.&lt;br&gt;&lt;br&gt;
The two most common are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LCOM1&lt;/strong&gt; – looks at &lt;strong&gt;pairs of methods&lt;/strong&gt;: the more pairs that share nothing, the higher the score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LCOM2&lt;/strong&gt; – groups methods into clusters that share at least one property; the number of clusters is the score.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Quick examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Messy class&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;foo()&lt;/code&gt; uses &lt;code&gt;$a,$d&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bar()&lt;/code&gt; uses nothing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;baz()&lt;/code&gt; uses &lt;code&gt;$b,$d&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qux()&lt;/code&gt; uses &lt;code&gt;$c&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&amp;gt; &lt;strong&gt;LCOM2&lt;/strong&gt; = 3 clusters (&lt;code&gt;{foo,baz}&lt;/code&gt;, &lt;code&gt;{bar}&lt;/code&gt;, &lt;code&gt;{qux}&lt;/code&gt;) -&amp;gt; &lt;strong&gt;3&lt;/strong&gt;&lt;br&gt;
  -&amp;gt; &lt;strong&gt;LCOM1&lt;/strong&gt; = 5 pairs don't share anything, 1 shares &lt;code&gt;$d&lt;/code&gt; (5 - 1 = 4) -&amp;gt; &lt;strong&gt;4&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cohesive class&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;All methods use &lt;code&gt;$a&lt;/code&gt; (one uses &lt;code&gt;$a,$b,$c,$d&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&amp;gt; &lt;strong&gt;LCOM2&lt;/strong&gt;: one cluster -&amp;gt; &lt;strong&gt;1&lt;/strong&gt;.&lt;br&gt;
  -&amp;gt; &lt;strong&gt;LCOM1&lt;/strong&gt;: every pair shares something -&amp;gt; &lt;strong&gt;0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both metrics point to the same thing (messy vs cohesive), but the values look different.&lt;/p&gt;
&lt;h3&gt;
  
  
  And beyond…
&lt;/h3&gt;

&lt;p&gt;There are also &lt;strong&gt;LCOM3&lt;/strong&gt; and &lt;strong&gt;LCOM4&lt;/strong&gt;, which tweak the maths further. I won’t dive into them here, but the key point is:&lt;br&gt;&lt;br&gt;
👉 different definitions can give different numbers, even for the same class. That’s why it’s best to treat LCOM as a &lt;strong&gt;relative signal&lt;/strong&gt; — spot the outliers, watch trends over time, don’t obsess over exact figures.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to measure LCOM in PHP
&lt;/h2&gt;

&lt;p&gt;The easiest way is to use static analysis tools such as &lt;strong&gt;&lt;a href="https://github.com/phpmetrics/PhpMetrics" rel="noopener noreferrer"&gt;phpmetrics&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
They scan your codebase and calculate LCOM values for each class.&lt;/p&gt;

&lt;p&gt;Example with phpmetrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php vendor/bin/phpmetrics &lt;span class="nt"&gt;--report-json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build/report.json src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phpmetrics also generates &lt;strong&gt;handy HTML reports with charts and diagrams&lt;/strong&gt;.&lt;br&gt;
These make it easy to spot classes with high LCOM values at a glance.&lt;/p&gt;

&lt;p&gt;If you just want to test it quickly without installing anything, you can use the &lt;a href="https://hub.docker.com/r/jakzal/phpqa/" rel="noopener noreferrer"&gt;phpqa Docker image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the one-liner I used to generate an HTML report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/project"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/tmp-phpqa:/tmp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; /project jakzal/phpqa &lt;span class="se"&gt;\&lt;/span&gt;
  phpmetrics &lt;span class="nt"&gt;--report-html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build/report.html src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the HTML report, open &lt;code&gt;report.html/oop.html&lt;/code&gt;.&lt;br&gt;
There you can sort classes by their &lt;strong&gt;LCOM value&lt;/strong&gt; (highest or lowest) to immediately find the worst offenders.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example in PHP code
&lt;/h2&gt;

&lt;p&gt;Let’s look at a simple class that shows poor cohesion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserManager&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$mailer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;addUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;sendNewsletter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// unrelated to user storage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;addUser&lt;/code&gt; and &lt;code&gt;findUser&lt;/code&gt; both use the &lt;code&gt;$users&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sendNewsletter&lt;/code&gt; uses &lt;code&gt;$mailer&lt;/code&gt; but has nothing to do with &lt;code&gt;$users&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means the class mixes two very different responsibilities. Tools will report a &lt;strong&gt;high LCOM&lt;/strong&gt; here, because the methods don’t share common data. In practice, this is a sign that you need to split the class.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to fix high LCOM
&lt;/h1&gt;

&lt;p&gt;When a class shows a high LCOM score, the remedy is almost always the same: &lt;strong&gt;split it into smaller, focused classes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the earlier &lt;code&gt;UserManager&lt;/code&gt; refactored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;addUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewsletterService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$mailer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;sendNewsletter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each class has a single responsibility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UserRepository&lt;/code&gt; handles storing and finding users.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NewsletterService&lt;/code&gt; deals with sending newsletters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes your &lt;strong&gt;design cleaner&lt;/strong&gt; and your &lt;strong&gt;tests simpler&lt;/strong&gt;: each class can be tested in isolation, with fewer dependencies and fewer edge cases to worry about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;LCOM might sound academic, but in practice it’s a simple early warning system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low LCOM (close to 0) -&amp;gt; cohesive class, one clear job.&lt;/li&gt;
&lt;li&gt;High LCOM -&amp;gt; class is mixing responsibilities, harder to maintain and test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need to obsess over the exact numbers. Use them to &lt;strong&gt;spot the outliers&lt;/strong&gt; in your codebase.&lt;/p&gt;

</description>
      <category>php</category>
      <category>cleanarchitecture</category>
      <category>codequality</category>
      <category>testing</category>
    </item>
    <item>
      <title>Benchmarking Array Merges in PHP: When to Optimise and Why It Matters</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Tue, 12 Aug 2025 20:05:08 +0000</pubDate>
      <link>https://dev.to/jszutkowski/benchmarking-array-merges-in-php-when-to-optimise-and-why-it-matters-2lf7</link>
      <guid>https://dev.to/jszutkowski/benchmarking-array-merges-in-php-when-to-optimise-and-why-it-matters-2lf7</guid>
      <description>&lt;h2&gt;
  
  
  The Warning That Started It All ⚠️
&lt;/h2&gt;

&lt;p&gt;Recently, while working on a PHP script that merged arrays inside a loop, I saw a warning from the PHP Inspections (EA Extended) tool:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;'array_merge(...)' is used in a loop and is a resource-greedy construction.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve learnt to trust this tool. In the past, its tips have led me to surprising performance discoveries — like in my earlier post about &lt;a href="https://dev.to/jszutkowski/static-vs-non-static-closures-in-php-a-surprising-benchmark-4ief"&gt;static vs non-static closures in PHP&lt;/a&gt;. So when it flagged this, I wanted to know: is merging arrays inside a loop really that bad? Or is it just one of those “best practices” that sound good but don’t matter much?&lt;/p&gt;

&lt;p&gt;The tool’s documentation also offered &lt;a href="https://github.com/kalessil/phpinspectionsea/blob/master/docs/performance.md#slow-array-function-used-in-loop" rel="noopener noreferrer"&gt;alternative approaches&lt;/a&gt; that might be faster. But I didn’t want to just take its word for it — I wanted proof. So I set out to run benchmarks in PHP 8.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Benchmark 🧪
&lt;/h2&gt;

&lt;p&gt;For this test, I used PHPBench. I created a benchmark class with different methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;array_merge()&lt;/code&gt; inside a loop.&lt;/li&gt;
&lt;li&gt;Spread operator (&lt;code&gt;...&lt;/code&gt;) inside a loop.&lt;/li&gt;
&lt;li&gt;Collecting data and merging once at the end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each scenario was tested with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique values.&lt;/li&gt;
&lt;li&gt;Repeated values.&lt;/li&gt;
&lt;li&gt;Multiple values per iteration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loop ran &lt;strong&gt;10,000 iterations&lt;/strong&gt; per test, and each test was repeated &lt;strong&gt;50 times&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmark Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Tests\App\Benchmark\MergeArrayInLoop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpBench\Attributes\Iterations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Iterations(50)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MergingArraysInALoopBench&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;int&lt;/span&gt; &lt;span class="no"&gt;ITERATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_merging_unique_in_a_loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_merging_non_unique_in_a_loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_merging_multiple_value_arrays_in_a_loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_merging_unique_in_a_loop_using_spread_operator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_merging_non_unique_in_a_loop_using_spread_operator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_merging_multiple_value_arrays_in_a_loop_using_spread_operator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_collecting_unique_and_merging_at_the_end&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_collecting_non_unique_and_merging_at_the_end&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bench_collecting_multiple_value_arrays_and_merging_at_the_end&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benchmark Results 📊
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------------------------+-----+-------------+-------------+--------------+-----------+-----------+
| subject                                                             | its | memory_real | memory_peak | memory_final | mode_time | mean_time |
+---------------------------------------------------------------------+-----+-------------+-------------+--------------+-----------+-----------+
| bench_merging_unique_in_a_loop                                      | 50  | 4.19mb      | 1.62mb      | 0.54mb       | 147.75ms  | 148.19ms  |
| bench_merging_non_unique_in_a_loop                                  | 50  | 4.19mb      | 1.62mb      | 0.54mb       | 148.62ms  | 148.35ms  |
| bench_merging_multiple_value_arrays_in_a_loop                       | 50  | 4.19mb      | 2.67mb      | 0.54mb       | 250.22ms  | 250.81ms  |
| bench_merging_unique_in_a_loop_using_spread_operator                | 50  | 4.19mb      | 1.62mb      | 0.54mb       | 147.14ms  | 147.53ms  |
| bench_merging_non_unique_in_a_loop_using_spread_operator            | 50  | 4.19mb      | 1.62mb      | 0.54mb       | 147.60ms  | 147.34ms  |
| bench_merging_multiple_value_arrays_in_a_loop_using_spread_operator | 50  | 4.19mb      | 2.67mb      | 0.54mb       | 248.54ms  | 248.48ms  |
| bench_collecting_unique_and_merging_at_the_end                      | 50  | 4.19mb      | 3.78mb      | 0.54mb       | 1.27ms    | 1.28ms    |
| bench_collecting_non_unique_and_merging_at_the_end                  | 50  | 2.10mb      | 1.62mb      | 0.54mb       | 0.64ms    | 0.66ms    |
| bench_collecting_multiple_value_arrays_and_merging_at_the_end       | 50  | 4.19mb      | 4.30mb      | 0.54mb       | 1.51ms    | 1.59ms    |
+---------------------------------------------------------------------+-----+-------------+-------------+--------------+-----------+-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the Results Show 🔍
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Merging inside the loop — whether with &lt;code&gt;array_merge()&lt;/code&gt; or spread — is &lt;strong&gt;much slower&lt;/strong&gt; (147–250 ms) than merging once at the end.&lt;/li&gt;
&lt;li&gt;The spread operator didn’t offer any speed advantage over &lt;code&gt;array_merge()&lt;/code&gt; in these cases.&lt;/li&gt;
&lt;li&gt;Merging once at the end was &lt;strong&gt;100x faster&lt;/strong&gt; (0.64–1.59 ms), but with a trade-off: higher peak memory usage (up to 4.3 MB).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical Takeaways 💡
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If speed matters most&lt;/strong&gt; – Gather data first, merge at the end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If memory is tight&lt;/strong&gt; – Merge in smaller steps to keep peak memory lower.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t expect magic from the spread operator&lt;/strong&gt; – It’s no faster here than &lt;code&gt;array_merge()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark in your own environment&lt;/strong&gt; – Hardware, PHP version, and dataset size can change results.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping It Up 🏁
&lt;/h2&gt;

&lt;p&gt;Performance advice should never be taken blindly. Tools like PHP Inspections are great at pointing out potential bottlenecks, but only real measurements will tell you if an optimisation is worth it in your situation.&lt;/p&gt;

</description>
      <category>php</category>
      <category>performance</category>
    </item>
    <item>
      <title>Static vs Non-Static Closures in PHP – A Surprising Benchmark</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Fri, 18 Jul 2025 20:08:19 +0000</pubDate>
      <link>https://dev.to/jszutkowski/static-vs-non-static-closures-in-php-a-surprising-benchmark-4ief</link>
      <guid>https://dev.to/jszutkowski/static-vs-non-static-closures-in-php-a-surprising-benchmark-4ief</guid>
      <description>&lt;p&gt;When PHP Inspections suggested to change my anonymous function in &lt;code&gt;array_map&lt;/code&gt; to a static one, I thought: &lt;em&gt;why bother?&lt;/em&gt; 🤷‍♂️ It seemed like a small style preference – not something that would really matter.&lt;/p&gt;

&lt;p&gt;But I got curious. 🔍 Could adding &lt;code&gt;static&lt;/code&gt; to a function actually make your code faster or use less memory? That simple question led me down a rabbit hole 🕳️ of Pull Requests, Stack Overflow threads, and performance benchmarks.&lt;/p&gt;

&lt;p&gt;This blog post tells the story of what I found – and why using &lt;code&gt;static&lt;/code&gt; might be a small habit that makes a big difference. 💡&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Request That Started It All
&lt;/h2&gt;

&lt;p&gt;While digging around, I found a &lt;a href="https://github.com/Ocramius/GeneratedHydrator/pull/83/files" rel="noopener noreferrer"&gt;GitHub Pull Request&lt;/a&gt; in the &lt;code&gt;Ocramius/GeneratedHydrator&lt;/code&gt; repository. In it, the author replaced normal anonymous functions with static ones – and claimed the change resulted in around &lt;strong&gt;15% faster execution&lt;/strong&gt;. ⚡&lt;/p&gt;

&lt;p&gt;This wasn’t a micro-optimisation just for fun. According to the author, removing access to &lt;code&gt;$this&lt;/code&gt; (which static closures do by default) helped PHP handle memory and execution more efficiently. 🧠&lt;/p&gt;

&lt;p&gt;It was the first sign that this so-called "style suggestion" might actually lead to meaningful performance improvements. 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;$this&lt;/code&gt; Matters in Closures
&lt;/h2&gt;

&lt;p&gt;Digging deeper, I found a great explanation on Stack Overflow. It turns out that one key difference with static closures is that they do &lt;strong&gt;not&lt;/strong&gt; bind to the current object context — meaning they don’t carry around a reference to &lt;code&gt;$this&lt;/code&gt;. 🧩&lt;/p&gt;

&lt;p&gt;Why does that matter?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The closure holding a reference to &lt;code&gt;$this&lt;/code&gt; might prevent garbage collection of that object, which in turn may also impact performance significantly."&lt;br&gt;
– &lt;a href="https://stackoverflow.com/questions/19899468/php-closures-why-the-static-in-the-anonymous-function-declaration-when-bindin/64481894#64481894" rel="noopener noreferrer"&gt;Stack Overflow&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In simpler terms: if a closure keeps a reference to its parent object, PHP might not free up that memory as quickly. 🐘 Over time, this can slow things down and eat up resources — especially if you’re creating a lot of closures in a loop.&lt;/p&gt;

&lt;p&gt;That’s exactly what the next example demonstrates. 🔬&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Benchmark That Proves the Point
&lt;/h2&gt;

&lt;p&gt;To see the difference for myself, I recreated a script shared on Stack Overflow. It creates many instances of a class and stores closures returned by a method — all without needing &lt;code&gt;$this&lt;/code&gt;. 🛠️&lt;/p&gt;

&lt;p&gt;Original source: &lt;a href="https://stackoverflow.com/questions/19899468/php-closures-why-the-static-in-the-anonymous-function-declaration-when-bindin/64481894#64481894" rel="noopener noreferrer"&gt;Stack Overflow&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LargeObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getItemProcessor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// some logic that doesn’t use $this&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$processors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LargeObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$processors&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$lo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getItemProcessor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;memory_get_usage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"This took %dms and %dMB of memory&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$memory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this with and without the &lt;code&gt;static&lt;/code&gt; keyword using PHP 8.4 and measured the results with PHPBench. 📊 Here’s what I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------------+-----+-------------+---------+---------+
| subject                    | its | memory_real | mode    | mean    |
+----------------------------+-----+-------------+---------+---------+
| benchUsingStaticKeyword    | 100 | 2.10mb      | 3.69ms  | 3.70ms  |
| benchNotUsingStaticKeyword | 100 | 71.30mb     | 17.03ms | 17.17ms |
+----------------------------+-----+-------------+---------+---------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s not a small gap – that’s &lt;strong&gt;more than 30x&lt;/strong&gt; more memory and &lt;strong&gt;over 4x&lt;/strong&gt; longer execution time without &lt;code&gt;static&lt;/code&gt;. 😳&lt;/p&gt;

&lt;p&gt;This convinced me: using &lt;code&gt;static&lt;/code&gt; where &lt;code&gt;$this&lt;/code&gt; isn’t needed is not just a style thing. It’s a performance win. 🏆&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating It with Rector
&lt;/h2&gt;

&lt;p&gt;After seeing how much of a difference &lt;code&gt;static&lt;/code&gt; can make, I started looking through my own project. 🧑‍💻 There were many closures that didn’t use &lt;code&gt;$this&lt;/code&gt; and could be made static. But going through them one by one? That would take ages. 😩&lt;/p&gt;

&lt;p&gt;So I turned to an old favourite: &lt;strong&gt;Rector&lt;/strong&gt; – a tool for automated PHP refactoring. 🤖&lt;/p&gt;

&lt;p&gt;It turns out Rector already has two rules that do exactly what I needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rectorphp/rector/blob/main/rules/CodingStyle/Rector/ArrowFunction/StaticArrowFunctionRector.php" rel="noopener noreferrer"&gt;StaticArrowFunctionRector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rectorphp/rector/blob/main/rules/CodingStyle/Rector/Closure/StaticClosureRector.php" rel="noopener noreferrer"&gt;StaticClosureRector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I added them to my Rector config and ran the tool on the whole codebase. It worked beautifully – all eligible closures were made static automatically. ✨&lt;/p&gt;

&lt;p&gt;To make this a permanent habit, I left the rules in the config. Now, every Pull Request runs Rector in &lt;code&gt;dry-run&lt;/code&gt; mode and fails if someone forgets to use &lt;code&gt;static&lt;/code&gt; where it’s possible. 🔄&lt;/p&gt;

&lt;p&gt;✅ Performance boost? Check.&lt;br&gt;
✅ No manual refactoring? Check.&lt;br&gt;
✅ Team-wide consistency? Check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What started as a minor suggestion from a code analysis tool turned into a valuable lesson in PHP performance. 📚&lt;/p&gt;

&lt;p&gt;By switching to &lt;code&gt;static&lt;/code&gt; closures when &lt;code&gt;$this&lt;/code&gt; isn't needed, you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;avoid holding unnecessary references, 🚫&lt;/li&gt;
&lt;li&gt;help PHP free memory more effectively, 🧹&lt;/li&gt;
&lt;li&gt;and get faster execution times. ⏱️&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a simple habit with real benefits – and thanks to tools like Rector, it’s easy to make it stick. 🧰&lt;/p&gt;

&lt;p&gt;Next time you’re writing a closure, stop and ask: &lt;em&gt;do I need &lt;code&gt;$this&lt;/code&gt; here?&lt;/em&gt; If not – make it static. ✅&lt;/p&gt;

</description>
      <category>php</category>
      <category>performance</category>
      <category>productivity</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Wed, 16 Apr 2025 07:12:01 +0000</pubDate>
      <link>https://dev.to/jszutkowski/-3b8b</link>
      <guid>https://dev.to/jszutkowski/-3b8b</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jszutkowski/mocking-api-requests-in-unit-tests-4j6o" class="crayons-story__hidden-navigation-link"&gt;Mocking API Requests in Unit Tests&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/jszutkowski" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F678224%2F8c3d4811-7e80-42cb-bf04-7a145ae11bcd.jpeg" alt="jszutkowski profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jszutkowski" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jarosław Szutkowski
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jarosław Szutkowski
                
              
              &lt;div id="story-author-preview-content-2380813" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jszutkowski" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F678224%2F8c3d4811-7e80-42cb-bf04-7a145ae11bcd.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jarosław Szutkowski&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/jszutkowski/mocking-api-requests-in-unit-tests-4j6o" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 4 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jszutkowski/mocking-api-requests-in-unit-tests-4j6o" id="article-link-2380813"&gt;
          Mocking API Requests in Unit Tests
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/php"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;php&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/phpunit"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;phpunit&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tdd"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tdd&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/jszutkowski/mocking-api-requests-in-unit-tests-4j6o" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/jszutkowski/mocking-api-requests-in-unit-tests-4j6o#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>php</category>
      <category>phpunit</category>
      <category>tdd</category>
      <category>api</category>
    </item>
    <item>
      <title>Mocking API Requests in Unit Tests</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Fri, 04 Apr 2025 18:47:41 +0000</pubDate>
      <link>https://dev.to/jszutkowski/mocking-api-requests-in-unit-tests-4j6o</link>
      <guid>https://dev.to/jszutkowski/mocking-api-requests-in-unit-tests-4j6o</guid>
      <description>&lt;p&gt;In many applications, it's common to send requests to external services to acquire various types of data. To ensure our code handles these responses properly, testing is essential. However, in a test environment, making actual calls to these external services is something we'd typically want to avoid. PHPUnit offers several ways to prevent this. In this blog post, I'll demonstrate how you can mock HTTP requests in your unit tests when you're using the GuzzleHttp client.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scenario Overview
&lt;/h1&gt;

&lt;p&gt;Let's consider a straightforward "geocoder" service that provides the coordinates of a location based on a given address. This geocoder leverages the GuzzleHttp client to communicate with an external service, Google Maps in this case, to retrieve the desired coordinates. Here's how the code is structured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Infrastructure\Geocoder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Domain\GeocoderInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Domain\ValueObject\Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Domain\ValueObject\Coordinates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleMapsGeocoder&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;GeocoderInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;string&lt;/span&gt; &lt;span class="no"&gt;ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://maps.googleapis.com/maps/api/geocode/json'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Client&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$apiKey&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Address&lt;/span&gt; &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Coordinates&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'query'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'address'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'components'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;\sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;'country:%s|locality:%s|postal_code:%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;postcode&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;\json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContents&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_THROW_ON_ERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'results'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$firstResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'results'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$firstResult&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'geometry'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'location_type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'ROOFTOP'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Coordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$firstResult&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'geometry'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'location'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'lat'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;$firstResult&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'geometry'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'location'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'lng'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Testing
&lt;/h1&gt;

&lt;p&gt;To test different scenarios, such as whether the response is correctly converted to a Coordinates object or if the response is empty, we need to mock the HTTP request. The simplest way to do this is by using the &lt;code&gt;MockHandler&lt;/code&gt; class from &lt;code&gt;GuzzleHttp&lt;/code&gt;. It lets us create a mock client that returns a predefined response. Here's how you can do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Handler\MockHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'handler'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockHandler&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can add a response to the handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nf"&gt;\json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'results'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'geometry'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'location'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s1"&gt;'lat'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'1.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s1"&gt;'lng'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="s1"&gt;'location_type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ROOFTOP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After we call external service, we can also check whether the request was made with the correct parameters by using &lt;code&gt;getLastRequest()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLastRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the whole unit test can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Tests\Infrastructure\Geocoder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Domain\ValueObject\Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Infrastructure\Geocoder\GoogleMapsGeocoder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Handler\MockHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Psr7\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PHPUnit\Framework\Attributes\Test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PHPUnit\Framework\TestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Http\Message\RequestInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleMapsGeocoderUnitTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;string&lt;/span&gt; &lt;span class="no"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'api-key'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;MockHandler&lt;/span&gt; &lt;span class="nv"&gt;$handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;GoogleMapsGeocoder&lt;/span&gt; &lt;span class="nv"&gt;$fixture&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'handler'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleMapsGeocoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Test]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;it_geocodes_address&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;countryCode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'UK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'London'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Buckingham Palace'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;postcode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'SW1A 1AA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$expectedLat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'51.5073509'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$expectedLng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'-0.1277583'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$responseData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'results'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'geometry'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s1"&gt;'location'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s1"&gt;'lat'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$expectedLat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s1"&gt;'lng'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$expectedLng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="s1"&gt;'location_type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ROOFTOP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;\json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$responseData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_THROW_ON_ERROR&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="nv"&gt;$coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$coordinates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$expectedLat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$coordinates&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$expectedLng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$coordinates&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$lastRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLastRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lastRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lastRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;assertRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RequestInterface&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Address&lt;/span&gt; &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$queryArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nf"&gt;\parse_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getQuery&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nv"&gt;$queryArray&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$queryArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$queryArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'address'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;\sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'country:%s|locality:%s|postal_code:%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;postcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$queryArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'components'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this blog post, I demonstrated how to mock HTTP requests in unit tests using the GuzzleHttp client. By using the &lt;code&gt;MockHandler&lt;/code&gt; class, we can create a mock client that returns predefined responses, allowing us to test different scenarios without making actual calls to external services. This approach is essential for ensuring our code handles responses correctly and efficiently.&lt;/p&gt;

</description>
      <category>php</category>
      <category>phpunit</category>
      <category>tdd</category>
      <category>api</category>
    </item>
    <item>
      <title>Using Arbitrary Precision For Calculations In PHP</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Fri, 10 May 2024 19:59:11 +0000</pubDate>
      <link>https://dev.to/jszutkowski/using-arbitrary-precision-in-php-mme</link>
      <guid>https://dev.to/jszutkowski/using-arbitrary-precision-in-php-mme</guid>
      <description>&lt;p&gt;Performing various calculations is an integral part of software development. Their accuracy often matters a lot. How to make sure that the calculations are correct? How to avoid rounding error? In PHP, when using float type, even simple calculations can lead to unexpected results. Not to look far, try to run the following code: &lt;code&gt;echo 0.1 + 0.2 - 0.3;&lt;/code&gt;. You'll probably be surprised by the result. The problem is that the float type is not precise enough to perform some calculations.&lt;/p&gt;

&lt;p&gt;However, there is a solution to this problem. In this article, I'll compare two external libraries that allow you to perform calculations with arbitrary precision. I'll also show you how to use them in your project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Brick Math
&lt;/h1&gt;

&lt;p&gt;The first library that allows to perform calculations with arbitrary precision is &lt;a href="https://github.com/brick/math" rel="noopener noreferrer"&gt;Brick Math&lt;/a&gt; in version &lt;code&gt;0.11.0&lt;/code&gt;. It's based on the &lt;a href="https://www.php.net/manual/en/book.gmp.php" rel="noopener noreferrer"&gt;GMP&lt;/a&gt; and &lt;a href="https://www.php.net/manual/en/book.bc.php" rel="noopener noreferrer"&gt;BC Math&lt;/a&gt; extensions. They are not required, but if they are installed, the library will use them to perform calculations and make them faster. If none of them is installed, the library will use its own implementation of the algorithms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;

&lt;p&gt;We have a few value objects available in the library, depending on the type of calculations we want to make. We have: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BigDecimal&lt;/code&gt; - for calculations with decimal numbers,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BigRational&lt;/code&gt; - for calculations with rational numbers,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BigInteger&lt;/code&gt; - for calculations with integers,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BigNumber&lt;/code&gt; - for calculations with numbers of any type. This class is abstract but has a static method &lt;code&gt;of()&lt;/code&gt; that allows you to create an instance of the appropriate class based on the passed value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The method &lt;code&gt;of()&lt;/code&gt; in all classes accepts instance of BigNumber objects, string, float and int values.&lt;br&gt;
Moreover, each of the class has it's own named constructors, like &lt;code&gt;BigInteger::fromBase&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Instantiation of value objects
&lt;/h2&gt;

&lt;p&gt;First of all, we have to create an appropriate value object. Let's create &lt;code&gt;BigInteger&lt;/code&gt; object from integer value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Brick\Math\BigInteger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999999999999999999999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1000000000000000000000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example we exceeded the maximum value of the native integer type, so the native int was implicitly converted to float. The &lt;code&gt;BigInteger&lt;/code&gt; object can handle such large numbers without any problems. We just have to create them with a string parameter instead of an integer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'999999999999999999999'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 999999999999999999999&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should be careful when creating &lt;code&gt;BigInteger&lt;/code&gt; object from non-int value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1.5'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// throws Brick\Math\Exception\RoundingNecessaryException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Calculations
&lt;/h2&gt;

&lt;p&gt;Now let's see how to perform calculations with &lt;code&gt;BigInteger&lt;/code&gt; objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;multipliedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'3'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dividedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rounding
&lt;/h2&gt;

&lt;p&gt;When using BigInteger, if for example a result of division is not an integer, the &lt;code&gt;RoundingNecessaryException&lt;/code&gt; exception will be thrown. We can avoid this by using the &lt;code&gt;dividedBy()&lt;/code&gt; method with the second parameter of RoundingMode const value, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'10'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dividedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RoundingMode&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HALF_UP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  PHPDecimal
&lt;/h1&gt;

&lt;p&gt;Another library that allows you to perform calculations with arbitrary precision is &lt;a href="https://github.com/php-decimal/php-decimal" rel="noopener noreferrer"&gt;PHPDecimal&lt;/a&gt;.&lt;br&gt;
Contrary to the previous lib, this one is not standalone - it requires &lt;code&gt;decimal&lt;/code&gt; extension to be installed. It's worth mentioning as it's a very powerful tool and has some pros that the previous lib doesn't have.&lt;/p&gt;
&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;

&lt;p&gt;It has one type of value object - &lt;code&gt;Decimal&lt;/code&gt; which is a wrapper for the &lt;code&gt;decimal&lt;/code&gt; extension.&lt;/p&gt;

&lt;p&gt;To create a &lt;code&gt;Decimal&lt;/code&gt; object, we use &lt;code&gt;new&lt;/code&gt; operator which takes string, int or other Decimal object as a parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$decimal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'999999999999999999999'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$decimal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 999999999999999999999&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite Decimal cannot be created from float, it can be weakly compared to float:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$decimal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'10'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$decimal&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$decimal&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Calculations
&lt;/h2&gt;

&lt;p&gt;The example from the previous lib can be rewritten as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'3'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the advantages of this lib over &lt;code&gt;brick/math&lt;/code&gt; is that operator overload can be used to make calculations. That definitely makes the code more readable when it comes to more complex calculations. The above example can be converted to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rounding
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Decimal&lt;/code&gt; has default rounding mode set to half-even (&lt;code&gt;Decimal::DEFAULT_ROUNDING&lt;/code&gt;). To change it, we can use variety of consts starting with &lt;code&gt;ROUND_&lt;/code&gt; prefix, e.g. &lt;code&gt;Decimal::ROUND_HALF_UP&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'10'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ROUND_HALF_UP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Which one to choose?
&lt;/h1&gt;

&lt;p&gt;Both libraries are very powerful and allow to perform calculations with arbitrary precision. However, they have some differences that may be important when choosing the right one for your project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Brick Math&lt;/code&gt; can be standalone and doesn't require any additional extensions to be installed (however installing one will speed up calculations). It's also more up-to-date. On the other hand, &lt;code&gt;PHPDecimal&lt;/code&gt; has some advantages over &lt;code&gt;Brick Math&lt;/code&gt; - it allows to use operator overload and is a bit simpler to use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Usage in the code
&lt;/h1&gt;

&lt;p&gt;Instead of using any of the above libraries in the code directly, it may be better to encapsulate it into a custom value object class. This way, we can easily change the library in the future without having to change the code in the whole project. This will make PHPDecimal's operator overload feature useless, but in case of changing a lib this will be much easier as no external code will leak out of the value object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;BigDecimal&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>php</category>
    </item>
    <item>
      <title>Pessimistic vs. Optimistic Locking in MySQL</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Wed, 10 Jan 2024 21:45:25 +0000</pubDate>
      <link>https://dev.to/jszutkowski/pessimistic-vs-optimistic-locking-in-mysql-3hgc</link>
      <guid>https://dev.to/jszutkowski/pessimistic-vs-optimistic-locking-in-mysql-3hgc</guid>
      <description>&lt;p&gt;When designing applications that use databases, we often encounter situations involving concurrent access to data. This can have various implications - the database state might become incorrect, or some data might be lost. To prevent such scenarios, we can use different methods of controlling access to resources, such as optimistic or pessimistic locking.&lt;/p&gt;

&lt;p&gt;Let's imagine a situation where two users are trying to update the same record using some application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdzwy881fs6htchqdwi2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdzwy881fs6htchqdwi2.png" alt="Diagram" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the diagram above, the first user retrieves a record from the account table and attempts to debit the account by $70 based on the received data. At the same time, the second user performs the same operation, based on the data fetched from database. After these two updates, the account balance will be negative, which we want to avoid for the purpose of this article. How can we prevent such situations at the database level?&lt;/p&gt;

&lt;p&gt;In MySQL 8, we can achieve this in two ways: by using pessimistic or optimistic locking.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pessimistic Locking
&lt;/h1&gt;

&lt;p&gt;In this type of locking, we prevent other users from performing operations on data tables or rows until the transaction is completed. Unlike optimistic locking, this mechanism is built into the database, granting us exclusive access to a specific resource. We distinguish between two types of locks: shared and exclusive. Since the example above concerns individual rows in the database, I'll focus here only on locking rows, not entire tables. Worth to mention that both table locking and individual record locking can be used in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of Locks
&lt;/h2&gt;

&lt;p&gt;As mentioned above, for row-level locking, we can use two types of locking - shared and exclusive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared lock
&lt;/h3&gt;

&lt;p&gt;Shared locking allows transactions that hold this type of lock to only read the same records. This means that other transactions cannot perform any operations on them, such as modifications or deletions.&lt;/p&gt;

&lt;p&gt;We can acquire this type of lock by adding &lt;code&gt;FOR SHARE&lt;/code&gt; at the end of the select query, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;owner_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SHARE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ending the transaction, either with &lt;code&gt;COMMIT&lt;/code&gt; or &lt;code&gt;ROLLBACK&lt;/code&gt;, releases the lock on the rows.  If another transaction tries to set an exclusive lock on the same record, it will have to wait until the shared lock is released.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exclusive lock
&lt;/h3&gt;

&lt;p&gt;This type of lock not only prevents modification but also prevents reading of locked records. If another transaction attempts to set a shared or exclusive lock on these rows, it will have to wait for the current transaction to release the lock.&lt;/p&gt;

&lt;p&gt;We establish the exclusive lock by adding &lt;code&gt;FOR UPDATE&lt;/code&gt; at the end of the select query, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;owner_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we retrieve such a record in another transaction without using any locking, it will be returned, but no operations can be performed on it until the lock is released.&lt;/p&gt;

&lt;h2&gt;
  
  
  Locking Ranges of Rows
&lt;/h2&gt;

&lt;p&gt;Above I showed how to lock individual records. MySQL also allows to lock entire ranges of data. For example, &lt;code&gt;SELECT * FROM accounts WHERE id &amp;gt; 1 FOR UPDATE&lt;/code&gt; locks all records with an id greater than 1 and prevents new records from being inserted.&lt;/p&gt;

&lt;p&gt;If we try to lock a range that has no records (max id = 10, and we try to lock id &amp;gt; 100), we can set a different lock on that range in another transaction.&lt;/p&gt;

&lt;p&gt;We can also establish such a lock using criteria other than the identifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'John'&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above query locks all rows where &lt;code&gt;first_name = John&lt;/code&gt;. Retrieving such a row or inserting a new one with the specified column value will not be permitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Locking Options
&lt;/h2&gt;

&lt;p&gt;For the above queries, or more specifically, the &lt;code&gt;FOR SHARE&lt;/code&gt; and &lt;code&gt;FOR UPDATE&lt;/code&gt; clauses, we can add two more options. The first one is &lt;code&gt;NOWAIT&lt;/code&gt;. This option prevents the query from waiting for locked rows to be released and throws an error if it fails to retrieve the data immediately. The second option is &lt;code&gt;SKIP LOCKED&lt;/code&gt;. It causes the query to retrieve only those rows that are not currently locked by another transaction.&lt;/p&gt;

&lt;p&gt;Examples of using these options are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'John'&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;NOWAIT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'John'&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dead locks
&lt;/h2&gt;

&lt;p&gt;With pessimistic locking, you may encounter a situation where multiple threads attempt to retrieve and lock the same row. If the row is not released within a specified time (default is 50 seconds in MySQL), waiting transactions will throw an error:&lt;br&gt;
&lt;code&gt;[40001][1205] Lock wait timeout exceeded; try restarting transaction&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Optimistic Locking
&lt;/h1&gt;

&lt;p&gt;Optimistic locking is not a feature of MySQL. This is a strategy where we take the version number of a record and check if it has changed when the data is updated. For example, let's say our account table has an additional column - &lt;code&gt;version&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;company_id&lt;/th&gt;
&lt;th&gt;balance&lt;/th&gt;
&lt;th&gt;version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We retrieve a record from the database using a select query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, when we want to update the record, we use the &lt;code&gt;version&lt;/code&gt; column, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the UPDATE clause, in WHERE section, we use the version number retrieved in the select query.&lt;/p&gt;

&lt;p&gt;The above approach ensures that if another thread updated the record after we retrieved it from the database, our update will fail because the version number will be different. This is the essence of the optimistic locking - if the number of updated rows is 1, we can consider the operation successful. If no rows were updated, we can for example handle this situation by throwing an exception in our application or handling it differently.&lt;/p&gt;

&lt;p&gt;The version doesn't necessarily have to be an integer. Other types, like dates or checksums, can be used for this purpose.&lt;/p&gt;

&lt;h1&gt;
  
  
  Which Locking to Choose?
&lt;/h1&gt;

&lt;p&gt;When it comes to selecting the appropriate locking strategy in MySQL, the decision largely depends on the specific use case and requirements of the application.&lt;/p&gt;

&lt;p&gt;Pessimistic locking may be a suitable choice in scenarios involving batch processing, particularly when multiple consumers are involved. In such cases, where each row needs to be processed, it is crucial to prevent multiple consumers from selecting the same row simultaneously. By using the &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; clause, you can ensure that only one consumer locks and processes a specific row, while others skip over it and proceed to the next available row. This approach helps maintain data integrity and prevents conflicts in batch processing scenarios.&lt;/p&gt;

&lt;p&gt;On the other hand, optimistic locking may be a preferred strategy when there is no risk of repeating the operation causing side effects. It is particularly suitable when your application does not involve calling external APIs or sending emails during the operation. In such cases, conflicts resulting from optimistic locking are unlikely to have significant consequences. By allowing multiple transactions to access and modify the same data concurrently, optimistic locking improves efficiency and performance, especially in scenarios where conflicts are infrequent.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The choice between pessimistic and optimistic locking depends on factors such as the expected contention level, the criticality of data integrity, and the performance requirements of your application. Optimistic locking is suitable when conflicts are infrequent, and efficiency is a priority. On the other hand, pessimistic locking ensures data integrity but may impact performance in highly concurrent environments. Understanding the characteristics and demands of your application will guide you in selecting the appropriate locking strategy.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>database</category>
    </item>
    <item>
      <title>Applying Content Security Policy in Symfony to Reduce XSS Risks</title>
      <dc:creator>Jarosław Szutkowski</dc:creator>
      <pubDate>Sun, 14 May 2023 18:29:28 +0000</pubDate>
      <link>https://dev.to/jszutkowski/applying-content-security-policy-in-symfony-to-reduce-xss-risks-5a4l</link>
      <guid>https://dev.to/jszutkowski/applying-content-security-policy-in-symfony-to-reduce-xss-risks-5a4l</guid>
      <description>&lt;p&gt;Protecting applications against XSS attacks is one of the most important things we can do to make them more secure. In this post, I'm going to show you how to configure Content Security Policy in Symfony to reduce the risk of XSS attacks.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is XSS
&lt;/h1&gt;

&lt;p&gt;Cross-Site Scripting (XSS) attacks are one of the most common attacks on web applications. They involve injecting JavaScript code into a page that can be executed in the user's browser. This way, an attacker can take control of the user's session, steal data or perform other undesirable actions. &lt;/p&gt;

&lt;h1&gt;
  
  
  What is Content Security Policy
&lt;/h1&gt;

&lt;p&gt;There are many ways to defend against these types of attacks. One of them is the use of the Content-Security-Policy (CSP) header. This header, sent in response to a browser request, specifies what resources (scripts, styles, fonts, etc.) can be loaded on our website. Thanks to this, even if a hacker manages to inject malicious code into our page, a browser that supports CSP will effectively block its execution, reducing the risk of attack.&lt;/p&gt;

&lt;h1&gt;
  
  
  Different approaches of applying CSP
&lt;/h1&gt;

&lt;p&gt;If we want to apply this method of securing our application, we can do it ourselves by adding a header to the response or use a popular library, NelmioSecurityBundle, which has many other security options. In this post, I'm going to focus on the second method.&lt;/p&gt;

&lt;h1&gt;
  
  
  Before we start
&lt;/h1&gt;

&lt;p&gt;I've prepared an example HTML code to illustrate how CSP will affect our website. Let's take a quick look at it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"font-weight: bold;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://code.jquery.com/jquery-3.6.4.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click me!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It involves loading a few external resources like styles and scripts for Bootstrap and jQuery, as well as using inline styles and scripts. In the next steps, I'm going to show you how to set up CSP and how it can impact our website.&lt;/p&gt;

&lt;p&gt;I'll be using Chrome console to display any violation of CSP.&lt;/p&gt;

&lt;p&gt;Without any CSP policy configured, the console should not report any violations and the button from above HTML should look like this after all styles and scripts are loaded and executed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuj3vd2zab1izwtgt0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuj3vd2zab1izwtgt0n.png" alt="Image description" width="175" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring Content Security Policy
&lt;/h1&gt;

&lt;p&gt;To begin using CSP, we need to add a few lines to &lt;code&gt;config/packages/nelmio_security.yaml&lt;/code&gt; file. I'm using version &lt;code&gt;3.0.0&lt;/code&gt; of NelmioSecurityBundle. We'll start with the most restrictive configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;nelmio_security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;csp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enforce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;report-uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%router.request_context.base_url%/nelmio/csp/report'&lt;/span&gt;
      &lt;span class="na"&gt;default-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;
      &lt;span class="na"&gt;script-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;self'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code configures CSP with &lt;code&gt;enforce&lt;/code&gt; mode, which blocks all resources except for those that are defined on the lists.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;report-uri&lt;/code&gt; defines a URI where the browser should send a report if it detects a violation of CSP. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;default-src&lt;/code&gt; specifies the default or fallback resources allowed to be loaded on the page. In our case, we don't want to allow any undefined sources to be loaded, so we set it to &lt;code&gt;'none'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;script-src&lt;/code&gt; defines a list of script resources that are allowed to be loaded and executed. Let's use &lt;code&gt;'self'&lt;/code&gt; to only allow scripts from website's origin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With CSP configured like this, the browser should block all resources that are not allowed. Let's check it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zsvxwh2hgoa7xfm9r1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zsvxwh2hgoa7xfm9r1p.png" alt="Image description" width="109" height="44"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The browser has blocked all resources that are not listed. It also blocked scripts and styles that are defined in the HTML file. According to this, the styles from Bootstrap were not applied to the button. Our styles did not change the colour of the text and did not make it bold. And our script did not change the text on the button. This is because we did not define them on &lt;code&gt;script-src&lt;/code&gt; and &lt;code&gt;style-src&lt;/code&gt; lists.&lt;/p&gt;

&lt;p&gt;The console displayed the following error messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refused to load the stylesheet 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'style-src-elem' was not explicitly set, so 'default-src' is used as a fallback.

Refused to apply inline style because it violates the following Content Security Policy directive: "default-src 'none'". Either the 'unsafe-inline' keyword, a hash ('sha256-So78BYT2mbjtQqZqHbPQDdRiZpvjnGBwZCYxIdxMMOE='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback.

Refused to apply inline style because it violates the following Content Security Policy directive: "default-src 'none'". Either the 'unsafe-inline' keyword, a hash ('sha256-+YWRMZ88jMyO7jVlBA52tZADiPobPIUA8LAWee68Fvs='), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback.

Refused to load the script 'https://code.jquery.com/jquery-3.6.4.min.js' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

Refused to load the script 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-aAnMJGUIj4IqwyFSfCsQ989Go5ey5e7X+YACSD316t8='), or a nonce ('nonce-...') is required to enable inline execution.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser also tried to send the violations to the URL we defined in &lt;code&gt;report-uri&lt;/code&gt;. They contained information about what resources had been blocked. Below is a sample report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"csp-report"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"document-uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost/content-security-policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"referrer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"violated-directive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script-src-elem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"effective-directive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script-src-elem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"original-policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default-src &lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;0027none&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;0027; script-src &lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;0027self&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;0027; report-uri /nelmio/csp/report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"disposition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"enforce"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"line-number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source-file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost/content-security-policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status-code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"script-sample"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Defining a list of allowed resources
&lt;/h1&gt;

&lt;p&gt;To restore our website's functionality, we need to define a list of resources that can be fetched and executed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;nelmio_security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;csp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;enforce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;report-uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%router.request_context.base_url%/nelmio/csp/report'&lt;/span&gt;
            &lt;span class="na"&gt;default-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;
            &lt;span class="na"&gt;script-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;self'&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unsafe-inline'&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code.jquery.com'&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cdn.jsdelivr.net'&lt;/span&gt;
            &lt;span class="na"&gt;style-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cdn.jsdelivr.net'&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unsafe-inline'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After refreshing the page, we can see that the button is styled again, and the text has changed. The console isn't showing any errors, so everything seems to be working as expected.&lt;br&gt;
What's more, we can see a &lt;code&gt;Content-Security-Policy&lt;/code&gt; header in the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-inline' code.jquery.com cdn.jsdelivr.net; style-src cdn.jsdelivr.net 'unsafe-inline'; report-uri /nelmio/csp/report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Dealing with unsafe inline and unsafe eval
&lt;/h1&gt;

&lt;p&gt;As you might have noticed, we added &lt;code&gt;unsafe-inline&lt;/code&gt; value to the style and script lists. This is not a good practice because if someone manages to inject malicious code, it will be executed, effectively disabling the XSS protection mechanism of CSP.&lt;/p&gt;

&lt;p&gt;But what if we need to use inline scripts or styles? We can still do it, but we have to use &lt;code&gt;nonce&lt;/code&gt; or &lt;code&gt;hash&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;What is &lt;code&gt;nonce&lt;/code&gt;? It's a random string that is generated for each request. It is used to allow usage of inline scripts and styles. The same &lt;code&gt;nonce&lt;/code&gt; value has to be applied to script or style tag and to the &lt;code&gt;Content-Security-Policy&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;In our case, with Nelmio Security Bundle and Twig, we can use the &lt;code&gt;csp_nonce&lt;/code&gt; function in Twig to generate a nonce. &lt;/p&gt;

&lt;p&gt;After using &lt;code&gt;csp_nonce&lt;/code&gt; function for either script or style, &lt;code&gt;nonce&lt;/code&gt; will be generated and automatically applied to the &lt;code&gt;Content-Security-Policy&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;Then, we will be able to remove &lt;code&gt;unsafe-inline&lt;/code&gt; value from &lt;code&gt;script-src&lt;/code&gt; and &lt;code&gt;style-src&lt;/code&gt; lists. In fact, those values don't work along with &lt;code&gt;nonce&lt;/code&gt;, which means that if we added nonce to CSP list, &lt;code&gt;unsafe-inline&lt;/code&gt; will be ignored.&lt;/p&gt;

&lt;p&gt;We have to remember to add &lt;code&gt;nonce&lt;/code&gt; to each script and style tag. Otherwise, they will not be executed.&lt;/p&gt;

&lt;p&gt;You may ask what about inline styles? It's not possible to generate nonce for them, so they will be blocked. A workaround for this case is to move inline styles to external files or to style tag with &lt;code&gt;nonce&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;In practice, updated Twig template will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;nonce=&lt;/span&gt;&lt;span class="s"&gt;"{{ csp_nonce('style') }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://code.jquery.com/jquery-3.6.4.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;nonce=&lt;/span&gt;&lt;span class="s"&gt;"{{ csp_nonce('script') }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click me!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Content-Security-Policy header will now contain nonce values for styles and scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-inline' code.jquery.com cdn.jsdelivr.net 'nonce-G41nkEkLQJ77SLEx0j3cXA=='; style-src cdn.jsdelivr.net 'unsafe-inline' 'nonce-G41nkEkLQJ77SLEx0j3cXA=='; report-uri /nelmio/csp/report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Report-Only mode
&lt;/h1&gt;

&lt;p&gt;If we want to implement Content-Security-Policy protection into an existing, large application, we may not be able to list all the resources. In such a case, before we turn on enforce mode, we can use report-only mode. In this mode, the CSP header will report all violations, but it won't block any resource. This allows us to see what resources are used on the site and which ones we should add to the allowed list. To enable report-only mode, we need to change the &lt;code&gt;enforce&lt;/code&gt; value to &lt;code&gt;report&lt;/code&gt; in the configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;nelmio_security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;csp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;report-uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%router.request_context.base_url%/nelmio/csp/report'&lt;/span&gt;
      &lt;span class="na"&gt;default-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;
      &lt;span class="na"&gt;script-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;self'&lt;/span&gt;
      &lt;span class="na"&gt;style-src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;self'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The console will then display errors about CSP violations, but no resource will be blocked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Report Only] Refused to load the stylesheet 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css' because it violates the following Content Security Policy directive: "style-src 'self' 'unsafe-inline' 'nonce-jbOYi9qK+tahki7w9Yw7Cw=='". Note that 'style-src-elem' was not explicitly set, so 'style-src' is used as a fallback.

[Report Only] Refused to load the script 'https://code.jquery.com/jquery-3.6.4.min.js' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'nonce-jbOYi9qK+tahki7w9Yw7Cw=='". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

[Report Only] Refused to load the script 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'nonce-jbOYi9qK+tahki7w9Yw7Cw=='". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also worth noting that the &lt;code&gt;Content-Security-Policy&lt;/code&gt; header has changed to &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy-Report-Only: default-src 'none'; script-src 'self' 'unsafe-inline' 'nonce-jbOYi9qK+tahki7w9Yw7Cw=='; style-src 'self' 'unsafe-inline' 'nonce-jbOYi9qK+tahki7w9Yw7Cw=='; report-uri /nelmio/csp/report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An important thing to mention is that we can use both enforce and report-only modes at the same time. In that case, all resources not listed on the &lt;code&gt;Content-Security-Policy&lt;/code&gt; list will be blocked, and all resources not listed on &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; will be reported. &lt;/p&gt;

&lt;p&gt;For large applications, it's worth starting with report-only mode and configuring the URL to report violations under the &lt;code&gt;report-uri&lt;/code&gt; key. It can be an internal endpoint in our application. We can also use dedicated portals for this purpose, e.g. &lt;a href="https://report-uri.com/" rel="noopener noreferrer"&gt;report-uri.com&lt;/a&gt;. Then, after collecting and listing all resources, we can switch to enforce mode.&lt;/p&gt;

&lt;h1&gt;
  
  
  More info
&lt;/h1&gt;

&lt;p&gt;Managing scripts and styles is just an example. There are many more options that can be used to secure our application. We can manage the list of allowed resources for images, fonts, frames, and more. You can find more information about the Content-Security-Policy header in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy" rel="noopener noreferrer"&gt;MDN documentation&lt;/a&gt;. It's also worth visiting the &lt;a href="https://symfony.com/bundles/NelmioSecurityBundle/current/index.html#content-security-policy" rel="noopener noreferrer"&gt;Nelmio Security Bundle documentation&lt;/a&gt; to learn more about it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This post aimed to introduce the basic concepts related to Content-Security-Policy and show how to implement this policy in Symfony-based application. Implementing it will further secure our site against XSS attacks, as well as the loading of external resources. However, we must remember that this is not the only way of securing our application. It is also worth remembering other security measures, such as validating form fields to ensure they do not contain malicious content and displaying content in templates in an appropriate manner.&lt;/p&gt;

</description>
      <category>php</category>
      <category>symfony</category>
      <category>security</category>
    </item>
  </channel>
</rss>
