<?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: devTalk</title>
    <description>The latest articles on DEV Community by devTalk (@devtalk94).</description>
    <link>https://dev.to/devtalk94</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3228626%2F5495a7f0-c356-4253-865c-b83286ec1fe4.jpg</url>
      <title>DEV Community: devTalk</title>
      <link>https://dev.to/devtalk94</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devtalk94"/>
    <language>en</language>
    <item>
      <title>Laravel Eloquent Serialization: Hidden, Appends, Dates</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Fri, 23 Oct 2026 16:01:59 +0000</pubDate>
      <link>https://dev.to/devtalk94/laravel-eloquent-serialization-hidden-appends-dates-37c9</link>
      <guid>https://dev.to/devtalk94/laravel-eloquent-serialization-hidden-appends-dates-37c9</guid>
      <description>&lt;p&gt;The easiest Laravel API is one line: &lt;code&gt;return User::all();&lt;/code&gt;. Eloquent turns the models into JSON for you. That convenience is also where leaks and slow endpoints start: a password hash in the response, a date in a format your frontend does not expect, or an "extra" computed field that quietly fires a query per row. This article walks through how Eloquent serialization works in Laravel 13, using the official documentation as the map and a real database to test the parts that surprise people.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics: toArray() and toJson()
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'roles'&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;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;             &lt;span class="c1"&gt;// attributes + loaded relationships, recursively&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;attributesToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// attributes only, no relationships&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;// JSON string&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;JSON_PRETTY_PRINT&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="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// same as toJson()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I confirmed that casting a model to a string produced exactly the same text as &lt;code&gt;toJson()&lt;/code&gt;. According to the docs, models and collections returned from a route or controller are converted to JSON automatically. I did not run a real route, so that part is from the documentation.&lt;/p&gt;

&lt;p&gt;One detail that trips people up: a loaded relationship appears in the output under a snake_case key even though the method is camelCase. My &lt;code&gt;blogPosts()&lt;/code&gt; relationship showed up as &lt;code&gt;blog_posts&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hide sensitive attributes
&lt;/h2&gt;

&lt;p&gt;In Laravel 13 the documentation uses the &lt;code&gt;Hidden&lt;/code&gt; attribute (the older &lt;code&gt;$hidden&lt;/code&gt; property is the same idea):&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;Illuminate\Database\Eloquent\Attributes\Hidden&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;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Hidden(['password', 'remember_token'])]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test result: &lt;code&gt;toArray()&lt;/code&gt; returned &lt;code&gt;id, name, email, birthday, created_at, updated_at&lt;/code&gt; and no password or token. Two important limits, both confirmed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hidden only affects serialization.&lt;/strong&gt; &lt;code&gt;$user-&amp;gt;password&lt;/code&gt; still returned the hash, and &lt;code&gt;getAttributes()&lt;/code&gt; still contained it. It stops the leak in JSON output, nothing more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can hide a relationship by name too.&lt;/strong&gt; Hiding &lt;code&gt;blogPosts&lt;/code&gt; removed &lt;code&gt;blog_posts&lt;/code&gt; from the output.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Or use an allow list with Visible
&lt;/h2&gt;

&lt;p&gt;If you would rather list what &lt;em&gt;is&lt;/em&gt; allowed, use &lt;code&gt;#[Visible([...])]&lt;/code&gt;. With &lt;code&gt;#[Visible(['name', 'email'])]&lt;/code&gt; the output was exactly &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;. An allow list is safer for models that grow over time, because a new column added later stays out of the API until you add it on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change visibility at runtime, and the gotcha
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'password'&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;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&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;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mergeHidden&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&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;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setHidden&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'email'&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;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// replaces the whole hidden list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docs describe these as changes for "one instance", and that is the catch: they &lt;strong&gt;modify the model object itself&lt;/strong&gt;. I called &lt;code&gt;makeVisible('password')&lt;/code&gt; and then called &lt;code&gt;toArray()&lt;/code&gt; on the same object again, and the password was still there. A fresh query returned a clean model with the password hidden. So never reuse a model after exposing a field for one response, for example by caching it or passing it to other code.&lt;/p&gt;

&lt;p&gt;Also note that &lt;code&gt;setHidden(['email'])&lt;/code&gt; replaces the list, so the password and token came back, and only the email was hidden. Use &lt;code&gt;mergeHidden()&lt;/code&gt; when you want to add to the existing list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add computed values with appends
&lt;/h2&gt;

&lt;p&gt;A value that has no column needs an accessor first. Then add it to the output with the &lt;code&gt;Appends&lt;/code&gt; attribute, using the snake_case name:&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;Illuminate\Database\Eloquent\Attributes\Appends&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;Illuminate\Database\Eloquent\Casts\Attribute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Appends(['is_admin'])]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&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;isAdmin&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Attribute&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Attribute&lt;/span&gt;&lt;span class="o"&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;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'yes'&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;The output contained &lt;code&gt;is_admin: "yes"&lt;/code&gt;. For a single response you can use &lt;code&gt;$user-&amp;gt;append('is_admin')&lt;/code&gt; (also worked), and &lt;code&gt;$user-&amp;gt;withoutAppends()&lt;/code&gt; removed the field again. The docs add that appended values still respect your &lt;code&gt;Hidden&lt;/code&gt; and &lt;code&gt;Visible&lt;/code&gt; settings. In my test the allow-listed model, which listed only &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;, did not show an appended &lt;code&gt;is_admin&lt;/code&gt; either.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hidden cost: appends can create N+1 queries
&lt;/h3&gt;

&lt;p&gt;An appended accessor runs for every model in the result. If it touches the database, you pay for it once per row. I appended a &lt;code&gt;post_count&lt;/code&gt; accessor that called &lt;code&gt;$this-&amp;gt;blogPosts()-&amp;gt;count()&lt;/code&gt; and serialized 3 users: &lt;strong&gt;4 queries&lt;/strong&gt;, one for the users and one per user. With 500 users that is 501 queries. The alternative is to let the database count in the same query:&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;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blogPosts'&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;get&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;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// 1 query, and the output has a blog_posts_count field&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep appended accessors cheap and based on data the model already has.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control date formats
&lt;/h2&gt;

&lt;p&gt;By default, dates are serialized in ISO 8601 form. My &lt;code&gt;created_at&lt;/code&gt; came out as &lt;code&gt;2026-01-01T10:00:00.000000Z&lt;/code&gt;. To change it for a single column, give the cast a format:&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;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;casts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&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="s1"&gt;'birthday'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'date:Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'joined_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'datetime:Y-m-d H:00'&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;To change every date on the model, override &lt;code&gt;serializeDate()&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DateTimeInterface&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;serializeDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DateTimeInterface&lt;/span&gt; &lt;span class="nv"&gt;$date&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="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&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;With that override, &lt;code&gt;created_at&lt;/code&gt; became &lt;code&gt;2026-01-01&lt;/code&gt;. The stored database value of &lt;code&gt;birthday&lt;/code&gt; was unchanged by the cast format: formatting affects only array and JSON output, as the docs say.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to move to API resources
&lt;/h2&gt;

&lt;p&gt;The documentation itself points to Eloquent API resources for more robust JSON output. Serialization is great for internal tools and simple endpoints. Once your API response needs a stable shape that differs from your table structure, versioning, or conditional fields, a resource class is clearer than a pile of &lt;code&gt;makeHidden()&lt;/code&gt; calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Passwords, tokens and internal flags: &lt;code&gt;Hidden&lt;/code&gt;, or better, an allow list with &lt;code&gt;Visible&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Remember that hidden does not stop direct access like &lt;code&gt;$user-&amp;gt;password&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;makeVisible()&lt;/code&gt; and &lt;code&gt;makeHidden()&lt;/code&gt; change the object, so do not reuse it.&lt;/li&gt;
&lt;li&gt;Appended accessors run per row: avoid queries inside them.&lt;/li&gt;
&lt;li&gt;Date format: per-column in &lt;code&gt;casts()&lt;/code&gt;, or everywhere with &lt;code&gt;serializeDate()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://laravel.com/framework/docs/13.x/eloquent-serialization" rel="noopener noreferrer"&gt;Laravel 13.x documentation: Eloquent Serialization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-eloquent-serialization" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>eloquent</category>
      <category>serialization</category>
      <category>json</category>
    </item>
    <item>
      <title>Laravel Token Guard Auth Bypass: Check and Fix</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Fri, 23 Oct 2026 14:46:35 +0000</pubDate>
      <link>https://dev.to/devtalk94/laravel-token-guard-auth-bypass-check-and-fix-1bhh</link>
      <guid>https://dev.to/devtalk94/laravel-token-guard-auth-bypass-check-and-fix-1bhh</guid>
      <description>&lt;p&gt;A critical vulnerability in Laravel's built-in &lt;code&gt;token&lt;/code&gt; API guard lets an unauthenticated visitor log in as an existing user without knowing that user's token. The Laravel team fixed it in August 2026, but the advisory only went public on 19 October 2026, and details and a public proof of concept followed within days. It is rated CVSS 9.2. Most modern Laravel apps are not exposed, because the guard has to be switched on in your config, but if yours is, this is a same-day update. Below: who is affected, a 2-minute check, the fix, and why it happens. I confirmed the root cause on a real MariaDB database and compared the code before and after the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What:&lt;/strong&gt; authentication bypass in &lt;code&gt;Illuminate\Auth\TokenGuard&lt;/code&gt;, tracked as GHSA-f9x3-7vq9-cph6, CVSS v4.0 9.2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who:&lt;/strong&gt; apps that use a guard with &lt;code&gt;'driver' =&amp;gt; 'token'&lt;/code&gt; in &lt;code&gt;config/auth.php&lt;/code&gt;, with users stored in MySQL or MariaDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; update &lt;code&gt;laravel/framework&lt;/code&gt; to 13.29.0 or newer (13.x), or 12.68.0 or newer (12.x).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not listed as affected:&lt;/strong&gt; Sanctum and Passport, which register their own guards. The session guard is not listed either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exploitation:&lt;/strong&gt; a public proof of concept has been reported. As of the latest report I found (Cyber Kendra, 21 October), no attacks in the wild had been seen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: check whether you use the token guard
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Is a guard using the token driver?&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"'driver' =&amp;gt; 'token'"&lt;/span&gt; config/auth.php

&lt;span class="c"&gt;# 2. Which framework version is installed?&lt;/span&gt;
composer show laravel/framework | &lt;span class="nb"&gt;grep &lt;/span&gt;versions

&lt;span class="c"&gt;# 3. Does Composer already know about this advisory?&lt;/span&gt;
composer audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the first command prints nothing, you are not using the vulnerable guard. Also check routes protected with something like &lt;code&gt;auth:api&lt;/code&gt; and make sure that guard is not the token driver. Older Laravel skeletons (up to about Laravel 7) shipped an &lt;code&gt;api&lt;/code&gt; guard with the token driver, so long-lived apps are the most likely to still have it. If &lt;code&gt;composer audit&lt;/code&gt; lists GHSA-f9x3-7vq9-cph6, you have the confirmation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: update
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer update laravel/framework
composer show laravel/framework | &lt;span class="nb"&gt;grep &lt;/span&gt;versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want 13.29.0+ on Laravel 13 or 12.68.0+ on Laravel 12. Then deploy and restart your PHP workers. The advisory offers no other workaround, so do not treat a rule in a firewall as a replacement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Versions: what I verified in the source
&lt;/h3&gt;

