1. Home
  2. /
  3. Portfolio
  4. /
  5. MarketMuscles
  6. /
  7. Static Analysis, Testing, and Linting in Laravel

Static Analysis, Testing, and Linting in Laravel

An overview of implementing static analysis, testing, and linting in Laravel

Laravel
PHP-CS-Fixer
Pint
Rector
ESLint
Prettier
PestPHP

As the codebase expanded and contributors increased, the need for standardization became even clearer. Thankfully, the PHP and Javascript ecosystems are robust and helpful in this.

PHP

For PHP, specifically Laravel, there are several packages that amped up the results:

  • Laravel Pint (powered by PHP-CS-Fixer) provides a consistent way to keep code style consistent. Paired with the Open Southeners VSCode plugin, we were able to auto-format our code on-save.
  • The Rector package, along with the Driftingly rules helped keep the codebase up to date with Laravel and PHP releases. A quick setup, and then adjustments of rules let us incrementally and automatically refactor the codebase.
  • A large number of our tests where already written with PHPUnit, but as PestPHP joined the scene, there was a clear advantage to write new tests using Pest. Thankfully, they can be used together, so we could incrementally improve the test suite and not have to rewrite everything.
  • Static Analysis tools are popular in other ecosystems, but PHPStan seems to be a quiet winner in the PHP world. Strong typing was helping for a lot of use cases, but PHPStan does an excellent job of being able to catch logic errors before writitng tests.

Javascript / Typescript / Vue

  • Adopting TypeScript was the biggest improvement to the Javascript parts of the app. Strict typing and built-in static analysis, along with the abiliity to incrementally transition, helped us find issues in legacy code and drastically improve the quality of new code.
  • ESLint brought linting feedback into the IDE and CI/CD pipeline. The dev team could get realtime visual feedback for syntax errors, ill-defined types, etc. The @typescript-eslint package helped expand that functionality to push our Typescript usage, eslint-plugin-unused-imports kept imports clean by removing unused code, and eslint-plugin-vue brought all of that power to Vue's SFC format.
  • Prettier gave us the ability to define code style conventions that could be used in the IDE's format-on-save feature, making sure all code is formatted correctly before it makes it into a commit. A nice-to-have feature was the prettier-plugin-tailwindcss plugin that sorted Tailwind classes based on a consistent order. Fairly minor, but it helped make scanning the code easier, and prevented unexpected behavior by always placing the modifiers at the end of the definition.

AI Adoption

As we worked towards adopting AI-assisted development, many of the aforementioned tools and configurations became critical parts of the guardrails that enabled rapid development.

Letting a GenAI system iterate without input is where the challenges multiply. In my experience, a strong base of test coverage, linters, and static analysis is the first defense against less-than-ideal input, whether it comes from an LLM or a human. Beyond that, LLM Rules are fine for guidance, but cannot be trusted to always be adhered to due to context rot.

I have found hooks to be the main line of defense, allowing more deterministic control of the results. This is where tests/linters/etc. come back into play, using hard results to manage and judge the output.

Utilizing Claude's lifecycle hooks allowed us to keep output consistent and avoid breaking existing functionality. The Stop and PostToolUse lifecycle events were the most useful.

The PostToolUse event was used to execute static analisys of changed files with PHPStan and TSC and a fast set of unit tests. This was used to catch breaking changes early on to prevent runaway loops and context drift.

The Stop event runs after the batch of work is complete and ran the rest of the test suite. The suite is a longer process, so it ran after an a batch, but prevented the loop from exiting if the suite failed. This set Claude up for a test-fix loop that keep the process going until it arrived at a satisfactory result.

AI Services

  • CodeRabbit - With a large increase of new input from multiple developers delivering GenAI code, reviewing Pull Requests started to become a bottleneck. Adding the linters, static analisys, and test suites, to the CI pipeline helped push back on clearly bad code, but it only goes so far. Thats where CodeRabbit comes in. It was able to review PRs before I got to them, and hone in on overlooked issues and either highlighted issues or provided suggested fixes. I appreciated this tool's outputs when trying to keep the PR pipeline moving.
  • Devin is very helpful for triaging issues and suggesting fixes. I had it connected to GitHub issues and tagging @devin in an issue started it's input loop. It would research the issue given the context in the ticket, dig into the codebase, and begin crafting a fix. This suggested PR could then be pulled by an engineer, inspected, and released if it was good enough.

Let's talk.

Selling something, starting something, or burning it all down to build something better - I'm here for it.