A growing project needs a clear layout. The standard rule is simple: the namespace mirrors the folder structure, and the file name equals the class name.
my-app/
├── composer.json
├── public/
│ └── index.php
├── src/
│ ├── Model/
│ │ └── Product.php App\Model\Product
│ ├── Service/
│ │ └── PriceCalculator.php App\Service\PriceCalculator
│ └── Http/
│ └── ProductController.php App\Http\ProductController
└── vendor/
The namespace App\ is mapped to the src/ folder. So App\Service\PriceCalculator lives in src/Service/PriceCalculator.php.
One class per file
Each class, interface, trait or enum gets its own file. The file name matches the type name exactly, including capital letters (important on Linux, where Product.php and product.php are different files).
<?php
declare(strict_types=1);
namespace App\Service;
final class PriceCalculator
{
public function withTax(float $price, float $rate = 0.18): float
{
return round($price * (1 + $rate), 2);
}
}
<?php
declare(strict_types=1);
namespace App\Model;
use App\Service\PriceCalculator;
final class Product
{
public function __construct(
private string $name,
private float $price,
private PriceCalculator $calculator = new PriceCalculator(),
) {}
public function label(): string
{
return $this->name . ": " . $this->calculator->withTax($this->price);
}
}
<?php
require_once __DIR__ . "/src/Service/PriceCalculator.php";
require_once __DIR__ . "/src/Model/Product.php";
use App\Model\Product;
echo (new Product("Pen", 100))->label();
Pen: 118
Writing require_once for every class does not scale. The next lessons show how an autoloader does it automatically.
Common layers
| Folder | Holds |
|---|---|
Model/ or Entity/ | Objects that represent your data (Product, User) |
Repository/ | Classes that load and save models |
Service/ | Business logic (PriceCalculator, OrderPlacer) |
Http/ or Controller/ | Code that receives requests |
Exception/ | Your custom exceptions |
Support/ or Util/ | Small helpers |
There is no single correct structure. Pick one, and keep it consistent.
Keep public/ thin
Only the public/ folder is reachable from the web. Put everything else (src/, vendor/, config) outside it. The public/index.php file loads the autoloader and starts the app. That way a server mistake cannot expose your code.
Name for the role
UserRepository, OrderService, InvalidEmailException. A reader can guess what each class does from its name and folder.
Do not nest too deeply
Two or three levels (App\Http\Controllers\Admin) is plenty. More than that makes names long and hard to import.
Why this matters in Laravel
app/Models, app/Http/Controllers, app/Services. The App\ namespace maps to the app/ folder with PSR-4. Once you know the rule, finding any class takes seconds.