&lt;p&gt;Sources disagree on version details, so I compared the actual code in the tagged releases. The fix is a type check in &lt;code&gt;TokenGuard.php&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;Release&lt;/th&gt;
&lt;th&gt;Has the &lt;code&gt;is_string()&lt;/code&gt; check?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;13.26.1 and older&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13.27.0, 13.28.0, 13.29.0&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12.67.0&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12.68.0&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11.56.0&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11.56.1&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10.49.0&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The code change appears from 13.27.0 on, but the advisory and Laravel News list 13.29.0 as the fixed version for Laravel 13, so move to 13.29.0 or newer and you are covered either way. The fix commit is dated 24 August 2026 and the releases 25 August 2026. On Laravel 11, one report says no patch exists while another lists 11.56.1; the source shows 11.56.1 does contain the check. On 10.x, the release I checked (10.49.0) has no fix, so check the advisory for your branch or upgrade to a supported version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;The explanation below follows the published analysis, and I tested each link in the chain.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before the fix, the guard only checked that the incoming token was &lt;em&gt;not empty&lt;/em&gt;. It did not check that it was a &lt;em&gt;string&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A non-string value, such as a list, passes that check and reaches the user lookup, where it is turned into a database query with a list of values.&lt;/li&gt;
&lt;li&gt;MySQL and MariaDB compare text columns with numbers by converting the text to a number, and text that does not start with a digit becomes 0.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 is easy to confirm yourself without any exploit. On MariaDB, with two users whose tokens start with letters, I ran three queries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Rows matched&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;where api_token = 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 (every user)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;where api_token in (0)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 (every user)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;where api_token = '0'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A numeric value matched every row, and the same value as text matched none. So a non-string "token" can match a real user's row, which is why the guard authenticates someone who never knew the real token. According to the analysis, the attacker cannot choose &lt;em&gt;which&lt;/em&gt; user they become, but that is little comfort when one of them is an admin. This is also why the advisory is specific to MySQL and MariaDB; I did not test other databases, so I cannot tell you they are safe.&lt;/p&gt;

&lt;p&gt;I also compared the guard code before (13.26.1) and after (13.29.0), using a stub user provider that records what reaches the database. Before the fix, a non-string token was passed to the provider and the guard resolved a user. After the fix, &lt;code&gt;validate()&lt;/code&gt; returned &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;user()&lt;/code&gt; returned &lt;code&gt;null&lt;/code&gt;, and nothing non-string reached the provider. That is a unit-level check with a stand-in provider, not a test against a running app.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about hashed tokens?
&lt;/h3&gt;

&lt;p&gt;If your guard has &lt;code&gt;'hash' =&amp;gt; true&lt;/code&gt;, an array token makes PHP's &lt;code&gt;hash()&lt;/code&gt; throw a &lt;code&gt;TypeError&lt;/code&gt; in the &lt;code&gt;user()&lt;/code&gt; path (I confirmed that call fails with an array). That produces an error instead of a match, but the advisory does not exempt hashed setups, so update regardless.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you can't update today
&lt;/h2&gt;

&lt;p&gt;There is no official workaround. The practical steps are to check whether you really use the guard, and if you do not need it, remove the token guard from &lt;code&gt;config/auth.php&lt;/code&gt; so no route can use it. In the longer term, consider moving API authentication to Sanctum, which is not listed as affected. Review access logs for unusual successful logins on token-protected routes, and treat accounts that never should have been reachable as suspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  A second, smaller fix in the same release
&lt;/h2&gt;

&lt;p&gt;Laravel News also reports a medium-severity issue (CVSS 6.5, GHSA-5wcm-3v2w-8362) in the JSON:API resources added in Laravel 12.45, where a read-only request could trigger a destructive operation on a related table. It is fixed in 13.27.0 and 12.68.0, so the same update covers it. I did not test this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://laravel-news.com/laravel-token-guard-auth-bypass" rel="noopener noreferrer"&gt;Laravel News: Critical Token Guard Auth Bypass Fixed in Laravel 13.29 and 12.68&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cyberkendra.com/2026/10/laravel-patches-critical-token-guard-auth-bypass.html" rel="noopener noreferrer"&gt;Cyber Kendra: Laravel Patches Critical Token Guard Auth Bypass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lilting.ch/en/articles/laravel-tokenguard-auth-bypass" rel="noopener noreferrer"&gt;Lilting: Laravel TokenGuard bypass on MySQL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-token-guard-authentication-bypass" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>security</category>
      <category>authentication</category>
      <category>vulnerability</category>
    </item>
    <item>
      <title>PHP file_exists() vs is_file(): Directory Bug Explained</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:18:42 +0000</pubDate>
      <link>https://dev.to/devtalk94/php-fileexists-vs-isfile-directory-bug-explained-32d2</link>
      <guid>https://dev.to/devtalk94/php-fileexists-vs-isfile-directory-bug-explained-32d2</guid>
      <description>&lt;p&gt;Here is a bug that passes every test until a user without a profile picture shows up. You build an image path from a folder and a filename, check &lt;code&gt;file_exists()&lt;/code&gt;, and serve the image. For users with no filename saved, the filename is empty, so the path points at the folder itself. The folder exists, so the check passes, and your code tries to serve a directory as if it were an image. The fix is one word: &lt;code&gt;is_file()&lt;/code&gt;. I reproduced the bug on PHP 8.3.6 so you can see exactly what happens next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug in four lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/uploads/avatars/'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$avatar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// this user never uploaded one&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$avatar&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// we get here, even though there is no image&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a PHP quirk, it is what the function is documented to do. The PHP manual describes &lt;code&gt;file_exists()&lt;/code&gt; as checking "whether a file or directory exists." The name says file, but the behavior is "anything at this path."&lt;/p&gt;

&lt;h2&gt;
  
  
  One detail the tip leaves out
&lt;/h2&gt;

&lt;p&gt;An empty string by itself does not pass: &lt;code&gt;file_exists('')&lt;/code&gt; returned &lt;code&gt;false&lt;/code&gt; in my test. The trouble starts when the empty value is &lt;strong&gt;joined to a directory path&lt;/strong&gt;. &lt;code&gt;'uploads/avatars/' . ''&lt;/code&gt; is just &lt;code&gt;'uploads/avatars/'&lt;/code&gt;, and that is a real folder. The same happens when the filename is &lt;code&gt;null&lt;/code&gt;: &lt;code&gt;(string) null&lt;/code&gt; is an empty string, so the join gives the same result. Real code hits this with things like &lt;code&gt;public_path('images/' . $user-&amp;gt;avatar)&lt;/code&gt; when &lt;code&gt;avatar&lt;/code&gt; is empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each function says about the same paths
&lt;/h2&gt;

&lt;p&gt;I ran four checks on a set of paths. This is the real output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;file_exists&lt;/th&gt;
&lt;th&gt;is_file&lt;/th&gt;
&lt;th&gt;is_dir&lt;/th&gt;
&lt;th&gt;is_readable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;uploads/avatars/&lt;/code&gt; (folder + empty name)&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;uploads/avatars/ada.png&lt;/code&gt; (real file)&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;uploads/avatars/nope.png&lt;/code&gt; (missing)&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;''&lt;/code&gt; (empty string)&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.&lt;/code&gt; and &lt;code&gt;..&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symlink to a file&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symlink to a folder&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken symlink&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things to notice. First, &lt;code&gt;is_readable()&lt;/code&gt; is not a fix: it is also true for the folder. Second, &lt;code&gt;is_file()&lt;/code&gt; follows symlinks, as the manual says, so a symlink to a real file still counts as a file. A broken symlink is false for everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the check wrongly passes
&lt;/h2&gt;

&lt;p&gt;The bug is not just a wrong boolean. I took the passing directory path and ran the usual next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;file_get_contents($dir)&lt;/code&gt; returned an empty string and emitted a warning: "Read of 12288 bytes failed with errno=21 Is a directory". Your page gets an empty image with a 200 status.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getimagesize($dir)&lt;/code&gt; returned &lt;code&gt;false&lt;/code&gt;, which can break code that expects an array.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;readfile($dir)&lt;/code&gt; returned &lt;code&gt;-1&lt;/code&gt; and printed nothing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unlink($dir)&lt;/code&gt; failed with "Is a directory". That is safe here, but a "delete the old file" step that quietly does nothing is its own bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a download or image endpoint, this means broken images, empty responses, or log files full of warnings, all from a check that looked correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$avatar&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;$avatar&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// safe: a real file with a name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// show the default avatar&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;is_file()&lt;/code&gt; returns true only for a regular file, so the folder case now correctly gives false. I added the explicit empty-name check as well, because it states the intent and avoids a pointless disk lookup, but &lt;code&gt;is_file()&lt;/code&gt; alone already fixes the bug. Use &lt;code&gt;is_dir()&lt;/code&gt; when you actually mean a folder, and keep &lt;code&gt;file_exists()&lt;/code&gt; for the rare case where either is acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laravel: same trap, same fix
&lt;/h2&gt;

&lt;p&gt;Laravel's helpers map straight onto these functions. In the framework source, &lt;code&gt;File::exists()&lt;/code&gt; calls &lt;code&gt;file_exists()&lt;/code&gt; and &lt;code&gt;File::isFile()&lt;/code&gt; calls &lt;code&gt;is_file()&lt;/code&gt;, so the same rule applies:&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;Illuminate\Support\Facades\File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;public_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// true for the folder when avatar is empty&lt;/span&gt;
&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;public_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;avatar&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;p&gt;The storage layer has a similar split. In the source, &lt;code&gt;Storage::exists()&lt;/code&gt; is documented as "Determine if a file or directory exists," and there are separate &lt;code&gt;fileExists()&lt;/code&gt; and &lt;code&gt;directoryExists()&lt;/code&gt; methods. I read this in the source and did not run it, because that needs the full Flysystem setup. If your path may be empty, use &lt;code&gt;fileExists()&lt;/code&gt; for files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more things worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Results are cached.&lt;/strong&gt; The manual notes that both functions cache their results within a request. If your script creates or deletes a file and checks again, call &lt;code&gt;clearstatcache()&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null&lt;/code&gt;&lt;strong&gt;is deprecated.&lt;/strong&gt; On PHP 8.3, &lt;code&gt;file_exists(null)&lt;/code&gt; and &lt;code&gt;is_file(null)&lt;/code&gt; both returned &lt;code&gt;false&lt;/code&gt; and raised a deprecation notice: "Passing null to parameter #1 ($filename) of type string is deprecated". Make sure your filename variable is a string.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/function.file-exists.php" rel="noopener noreferrer"&gt;PHP manual: file_exists()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/function.is-file.php" rel="noopener noreferrer"&gt;PHP manual: is_file()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/php-file-exists-vs-is-file" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>filesystem</category>
      <category>fileexists</category>
      <category>isfile</category>
    </item>
    <item>
      <title>structuredClone vs JSON.stringify vs Spread: Deep Copy Guide</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Thu, 22 Oct 2026 15:41:08 +0000</pubDate>
      <link>https://dev.to/devtalk94/structuredclone-vs-jsonstringify-vs-spread-deep-copy-guide-4mo6</link>
      <guid>https://dev.to/devtalk94/structuredclone-vs-jsonstringify-vs-spread-deep-copy-guide-4mo6</guid>
      <description>&lt;p&gt;Sooner or later every JavaScript developer copies an object, changes the copy, and finds the original changed too. The usual fixes are the spread operator, the old &lt;code&gt;JSON.parse(JSON.stringify(obj))&lt;/code&gt; trick, and the newer &lt;code&gt;structuredClone()&lt;/code&gt;. They look interchangeable. They are not, and the differences show up as bugs only when your data contains dates, sets, &lt;code&gt;undefined&lt;/code&gt;, or a circular reference. I ran all three on Node 22 and in Chromium, and checked the behavior against MDN, so you can see what each one really does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a plain copy is not enough
&lt;/h2&gt;

