# Coding Standards

## Overview

This document defines the coding standards and style guidelines for the Building Management System. All code must adhere to these standards for consistency and maintainability.

## PHP Standards

### PSR Compliance

The project follows these PHP-FIG standards:
- **PSR-1**: Basic Coding Standard
- **PSR-4**: Autoloading Standard
- **PSR-12**: Extended Coding Style Guide

### PHP Version

```php
// Minimum PHP version: 8.2
// Use modern PHP features

// Readonly classes
readonly final class UserDTO
{
    public function __construct(
        public string $name,
        public string $email,
    ) {}
}

// Match expressions
$status = match ($code) {
    200 => 'OK',
    404 => 'Not Found',
    500 => 'Server Error',
    default => 'Unknown',
};

// Named arguments
$user = User::create(
    name: $dto->name,
    email: $dto->email,
);

// Null-safe operator
$company = $user?->company?->name;
```

## Code Formatting

### Indentation and Spacing

```php
<?php

namespace App\Domains\Catalog\Products\Services;

use App\Domains\Catalog\Products\DTOs\ProductDTO;
use App\Domains\Catalog\Products\Models\Product;
use Illuminate\Support\Facades\DB;

class ProductService
{
    // 4 spaces for indentation
    public function __construct(
        private ProductRepositoryContract $repository
    ) {}

    // Blank line between methods
    public function create(ProductDTO $dto): Product
    {
        // Spaces around operators
        $total = $dto->price * $dto->quantity;

        // Spaces after control keywords
        if ($total > 1000) {
            $discount = $total * 0.1;
        }

        return DB::transaction(function () use ($dto) {
            return $this->repository->create($dto->toArray());
        });
    }
}
```

### Line Length

- Maximum line length: **120 characters**
- For long method signatures, break after each parameter:

```php
// Good - parameters on separate lines
public function processOrder(
    int $orderId,
    array $items,
    float $discount,
    ?string $couponCode = null
): Order {
    // ...
}

// Avoid - too long
public function processOrder(int $orderId, array $items, float $discount, ?string $couponCode = null): Order {
```

### Braces and Brackets

```php
// Class braces on new line
class ProductService
{
    // Method braces on same line for single-line
    public function __construct(private Repository $repo) {}

    // Method braces on new line for multi-line
    public function create(ProductDTO $dto): Product
    {
        // Control structures - brace on same line
        if ($condition) {
            // ...
        } elseif ($otherCondition) {
            // ...
        } else {
            // ...
        }

        // Loops
        foreach ($items as $item) {
            // ...
        }

        // Switch statements
        switch ($status) {
            case 'pending':
                // ...
                break;
            case 'approved':
                // ...
                break;
            default:
                // ...
        }
    }
}
```

## Class Structure

### Standard Order

```php
<?php

namespace App\Domains\Catalog\Products\Models;

use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Product extends BaseModel
{
    // 1. Traits
    use HasFactory, SoftDeletes, Loggable;

    // 2. Constants
    public const STATUS_ACTIVE = 'active';
    public const STATUS_INACTIVE = 'inactive';

    // 3. Static properties
    public static array $filtersCols = ['category_id', 'status'];
    public static array $searchCols = ['name', 'sku'];

    // 4. Properties
    protected $table = 'products';
    protected $fillable = ['name', 'sku', 'price', 'category_id'];
    protected $casts = [
        'price' => 'decimal:4',
        'is_active' => 'boolean',
        'metadata' => 'array',
    ];

    // 5. Boot method
    protected static function boot()
    {
        parent::boot();
        // ...
    }

    // 6. Relationships
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    public function variants(): HasMany
    {
        return $this->hasMany(ProductVariant::class);
    }

    // 7. Accessors and Mutators
    protected function formattedPrice(): Attribute
    {
        return Attribute::make(
            get: fn() => number_format($this->price, 2),
        );
    }

    // 8. Scopes
    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    // 9. Custom methods
    public function isInStock(): bool
    {
        return $this->quantity > 0;
    }
}
```

