How to use Windsurf AI in Laravel — the right way (and what to avoid)

Martin SchenkMartin Schenk
4 min read

In recent months, developer tools powered by AI have exploded in popularity. One of the most interesting tools for PHP developers is Windsurf, a smart VS Code-based IDE that generates, explains, and refactors code based on natural language prompts.

At first glance, it feels magical: "Create a Laravel service class to compress images with ImageMagick" — and boom, a ready-to-use file appears.

But here's the catch: AI-generated code is not always correct, safe, or well-structured.
If you're not careful, you may end up introducing bugs, architectural chaos, or even security risks — all while thinking you're saving time.


The good part: Speed and flow

AI-assisted coding can be a productivity dream. You get:

  • Quick scaffolding (classes, jobs, tests)

  • Instant examples

  • Code completions that feel like pair programming

But like every tool, it must be used wisely — especially in a framework like Laravel, where conventions and architecture matter.


What to watch out for (based on real-world cases)

After digging into how Windsurf and similar tools behave in real Laravel projects, here are typical problems that can occur:

  • Hallucinated methods or classes: The AI invents code that looks real but doesn’t exist.

  • Missing validation or edge case handling

  • Security gaps: e.g. protected $guarded = [] in models (mass assignment risk)

  • Hardcoded logic where config should be used

  • Misplaced logic: Business logic ends up in controllers or Blade views (bad MVC)

  • Unreliable "refactoring": Sometimes the AI changes behavior instead of just structure

  • Duplicate or overly verbose code


The solution: A clear set of rules for AI

To prevent bad code and guide the AI toward real Laravel standards, I created a .windsurf.json config file that defines exactly how I want my code to be structured.

This config:

  • Forces strict refactoring (no behavior changes!)

  • Requires clean separation (Services, Jobs, Form Requests)

  • Uses Laravel’s built-in features (Eloquent, Middleware, Validation)

  • Ensures the code is production-ready — no mocks, no placeholders

  • Keeps the code maintainable and testable

  • Works on macOS dev and Linux VPS servers


✅ Full Example: .windsurf.json (Laravel + AI best practices)

{
  "projectType": "laravel",
  "frameworkVersion": "Laravel 11",
  "phpVersion": "8.2",
  "rules": {
    "codingStyle": "psr-12",
    "architecture": "config-driven-queue-jobs",
    "preferRealCode": true,
    "useInterfacesForServices": true,
    "autoRegisterServices": true,
    "splitLargeClasses": true,
    "generateTests": true,
    "avoidMocks": true,
    "commentStyle": "phpdoc",
    "strictTyping": true
  },
  "focusOn": [
    "app/Services",
    "app/Jobs",
    "app/Console",
    "config/",
    "routes/web.php",
    "routes/console.php"
  ],
  "exclude": [
    "vendor/",
    "node_modules/",
    "storage/",
    "public/",
    ".env",
    ".idea/",
    "tests/Browser/",
    "bootstrap/cache/"
  ],
  "goals": [
    "Refactor without changing behavior.",
    "Remove redundancy using services, helpers, collections.",
    "Follow Laravel standards: Eloquent, FormRequests, Middleware, Policies.",
    "Keep business logic out of views and controllers.",
    "Use production-ready code only – no placeholders or mocks.",
    "Ensure compatibility between macOS dev and Linux production."
  ],
  "projectConfig": {
    "categories": {
      "source": "config/categories.php",
      "rules": [
        "Never hardcode category names — always use config.",
        "Categories may define prompts, publishing rules, and media behavior."
      ]
    }
  },
  "refactoring": {
    "rules": [
      "Definition: Refactoring means changing internal structure without changing external behavior.",
      "When 'refactoring' is requested, the output and logic must remain 100% unchanged.",
      "Inputs, outputs, conditions, and side effects must stay identical.",
      "Only internal structure, names, or modularity may improve.",
      "Each refactor must be validated via tests or manual comparison."
    ]
  },
  "environmentHandling": {
    "rules": [
      "Never modify the .env file directly.",
      "Always prompt before changing environment variables."
    ]
  },
  "deploymentConsiderations": {
    "rules": [
      "Code must run identically on macOS (dev) and Linux VPS (production).",
      "Avoid system-dependent paths or tools (e.g. brew-only installs)."
    ]
  },
  "codingStandards": {
    "rules": [
      "Follow PSR-12 and enable strict_types.",
      "Use descriptive variable and method names.",
      "Follow Laravel naming conventions (UserController, ArticleService, etc.)"
    ]
  },
  "redundancyElimination": {
    "rules": [
      "Eliminate duplicate logic via helpers, traits, services.",
      "Prefer Laravel collections (map, filter, reduce) over manual loops."
    ]
  },
  "fileManagement": {
    "rules": [
      "Avoid files over 500 lines – split responsibilities.",
      "Keep controllers light – delegate to services."
    ]
  },
  "documentation": {
    "rules": [
      "Use PHPDoc for all public methods and classes.",
      "Add inline comments for complex or non-obvious logic."
    ]
  },
  "laravelBestPractices": {
    "rules": [
      "Use dependency injection instead of facades.",
      "Handle auth and roles via middleware or policies.",
      "Use FormRequest for validation – never validate in the controller.",
      "Prefer Eloquent with scopes and relationships over raw queries."
    ]
  },
  "codeGeneration": {
    "rules": [
      "No partial code or pseudo-code.",
      "Every method must be fully implemented and usable.",
      "Edge cases, error handling, and return types must be included."
    ]
  },
  "testing": {
    "rules": [
      "Generate testable, decoupled code.",
      "Follow Laravel's test structure (Unit, Feature).",
      "Create test classes when appropriate."
    ]
  }
}
0
Subscribe to my newsletter

Read articles from Martin Schenk directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Martin Schenk
Martin Schenk