&lt;p&gt;Spread (&lt;code&gt;{...obj}&lt;/code&gt;) and &lt;code&gt;Object.assign&lt;/code&gt; make a &lt;strong&gt;shallow&lt;/strong&gt; copy: top-level values are copied, but nested objects are shared. I copied an object with spread and pushed to a nested array on the copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&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="nx"&gt;original&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// [ 1, 2, 3 ]  - the original changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your object has nested data and you plan to change it, you need a deep copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The JSON trick, and what it quietly destroys
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;JSON.parse(JSON.stringify(obj))&lt;/code&gt; works by turning your data into text and back. That means anything JSON cannot represent is changed or lost. These are real outputs from Node 22:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;JSON round-trip&lt;/th&gt;
&lt;th&gt;structuredClone()&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Becomes an ISO string&lt;/td&gt;
&lt;td&gt;Stays a real &lt;code&gt;Date&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;undefined&lt;/code&gt; property&lt;/td&gt;
&lt;td&gt;Key disappears&lt;/td&gt;
&lt;td&gt;Kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NaN&lt;/code&gt;, &lt;code&gt;Infinity&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Become &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Become &lt;code&gt;{}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Cloned correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RegExp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Becomes &lt;code&gt;{}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Cloned, flags kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BigInt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Throws a TypeError&lt;/td&gt;
&lt;td&gt;Cloned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circular reference&lt;/td&gt;
&lt;td&gt;Throws a TypeError&lt;/td&gt;
&lt;td&gt;Cloned, cycle preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function property&lt;/td&gt;
&lt;td&gt;Silently dropped&lt;/td&gt;
&lt;td&gt;Throws &lt;code&gt;DataCloneError&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Becomes &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Kept&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The dangerous rows are the silent ones. A &lt;code&gt;Date&lt;/code&gt; turning into a string, or a &lt;code&gt;Set&lt;/code&gt; turning into &lt;code&gt;{}&lt;/code&gt;, does not throw anything. Your code just breaks later, somewhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  structuredClone(): the built-in deep copy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-01-01T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;tags&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;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paid&lt;/span&gt;&lt;span class="dl"&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;gift&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;structuredClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                   &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It handles circular references too, and keeps shared references shared. When I cloned &lt;code&gt;{ a: shared, b: shared }&lt;/code&gt;, the clone's &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; were the same object, but a different object from the original &lt;code&gt;shared&lt;/code&gt;. MDN lists it as widely available in browsers since March 2022, and I confirmed it exists in Node 22 and in Chromium 141.&lt;/p&gt;

&lt;h2&gt;
  
  
  What structuredClone() cannot do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Functions throw.&lt;/strong&gt; &lt;code&gt;structuredClone({ fn() {} })&lt;/code&gt; raised a &lt;code&gt;DataCloneError&lt;/code&gt; (a &lt;code&gt;DOMException&lt;/code&gt;). The same happened for a Promise and a WeakMap. A Symbol value throws too, though a Symbol used as a key is silently skipped. In a browser, DOM nodes throw as well, which I confirmed with &lt;code&gt;document.body&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class instances lose their class.&lt;/strong&gt; I cloned &lt;code&gt;new User('ada')&lt;/code&gt; and got a plain object with &lt;code&gt;name&lt;/code&gt; only. It was not an &lt;code&gt;instanceof User&lt;/code&gt;, and the &lt;code&gt;greet()&lt;/code&gt; method was gone. MDN says the prototype chain is not walked or duplicated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getters become plain values.&lt;/strong&gt; An object with &lt;code&gt;get x() { return 5 }&lt;/code&gt; was cloned as &lt;code&gt;{ x: 5 }&lt;/code&gt;, a normal writable property. Property descriptors are not copied.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-enumerable properties are dropped.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to copy class instances, write a &lt;code&gt;clone()&lt;/code&gt; method on the class, or rebuild the instance from the cloned data with &lt;code&gt;new User(...)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it faster? Not in my tests
&lt;/h2&gt;

&lt;p&gt;You may read that &lt;code&gt;structuredClone&lt;/code&gt; is the fast option. On plain JSON-like data, it was not. Median of 5 runs, Node 22 on a small container:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;structuredClone&lt;/th&gt;
&lt;th&gt;JSON round-trip&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small object (about 10 values)&lt;/td&gt;
&lt;td&gt;4.0 µs&lt;/td&gt;
&lt;td&gt;1.9 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5,000 user objects&lt;/td&gt;
&lt;td&gt;14.6 ms&lt;/td&gt;
&lt;td&gt;10.2 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Chromium 141 showed the same ordering (4.9 vs 1.6 µs, and 16.7 vs 10.9 ms). So the JSON trick was faster on plain data in both. The numbers are from one machine and one data shape, so use them for direction, not as exact figures. The point: choose &lt;code&gt;structuredClone&lt;/code&gt; for correctness, not speed. If you are cloning in a hot loop, ask whether you need a deep copy at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should you use?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flat object, or you only change top-level values&lt;/td&gt;
&lt;td&gt;Spread: &lt;code&gt;{ ...obj }&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nested data, may contain dates, Sets, Maps, undefined or cycles&lt;/td&gt;
&lt;td&gt;&lt;code&gt;structuredClone()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain JSON-safe data and you want the fastest option&lt;/td&gt;
&lt;td&gt;JSON round-trip works, if you are sure of the data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contains functions or class instances you need to keep&lt;/td&gt;
&lt;td&gt;A custom clone, or a library such as lodash's &lt;code&gt;cloneDeep&lt;/code&gt; (not tested here)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copying state in React or similar&lt;/td&gt;
&lt;td&gt;Usually avoid deep copies: spread only the path you change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A bonus: moving large data without copying
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;structuredClone&lt;/code&gt; has a second argument for transferring data instead of copying it. I created an 8-byte &lt;code&gt;ArrayBuffer&lt;/code&gt; and cloned it with &lt;code&gt;{ transfer: [buffer] }&lt;/code&gt;. The clone had 8 bytes, and the original dropped to 0 bytes. It is useful for big binary data, but the original becomes unusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone" rel="noopener noreferrer"&gt;MDN: structuredClone()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm" rel="noopener noreferrer"&gt;MDN: The structured clone algorithm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/javascript-structuredclone-vs-json-deep-copy" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>structuredclone</category>
      <category>deepcopy</category>
      <category>json</category>
    </item>
    <item>
      <title>PHP Critical RCE Fix: Update to 8.5.12, 8.4.27, 8.3.36</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Thu, 22 Oct 2026 12:51:23 +0000</pubDate>
      <link>https://dev.to/devtalk94/php-critical-rce-fix-update-to-8512-8427-8336-11kp</link>
      <guid>https://dev.to/devtalk94/php-critical-rce-fix-update-to-8512-8427-8336-11kp</guid>
      <description>&lt;p&gt;On 21 October 2026 the PHP project released security updates for every supported branch, and the front page of php.net carries a banner that reads "Critical remote code execution vulnerabilities, update now." Two flaws can allow unauthenticated remote code execution, and both are rated CVSS v4.0 9.2. If you run PHP behind a web server, this is a patch-today item. This article covers what was announced, how to tell whether your server is covered (the version number can mislead you), how to update, and what to do if you truly cannot update right now. I stick to the official advisory and avoid anything that would help an attacker.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed versions:&lt;/strong&gt; PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerabilities:&lt;/strong&gt; CVE-2026-109618 and CVE-2026-109619, both unauthenticated remote code execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who is at risk:&lt;/strong&gt; per php.net, all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. The risk is highest for mod_php.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete fix:&lt;/strong&gt; update. php.net says there are no complete mitigations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHP 8.1 and older&lt;/strong&gt; get no updates from the PHP project. PHP 8.2 reaches end of life on 31 December 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the two vulnerabilities are
&lt;/h2&gt;

&lt;p&gt;This is how php.net describes them, in its own words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109618, RCE via out-of-memory corruption.&lt;/strong&gt; Out-of-memory handling can interrupt PHP while internal request data is only partly initialized, and request cleanup may then treat the invalid data as valid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109619, RCE via stale global state.&lt;/strong&gt; Request-owned internal state can survive after its memory has been freed, and a later request handled by the same worker may use that stale state before normal initialization resets it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical meaning: these bugs live in the PHP engine, not in your application. Laravel, WordPress, Symfony or hand-written code does not matter. The PHP team says that for mod_php there is a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs, so a web application firewall or tight application code does not save you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: find out what you actually run
&lt;/h2&gt;

&lt;p&gt;Do not trust one command. The CLI and the web server can use different PHP builds on the same machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the CLI&lt;/span&gt;
php &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# PHP-FPM (adjust for your version)&lt;/span&gt;
php-fpm &lt;span class="nt"&gt;-v&lt;/span&gt;          &lt;span class="c"&gt;# or: php-fpm8.3 -v&lt;/span&gt;

&lt;span class="c"&gt;# Apache: is mod_php loaded?&lt;/span&gt;
apachectl &lt;span class="nt"&gt;-M&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run containers, check inside the container (&lt;code&gt;docker exec &amp;lt;container&amp;gt; php -v&lt;/code&gt;), because the host's PHP is irrelevant to the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  The version number trap
&lt;/h3&gt;

&lt;p&gt;Linux distributions patch security bugs without changing the upstream version number. On the Ubuntu 24.04 machine I used for testing, &lt;code&gt;php -v&lt;/code&gt; reports &lt;code&gt;8.3.6&lt;/code&gt;, but the installed package is &lt;code&gt;8.3.6-0ubuntu0.24.04.11&lt;/code&gt; and its changelog lists security fixes from 2026. If you compare &lt;code&gt;8.3.6&lt;/code&gt; to &lt;code&gt;8.3.36&lt;/code&gt; you will think the server is ancient when it may be fully patched, or the reverse. For distro packages, the changelog is the real answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debian / Ubuntu&lt;/span&gt;
zcat /usr/share/doc/php8.3-cli/changelog.Debian.gz | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"CVE-2026-109618|CVE-2026-109619"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my test machine this printed nothing (count 0), which means that package had not received the fix yet. For RPM-based systems, &lt;code&gt;rpm -q --changelog php | grep CVE-2026-109618&lt;/code&gt; is the equivalent, but I did not run it. If the grep shows both CVEs, the package has the fix, whatever the version string says.&lt;/p&gt;

&lt;p&gt;For builds taken straight from php.net, the version is reliable. Here is a small helper that applies the fixed versions from the advisory (I ran it against a list of sample versions):&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;function&lt;/span&gt; &lt;span class="n"&gt;php_upstream_status&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;$v&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="nv"&gt;$fixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'8.5'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'8.5.12'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'8.4'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'8.4.27'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'8.3'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'8.3.36'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'8.2'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'8.2.35'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;array_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$v&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;2&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="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fixed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$branch&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="s1"&gt;'no upstream release for this branch: use a vendor backport or upgrade'&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="nb"&gt;version_compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fixed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'upstream version includes the fix'&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"older than &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$fixed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: update (or confirm a vendor backport)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;php_upstream_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;PHP_VERSION&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reported 8.3.35 and 8.4.26 as needing the update, and 8.3.36 and 8.4.27 as fixed. Treat it as a first filter for upstream builds, not proof for distro packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: update, then restart
&lt;/h2&gt;

&lt;p&gt;How you update depends on where PHP comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Distribution packages:&lt;/strong&gt; run your normal package update and check your vendor's notice. php.net says distributions and LTS vendors received the patches in advance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker:&lt;/strong&gt; rebuild your image from a patched base tag, not just restart the container. Confirm with &lt;code&gt;php -v&lt;/code&gt; inside it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built from source:&lt;/strong&gt; get the fixed release from php.net and rebuild.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHP 7.4, 8.0 and 8.1:&lt;/strong&gt; the PHP team provided minimal source backports, and some vendors ship backports of their own. Better: move to a supported branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the package is updated, &lt;strong&gt;restart the processes that serve requests&lt;/strong&gt; (PHP-FPM, or Apache for mod_php). A running worker keeps the old code in memory until it is restarted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting platforms and panels
&lt;/h3&gt;