## Type Declarations

### Always Use Type Hints

```php
<?php

class InvoiceService
{
    // Property types
    private InvoiceRepository $repository;
    private ?Logger $logger = null;

    // Constructor promotion
    public function __construct(
        private InvoiceRepository $repository,
        private TaxCalculator $taxCalculator
    ) {}

    // Return types (including void and never)
    public function find(int $id): ?Invoice
    {
        return $this->repository->find($id);
    }

    public function calculate(Invoice $invoice): float
    {
        return $this->taxCalculator->calculate($invoice);
    }

    public function delete(int $id): void
    {
        $this->repository->delete($id);
    }

    // Union types
    public function process(int|string $identifier): Invoice
    {
        // ...
    }

    // Nullable types
    public function getDiscount(?Customer $customer): float
    {
        return $customer?->discount_percentage ?? 0.0;
    }
}
```

### Collection and Array Types

```php
<?php

use Illuminate\Support\Collection;

class ProductService
{
    /**
     * @param array<int, ProductDTO> $products
     * @return Collection<int, Product>
     */
    public function createMany(array $products): Collection
    {
        return collect($products)->map(
            fn(ProductDTO $dto) => $this->create($dto)
        );
    }
}
```

## Imports and Namespaces

### Import Order

```php
<?php

namespace App\Domains\Catalog\Products\Services;

// 1. PHP built-in classes
use Exception;
use InvalidArgumentException;

// 2. Framework classes
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

// 3. Application classes (alphabetically)
use App\Core\Interfaces\DTOInterface;
use App\Domains\Catalog\Products\Contracts\ProductRepositoryContract;
use App\Domains\Catalog\Products\DTOs\ProductDTO;
use App\Domains\Catalog\Products\Events\ProductCreated;
use App\Domains\Catalog\Products\Models\Product;

class ProductService
{
    // ...
}
```

### Import Rules

```php
// Good - single class imports
use App\Domains\Catalog\Products\Models\Product;
use App\Domains\Catalog\Products\Models\ProductVariant;

// Avoid - group imports
use App\Domains\Catalog\Products\Models\{Product, ProductVariant};

// Good - aliasing when needed
use App\Domains\Accounting\Sales\Models\Invoice as SalesInvoice;
use App\Domains\Procurement\Models\Invoice as ProcurementInvoice;
```

## Documentation

### DocBlocks

```php
<?php

/**
 * Service for managing product inventory
 *
 * Handles stock levels, reservations, and inventory transactions
 */
class InventoryService
{
    /**
     * Reserve inventory for an order
     *
     * @param int $productId Product to reserve
     * @param int $quantity Amount to reserve
     * @param string|null $reference Optional reference number
     *
     * @return InventoryReservation The created reservation
     *
     * @throws InsufficientStockException When not enough stock available
     * @throws ProductNotFoundException When product doesn't exist
     */
    public function reserve(
        int $productId,
        int $quantity,
        ?string $reference = null
    ): InventoryReservation {
        // ...
    }

    /**
     * Get available stock for a product
     */
    public function getAvailableStock(int $productId): int
    {
        // Simple methods may have simple doc blocks
    }
}
```

### When to Document

- Document complex business logic
- Document non-obvious behavior
- Document exceptions thrown
- Avoid documenting obvious code

```php
// Unnecessary - obvious from code
/**
 * Get user by ID
 * @param int $id
 * @return User
 */
public function find(int $id): User

// Good - explains business rule
/**
 * Calculate shipping cost
 *
 * Free shipping for orders over $100 for premium customers,
 * or orders over $200 for standard customers.
 */
public function calculateShipping(Order $order, Customer $customer): float
```

## Eloquent Best Practices

### Model Definitions

