# 🏗 Laravel Base — Architecture Documentation

> A production-ready, enterprise-grade Laravel 13 foundation built for scalability, performance, and developer velocity.

---

## Table of Contents

1. [Overview](#overview)
2. [Architecture Principles](#architecture-principles)
3. [Layer Diagram](#layer-diagram)
4. [Directory Structure](#directory-structure)
5. [Core Foundation](#core-foundation)
6. [Domain Layer](#domain-layer)
7. [Infrastructure Layer](#infrastructure-layer)
8. [Service Layer (CQRS-Light)](#service-layer-cqrs-light)
9. [HTTP Layer](#http-layer)
10. [Middleware Stack](#middleware-stack)
11. [API Versioning](#api-versioning)
12. [RBAC System](#rbac-system)
13. [Audit Trail](#audit-trail)
14. [Smart Caching](#smart-caching)
15. [Feature Flags](#feature-flags)
16. [Geo System](#geo-system)
17. [EventBus](#eventbus)
18. [Resilience Patterns](#resilience-patterns)
19. [Observability](#observability)
20. [Code Generators](#code-generators)
21. [Configuration Reference](#configuration-reference)
22. [Request Lifecycle](#request-lifecycle)
23. [Extending the Base](#extending-the-base)

---

## Overview

This base project provides a complete backend architecture for API-first applications serving both **admin dashboards** and **mobile/web clients**. It implements:

| Capability | Implementation |
|-----------|----------------|
| Clean Architecture | Concentric layers with inward dependencies |
| CQRS-Light | Separated read/write services, same database |
| Pipeline Pattern | Composable save/delete/index operations |
| Repository Pattern | Abstracted data access behind interfaces |
| Domain-Driven Design | Actions, ValueObjects, Events, Business Rules |
| RBAC | Hierarchical roles, route-based auto-check |
| Smart Caching | Version-counter automatic invalidation |
| Feature Flags | Targeted toggles with zero-DB evaluation |
| Resilience | Circuit breaker, idempotency, rate limiting |
| Observability | Correlation IDs, performance monitoring |


---

## Architecture Principles

| Principle | How It's Applied |
|-----------|-----------------|
| **Dependency Inversion** | All infrastructure accessed via contracts (interfaces) |
| **Single Responsibility** | One class = one job. Actions do one thing. Pipes do one thing. |
| **Open/Closed** | Add pipes to pipeline without modifying base. Add resolvers without editing GeoContext. |
| **Interface Segregation** | Small, focused contracts. `SmartCacheInterface` has 2 methods, not 20. |
| **Don't Repeat Yourself** | Base services handle 90% of CRUD. Override hooks for the 10%. |
| **Convention over Configuration** | RBAC auto-resolves from route names. No manual permission strings. |
| **Fail-Closed** | Circuit breaker returns fallback. Audit failure doesn't break saves. |

---

## Layer Diagram

```
┌─────────────────────────────────────────────────────────────────┐
│                        HTTP LAYER                                 │
│  Controllers (thin) → validate + delegate                        │
│  Middleware → auth, RBAC, geo, correlation, performance          │
│  Resources → Admin (rich) / Client (lean)                        │
└───────────────────────────────┬─────────────────────────────────┘
                                │
┌───────────────────────────────▼─────────────────────────────────┐
│                      SERVICE LAYER                                │
│  Admin/Commands/ ─── write operations                            │
│  Admin/Queries/  ─── read operations                             │
│  Client/Commands/ ── write operations                            │
│  Client/Queries/  ── read operations                             │
└───────────────────────────────┬─────────────────────────────────┘
                                │
┌───────────────────────────────▼─────────────────────────────────┐
│                      DOMAIN LAYER                                 │
│  Actions ─── single-responsibility business operations           │
│  ValueObjects ─── immutable typed data                           │
│  Events ─── domain occurrences (OrderPlaced, UserBanned)         │
│  Rules ─── composable business guards                            │
└───────────────────────────────┬─────────────────────────────────┘
                                │
┌───────────────────────────────▼─────────────────────────────────┐
│                   INFRASTRUCTURE LAYER                            │
│  RBAC │ Audit │ Cache │ EventBus │ FeatureFlags │ Geo            │
│  Search │ Payments │ Config │ Resilience │ Observability         │
└───────────────────────────────┬─────────────────────────────────┘
                                │
┌───────────────────────────────▼─────────────────────────────────┐
│                    CORE FOUNDATION                                │
│  Contracts │ Repository │ Pipeline │ Response │ Exceptions       │
│  BaseQueryService │ BaseCommandService │ BaseCRUDService         │
└─────────────────────────────────────────────────────────────────┘
```

**Rule: Dependencies always point INWARD.** HTTP depends on Service, Service depends on Domain, Domain depends on Core. Never the reverse.

---

## Directory Structure

```
app/
├── Console/Commands/           # Artisan generators (make:action, make:query, etc.)
├── Contracts/                  # 🔌 Interfaces for all swappable services
│   ├── Audit/                  #    AuditServiceInterface
│   ├── Cache/                  #    SmartCacheInterface
│   ├── EventBus/               #    EventDispatcherInterface, EventHandlerInterface
│   ├── FeatureFlags/           #    FeatureFlagServiceInterface
│   ├── Geo/                    #    GeoResolverInterface, CurrencyServiceInterface
│   ├── Payments/               #    PaymentGatewayInterface, IntegrationRegistryInterface
│   ├── Repository/             #    IBaseRepository
│   └── Search/                 #    SearchServiceInterface
├── Core/                       # 🧱 Foundation classes
│   ├── Exceptions/             #    DomainException hierarchy + ErrorCode constants
│   ├── Pipeline/               #    Save/ (9 pipes), Delete/ (5 pipes), Index/ (5 pipes)
│   ├── Repository/             #    BaseRepository (Eloquent implementation)
│   ├── Response/               #    ApiResponse fluent builder
│   └── Service/                #    BaseQueryService, BaseCommandService, BaseCRUDService
├── Domain/                     # 🎯 Pure business logic
│   └── Shared/
│       ├── Actions/            #    BaseAction (enforce rules → execute → dispatch events)
│       ├── Events/             #    BaseDomainEvent (occurredAt, actorId)
│       ├── Rules/              #    BusinessRule interface + RuleChain evaluator
│       └── ValueObjects/       #    BaseValueObject (immutable, equals(), toArray())
├── Enums/                      # Business enumerations (UserType, PaymentMethod, etc.)
├── Http/
│   ├── Controllers/Api/V1/
│   │   ├── Admin/              #    Role, Category, User, FeatureFlag, Setting, AuditLog, Dashboard
│   │   ├── Client/             #    Profile, Address, Notification, Wallet, Content, Search, Geo
│   │   └── Shared/             #    Health, Bootstrap
│   ├── Middleware/             #    ActiveMiddleware, GzipMiddleware, RbacAutoCheck, SetLocale
│   ├── Requests/Admin/         #    Admin-specific validation (to be filled per feature)
│   ├── Requests/Client/        #    Client-specific validation (to be filled per feature)
│   └── Resources/V1/
│       ├── Admin/              #    Rich responses (audit info, deep relations)
│       └── Client/             #    Lean responses (mobile-optimized)
├── Infrastructure/             # 🔌 External integrations (swappable)
│   ├── Audit/                  #    AuditService, AuditRetentionCommand
│   ├── Auth/                   #    RbacEngine, RoleHierarchy, PermissionCache, PermissionResolver
│   ├── Cache/                  #    VersionCounter, SmartResponseCache, AnalyticsBuffer
│   ├── Config/                 #    BootstrapConfigService
│   ├── EventBus/               #    EventBusDispatcher (Redis pub/sub)
│   ├── FeatureFlags/           #    FeatureFlagService, FlagTargetingEvaluator
│   ├── Geo/                    #    GeoContext, Resolvers/ (Header, Profile, IP), Spatial/
│   ├── Observability/          #    CorrelationIdMiddleware, PerformanceMonitor
│   ├── Payments/               #    PaymentGatewayRegistry
│   ├── Resilience/             #    IdempotencyMiddleware, CircuitBreaker
│   └── Search/                 #    SearchHistoryService
├── Services/                   # 🎼 Application orchestration (CQRS-light)
│   ├── Admin/Commands/         #    RoleCommandService, CategoryCommandService
│   ├── Admin/Queries/          #    RoleQueryService, CategoryQueryService
│   ├── Client/Commands/        #    CustomerAddressCommandService
│   └── Client/Queries/         #    CustomerAddressQueryService, CatalogQueryService
├── Models/                     # Eloquent models
├── Jobs/                       # Queue jobs
├── Observers/                  # Model lifecycle hooks
├── Providers/                  # Service providers (App, Core, Notification, Route, Telescope)
├── Traits/                     # Reusable traits
├── Rules/                      # Custom validation rules
├── Notifications/              # Notification classes
└── Helpers/                    # Global helper functions
```


---

## Core Foundation

### ApiResponse Builder

Replaces the legacy `json()` helper with a fluent, testable class.

```php
// Success with data
return ApiResponse::success($users)->message('Users retrieved')->send();

// Success with pagination
return ApiResponse::success($collection)->paginated($paginator)->send();

// Error
return ApiResponse::error('Service unavailable', 503)->meta(['error_code' => 'CIRCUIT_OPEN'])->send();

// Failure (validation)
return ApiResponse::fail('Invalid input', 422)->meta(['errors' => $errors])->send();
```

**Response envelope (always consistent):**
```json
{
    "status": "success|fail|error",
    "message": "Human-readable message",
    "data": { ... },
    "meta": { "current_page": 1, "per_page": 15, "total": 100, "last_page": 7 }
}
```

### Domain Exceptions

| Exception | HTTP | When |
|-----------|------|------|
| `EntityNotFoundException` | 404 | Model not found by ID |
| `BusinessRuleViolation` | 422 | Business rule check fails |
| `ValidationFailedException` | 422 | Domain-level validation fails |
| `InfrastructureException` | 503 | External service down |

All exceptions carry an `errorCode` string from `ErrorCode::class` for machine-readable error handling.

### BaseRepository

Provides chainable query building with automatic state reset:

```php
$users = app(UserRepository::class)
    ->with(['roles', 'addresses'])
    ->filter(['is_active' => true])
    ->search('john', ['full_name', 'email'])
    ->filterByDate('created_at', '2025-01-01', '2025-06-30')
    ->sortBy('created_at', 'desc')
    ->paginate(20);
```

**Safety guards:**
- `paginate()` clamps between 1-100 items
- `findOrFail()` throws `EntityNotFoundException` (not Laravel's generic ModelNotFoundException)
- `create()`/`update()` with empty data throws `ValidationFailedException`
- Query resets after every terminal operation (no scope leakage)

### Pipeline System

Operations decomposed into composable steps:

**Save Pipeline (9 pipes):**
```
BeforeSave → FillModel → SaveModel → SyncRelations → HandleMedia
           → AuditChange → InvalidateCache → DispatchEvents → AfterSave
```

**Delete Pipeline (5 pipes):**
```
BeforeDelete → DetachRelations → CleanupMedia → DeleteModel → AfterDelete
```

**Index Pipeline (5 pipes):**
```
ApplyScopes → ApplyFilters → ApplySearch → ApplySorting → ApplyPagination
```

**Customization:** Override `getSavePipes()` in your service to add, remove, or reorder pipes:

```php
protected function getSavePipes(): array
{
    return [
        BeforeSave::class,
        FillModel::class,
        SaveModel::class,
        SyncRelations::class,
        MyCustomValidation::class,  // ← added
        AfterSave::class,
        // HandleMedia removed (no media on this model)
    ];
}
```

---

## Domain Layer

### Actions

Single-responsibility business operations. Reusable across controllers, jobs, commands.

```php
class ApplyCouponAction extends BaseAction
{
    public function execute(mixed ...$args): mixed
    {
        [$cart, $code] = $args;
        $coupon = Coupon::where('code', $code)->firstOrFail();

        // Validate business rules (ALL checked, not just first failure)
        $this->enforce(
            new CouponIsActive($coupon),
            new CouponHasUsagesLeft($coupon),
            new OrderMeetsMinimum($cart, $coupon),
        );

        // Perform operation...
        $cart->update(['coupon_id' => $coupon->id]);

        // Dispatch event
        $this->dispatch(new CouponApplied($cart, $coupon));

        return $total;
    }
}
```

### Value Objects

Immutable typed data — invalid states impossible:

```php
final class CartTotal extends BaseValueObject
{
    public function __construct(
        public readonly float $subtotal,
        public readonly float $discount,
        public readonly float $total,
    ) {
        if ($this->total < 0) {
            throw new ValidationFailedException(message: 'Total cannot be negative');
        }
    }

    public function toArray(): array { ... }
}
```

### Business Rules

Composable guards evaluated together (API sees ALL violations at once):

```php
$chain = new RuleChain([
    new CouponIsActive($coupon),
    new UserHasBalance($user, $amount),
    new FeatureIsEnabled('checkout'),
]);
$chain->evaluate(); // Throws BusinessRuleViolation with ALL failures
```

---

## Infrastructure Layer


### Payment Gateway Registry

Admin-swappable payment integrations without code changes:

```php
// Resolves the correct gateway for method + country + platform
$gateway = app(IntegrationRegistryInterface::class)->resolve('stripe', [
    'country' => 'SA',
    'platform' => 'ios',
]);

$result = $gateway->charge($order);
$result = $gateway->refund($transaction, amount: 50.00);
```

Integrations are stored in the `integrations` table with encrypted credentials, country/platform targeting, and priority ordering.

### Bootstrap Config Service

Single endpoint for client app initialization:

```
GET /api/v1/config/bootstrap?platform=ios&version=2.3.0
```

Returns: feature flags, active integrations, UI config, version requirements, maintenance status. Cached with version-counter invalidation.

---

## Service Layer (CQRS-Light)

Read and write operations are **separated into distinct classes**:

```
Services/
├── Admin/
│   ├── Commands/           # Write: create, update, delete, toggle
│   │   ├── RoleCommandService.php
│   │   └── CategoryCommandService.php
│   └── Queries/            # Read: index, show, search, export
│       ├── RoleQueryService.php
│       └── CategoryQueryService.php
└── Client/
    ├── Commands/           # Write operations for client
    │   └── CustomerAddressCommandService.php
    └── Queries/            # Read operations for client
        └── CustomerAddressQueryService.php
```

**Why separate?**
- Reads can be cached aggressively, writes cannot
- Reads can use read replicas, writes go to primary
- Read services never modify state (safe, idempotent)
- Write services own transactions and side effects

**Usage in controllers:**

```php
class RoleController extends Controller
{
    public function __construct(
        private readonly RoleQueryService $queries,
        private readonly RoleCommandService $commands,
    ) {}

    public function index(Request $request): JsonResponse
    {
        return $this->queries->index($request, ['translations', 'permissions'], JsonResource::class);
    }

    public function store(Request $request): JsonResponse
    {
        return $this->commands->save($request);
    }
}
```

---

## HTTP Layer

### Controller Convention

Controllers are **thin** — validate and delegate:

```php
class FeatureFlagController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        $request->validate([...]);          // ← validate
        $flag = FeatureFlag::create(...);   // ← delegate
        $this->flagService->invalidate();   // ← side effect
        return ApiResponse::success($flag); // ← respond
    }
}
```

No business logic in controllers. No direct DB queries in controllers (except simple lookups). Complex operations → Actions or Services.

---

## Middleware Stack

| Middleware | Applied To | Purpose |
|-----------|-----------|---------|
| `SetLocale` | All | Set app locale from `Accept-Language` header |
| `GzipMiddleware` | All | Compress responses for `Accept-Encoding: gzip` |
| `RbacAutoCheck` | Admin routes | Auto-resolve permission from route name |
| `GeoContextMiddleware` | Client routes | Resolve country/currency/coords |
| `ActiveMiddleware` | Auth routes | Block inactive/banned users |
| `IdempotencyMiddleware` | Client writes | Prevent duplicate operations |
| `CorrelationIdMiddleware` | All (planned) | Request tracing across logs/jobs |
| `PerformanceMonitor` | All (planned) | Duration/query/memory tracking |

---

## API Versioning

```
routes/api/v1/
├── admin.php       # All admin endpoints (prefix: /api/v1/admin)
├── client.php      # All client endpoints (prefix: /api/v1/client)
└── shared.php      # Shared endpoints (prefix: /api/v1)
```

**Adding V2:** Create `routes/api/v2/`, register in `bootstrap/app.php`. V1 routes remain untouched.

**Current endpoints (50 routes):**

| Prefix | Endpoints |
|--------|-----------|
| `/api/v1/admin/` | roles (CRUD), permissions, users, categories, feature-flags, settings, audit-logs, dashboard |
| `/api/v1/client/` | profile, addresses, notifications, wallet, search, content (faqs/pages/sliders), geo (countries/cities) |
| `/api/v1/` | health, config/bootstrap |

---

## RBAC System

### How It Works

1. Admin route has a name: `admin.orders.update`. The route name **is** the permission.
2. `RbacAutoCheck` (`rbac.auto`) skips the identity routes (`admin.auth.*`, `admin.profile.*`).
3. `PermissionResolver::allows()` passes a `super_admin` on every route; a
   supervisor passes only when the route name is in her effective grants.
4. Otherwise HTTP 403 `PERMISSION_DENIED` with `X-Missing-Permission: admin.orders.update`.
5. The dashboard reads the signed-in admin's grants from `GET /admin/auth/my-permissions`
   (flat route names + granted actions per section, same `PermissionResolver`
   source). A super admin gets `is_super_admin: true` with empty lists (TD-53).

Enforced in every environment, testing included (TD-28). Unnamed or
uncatalogued routes are refused.

### Catalog

`config/rbac.php` groups every admin route into one of 21 dashboard sections ×
5 actions (view / create / update / delete / approve). `php artisan
rbac:sync-permissions` writes one `permissions` row per route (`route_name`,
`resource` = section, `action`, ar/en titles), prunes rows of removed routes,
and fails when a route has no section (TD-27). The permission seeder runs it.

### Hierarchy

A senior (parent) role inherits the permissions of its **active descendants**.
A child never inherits from its parent (TD-28):

```
Operations Manager (level 0) — own + Orders Officer + Support Agent
├── Orders Officer (level 1) — own only
└── Support Agent (level 1) — own only
```

Effective grants = role's own ∪ active descendants' ∪ direct grants (`permission_user`).

### Caching

Effective grants are cached per user for `rbac.cache_ttl` (1 hour). Role
changes and the catalog sync bump a global version so every entry misses at
once; account changes forget that user's entry. The application cache is
never flushed.

---

## Audit Trail

**Automatic** — no manual code needed. The `AuditChange` pipeline pipe writes the
entry inside the save transaction (a rolled-back save leaves no row) and captures:

| Field | Content |
|-------|---------|
| `auditable_type` | Model class (e.g., `App\Models\Order`) |
| `auditable_id` | Model primary key |
| `action` | `created`, `updated`, `deleted`, `admin_action` |
| `old_values` | Previous values (updates only) |
| `new_values` | New values |
| `actor_id` | Who made the change |
| `ip_address` | Request IP |
| `correlation_id` | Request trace ID |

**Sensitive fields excluded:** passwords, tokens, secrets (configurable in `config/audit.php`).

**Retention:** Configurable per model. Default 90 days. Run `php artisan audit:prune` daily.

---

## Smart Caching

### Version-Counter Pattern

```php
// Cache a response — auto-invalidates when products table changes
$data = app(SmartCacheInterface::class)->remember(
    'product_list',
    ['products', 'categories'],  // tables to monitor
    fn() => Product::with('category')->get(),
    ttl: 3600
);
```

**How invalidation works:**
1. Cache key includes version numbers: `product_list:abc123` (hash of table versions)
2. When a product is saved, `InvalidateCache` pipe bumps `version:table:products`
3. Next read generates a different hash → cache miss → fresh data

**Zero stale data. Zero manual `Cache::forget()` calls.**

### AnalyticsBuffer

High-frequency events (views, clicks) buffered in Redis, flushed to DB every 5 minutes:

```php
AnalyticsBuffer::increment('product_view', $productId, [
    'country' => 'EG',
    'platform' => 'ios',
]);
// Zero DB writes — Redis only. Flushed by: php artisan cache:flush-analytics
```

---

## Feature Flags

```php
// Check anywhere in code (zero DB queries — cached)
if (feature('checkout_v2')) {
    // New checkout flow
}

// Targeting rules (evaluated per-request context)
// Flag enabled only for Egyptian users on iOS above version 2.0:
{
    "targeting_rules": [
        {"type": "country", "values": ["EG"]},
        {"type": "version", "min_version": "2.0.0"},
        {"type": "percentage", "value": 50, "seed": "checkout_v2"}
    ]
}
```

Admin CRUD at `/api/v1/admin/feature-flags` with toggle endpoint.

---

## Geo System

### Request-Scoped Context

Resolved once per request by `GeoContextMiddleware`:

```php
GeoContext::current()->country;    // "EG"
GeoContext::current()->currency;   // "EGP"
GeoContext::current()->latitude;   // 30.0444
GeoContext::current()->source;     // "header" | "profile" | "ip" | "default"
```

### Resolution Chain

1. **Headers** (`X-Country`, `X-City`) — trusted from mobile GPS
2. **User Profile** — authenticated user's stored location
3. **IP Geolocation** — cached per IP for 6 hours
4. **Config Default** — fallback from `config/geo.php`

### Spatial Queries

```php
// Models using HasCoordinates trait:
ShowRoom::nearby(lat: 30.04, lng: 31.23, radiusKm: 10)->get();
ShowRoom::orderByDistance(30.04, 31.23)->paginate();
```

---

## EventBus

Redis pub/sub for real-time domain events:

```php
app(EventDispatcherInterface::class)->dispatch('orders', 'order.created', [
    'order_id' => $order->id,
    'user_id'  => $user->id,
]);
```

**Features:**
- Event whitelist (unregistered events silently skipped)
- Per-user rate limiting (5 events/minute/user/type)
- Channel-based routing (`orders`, `users`, `notifications`)

---

## Resilience Patterns

### Idempotency Middleware

Prevents duplicate operations from network retries:

```
POST /api/v1/client/checkout
Idempotency-Key: abc-123-def

→ First call: processes, caches response
→ Second call (same key): returns cached response (no duplicate order)
→ Concurrent call: HTTP 409 Conflict
```

Keys scoped per user. TTL: 24 hours (configurable).

### Circuit Breaker

Protects against cascading failures from external services:

```php
$result = app(CircuitBreaker::class)->call(
    service: 'stripe',
    operation: fn() => $stripe->charge($amount),
    fallback: fn() => ['success' => false, 'message' => 'Payment unavailable'],
);
```

**States:** Closed → Open (after 5 failures) → Half-Open (probe after 30s) → Closed (on probe success)

---

## Observability

### Correlation ID

Every request gets a UUID propagated through all logs, jobs, and external calls:

```
Request → Log: {"correlation_id": "abc-123", ...}
       → Job: {"correlation_id": "abc-123", ...}
       → Response header: X-Correlation-ID: abc-123
```

### Performance Monitor

Debug headers, only when `APP_DEBUG=true` outside production (never in a
production build; `X-Powered-By` is always removed):
```
X-Duration-Ms: 42.5
X-Query-Count: 3
X-Memory-MB: 12.4
```

Slow requests (>2s) logged as warnings with full context.

---

## Code Generators

| Command | Generated File |
|---------|---------------|
| `make:action Checkout/ApplyCouponAction` | `app/Domain/Checkout/Actions/ApplyCouponAction.php` |
| `make:value-object Cart/CartTotal` | `app/Domain/Cart/ValueObjects/CartTotal.php` |
| `make:query OrderQueryService --context=Admin` | `app/Services/Admin/Queries/OrderQueryService.php` |
| `make:command-service OrderCommandService --context=Admin` | `app/Services/Admin/Commands/OrderCommandService.php` |
| `make:enum OrderStatus` | `app/Enums/OrderStatus.php` |

All generators produce fully documented, standards-compliant code with section separators and PHPDoc blocks.

---


## Configuration Reference

| File | Purpose | Key Settings |
|------|---------|-------------|
| `config/audit.php` | Audit trail | `enabled`, `retention_days`, `sensitive_fields`, `excluded_models` |
| `config/geo.php` | Geographic system | `default_country`, `default_currency`, `header_keys`, `hierarchy` |
| `config/resilience.php` | Fault tolerance | `idempotency_ttl`, `circuit_breaker.{service}.threshold/timeout` |
| `config/observability.php` | Monitoring | `slow_request_threshold` (ms) |
| `config/bootstrap.php` | Client config | `min_version.{platform}`, `maintenance.enabled/message` |

---

## Request Lifecycle

### Admin Request: "Update Category"

```
PUT /api/v1/admin/categories/5
│
├── Middleware: auth:api → active → rbac.auto
│   └── RbacAutoCheck: "admin.categories.update" → checks "categories.update" permission
│
├── Controller: CategoryController@update
│   └── Validates request
│   └── Delegates to CategoryCommandService
│
├── Service: CategoryCommandService->save($request, id: 5)
│   └── DB::beginTransaction()
│   └── Pipeline: BeforeSave → FillModel → SaveModel → SyncRelations → HandleMedia
│                 → AuditChange → InvalidateCache → DispatchEvents → AfterSave
│   └── DB::commit()
│
├── Pipeline side effects (automatic):
│   ├── AuditChange: logs old/new values to audit_logs table
│   ├── InvalidateCache: bumps version:table:categories counter
│   └── DispatchEvents: fires CategoryUpdated event (if defined)
│
└── Response: ApiResponse::success($data)->message('Data saved')->httpStatus(200)
```

### Client Request: "Get FAQs"

> Illustrates the base request flow. The guest `faqs` route is unregistered
> until `admin/content.md` rebuilds it on the schema tables (TD-30).

```
GET /api/v1/client/faqs
│
├── Middleware: geo.context
│   └── GeoContextMiddleware: resolves country/currency from header/IP
│
├── Controller: ContentController@faqs
│   └── SmartCacheInterface->remember('faqs:ar', ['faqs'], callback)
│       └── Cache HIT? → return cached (3ms)
│       └── Cache MISS? → query DB, cache result, return (45ms)
│
└── Response: ApiResponse::success($faqs)->message('FAQs retrieved')
```

---

## Extending the Base

### Adding a New Feature (e.g., "Orders")

```bash
# 1. Generate the services
php artisan make:query OrderQueryService --context=Admin
php artisan make:command-service OrderCommandService --context=Admin
php artisan make:query OrderQueryService --context=Client

# 2. Generate domain components
php artisan make:action Checkout/CreateOrderAction
php artisan make:value-object Checkout/OrderTotal

# 3. Create model + migration
php artisan make:model Order -m

# 4. Create controller (manually — thin, follows convention)
# 5. Register routes in routes/api/v1/admin.php and client.php
# 6. Done.
```

### Overriding Infrastructure in a Derived Project

```php
// In your project's AppServiceProvider:
public function register(): void
{
    // Swap the cache implementation
    $this->app->singleton(SmartCacheInterface::class, MyRedisClusterCache::class);

    // Swap the payment registry
    $this->app->bind(IntegrationRegistryInterface::class, MyCustomRegistry::class);
}
```

No base project files modified. Contract stays the same, implementation changes.

### Adding a Custom Pipeline Pipe

```php
// app/Core/Pipeline/Save/SendWebhook.php
class SendWebhook
{
    public function __invoke(array $payload, Closure $next): mixed
    {
        $result = $next($payload); // Let save complete first
        [$request, $model, $service] = $payload;

        // Fire webhook after save
        dispatch(new WebhookJob($model));

        return $result;
    }
}

// In your service:
protected function getSavePipes(): array
{
    return [
        ...parent::getSavePipes(),
        SendWebhook::class, // ← appended
    ];
}
```

---

## Database Migrations

| Migration | Table | Purpose |
|-----------|-------|---------|
| `add_rbac_hierarchy_to_roles_table` | `roles` | Adds `parent_id`, `level` for role hierarchy |
| `add_resource_action_to_permissions_table` | `permissions` | Adds `resource`, `action` for route-based RBAC |
| `create_audit_logs_table` | `audit_logs` | Model change tracking |
| `create_feature_flags_table` | `feature_flags` | Server-side feature toggles |
| `create_integrations_table` | `integrations` | 3rd party service registry |
| `create_app_configs_table` | `app_configs` | Dynamic server-driven configuration |
| `create_search_histories_table` | `search_histories` | User search behavior tracking |

---

## Environment Variables

```env
# Audit
AUDIT_ENABLED=true
AUDIT_RETENTION_DAYS=90

# Geo
GEO_DEFAULT_COUNTRY=EG
GEO_DEFAULT_CURRENCY=EGP

# Resilience
IDEMPOTENCY_TTL=86400

# Observability
SLOW_REQUEST_THRESHOLD_MS=2000

# Bootstrap
MIN_APP_VERSION_IOS=1.0.0
MIN_APP_VERSION_ANDROID=1.0.0
APP_MAINTENANCE=false
APP_MAINTENANCE_MESSAGE="Service temporarily unavailable"
```

---

## Performance Characteristics

| Scenario | Expected | How |
|----------|----------|-----|
| Cached read endpoint | 3-8ms | SmartResponseCache (version-counter) |
| Uncached read endpoint | 20-80ms | Direct DB with pipeline |
| Write endpoint (admin) | 30-100ms | Transaction + pipeline + audit |
| Feature flag check | <0.1ms | In-memory cache (loaded once per 5 min) |
| RBAC permission check | <0.1ms | Per-request cache (loaded once per session) |
| GeoIP resolution | 0ms (cached) | 6-hour IP cache |

---

*Generated from Laravel Base — July 2025*