&lt;p&gt;According to &lt;a href="https://laravel-news.com/php-critical-rce-security-releases" rel="noopener noreferrer"&gt;Laravel News&lt;/a&gt;: Laravel Cloud is patched in place; Forge needs a manual "Update PHP" (or an update over SSH, then confirm with &lt;code&gt;php -v&lt;/code&gt;); on Vapor, patched runtimes are live but you must redeploy. It also notes that there is no patch for PHP 8.0 or 8.1 on Forge and Vapor. &lt;a href="https://support.plesk.com/hc/en-us/articles/44397729587351-Vulnerability-CVE-2026-109618-and-CVE-2026-109619-Remote-code-execution-in-PHP-shipped-with-Plesk" rel="noopener noreferrer"&gt;Plesk&lt;/a&gt; lists 8.5.12, 8.4.27, 8.3.36 and 8.2.35 as fixed versions and says the PHP builds arrive through its normal update process. &lt;a href="https://www.inmotionhosting.com/support/news/php-critical-security-release-october-2026/" rel="noopener noreferrer"&gt;InMotion Hosting&lt;/a&gt; says it turned off &lt;code&gt;session.upload_progress.enabled&lt;/code&gt; on its managed servers as a precaution while patches roll out. Status changes quickly, so check your own provider's page.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you cannot update today
&lt;/h2&gt;

&lt;p&gt;php.net is clear that these measures reduce exposure and do not make an installation safe. Use them only to buy a few hours or days. This is the configuration from the advisory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;; php.ini  (apply before request parsing, then restart workers)
&lt;/span&gt;&lt;span class="py"&gt;memory_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;-1&lt;/span&gt;
&lt;span class="py"&gt;post_max_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;8M          ; 32M only if you need uploads&lt;/span&gt;
&lt;span class="py"&gt;upload_max_filesize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;8M    ; keep below post_max_size&lt;/span&gt;
&lt;span class="py"&gt;session.upload_progress.enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;fatal_error_backtraces&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0  ; PHP 8.5 only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per the advisory, the memory setting is for CVE-2026-109618, together with a container or cgroup memory limit as an availability boundary (and not &lt;code&gt;RLIMIT_AS&lt;/code&gt; or &lt;code&gt;RLIMIT_DATA&lt;/code&gt;). The upload-progress and backtrace settings are for CVE-2026-109619, and it also asks you to fix fatal errors and execution timeouts that an attacker can reach.&lt;/p&gt;

&lt;p&gt;The advisory says to set these "in a way applications can't override." I checked why on PHP 8.3.6, using each setting's access level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Who can change it&lt;/th&gt;
&lt;th&gt;Result of &lt;code&gt;ini_set()&lt;/code&gt; in my test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anywhere, including application code&lt;/td&gt;
&lt;td&gt;Succeeded (returned the old value)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;post_max_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;php.ini and server config only&lt;/td&gt;
&lt;td&gt;Returned &lt;code&gt;false&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session.upload_progress.enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-directory level (php.ini, server config, .user.ini)&lt;/td&gt;
&lt;td&gt;Returned &lt;code&gt;false&lt;/code&gt;; value stayed &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So application code can raise &lt;code&gt;memory_limit&lt;/code&gt; again at runtime. That is why the advisory suggests locking it with &lt;code&gt;php_admin_value&lt;/code&gt; in the FPM pool or Apache config. The upload-progress setting cannot be changed with &lt;code&gt;ini_set()&lt;/code&gt;, but it is a per-directory setting, which is why the advisory warns against relying on &lt;code&gt;.user.ini&lt;/code&gt; and asks for php.ini, the pool or the server configuration. &lt;code&gt;fatal_error_backtraces&lt;/code&gt; does not exist in PHP 8.3 (it showed as unavailable), consistent with the "PHP 8.5 only" note.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is anyone exploiting this?
&lt;/h2&gt;

&lt;p&gt;Sources differ in what they report. php.net says it has a demonstrated exploit for mod_php. The Laravel News article does not report observed attacks in the wild. InMotion says a public exploit appeared before the patch. I could not independently confirm active attacks, and you should not wait for confirmation. If you think a server may already be compromised, update first, then review web server and PHP logs, rotate credentials and keys that were readable by PHP, and restore from a known clean state where you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this affect Laravel specifically?
&lt;/h3&gt;

&lt;p&gt;The flaws are in PHP itself, so every PHP framework and CMS on an unpatched runtime is exposed. There is no Laravel or Composer package update that fixes it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about CLI-only scripts and queue workers?
&lt;/h3&gt;

&lt;p&gt;The advisory is about installations serving web requests. The CLI and web server commonly come from the same package, so updating covers both, and I would not leave any server on the old build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is PHP 8.2 still OK to run?
&lt;/h3&gt;

&lt;p&gt;8.2.35 contains the fix, but php.net notes PHP 8.2 reaches end of life on 31 December 2026 and gets no security fixes after that. Plan your upgrade now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/" rel="noopener noreferrer"&gt;PHP.net: Security advisory, 21 October 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://laravel-news.com/php-critical-rce-security-releases" rel="noopener noreferrer"&gt;Laravel News: PHP Security Releases Fix Two Critical RCE Vulnerabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.plesk.com/hc/en-us/articles/44397729587351-Vulnerability-CVE-2026-109618-and-CVE-2026-109619-Remote-code-execution-in-PHP-shipped-with-Plesk" rel="noopener noreferrer"&gt;Plesk: CVE-2026-109618 and CVE-2026-109619&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.inmotionhosting.com/support/news/php-critical-security-release-october-2026/" rel="noopener noreferrer"&gt;InMotion Hosting: PHP Security Release&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/php-critical-rce-security-update-cve-2026-109618" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rce</category>
      <category>phpfpm</category>
      <category>apache</category>
      <category>serveradministration</category>
    </item>
    <item>
      <title>Laravel integer:strict Validation Rule Explained</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Thu, 22 Oct 2026 02:43:06 +0000</pubDate>
      <link>https://dev.to/devtalk94/laravel-integerstrict-validation-rule-explained-553a</link>
      <guid>https://dev.to/devtalk94/laravel-integerstrict-validation-rule-explained-553a</guid>
      <description>&lt;p&gt;Here is a small surprise in Laravel validation. The &lt;code&gt;integer&lt;/code&gt; rule passes the string &lt;code&gt;"10"&lt;/code&gt;. For a form that is fine, because browsers send everything as text. For a JSON API it is a quiet contract break: your docs say &lt;code&gt;age&lt;/code&gt; is an integer, a client sends &lt;code&gt;"age": "10"&lt;/code&gt;, and you accept it. Laravel has a one-word fix, &lt;code&gt;integer:strict&lt;/code&gt;. I ran both rules against a table of values on Laravel 13.35.0 so you can see exactly what changes, including a few results I did not expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tip in one line
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&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;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'age'&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;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'integer:strict'&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;The Laravel documentation describes the &lt;code&gt;strict&lt;/code&gt; parameter as considering the field valid only if its type is &lt;code&gt;integer&lt;/code&gt;, so strings with integer values are invalid. It also notes that plain &lt;code&gt;integer&lt;/code&gt; does not check the PHP variable type, only that the input is accepted by PHP's &lt;code&gt;FILTER_VALIDATE_INT&lt;/code&gt;. The framework source shows the difference clearly: strict mode is &lt;code&gt;is_int($value)&lt;/code&gt;, and normal mode is &lt;code&gt;filter_var($value, FILTER_VALIDATE_INT) !== false&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually passes
&lt;/h2&gt;

&lt;p&gt;I ran each value through the real validator. Here are the results, with &lt;code&gt;10.0&lt;/code&gt; meaning a float and &lt;code&gt;"10"&lt;/code&gt; a string:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;integer&lt;/th&gt;
&lt;th&gt;integer:strict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10&lt;/code&gt; (int)&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;0&lt;/code&gt; (int)&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"10"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10.0&lt;/code&gt; (float)&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;" 10"&lt;/code&gt; (leading space)&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"+10"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"10.0"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"010"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;"abc"&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;td&gt;fail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tip is right: with &lt;code&gt;integer:strict&lt;/code&gt;, &lt;code&gt;"10"&lt;/code&gt;, &lt;code&gt;10.0&lt;/code&gt; and &lt;code&gt;true&lt;/code&gt; all fail, and only a real int passes. The surprise is on the left side. Plain &lt;code&gt;integer&lt;/code&gt; accepts &lt;code&gt;true&lt;/code&gt;, and also strings with a leading space or plus sign. So a client sending &lt;code&gt;{"quantity": true}&lt;/code&gt; gets through the normal rule. That is exactly the kind of input that ends up as &lt;code&gt;1&lt;/code&gt; in your database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where strict mode works, and where it breaks
&lt;/h2&gt;

&lt;p&gt;This is the part that can bite you. Strict mode checks the PHP type of the value, so it depends on how the data arrived:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JSON request body:&lt;/strong&gt; types survive. Decoding &lt;code&gt;{"age":10,"age_s":"10","age_f":10.0,"age_b":true}&lt;/code&gt; gave an int, a string, a float and a bool, so strict validation can tell them apart. This is the case it is built for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML forms and query strings&lt;/strong&gt; (&lt;code&gt;?page=2&lt;/code&gt;): every value is text. &lt;code&gt;integer:strict&lt;/code&gt; would reject all of them, including a perfectly normal &lt;code&gt;2&lt;/code&gt;. Use plain &lt;code&gt;integer&lt;/code&gt; there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one endpoint serves both JSON clients and form posts, do not apply strict mode to it blindly. Test it with the real kind of requests your clients send.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same idea for numeric and boolean
&lt;/h2&gt;

&lt;p&gt;The documentation shows a &lt;code&gt;strict&lt;/code&gt; parameter for two related rules, and I tested both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;numeric:strict&lt;/code&gt; rejects numeric strings. In my run &lt;code&gt;"10"&lt;/code&gt; failed, while &lt;code&gt;10&lt;/code&gt;, &lt;code&gt;10.0&lt;/code&gt; and &lt;code&gt;1.5&lt;/code&gt; passed. Use it for prices and amounts where you want a JSON number, not text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;boolean:strict&lt;/code&gt; accepts only real &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt;. Plain &lt;code&gt;boolean&lt;/code&gt; also accepted &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;"1"&lt;/code&gt; and &lt;code&gt;"0"&lt;/code&gt;, and all four failed in strict mode. Note that &lt;code&gt;"true"&lt;/code&gt; as a string fails both versions.
&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;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'quantity'&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;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'integer:strict'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'min:1'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'price'&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;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'numeric:strict'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'gift_wrap'&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;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'boolean:strict'&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;
  
  
  Missing and null values
&lt;/h2&gt;

&lt;p&gt;Strict mode does not change how required and nullable work. With &lt;code&gt;required|integer:strict&lt;/code&gt;, a missing field failed. A &lt;code&gt;null&lt;/code&gt; value failed unless the rule list included &lt;code&gt;nullable&lt;/code&gt;, and with &lt;code&gt;nullable|integer:strict&lt;/code&gt; it passed. Add &lt;code&gt;nullable&lt;/code&gt; only on fields that are really optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use it?
&lt;/h2&gt;

&lt;p&gt;For a public JSON API with documented types, yes. It gives clients a clear error the first time they send the wrong type, instead of letting sloppy data in and fixing it later. The honest trade-off: stricter rules mean some existing clients that send &lt;code&gt;"10"&lt;/code&gt; will start getting 422 errors. If that is a risk, roll it out on new endpoints first, or version the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://laravel.com/framework/docs/13.x/validation#rule-integer" rel="noopener noreferrer"&gt;Laravel 13.x documentation: Validation, integer rule&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-integer-strict-validation" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>validation</category>
      <category>api</category>
      <category>json</category>
    </item>
    <item>
      <title>8 Laravel Quick Tips You Can Use Today</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:49:15 +0000</pubDate>
      <link>https://dev.to/devtalk94/8-laravel-quick-tips-you-can-use-today-385f</link>
      <guid>https://dev.to/devtalk94/8-laravel-quick-tips-you-can-use-today-385f</guid>
      <description>&lt;p&gt;Some Laravel features are famous, and some sit in the docs for years while people write the same longer code around them. These eight are small, each takes under a minute to try, and each one removes either a chunk of code or a whole class of bug. I ran every example on Laravel 13.35.0 with a real database, so the outputs below are what actually came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. See the real SQL with toRawSql()
