Here's the rule: if setup takes more than one command, someone will get it wrong, and that person's first experience with your codebase will be an error message that has nothing to do with the actual product.
# Bad: setup as a checklist someone copies into a wiki
$ php artisan serve # terminal one
$ php artisan queue:listen --tries=1 # terminal two, don't forget it
$ php artisan pail --timeout=0 # terminal three
Nobody remembers all of that on day one, and everybody remembers a different subset by day thirty. One developer forgets the queue listener and can't figure out why DeleteLicenseJob never runs. Each of these is a real problem with a fake cause: they didn't do anything wrong, the process asked them to remember too much.
// composer.json
"scripts": {
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
]
}
composer run dev starts the server, the queue worker, the log tailer, and the frontend build together in one terminal, labeled so you can tell them apart. The whole install path is four commands: composer install, cp .env.example .env, php artisan key:generate, composer run dev, every one scriptable, which means every one testable. If a new hire can't get the API running, that's a bug in your setup, not a gap in their competence.
Conventions A Newcomer Can Infer
A convention only counts if a newcomer can guess it correctly without asking. If they have to ask, it isn't a convention, it's a secret.
BaseFormRequest is one example. Every request class extends it, and the abstract rules() method forces every subclass to declare its rules the same way, with failedValidation() already wired to a structured error response. A developer who's read one FormRequest has read all of them, because the base class won't let them diverge.
DeleteLicenseJob shows the same discipline from a different angle, and its name isn't a coincidence:
// tests/Feature/ArchTest.php
it('checks that my jobs have suffix Job')
->expect('App\Jobs')
->toHaveSuffix('Job');
This test doesn't check behavior. It checks shape. Any class in App\Jobs not named SomethingJob fails the build, no ambiguity about whether it's a preference or a rule. A newcomer opening DeleteLicenseJob.php already knows, before reading a line, that it implements ShouldQueue, uses the single Queueable trait, and takes its payload through constructor promotion, because every job in this fleet does exactly that.
Naming tells you the contract. A Job suffix means ShouldQueue and asynchronous execution. Structure tells you where logic lives. Controllers stay thin, facades hold the public interface, drivers hold the provider-specific detail. Tests tell you what's enforced, not documented. ArchTest.php doesn't describe the convention, it fails the build when the convention breaks. That's the difference between a convention and a suggestion.