| name | laravel:documentation-best-practices |
| description | Write meaningful documentation that explains why not what; focus on complex business logic and self-documenting code |
Documentation Best Practices
Keep documentation minimal and meaningful. Well-written code with descriptive names often eliminates the need for comments. Document the "why" not the "what", and focus on complex business logic, not obvious code.
When NOT to Document
class UserController
{
public function __construct(
// Inject user repository
private UserRepository $repository
) {
$this->repository = $repository;
}
public function index()
{
return $this->repository->all();
}
}
$user->age = 25;
$total = $price * $quantity;
if ($user->isActive()) {
$this->sendEmail($user);
}
When TO Document
1. Complex Business Logic
class PricingCalculator
{
public function calculateTotal(Order $order): float
{
$subtotal = $order->items
->reject(fn($item) => $item->is_on_sale)
->sum(fn($item) => $item->price * $item->quantity);
$regularItems = $order->items
->filter(fn($item) => !$item->is_on_sale);
$discount = match(true) {
$regularItems->sum('quantity') >= 100 => 0.15,
$regularItems->sum('quantity') >= 50 => 0.10,
$regularItems->sum('quantity') >= 10 => 0.05,
default => 0
};
if ($order->customer->is_vip) {
$discount += 0.05;
}
return $subtotal * (1 - $discount) + $this->calculateSaleItemsTotal($order);
}
}
2. Non-Obvious Solutions
class QueryOptimizer
{
public function getActiveUsersWithRecentOrders()
{
return User::whereIn('id', function ($query) {
$query->select('user_id')
->from('orders')
->where('created_at', '>', now()->subDays(30))
->groupBy('user_id');
})->get();
}
public function getPolymorphicItems()
{
return Item::where('active', true)->get();
}
}
3. Workarounds and Hacks
class PaymentGateway
{
public function chargeLargeAmount(int $amountInCents): array
{
if ($amountInCents <= 99999900) {
return [$this->charge($amountInCents)];
}
$charges = [];
$remaining = $amountInCents;
while ($remaining > 0) {
$chargeAmount = min($remaining, 99999900);
$charges[] = $this->charge($chargeAmount);
$remaining -= $chargeAmount;
}
return $charges;
}
}
4. External Dependencies and Integration Points
class ThirdPartyApiClient
{
public function makeRequest(string $endpoint, array $data = []): array
{
}
public function sendScheduledEvent(DateTime $scheduledAt, array $event): void
{
$scheduledAt->setTimezone(new DateTimeZone('America/New_York'));
}
}
Self-Documenting Code Techniques
1. Descriptive Naming
public function calc($u, $i) // Calculate discount for user and items
{
$d = 0;
if ($u->vip) {
$d = 0.1;
}
return $i * (1 - $d);
}
public function calculateDiscountedPrice(User $customer, float $originalPrice): float
{
$discountPercentage = $customer->is_vip ? 0.1 : 0;
return $originalPrice * (1 - $discountPercentage);
}
2. Extract Methods for Clarity
if ($user->created_at > now()->subDays(7) &&
$user->orders()->count() == 0 &&
!$user->hasVerifiedEmail()) {
$this->sendWelcomeReminder($user);
}
if ($this->isNewUnengagedUser($user)) {
$this->sendWelcomeReminder($user);
}
private function isNewUnengagedUser(User $user): bool
{
return $user->created_at > now()->subDays(7)
&& $user->orders()->count() == 0
&& !$user->hasVerifiedEmail();
}
3. Type Declarations and Return Types
function process($data)
{
}
function processOrderItems(Collection $items): OrderSummary
{
}
4. Value Objects for Domain Concepts
public function setPrice(string $price)
{
$this->price = $price;
}
public function setPrice(Money $price)
{
$this->price = $price;
}
class Money
{
public function __construct(
private int $cents,
private string $currency = 'USD'
) {
if ($cents < 0) {
throw new InvalidArgumentException('Amount cannot be negative');
}
}
public function formatted(): string
{
return number_format($this->cents / 100, 2);
}
}
PHPDoc Best Practices
When to Use PHPDoc
public function processRefund(
Order $order,
?float $amount = null,
string $reason = 'Customer request'
): Refund {
}
IDE Helper Annotations
class UserRepository
{
public function getActiveUsers(): Collection
{
return User::where('active', true)->get();
}
public function applyFilters(array $filters): Builder
{
return User::query()->where($filters);
}
}
Deprecation Notices
class PaymentService
{
public function processPayment($amount)
{
trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
return $this->processPaymentWithStripe($amount);
}
}
API Documentation
1. Controller Method Documentation
class ApiController extends Controller
{
public function index(Request $request)
{
}
}
2. API Resource Documentation
class ProductResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'price' => $this->price->formatted(),
'category' => new CategoryResource($this->whenLoaded('category')),
'reviews' => ReviewResource::collection($this->whenLoaded('reviews')),
'created_at' => $this->created_at->toIso8601String(),
];
}
}
README Documentation
Project README Template
# Project Name
Brief description of what this project does.
## Requirements
- PHP 8.2+
- MySQL 8.0+
- Redis 6.0+
- Node.js 18+
## Installation
\```bash
# Clone repository
git clone https://github.com/username/project.git
cd project
# Install dependencies
composer install
npm install
# Environment setup
cp .env.example .env
php artisan key:generate
# Database setup
php artisan migrate --seed
# Start development server
php artisan serve
\```
## Key Features
- Feature 1: Brief description
- Feature 2: Brief description
- Feature 3: Brief description
## Architecture Decisions
### Why We Use X Instead of Y
Brief explanation of important technical decisions.
### Database Design
Key points about the database structure.
## Testing
\```bash
# Run all tests
php artisan test
# Run specific test suite
php artisan test --testsuite=Feature
# With coverage
php artisan test --coverage
\```
## Deployment
Instructions for deploying to production.
## Troubleshooting
### Common Issue 1
Solution to common issue 1.
### Common Issue 2
Solution to common issue 2.
Configuration Documentation
return [
'cache_ttl' => [
'short' => env('CACHE_TTL_SHORT', 60),
'medium' => env('CACHE_TTL_MEDIUM', 300),
'long' => env('CACHE_TTL_LONG', 3600),
'forever' => env('CACHE_TTL_FOREVER', 86400),
],
'external_apis' => [
'weather' => [
'base_url' => env('WEATHER_API_URL', 'https://api.weather.com'),
'timeout' => 5,
'retries' => 3,
],
],
];
Migration Documentation
class CreateOrdersTable extends Migration
{
public function up(): void
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('status')->default('pending')->index();
$table->unsignedInteger('subtotal');
$table->unsignedInteger('tax');
$table->unsignedInteger('total');
$table->json('shipping_address');
$table->softDeletes();
$table->timestamps();
$table->index(['user_id', 'status', 'created_at']);
});
}
}
Testing Documentation
test('document complex test scenarios', function () {
$expiredCode = DiscountCode::factory()->expired()->create();
$cart = Cart::factory()->withItems(3)->create();
$originalTotal = $cart->total;
$response = $this->postJson("/api/cart/{$cart->id}/discount", [
'code' => $expiredCode->code,
]);
$response->assertUnprocessable()
->assertJsonPath('errors.code.0', 'This discount code has expired.');
expect($cart->fresh()->total)->toBe($originalTotal);
});
Best Practices Summary
- Code should be self-documenting through good naming
- Document WHY, not WHAT
- Keep comments close to the code they describe
- Update documentation when code changes
- Use tools to generate API documentation
- Document complex business rules thoroughly
- Include examples in documentation
- Document breaking changes clearly
- Keep README files up to date
- Document environmental dependencies
Remember: The best documentation is code that doesn't need documentation. Strive for clarity in your code first, then document what remains complex or non-obvious.