&lt;/h2&gt;

&lt;p&gt;You want to know what query a chain of conditions produces. Instead of reading the query log, ask the builder:&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;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'draft'&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reads'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&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;'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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&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;toRawSql&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// select * from `posts` where `status` = 'draft' and `reads` &amp;gt; 5 order by `id` asc limit 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The values are already filled in, so you can paste the result straight into a database client and run &lt;code&gt;EXPLAIN&lt;/code&gt; on it. The query builder also has &lt;code&gt;dumpRawSql()&lt;/code&gt; and &lt;code&gt;ddRawSql()&lt;/code&gt; for printing it in the middle of a chain (I confirmed these exist in the framework source; I ran &lt;code&gt;toRawSql()&lt;/code&gt; itself). Treat the output as debugging information only and keep it out of logs on production if the values may be private.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use sole() when exactly one row must exist
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;first()&lt;/code&gt; happily returns the first match, even when you expected only one. &lt;code&gt;sole()&lt;/code&gt; insists:&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;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'A'&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;sole&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// returns the model&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'B'&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;sole&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// two rows exist&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Z'&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;sole&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// none exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My results: one match returned the model, two matches threw &lt;code&gt;MultipleRecordsFoundException&lt;/code&gt; ("2 records were found."), and zero matches threw the usual &lt;code&gt;ModelNotFoundException&lt;/code&gt;. Good for lookups like "the one active subscription for this user" where a duplicate means your data is broken and you want to know now.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. isDirty() before saving, wasChanged() after
&lt;/h2&gt;

&lt;p&gt;These two get mixed up all the time. The test shows the difference:&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;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'New title'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isDirty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// true  (changed, not saved yet)&lt;/span&gt;
&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;wasChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// false&lt;/span&gt;

&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isDirty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// false (nothing pending now)&lt;/span&gt;
&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;wasChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// true  (it changed during the last save)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;isDirty()&lt;/code&gt; in code that runs before the save, and &lt;code&gt;wasChanged()&lt;/code&gt; afterwards, for example in an observer's &lt;code&gt;updated&lt;/code&gt; method when you only want to send an email if the status changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Copy a record with replicate()
&lt;/h2&gt;

&lt;p&gt;Need a "duplicate this post" button? &lt;code&gt;replicate()&lt;/code&gt; gives you an unsaved copy:&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;$copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;replicate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// exists = false, id = null, created_at = null&lt;/span&gt;
&lt;span class="nv"&gt;$copy&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Copy of '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$copy&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                &lt;span class="c1"&gt;// inserted as a new row&lt;/span&gt;

&lt;span class="nv"&gt;$copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;replicate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;   &lt;span class="c1"&gt;// skip the status attribute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The primary key and timestamps are not copied, and the title came across, as I verified. The array argument lists attributes to leave out. Relationships are not copied, so duplicate child rows yourself if you need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Update extra columns while incrementing
&lt;/h2&gt;

&lt;p&gt;A page-view counter often also needs a "last viewed" time. One query does both:&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;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reads'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'last_read_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The row afterwards had &lt;code&gt;reads = 1&lt;/code&gt; and the new &lt;code&gt;last_read_at&lt;/code&gt;. Because it is one atomic UPDATE, two requests arriving together do not overwrite each other the way &lt;code&gt;$post-&amp;gt;reads++; $post-&amp;gt;save();&lt;/code&gt; can. One thing to remember from the timestamps article: &lt;code&gt;increment()&lt;/code&gt; also bumps &lt;code&gt;updated_at&lt;/code&gt; unless you wrap it in &lt;code&gt;Model::withoutTimestamps()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Build optional filters with when()
&lt;/h2&gt;

&lt;p&gt;Instead of a stack of &lt;code&gt;if&lt;/code&gt; statements around your query:&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;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$status&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;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'min_reads'&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reads'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$min&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a null status and a minimum of 3, the SQL was just &lt;code&gt;select * from posts where reads &amp;gt;= 3&lt;/code&gt;. The empty filter added nothing. One catch: &lt;code&gt;when()&lt;/code&gt; skips a value that is falsy, so a filter value of &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;'0'&lt;/code&gt; is ignored. If zero is a valid choice, check explicitly with &lt;code&gt;-&amp;gt;when($min !== null, ...)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Run something once per object with once()
&lt;/h2&gt;

&lt;p&gt;If a method does expensive work and gets called several times, wrap the body in &lt;code&gt;once()&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;heavy&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="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&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;// expensive work&lt;/span&gt;
        &lt;span class="k"&gt;return&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;calculate&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;I called such a method three times on one object and the closure ran once. A second object ran its own closure once, so the cached result belongs to each object and does not leak between them. That also means it lasts only as long as the object does.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Turn on strict mode in development
&lt;/h2&gt;

&lt;p&gt;This is the one I would add first to any project. &lt;code&gt;Model::shouldBeStrict()&lt;/code&gt; switches on three safety checks together:&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="c1"&gt;// app/Providers/AppServiceProvider.php, in boot()&lt;/span&gt;
&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;shouldBeStrict&lt;/span&gt;&lt;span class="p"&gt;(&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;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each check did in my test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lazy loading:&lt;/strong&gt; looping over &lt;code&gt;Post::get()&lt;/code&gt; and touching &lt;code&gt;$post-&amp;gt;comments&lt;/code&gt; threw &lt;code&gt;LazyLoadingViolationException&lt;/code&gt; ("Attempted to lazy load [comments] on model [Post] but lazy loading is disabled."). That is the classic N+1 problem, caught during development. Loading a single model with &lt;code&gt;first()&lt;/code&gt; and then its relation did not throw, so the check targets models loaded together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silently dropped attributes:&lt;/strong&gt; &lt;code&gt;new Post(['title' =&amp;gt; 'x', 'reads' =&amp;gt; 5])&lt;/code&gt; where &lt;code&gt;reads&lt;/code&gt; is not fillable threw &lt;code&gt;MassAssignmentException&lt;/code&gt; instead of quietly ignoring it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing attributes:&lt;/strong&gt; selecting only &lt;code&gt;id&lt;/code&gt; and then reading &lt;code&gt;$post-&amp;gt;title&lt;/code&gt; threw &lt;code&gt;MissingAttributeException&lt;/code&gt; instead of returning &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running it only outside production (as above) is a common choice, because you want these mistakes to fail loudly while you build, not break a live page. After I turned strict mode off, &lt;code&gt;Post::with('comments')&lt;/code&gt; loaded the same data without any errors, which is the usual fix for a lazy loading violation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-quick-tips" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>eloquent</category>
      <category>tips</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Laravel #[Refreshes]: Refresh Attributes After Writes</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:30:51 +0000</pubDate>
      <link>https://dev.to/devtalk94/laravel-refreshes-refresh-attributes-after-writes-7fl</link>
      <guid>https://dev.to/devtalk94/laravel-refreshes-refresh-attributes-after-writes-7fl</guid>
      <description>&lt;p&gt;You create a model, and one of its columns is calculated by the database: a full name built from two fields, a slug, a total. You save it and read the value back. You get &lt;code&gt;null&lt;/code&gt;, even though the row in the database is correct. The model only knows what you gave it, and the database never told it what it computed. The Laravel 13 documentation has a small feature for exactly this: the &lt;code&gt;#[Refreshes]&lt;/code&gt; attribute. I ran it against a real MariaDB database to see what it does and what it costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, reproduced
&lt;/h2&gt;

&lt;p&gt;Here is a table with generated columns. &lt;code&gt;full_name&lt;/code&gt; is stored, and &lt;code&gt;slug&lt;/code&gt; is computed when read:&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;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'first_name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'last_name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'full_name'&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;storedAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"concat(first_name, ' ', last_name)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'slug'&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;virtualAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lower(replace(concat(first_name, '-', last_name), ' ', '-'))"&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;With a normal model, this happened:&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;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'first_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ada'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'last_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Lovelace'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// null  (the database has "Ada Lovelace")&lt;/span&gt;
&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eloquent ran one query, the INSERT, and did not read anything back. After an update it was the same: I changed the first name to Grace and saved, the database now said "Grace Lovelace", and the model still held &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: #[Refreshes]
&lt;/h2&gt;

&lt;p&gt;Tell the model which attributes to reload after a write. This is straight from the documentation:&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;Illuminate\Database\Eloquent\Attributes\Refreshes&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;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Refreshes('full_name')]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more than one, pass an array:&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="na"&gt;#[Refreshes(['full_name', 'slug'])]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results with the attribute in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After &lt;code&gt;User::create(...)&lt;/code&gt;, &lt;code&gt;full_name&lt;/code&gt; was &lt;code&gt;"Ada Lovelace"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;After changing &lt;code&gt;first_name&lt;/code&gt; to Grace and saving, &lt;code&gt;full_name&lt;/code&gt; was &lt;code&gt;"Grace Lovelace"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;['full_name', 'slug']&lt;/code&gt;, both values came back (&lt;code&gt;Alan Turing&lt;/code&gt; and &lt;code&gt;alan-turing&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Only the columns you list are loaded. In the single-column example, &lt;code&gt;slug&lt;/code&gt; was still &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The refreshed values do not leave the model marked as changed: &lt;code&gt;isDirty()&lt;/code&gt; returned &lt;code&gt;false&lt;/code&gt; afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it costs: one extra query per write
&lt;/h2&gt;

&lt;p&gt;This is the trade-off to know before you use it. I logged the SQL:&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;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="nv"&gt;`users`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`first_name`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`last_name`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&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="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="nv"&gt;`full_name`&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;`users`&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An update behaves the same way: the UPDATE, then a SELECT for just the listed columns. Reading the framework source, that SELECT uses the write connection, which is the safe choice when you have read replicas, because a replica may not have your new row yet. If nothing changed, &lt;code&gt;save()&lt;/code&gt; ran no queries at all, so the refresh did not add any either.&lt;/p&gt;

&lt;p&gt;The refresh also works with &lt;code&gt;increment()&lt;/code&gt;. I used a stored column &lt;code&gt;logins_x2&lt;/code&gt; defined as &lt;code&gt;logins * 2&lt;/code&gt; and listed it in the attribute. After &lt;code&gt;$user-&amp;gt;increment('logins', 5)&lt;/code&gt;, the model showed &lt;code&gt;logins = 5&lt;/code&gt; and &lt;code&gt;logins_x2 = 10&lt;/code&gt; using two queries (the UPDATE and the SELECT).&lt;/p&gt;

&lt;h2&gt;
  
  
  Refreshes vs. refresh()
&lt;/h2&gt;

&lt;p&gt;You could call &lt;code&gt;$user-&amp;gt;refresh()&lt;/code&gt; after saving. It reloads every column, not just the ones you care about, and you have to remember to call it at every write. &lt;code&gt;#[Refreshes]&lt;/code&gt; does it automatically for the columns you name, wherever the model is saved. Choose &lt;code&gt;refresh()&lt;/code&gt; if you only need the value in one place, and the attribute if the model should always be correct after writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use it, and when to skip it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use it&lt;/strong&gt; when your code or API response reads a generated column right after a write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip it&lt;/strong&gt; when nobody reads that column straight after saving. You would pay for a SELECT that nothing uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be careful&lt;/strong&gt; on bulk jobs. Creating 10,000 models one by one now means 20,000 queries. Bulk &lt;code&gt;insert()&lt;/code&gt; does not create model instances, so it is unaffected, but it also gives you no refreshed values.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does it work for columns set by triggers or database defaults?
&lt;/h3&gt;

&lt;p&gt;The docs describe it for generated columns. The mechanism reloads whatever columns you list, so it should work for any column the database fills in, but I only tested generated columns (stored and virtual).&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Laravel version has it?
&lt;/h3&gt;

