Skip to content
Back to blog
LaravelAIBest practices

Three mistakes AI keeps making in Laravel

Coding assistants write Laravel fast, but they always trip over the same three stones. Learn to spot them before you hit merge.

Ismael Catala3 min read

I've spent months reviewing pull requests written with the help of coding assistants. The average quality has gone way up, but three mistakes keep repeating with suspicious consistency. None of them break the app on merge day: all of them blow up three months later.

1. N+1 queries disguised as clean code

AI loves accessors. Ask it for "show each post's author name" and you get something like this:

class Post extends Model
{
    public function getAuthorNameAttribute(): string
    {
        return $this->user->name;
    }
}

It's readable, it's correct and it's a time bomb. On a list of fifty posts you just fired fifty-one queries. The accessor hides the relationship, so you can't even see it while reading the view.

The fix is the usual one, but you have to ask for it explicitly:

Post::query()->with('user')->latest()->paginate(20);

My rule: any accessor that touches a relationship ships with its $with or it doesn't ship at all. If the model almost always needs that relationship, declare it on the model itself.

2. Validating in the controller instead of a Form Request

When you ask for an endpoint, AI tends to solve everything inside the method:

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|string|max:255',
        'body' => 'required|string',
    ]);
 
    return Post::create($validated);
}

It works. The problem shows up when that same resource is also created from an Artisan command, from a job and from API v2. Now the rules live in four places and they've already drifted apart.

A FormRequest costs one php artisan make:request and gives you validation, authorization and messages in a single place:

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }
 
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string'],
        ];
    }
}

Note the rules as arrays instead of pipe-separated strings. That's not cosmetic: with rules containing regular expressions or commas, pipe syntax breaks in ways that are very hard to debug.

3. Ignoring transactions

This is the most expensive of the three. You ask for "create the order and decrement stock" and you get two operations in a row, with no safety net:

$order = Order::create($data);
$product->decrement('stock', $data['quantity']);

If the second line fails you're left with an order whose stock was never decremented. Nobody notices until someone reconciles inventory at month end.

DB::transaction(function () use ($data, $product) {
    $order = Order::create($data);
    $product->decrement('stock', $data['quantity']);
 
    return $order;
});

How I handle it

I haven't stopped using assistants: I write faster and with less fatigue. What changed is where I put my attention. I used to read code hunting for syntax errors; now I read hunting for hidden queries, duplicated rules and writes without a transaction.

One more thing: if you give the assistant context about your project — your conventions, your models, your repositories — these three mistakes drop dramatically. AI isn't bad at writing Laravel; it's bad at guessing the Laravel you write.