```php
<?php

class Order extends BaseModel
{
    // Use $fillable, not $guarded
    protected $fillable = [
        'customer_id',
        'status',
        'total_amount',
        'notes',
    ];

    // Define casts for proper types
    protected $casts = [
        'total_amount' => 'decimal:4',
        'is_paid' => 'boolean',
        'metadata' => 'array',
        'shipped_at' => 'datetime',
    ];

    // Use relationship methods
    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    // Prefer scopes over raw queries
    public function scopePending($query)
    {
        return $query->where('status', 'pending');
    }

    public function scopeForCustomer($query, int $customerId)
    {
        return $query->where('customer_id', $customerId);
    }
}
```

### Query Building

```php
// Good - readable, uses scopes
$orders = Order::query()
    ->pending()
    ->forCustomer($customerId)
    ->with(['items', 'customer'])
    ->orderByDesc('created_at')
    ->paginate(15);

// Avoid - long chains without breaks
$orders = Order::where('status', 'pending')->where('customer_id', $customerId)->with(['items', 'customer'])->orderByDesc('created_at')->paginate(15);

// Good - use when() for conditional queries
$orders = Order::query()
    ->when($request->status, fn($q, $status) => $q->where('status', $status))
    ->when($request->from_date, fn($q, $date) => $q->whereDate('created_at', '>=', $date))
    ->paginate(15);
```

## Controller Standards

```php
<?php

namespace App\Http\Controllers\V1\Catalog;

use App\Domains\Catalog\Products\DTOs\ProductDTO;
use App\Domains\Catalog\Products\Services\ProductService;
use App\Http\Controllers\Controller;
use App\Http\Requests\V1\Catalog\StoreProductRequest;
use App\Http\Requests\V1\Catalog\UpdateProductRequest;
use App\Http\Resources\V1\Catalog\ProductResource;
use Illuminate\Http\JsonResponse;

class ProductController extends Controller
{
    public function __construct(
        private ProductService $service
    ) {}

    // RESTful action names
    public function index(): JsonResponse
    {
        $products = $this->service->list();

        return $this->sendPaginatedResponse(
            ProductResource::collection($products)
        );
    }

    public function store(StoreProductRequest $request): JsonResponse
    {
        $dto = ProductDTO::fromRequest($request->validated());
        $product = $this->service->create($dto);

        return $this->sendSuccessResponse(
            data: new ProductResource($product),
            message: __('Product created successfully'),
            code: 201
        );
    }

    public function show(int $id): JsonResponse
    {
        $product = $this->service->find($id);

        return $this->sendSuccessResponse(
            data: new ProductResource($product)
        );
    }

    public function update(UpdateProductRequest $request, int $id): JsonResponse
    {
        $dto = ProductDTO::fromRequest($request->validated());
        $product = $this->service->update($id, $dto);

        return $this->sendSuccessResponse(
            data: new ProductResource($product),
            message: __('Product updated successfully')
        );
    }

    public function destroy(int $id): JsonResponse
    {
        $this->service->delete($id);

        return $this->sendSuccessResponse(
            message: __('Product deleted successfully')
        );
    }
}
```

## Code Quality Tools

### PHP CS Fixer Configuration

```php
// .php-cs-fixer.php
<?php

return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'no_unused_imports' => true,
        'trailing_comma_in_multiline' => true,
        'single_quote' => true,
    ])
    ->setFinder(
        PhpCsFixer\Finder::create()
            ->in(__DIR__ . '/app')
            ->in(__DIR__ . '/tests')
    );
```

### PHPStan Configuration

```yaml
# phpstan.neon
parameters:
    level: 6
    paths:
        - app
    excludePaths:
        - app/Http/Controllers/Controller.php
```

## Git Commit Standards

```bash
# Format: type(scope): description

# Types
feat:     New feature
fix:      Bug fix
docs:     Documentation only
style:    Formatting, no code change
refactor: Code refactoring
test:     Adding tests
chore:    Maintenance tasks

# Examples
feat(products): add bulk import functionality
fix(invoices): correct tax calculation for exempt items
docs(api): update authentication documentation
refactor(services): extract validation logic to trait
```