&lt;p&gt;It is in the Laravel 13.x documentation and in the 13.35.0 source I tested. I did not check older releases, so on an older version use &lt;code&gt;refresh()&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://laravel.com/framework/docs/13.x/eloquent#refreshing-attributes-after-writes" rel="noopener noreferrer"&gt;Laravel 13.x documentation: Refreshing Attributes After Writes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-refreshes-attribute-generated-columns" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel13</category>
      <category>eloquent</category>
      <category>generatedcolumns</category>
      <category>attributes</category>
    </item>
    <item>
      <title>Laravel 13 #[Connection] Attribute: Per-Model Database</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:05:07 +0000</pubDate>
      <link>https://dev.to/devtalk94/laravel-13-connection-attribute-per-model-database-15cf</link>
      <guid>https://dev.to/devtalk94/laravel-13-connection-attribute-per-model-database-15cf</guid>
      <description>&lt;p&gt;Most Laravel apps talk to one database and never think about connections. Then a reporting database shows up, or a legacy system, or a read-only analytics replica, and suddenly a few models need to talk somewhere else. Laravel 13's Eloquent documentation shows a clean way to do it: a &lt;code&gt;#[Connection]&lt;/code&gt; attribute on the model class. I tested how it behaves with two real databases, including a few details the docs do not mention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic example
&lt;/h2&gt;

&lt;p&gt;First, define the extra connection in &lt;code&gt;config/database.php&lt;/code&gt; as you normally would, for example one called &lt;code&gt;reporting&lt;/code&gt;. Then point a model at 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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&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;Illuminate\Database\Eloquent\Attributes\Connection&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;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Connection('reporting')]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesSummary&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole change. Every query this model runs, reads and writes, goes to the &lt;code&gt;reporting&lt;/code&gt; connection. Without the attribute, a model uses your application's default connection. The documentation example uses the same pattern with &lt;code&gt;#[Connection('mysql')]&lt;/code&gt; on a &lt;code&gt;Flight&lt;/code&gt; model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tested
&lt;/h2&gt;

&lt;p&gt;I set up two MariaDB databases, one as the default and one as &lt;code&gt;reporting&lt;/code&gt;, each holding a table with the same name but different data, so any query that went to the wrong place was obvious. Results from the real Laravel 13.35.0 Eloquent classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model with &lt;code&gt;#[Connection('reporting')]&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Reads came from the reporting database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model with no attribute&lt;/td&gt;
&lt;td&gt;Used the default database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saving a new record on the attributed model&lt;/td&gt;
&lt;td&gt;Row landed in the reporting database, default untouched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attribute on a base class, child extends it&lt;/td&gt;
&lt;td&gt;Child used the reporting connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attribute plus &lt;code&gt;protected $connection = 'default'&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The property won&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Model::on('default')&lt;/code&gt; on an attributed model&lt;/td&gt;
&lt;td&gt;Overrode the attribute for that query&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Three details worth knowing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. You can use an enum instead of a string
&lt;/h3&gt;

&lt;p&gt;The attribute's constructor in the framework source accepts either a string or a PHP enum. I confirmed that a string-backed enum case resolves to the right connection:&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="n"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Db&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Reporting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'reporting'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;#[Connection(Db::Reporting)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesSummary&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nc"&gt;SalesSummary&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Db&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Reporting&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;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not shown on the docs page, so it comes from the source and my test, not the documentation. It saves you from typos in connection names scattered across models. Using it needs an enum you maintain yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Set it once on a base class
&lt;/h3&gt;

&lt;p&gt;If ten models live in the reporting database, do not repeat the attribute ten times. Put it on an abstract base class and extend that. My child model picked up the connection from its parent.&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="na"&gt;#[Connection('reporting')]&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportingModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&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;SalesSummary&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ReportingModel&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;DailyRevenue&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ReportingModel&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Relationships follow the parent unless the related model says otherwise
&lt;/h3&gt;

&lt;p&gt;This is the one that bites. I tested three setups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A parent on the default connection with a &lt;code&gt;hasMany&lt;/code&gt; to a plain child: the children came from the default database.&lt;/li&gt;
&lt;li&gt;The same parent loaded with &lt;code&gt;Parent::on('reporting')&lt;/code&gt;: the plain child came from &lt;strong&gt;reporting&lt;/strong&gt;. The relation inherits the parent's connection.&lt;/li&gt;
&lt;li&gt;A related model with its own &lt;code&gt;#[Connection('reporting')]&lt;/code&gt;, loaded from a default-connection parent: it used its own connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a child without an attribute follows whichever connection its parent was loaded from. If you want a related model to always use a specific database, give that model its own attribute. Also keep in mind that a single SQL query cannot span two separate database servers, so a relationship across connections is loaded with separate queries rather than a join. Avoid &lt;code&gt;whereHas&lt;/code&gt; and similar across models on different connections unless the tables really live in the same database server.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this is actually useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reporting or analytics database.&lt;/strong&gt; Heavy queries stay away from your main database. A good production setup is a read-only database user on that connection, so a bug cannot write to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy or third-party database.&lt;/strong&gt; Wrap the old tables in models without moving data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-database setups.&lt;/strong&gt; Different parts of the app, such as a billing database and a main database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The connection name is not in&lt;/strong&gt;&lt;code&gt;config/database.php&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; The attribute only names a connection, it does not create one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expecting transactions to span connections.&lt;/strong&gt; A transaction belongs to one connection, so work on two connections is not covered by one &lt;code&gt;DB::transaction()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting that the property wins.&lt;/strong&gt; If an old model still has &lt;code&gt;protected $connection&lt;/code&gt;, it overrides the attribute.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://laravel.com/framework/docs/13.x/eloquent#database-connections" rel="noopener noreferrer"&gt;Laravel 13.x documentation: Database Connections&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-13-connection-attribute" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel13</category>
      <category>eloquent</category>
      <category>database</category>
      <category>attributes</category>
    </item>
    <item>
      <title>Laravel Query Optimization: 4 Simple Fixes for Faster Apps</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Tue, 20 Oct 2026 02:02:20 +0000</pubDate>
      <link>https://dev.to/devtalk94/laravel-query-optimization-4-simple-fixes-for-faster-apps-43ok</link>
      <guid>https://dev.to/devtalk94/laravel-query-optimization-4-simple-fixes-for-faster-apps-43ok</guid>
      <description>&lt;p&gt;When a Laravel page or job feels slow, the database is the first suspect, and usually the right one. The good news is that you rarely need clever tricks. Most slowness comes from a few everyday habits: wrapping a column in a function so the database cannot use its index, doing arithmetic in PHP that the database does in milliseconds, writing rows one at a time, and loading a huge pile of rows into memory at once. I tested four of these on a table with one million orders (Laravel 13, MariaDB 10.11), so you can see how big the difference really is. (Selecting fewer columns, indexes and eager loading are covered in separate articles, so they are not repeated here.)&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Do not wrap an indexed column in a function
&lt;/h3&gt;

&lt;p&gt;This one catches many people because the code looks perfectly reasonable. &lt;code&gt;whereDate()&lt;/code&gt; is convenient, but it wraps the column in a function, and the database can no longer use the index to jump to the right rows:&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="c1"&gt;// Looks nice, scans the whole index&lt;/span&gt;
&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;whereDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2024-03-15'&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;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// select ... where date(`created_at`) = ?&lt;/span&gt;

&lt;span class="c1"&gt;// Same result, uses the index to jump to one day&lt;/span&gt;
&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2024-03-15 00:00:00'&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2024-03-16 00:00:00'&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;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both returned 2,880 orders, with an index on &lt;code&gt;created_at&lt;/code&gt; in place. The &lt;code&gt;whereDate&lt;/code&gt; version took about 172 ms and examined roughly 996,000 index entries. The range version took 1.3 ms and examined 2,880. The range also has a side benefit: it handles the end of the day correctly, with no confusion about midnight.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Let the database do the math
&lt;/h3&gt;

&lt;p&gt;It is tempting to fetch rows and then total them up in PHP, because Laravel collections make it so easy: &lt;code&gt;get()-&amp;gt;sum('total_cents')&lt;/code&gt;. The catch is that &lt;code&gt;get()&lt;/code&gt; builds a full model object for every row first, just so you can add up one number. The database can add the numbers itself and send back a single 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="c1"&gt;// Builds 250,000 models, then adds them up in PHP&lt;/span&gt;
&lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paid'&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;get&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;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total_cents'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The database does the addition, PHP gets one number&lt;/span&gt;
&lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paid'&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;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total_cents'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over 250,000 paid orders, both returned 12,500,250,000. The collection version took 5.8 seconds and peaked at 302 MB of memory. The database version took 0.15 seconds and 6 MB. The same applies to &lt;code&gt;count()&lt;/code&gt;, &lt;code&gt;avg()&lt;/code&gt;, &lt;code&gt;min()&lt;/code&gt; and &lt;code&gt;max()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Grouping is where it gets dramatic. Here is the total per status across all one million orders:&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="c1"&gt;// Loads 1,000,000 rows, then groups them in PHP&lt;/span&gt;
&lt;span class="nv"&gt;$totals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;select&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="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'total_cents'&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;get&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;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$rows&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total_cents'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// One query, four rows come back&lt;/span&gt;
&lt;span class="nv"&gt;$totals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;selectRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status, sum(total_cents) as total'&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;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&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;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PHP version took about 25.8 seconds and peaked above 1.1 GB. The database version took 1.1 seconds and 6 MB. Rule of thumb: if you only want a number or a summary, ask for the number or the summary, not the rows. One small thing to watch: the database can return sums as strings (my &lt;code&gt;pluck&lt;/code&gt; result came back as &lt;code&gt;"12500250000"&lt;/code&gt;), so cast to &lt;code&gt;int&lt;/code&gt; if you need a number.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Write in bulk, not one row at a time
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;create()&lt;/code&gt; or &lt;code&gt;save()&lt;/code&gt; is its own trip to the database. A few is fine. Thousands in a loop add up fast, because each one pays the full round trip and commit cost. I inserted 5,000 rows three ways:&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="c1"&gt;// 5,000 separate queries, each committed on its own&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;$rows&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Same 5,000 queries, but one commit at the end&lt;/span&gt;
&lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rows&lt;/span&gt;&lt;span class="p"&gt;)&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;$rows&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&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="c1"&gt;// 5 queries of 1,000 rows each&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rows&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="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$batch&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;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Queries&lt;/th&gt;
&lt;th&gt;Time for 5,000 rows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;create()&lt;/code&gt; in a loop&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;1.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;create()&lt;/code&gt; in one transaction&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;1.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;insert()&lt;/code&gt; in chunks of 1,000&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0.06 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Updates work the same way. I had 10,000 pending rows to mark as done. Looping over them and calling &lt;code&gt;save()&lt;/code&gt; took 5.0 seconds and ran 10,001 queries (one to read, 10,000 to write). A single mass update did the same job in 0.06 seconds with one query:&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="c1"&gt;// 10,001 queries&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Work&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&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;$work&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'done'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 1 query&lt;/span&gt;
&lt;span class="nc"&gt;Work&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&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;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'done'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a trade-off, and it is worth knowing before you switch. Bulk &lt;code&gt;insert()&lt;/code&gt; and mass &lt;code&gt;update()&lt;/code&gt; skip model events, so observers and listeners do not fire, and &lt;code&gt;insert()&lt;/code&gt; does not fill &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; for you (I confirmed both when comparing Eloquent and the query builder). If your app depends on those, add the timestamps to your rows yourself or keep the slower path for those tables. For plain data like imports, counters and status flags, bulk is the better choice. For inserts that may already exist, look at &lt;code&gt;upsert()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Process big tables in batches: chunkById, lazyById and cursor
&lt;/h3&gt;

&lt;p&gt;Calling &lt;code&gt;get()&lt;/code&gt; on a huge table loads every row as an object into memory at once. That is fine for hundreds of rows and dangerous for hundreds of thousands. For exports, cleanups and background jobs, Laravel gives you four ways to work through rows without holding them all at once. They are not equally good, so here is what I measured.&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="c1"&gt;// 1. Batches of rows, you handle each batch&lt;/span&gt;
&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&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="s1"&gt;'&amp;lt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300000&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;chunkById&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="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&lt;/span&gt;&lt;span class="p"&gt;)&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;$orders&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// do the work&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 2. One row at a time, loaded in batches behind the scenes&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&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="s1"&gt;'&amp;lt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300000&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;lazyById&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="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do the work&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 3. One query, one row at a time&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&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="s1"&gt;'&amp;lt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300000&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;cursor&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;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do the work&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 4. lazy(): like lazyById, but pages with offsets (usually the wrong choice)&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&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="s1"&gt;'&amp;lt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300000&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;lazy&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="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each test below ran in a fresh PHP process. The first set walks 300,000 narrow order rows. The second walks 5,000 articles with a body of roughly 30 KB each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;300,000 narrow rows&lt;/th&gt;
&lt;th&gt;5,000 wide rows (memory)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;get()&lt;/code&gt; (everything at once)&lt;/td&gt;
&lt;td&gt;5.4 s, 368 MB&lt;/td&gt;
&lt;td&gt;324 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chunkById()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.3 s, 8 MB&lt;/td&gt;
&lt;td&gt;38 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lazyById()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.4 s, 10 MB&lt;/td&gt;
&lt;td&gt;56 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cursor()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.0 s, 28 MB&lt;/td&gt;
&lt;td&gt;166 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lazy()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;13.4 s, 10 MB&lt;/td&gt;
&lt;td&gt;56 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  What the numbers say
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chunkById()&lt;/code&gt;&lt;strong&gt;and&lt;/strong&gt;&lt;code&gt;lazyById()&lt;/code&gt;&lt;strong&gt;are the good defaults.&lt;/strong&gt; They were the fastest and kept memory low. They work by asking for rows with an id greater than the last one seen, so each batch costs about the same no matter how deep you are. Pick &lt;code&gt;chunkById()&lt;/code&gt; if you want to handle a batch at a time, and &lt;code&gt;lazyById()&lt;/code&gt; if you prefer a simple &lt;code&gt;foreach&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cursor()&lt;/code&gt;&lt;strong&gt;is easy to misjudge.&lt;/strong&gt; It runs a single query and gives you one model at a time, so it sounds like the lightest option. On narrow rows it used 28 MB, but on the 30 KB articles it peaked at 166 MB, compared with 38 MB for &lt;code&gt;chunkById()&lt;/code&gt;. Memory grew with the size of each row, which is consistent with the database driver fetching the whole result into PHP before Laravel hands rows over one by one (I did not dig into the driver to confirm that). It was also slower than the batched methods on the 300,000-row run. Use it for narrow rows, and pair it with &lt;code&gt;select()&lt;/code&gt; so each row stays small when your table is wide.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lazy()&lt;/code&gt;&lt;strong&gt;is the odd one out.&lt;/strong&gt; It looks like &lt;code&gt;lazyById()&lt;/code&gt;, but it pages with offsets, so it re-skips more and more rows as it goes. It took 13.4 s against 3.4 s for &lt;code&gt;lazyById()&lt;/code&gt;, and it has a worse problem, shown next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The trap: changing the rows you are looping over
&lt;/h4&gt;

&lt;p&gt;A very common job is "find everything pending, process it, mark it done". I tested exactly that on 10,000 pending rows, updating each row's status inside the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lazy()      processed 5,000 of 10,000, 5,000 still pending
lazyById()  processed 10,000 of 10,000, 0 still pending
cursor()    processed 10,000 of 10,000, 0 still pending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;lazy()&lt;/code&gt;, every row that gets marked done disappears from the filter, so the next page's offset jumps over rows that were never processed. Half the work was silently skipped, with no error. The same offset-based behaviour applies to the plain &lt;code&gt;chunk()&lt;/code&gt; method, so use the &lt;code&gt;ById&lt;/code&gt; versions whenever your loop changes the column you filter on. &lt;code&gt;cursor()&lt;/code&gt; was safe here because the whole result was already fetched when the loop started.&lt;/p&gt;

&lt;h4&gt;
  
  
  Is this an optimization for web pages?
&lt;/h4&gt;

&lt;p&gt;Not really. These methods save memory for batch jobs: exports, imports, cleanups, queue jobs and artisan commands. For a page or API response, show a limited number of rows with pagination instead, and do not loop over a whole table inside a web request.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to find the slow queries in the first place
&lt;/h3&gt;

&lt;p&gt;Guessing is slow. Three tools you already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;toSql()&lt;/code&gt; shows the SQL a query builder will run, so you can see what you actually asked for.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;explain()&lt;/code&gt; asks the database how it plans to run the query. Look for &lt;code&gt;type: ALL&lt;/code&gt; and very large &lt;code&gt;rows&lt;/code&gt; numbers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB::listen()&lt;/code&gt; lets you log every query with its bindings and time. In my test, one callback printed &lt;code&gt;select count(*) as aggregate from orders where date(created_at) = ? | ["2024-03-15"] | 252.84 ms&lt;/code&gt; for the slow query above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick checklist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Compare dates with a range, not with a function on the column.&lt;/li&gt;
&lt;li&gt;Ask the database for totals and groups instead of loading rows to add them up.&lt;/li&gt;
&lt;li&gt;Insert and update in bulk, and remember that bulk skips model events.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;chunkById()&lt;/code&gt; or &lt;code&gt;lazyById()&lt;/code&gt; for batch jobs over more than a few thousand rows, and avoid &lt;code&gt;lazy()&lt;/code&gt; and &lt;code&gt;chunk()&lt;/code&gt; when you update the rows you filter on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frequently asked questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Is it safe to skip model events with bulk writes?
&lt;/h4&gt;

&lt;p&gt;It depends on your app. If you use observers for audit logs, search indexing or cache clearing, bulk writes will skip them. In that case, trigger the same work yourself after the bulk operation, or keep the per-row path for that model.&lt;/p&gt;

&lt;h4&gt;
  
  
  How big should an insert batch be?
&lt;/h4&gt;

&lt;p&gt;I used 1,000 rows per query, which worked well. Very large batches can hit the database's maximum packet size or placeholder limits, so start around 500 to 1,000 and measure.&lt;/p&gt;

&lt;h4&gt;
  
  
  Is Eloquent the reason my queries are slow?
&lt;/h4&gt;

&lt;p&gt;Usually not. Eloquent adds some overhead for building model objects, which is why large result sets use more memory, but most slow pages are caused by the query itself or by doing in PHP what the database should do.&lt;/p&gt;

&lt;h4&gt;
  
  
  Will caching fix a slow query?
&lt;/h4&gt;

&lt;p&gt;It can hide one, but the query stays slow every time the cache expires. Fix the query first, then cache what is still expensive.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-query-optimization-simple-fixes" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>eloquent</category>
      <category>mysql</category>
      <category>performance</category>
    </item>
    <item>
      <title>Laravel Default Attribute Values: $attributes Explained</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Mon, 19 Oct 2026 16:28:20 +0000</pubDate>
      <link>https://dev.to/devtalk94/laravel-default-attribute-values-attributes-explained-3e56</link>
      <guid>https://dev.to/devtalk94/laravel-default-attribute-values-attributes-explained-3e56</guid>
      <description>&lt;p&gt;Create a new Eloquent model, ask for &lt;code&gt;$flight-&amp;gt;delayed&lt;/code&gt;, and you get &lt;code&gt;null&lt;/code&gt;. Not &lt;code&gt;false&lt;/code&gt;, even if your database column defaults to false. That gap between "the database knows the default" and "the model knows the default" causes small, annoying bugs. Laravel gives you a one-property fix: &lt;code&gt;$attributes&lt;/code&gt;. This article shows how it works, where it stops helping, and the format mistake that throws an error. Everything here was run against Laravel 13.35.0 Eloquent and MariaDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic example
&lt;/h2&gt;

&lt;p&gt;Add an &lt;code&gt;$attributes&lt;/code&gt; array to your model:&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&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;Illuminate\Database\Eloquent\Model&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;Flight&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * The model's default values for attributes.
     *
     * @var array&amp;lt;string, mixed&amp;gt;
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'delayed'&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;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;This is the example from the Laravel documentation. A new instance now carries those values from the moment it is created, before anything touches the database:&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;$flight&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;Flight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// '[]' (or [] if you cast it, see below)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why the database default is not enough
&lt;/h2&gt;

&lt;p&gt;Say your migration has &lt;code&gt;$table-&amp;gt;boolean('delayed')-&amp;gt;default(false)&lt;/code&gt; and &lt;code&gt;$table-&amp;gt;string('status')-&amp;gt;default('scheduled')&lt;/code&gt;, but the model has no &lt;code&gt;$attributes&lt;/code&gt;. I ran 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="nv"&gt;$flight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flight&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// null  (the row in the database has 0)&lt;/span&gt;
&lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// null  (the row in the database has 'scheduled')&lt;/span&gt;

&lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// 'scheduled'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The insert never mentioned those columns, so the database filled them in, and the model object never found out. If you return that model straight from an API controller, your JSON says &lt;code&gt;null&lt;/code&gt; while the database says something else. Putting the same defaults in &lt;code&gt;$attributes&lt;/code&gt; removes the mismatch, and it also makes the value available on a fresh &lt;code&gt;new Flight&lt;/code&gt; for forms and validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule: use the raw, storable format
&lt;/h2&gt;

&lt;p&gt;The docs say values in &lt;code&gt;$attributes&lt;/code&gt; must be in the raw format, as if just read from the database. That is why the example uses the string &lt;code&gt;'[]'&lt;/code&gt;, not an empty array. If you cast the column, Laravel converts the raw value when you read 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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Flight&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&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;$attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'delayed'&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;span class="s1"&gt;'status'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'scheduled'&lt;/span&gt;&lt;span class="p"&gt;,&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;casts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&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="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'array'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'delayed'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'boolean'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;FlightStatus&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="c1"&gt;// a backed enum&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;In my test, a new instance returned &lt;code&gt;[]&lt;/code&gt; for &lt;code&gt;options&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt; for &lt;code&gt;delayed&lt;/code&gt;, and the enum case for &lt;code&gt;status&lt;/code&gt;, and a plain &lt;code&gt;save()&lt;/code&gt; wrote &lt;code&gt;'[]'&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;'scheduled'&lt;/code&gt; to the row. The mistake to avoid is putting &lt;code&gt;[]&lt;/code&gt; (a real array) in &lt;code&gt;$attributes&lt;/code&gt; next to an &lt;code&gt;array&lt;/code&gt; cast. I tried it, and just reading the attribute threw a &lt;code&gt;TypeError&lt;/code&gt; from &lt;code&gt;json_decode()&lt;/code&gt; because it received an array instead of a string. Use &lt;code&gt;'[]'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What defaults do not do
&lt;/h2&gt;

&lt;p&gt;These are the behaviors that surprise people, all confirmed by running them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Flight::create(['delayed' =&amp;gt; true])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your value wins over the default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Flight::create(['status' =&amp;gt; null])&lt;/code&gt; on a NOT NULL column&lt;/td&gt;
&lt;td&gt;The default is replaced by &lt;code&gt;null&lt;/code&gt; and the database rejects it (integrity constraint error, column cannot be null)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Flight::create(['options' =&amp;gt; null])&lt;/code&gt; on a nullable column&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;null&lt;/code&gt; is stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Flight::select('id')-&amp;gt;first()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defaults are not added. Only &lt;code&gt;id&lt;/code&gt; is present and &lt;code&gt;$flight-&amp;gt;delayed&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loading a row from the database&lt;/td&gt;
&lt;td&gt;Database values are used, the defaults do not override them&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second row is the important one. A model default only applies when the attribute is not set. If a request passes an explicit &lt;code&gt;null&lt;/code&gt;, it wins. Keep your validation in place and do not treat &lt;code&gt;$attributes&lt;/code&gt; as a safety net.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a default needs to be calculated
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;$attributes&lt;/code&gt; property holds fixed values only. A token, a reference number or anything based on the current time needs code. A &lt;code&gt;creating&lt;/code&gt; event works (I tested the same pattern with a generated code and it was saved on insert):&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;Illuminate\Support\Str&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;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;booted&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;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;creating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Flight&lt;/span&gt; &lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$flight&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="s1"&gt;'FL-'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;strtoupper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&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;For UUID or ULID primary keys, Laravel has dedicated traits (&lt;code&gt;HasUuids&lt;/code&gt; and &lt;code&gt;HasUlids&lt;/code&gt;), so you do not need this pattern for IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model default or database default?
&lt;/h2&gt;

&lt;p&gt;You can use both, and for important columns I would. They protect different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database default:&lt;/strong&gt; protects the data from every writer, including raw queries, imports, other apps and bulk &lt;code&gt;insert()&lt;/code&gt; calls that skip models.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$attributes&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt; makes the model object correct right away, with no refresh and no extra query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just keep the two values identical. If they drift apart, the model and the database will disagree about what "default" means, and that is a very hard bug to spot.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://laravel.com/framework/docs/13.x/eloquent#default-attribute-values" rel="noopener noreferrer"&gt;Laravel 13.x documentation: Default Attribute Values&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/laravel-default-attribute-values" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>eloquent</category>
      <category>models</category>
      <category>defaultvalues</category>
    </item>
    <item>
      <title>Best Linux Commands for Developers and Sysadmins</title>
      <dc:creator>devTalk</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:59:34 +0000</pubDate>
      <link>https://dev.to/devtalk94/best-linux-commands-for-developers-and-sysadmins-4enh</link>
      <guid>https://dev.to/devtalk94/best-linux-commands-for-developers-and-sysadmins-4enh</guid>
      <description>&lt;p&gt;Most "top Linux commands" lists give you &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;cd&lt;/code&gt; and &lt;code&gt;pwd&lt;/code&gt; and call it a day. You already know those. This list is organised by the problems you actually hit on a server: the disk is full, something is using your port, one endpoint is slow, a log has a million lines. Every command below was run on Ubuntu 24.04 with GNU coreutils, and the outputs are from those runs, using a small sandbox with a fake Laravel-style folder and a generated access log.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. "The disk is filling up": du and find
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;--max-depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 app | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="c"&gt;# 12K    app/public&lt;/span&gt;
&lt;span class="c"&gt;# 792K   app/storage&lt;/span&gt;
&lt;span class="c"&gt;# 2.9M   app/vendor&lt;/span&gt;
&lt;span class="c"&gt;# 3.7M   app&lt;/span&gt;

find app &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-size&lt;/span&gt; +500k &lt;span class="nt"&gt;-printf&lt;/span&gt; &lt;span class="s1"&gt;'%s %p\n'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;span class="c"&gt;# 3000000 app/vendor/big.bin&lt;/span&gt;
&lt;span class="c"&gt;# 800000 app/storage/logs/laravel.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command sizes each folder one level deep, and &lt;code&gt;sort -h&lt;/code&gt; understands &lt;code&gt;K&lt;/code&gt;, &lt;code&gt;M&lt;/code&gt; and &lt;code&gt;G&lt;/code&gt; suffixes so the biggest is last. The second lists individual large files. Run the first one at &lt;code&gt;/var&lt;/code&gt; or your project root and drill down. (&lt;code&gt;--max-depth&lt;/code&gt; is the GNU spelling; on macOS use &lt;code&gt;-d 1&lt;/code&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "I deleted the log but the space did not come back": lsof +L1
&lt;/h3&gt;

&lt;p&gt;If a process still has a file open when you delete it, the disk space stays allocated until that process closes it or exits. This is a classic cause of a full disk that &lt;code&gt;du&lt;/code&gt; cannot explain. List open files that have been unlinked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; +L1
&lt;span class="c"&gt;# tail  260  root  3r  REG  254,0  20000000  0  1001125  /path/to/big.log (deleted)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created a 20 MB file, kept it open with &lt;code&gt;tail -f&lt;/code&gt;, deleted it, and &lt;code&gt;lsof&lt;/code&gt; showed it with a link count of 0 and the word &lt;code&gt;(deleted)&lt;/code&gt;. The fix is to restart or signal the owning process (for a log, ask it to reopen its files). At 20 MB the change was too small to show in &lt;code&gt;df -h&lt;/code&gt;, so I am not claiming a number there, but the mechanism is the one that bites on production servers with multi-gigabyte logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "What is using this port?": ss
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-ltnp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;8765
&lt;span class="c"&gt;# LISTEN 0 5 127.0.0.1:8765 0.0.0.0:* users:(("python3",pid=252,fd=3))&lt;/span&gt;

lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-iTCP&lt;/span&gt;:8765 &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-l&lt;/code&gt; listening, &lt;code&gt;-t&lt;/code&gt; TCP, &lt;code&gt;-n&lt;/code&gt; no name lookups, &lt;code&gt;-p&lt;/code&gt; show the process. You usually need &lt;code&gt;sudo&lt;/code&gt; to see processes owned by other users. &lt;code&gt;ss&lt;/code&gt; replaces the older &lt;code&gt;netstat&lt;/code&gt; and is installed by default on current Ubuntu.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. "Who is hitting my server?": awk, sort, uniq
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $9}'&lt;/span&gt; access.log | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;span class="c"&gt;#   794 200&lt;/span&gt;
&lt;span class="c"&gt;#   113 404&lt;/span&gt;
&lt;span class="c"&gt;#    52 301&lt;/span&gt;
&lt;span class="c"&gt;#    41 500&lt;/span&gt;

&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; access.log | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt;
&lt;span class="c"&gt;#   539 10.0.0.1&lt;/span&gt;
&lt;span class="c"&gt;#   192 10.0.0.2&lt;/span&gt;
&lt;span class="c"&gt;#   125 10.0.0.3&lt;/span&gt;

&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$9 ~ /^5/ {print $7}'&lt;/span&gt; access.log | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt;
&lt;span class="c"&gt;#     8 /p3&lt;/span&gt;
&lt;span class="c"&gt;#     7 /p1&lt;/span&gt;
&lt;span class="c"&gt;#     7 /p0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the pattern worth memorising: extract a field, &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;uniq -c&lt;/code&gt; to count, &lt;code&gt;sort -rn&lt;/code&gt; to rank. The field numbers (&lt;code&gt;$9&lt;/code&gt; is the status code, &lt;code&gt;$7&lt;/code&gt; the path, &lt;code&gt;$1&lt;/code&gt; the client IP) are for the standard combined log format, so check your own format first. Always &lt;code&gt;sort&lt;/code&gt; before &lt;code&gt;uniq&lt;/code&gt;, because &lt;code&gt;uniq&lt;/code&gt; only collapses adjacent duplicates.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. "Where is this string used?": grep
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"TODO"&lt;/span&gt; app &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*.php'&lt;/span&gt;
&lt;span class="c"&gt;# app/public/b.php:1:&amp;lt;?php // TODO fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-r&lt;/code&gt; recurses, &lt;code&gt;-n&lt;/code&gt; prints line numbers, and &lt;code&gt;--include&lt;/code&gt; limits the search to the file types you care about so you do not grep through &lt;code&gt;vendor&lt;/code&gt; binaries. If you have ripgrep (&lt;code&gt;rg&lt;/code&gt;) installed, it is faster and skips ignored files by default, but plain &lt;code&gt;grep&lt;/code&gt; is on every server.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. "Clean up old files without regret": find with -print first
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find app/storage/logs &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.log'&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +7 &lt;span class="nt"&gt;-print&lt;/span&gt;     &lt;span class="c"&gt;# look first&lt;/span&gt;
find app/storage/logs &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.log'&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +7 &lt;span class="nt"&gt;-delete&lt;/span&gt;    &lt;span class="c"&gt;# then delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the test, the first command listed &lt;code&gt;old1.log&lt;/code&gt; and &lt;code&gt;old2.log&lt;/code&gt; (back-dated 10 days) and left &lt;code&gt;laravel.log&lt;/code&gt; alone; the second removed only those two. Run the exact same expression with &lt;code&gt;-print&lt;/code&gt; before you swap in &lt;code&gt;-delete&lt;/code&gt;. Put &lt;code&gt;-delete&lt;/code&gt; last, because &lt;code&gt;find&lt;/code&gt; evaluates the expression left to right.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. "Do this to 1,000 files, faster": xargs -P
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find imgs &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.txt'&lt;/span&gt; &lt;span class="nt"&gt;-print0&lt;/span&gt; | xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nt"&gt;-P4&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'sleep 0.5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight tasks that each take half a second ran in about 1.0 seconds with &lt;code&gt;-P4&lt;/code&gt; and 4.0 seconds serially. &lt;code&gt;-print0&lt;/code&gt; with &lt;code&gt;-0&lt;/code&gt; keeps filenames with spaces intact, &lt;code&gt;-n1&lt;/code&gt; passes one file per command, and &lt;code&gt;-P4&lt;/code&gt; runs four at a time. Use it for image conversion, compression, or any CPU-bound per-file job, and keep &lt;code&gt;-P&lt;/code&gt; near your core count.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. "Copy this safely": rsync --dry-run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-avh&lt;/span&gt; &lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt; src/ dst/
&lt;span class="c"&gt;# deleting old&lt;/span&gt;
&lt;span class="c"&gt;# a&lt;/span&gt;
&lt;span class="c"&gt;# b&lt;/span&gt;
&lt;span class="c"&gt;# (DRY RUN)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;--delete&lt;/code&gt;, rsync removes files in the destination that are not in the source. The dry run shows exactly that, &lt;code&gt;deleting old&lt;/code&gt;, before anything happens. Drop &lt;code&gt;--dry-run&lt;/code&gt; only when the list looks right. The trailing slash on &lt;code&gt;src/&lt;/code&gt; matters: it means "the contents of src", not the folder itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. "Why is this endpoint slow?": curl -w
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'code=%{http_code} connect=%{time_connect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n'&lt;/span&gt; https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Against a local test server this printed &lt;code&gt;code=200 connect=0.000518s ttfb=0.005061s total=0.005155s&lt;/code&gt;. Against a real site, a large gap between &lt;code&gt;connect&lt;/code&gt; and &lt;code&gt;ttfb&lt;/code&gt; means the server is slow to respond; a slow &lt;code&gt;connect&lt;/code&gt; points at the network. Pipe JSON APIs through &lt;code&gt;jq&lt;/code&gt; next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"items":[{"name":"a","ok":true},{"name":"b","ok":false}]}'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.items[] | select(.ok==false) | .name'&lt;/span&gt;
&lt;span class="c"&gt;# b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10. "What is eating memory?": ps
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps aux &lt;span class="nt"&gt;--sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-%mem | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--sort=-%mem&lt;/code&gt; puts the biggest memory user first. The columns &lt;code&gt;RSS&lt;/code&gt; (resident memory in KB) and &lt;code&gt;%MEM&lt;/code&gt; are the ones to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently asked questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Should I learn grep or ripgrep?
&lt;/h4&gt;

&lt;p&gt;Learn &lt;code&gt;grep&lt;/code&gt; first because it exists on every server. Add &lt;code&gt;rg&lt;/code&gt; on your own machine for speed.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why does uniq -c show the same line twice?
&lt;/h4&gt;

&lt;p&gt;Because the input was not sorted. &lt;code&gt;uniq&lt;/code&gt; only merges adjacent duplicates, so put &lt;code&gt;sort&lt;/code&gt; in front of it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Are these commands the same on macOS?
&lt;/h4&gt;

&lt;p&gt;Mostly, but some flags are GNU-specific, such as &lt;code&gt;du --max-depth&lt;/code&gt;, &lt;code&gt;find -printf&lt;/code&gt; and &lt;code&gt;sort -h&lt;/code&gt; on older systems. On macOS use &lt;code&gt;du -d 1&lt;/code&gt; or install GNU coreutils.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://dev-talk.com/post/best-linux-commands-for-developers" rel="noopener noreferrer"&gt;DEV Talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>bash</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
