# Error Handling

## Overview

Proper error handling ensures a robust application that provides meaningful feedback to users and developers. This document covers validation, exceptions, and error responses.

## Validation

### Form Request Validation

**Location**: `app/Http/Requests/`

```php
<?php

namespace App\Http\Requests\V1\Catalog;

use Illuminate\Foundation\Http\FormRequest;

class StoreProductRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request
     */
    public function authorize(): bool
    {
        return $this->user()->can('create', Product::class);
    }

    /**
     * Get the validation rules
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'sku' => ['required', 'string', 'unique:products,sku'],
            'price' => ['required', 'numeric', 'min:0'],
            'category_id' => ['required', 'integer', 'exists:categories,id'],
            'description' => ['nullable', 'string', 'max:1000'],
            'is_active' => ['boolean'],
            'variants' => ['array'],
            'variants.*.name' => ['required_with:variants', 'string'],
            'variants.*.price' => ['required_with:variants', 'numeric', 'min:0'],
        ];
    }

    /**
     * Custom error messages
     */
    public function messages(): array
    {
        return [
            'name.required' => __('Product name is required'),
            'sku.unique' => __('This SKU is already in use'),
            'category_id.exists' => __('Selected category does not exist'),
        ];
    }

    /**
     * Custom attribute names
     */
    public function attributes(): array
    {
        return [
            'sku' => __('SKU'),
            'category_id' => __('category'),
        ];
    }
}
```

### Complex Validation with Validator Hook

```php
<?php

namespace App\Http\Requests\V1\Accounting;

use App\Domains\Accounting\ApprovalSetting\Http\Requests\V1\Traits\ValidatesActivationConditions;
use Illuminate\Foundation\Http\FormRequest;

class ApprovalSettingRequest extends FormRequest
{
    use ValidatesActivationConditions;

    public function rules(): array
    {
        $settingId = $this->route('approval_setting') ?? $this->id ?? 'NULL';

        return [
            'department' => [
                'required',
                'string',
                'unique:approval_settings,department,' . $settingId . ',id,company_id,' . company()->id,
            ],
            'requires_approval' => ['boolean'],
            'approval_path' => ['required', 'string', 'in:individual,parallel'],
            'is_active' => ['boolean'],
            'max_response_time' => ['nullable', 'integer', 'min:1'],
            'approvers' => ['required', 'array', 'min:1'],
            'approvers.*.labor_category_id' => ['nullable', 'integer', 'exists:labor_categories,id'],
            'approvers.*.user_id' => ['nullable', 'integer', 'exists:users,id'],
            'approvers.*.order' => ['required', 'integer', 'min:1'],
        ];
    }

    /**
     * Configure the validator instance
     */
    public function withValidator($validator)
    {
        $validator->after(function ($validator) {
            if ($this->has('approvers')) {
                foreach ($this->approvers as $index => $approver) {
                    $hasLaborCategory = !empty($approver['labor_category_id']);
                    $hasUser = !empty($approver['user_id']);

                    if (!$hasLaborCategory && !$hasUser) {
                        $validator->errors()->add(
                            "approvers.{$index}",
                            __('You must specify either a role or a user for the approver')
                        );
                    }

                    if ($hasLaborCategory && $hasUser) {
                        $validator->errors()->add(
                            "approvers.{$index}",
                            __('You cannot specify both the role and the user')
                        );
                    }
                }
            }
        });
    }
}
```

### Validation Trait

```php
<?php

namespace App\Http\Requests\V1\Traits;

trait ValidatesActivationConditions
{
    protected function getActivationConditionRules(?string $department = null, string $prefix = ''): array
    {
        $fieldService = app(DepartmentFieldService::class);
        $availableFields = $department
            ? array_keys($fieldService->getFieldsForDepartment($department))
            : [];

        return [
            "{$prefix}field" => ['required', 'string', 'in:' . implode(',', $availableFields)],
            "{$prefix}operator" => ['required', 'string'],
            "{$prefix}value" => ['required'],
        ];
    }

    protected function validateActivationConditionCompatibility(
        $validator,
        string $department,
        string $field,
        string $operator,
        string $errorPrefix = ''
    ): void {
        $fieldService = app(DepartmentFieldService::class);
        $fieldType = $fieldService->getFieldType($department, $field);

        if (!$fieldType) {
            $validator->errors()->add(
                $errorPrefix . 'field',
                __('Field does not exist for this department')
            );
            return;
        }

        $allowedOperators = $fieldService->getAllowedOperators($fieldType);

        if (!in_array($operator, $allowedOperators)) {
            $validator->errors()->add(
                $errorPrefix . 'operator',
                __('Operator :operator is not allowed for field type :type', [
                    'operator' => $operator,
                    'type' => $fieldType,
                ])
            );
        }
    }
}
```

## Exception Handling

### Built-in Laravel Exceptions

```php
<?php

use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\Access\AuthorizationException;

// Validation exception
throw ValidationException::withMessages([
    'email' => ['The email is already taken.'],
    'password' => ['The password is too weak.'],
]);

// Model not found
throw new ModelNotFoundException('Product not found');

// Authentication
throw new AuthenticationException('Unauthenticated');

// Authorization
throw new AuthorizationException('You do not have permission');
```

### Custom Domain Exceptions

```php
<?php

namespace App\Exceptions;

use Exception;

class InsufficientBalanceException extends Exception
{
    public function __construct(
        public float $required,
        public float $available,
        string $message = null
    ) {
        parent::__construct($message ?? "Insufficient balance. Required: {$required}, Available: {$available}");
    }

    /**
     * Report the exception
     */
    public function report(): bool
    {
        // Log the exception
        \Log::warning('Insufficient balance attempt', [
            'required' => $this->required,
            'available' => $this->available,
            'user_id' => auth()->id(),
        ]);

        return false; // Don't report to error tracking
    }

    /**
     * Render the exception as an HTTP response
     */
    public function render($request)
    {
        return response()->json([
            'success' => false,
            'code' => 422,
            'message' => $this->getMessage(),
            'errors' => [
                'balance' => [
                    __('Insufficient balance. Required: :required, Available: :available', [
                        'required' => number_format($this->required, 2),
                        'available' => number_format($this->available, 2),
                    ])
                ]
            ]
        ], 422);
    }
}
```

### More Custom Exceptions

```php
<?php

namespace App\Exceptions;

// Product-related exceptions
class ProductNotFoundException extends Exception
{
    public function __construct(int $productId)
    {
        parent::__construct("Product with ID {$productId} not found");
    }
}

class InsufficientStockException extends Exception
{
    public function __construct(
        public int $productId,
        public float $requested,
        public float $available
    ) {
        parent::__construct("Insufficient stock for product {$productId}");
    }
}

// Order-related exceptions
class OrderCannotBeCancelledException extends Exception
{
    public function __construct(string $status)
    {
        parent::__construct("Order cannot be cancelled when status is {$status}");
    }
}

// Business rule exceptions
class DuplicateEntryException extends Exception {}
class InvalidStateTransitionException extends Exception {}
class DependencyExistsException extends Exception {}
```

### Exception Usage in Services

```php
<?php

namespace App\Domains\Accounting\Inventory\Services;

use App\Exceptions\InsufficientStockException;
use App\Exceptions\ProductNotFoundException;

class InventoryService
{
    public function consume(int $productId, float $quantity): void
    {
        $product = Product::find($productId);

        if (!$product) {
            throw new ProductNotFoundException($productId);
        }

        $available = $this->getAvailableStock($productId);

        if ($available < $quantity) {
            throw new InsufficientStockException(
                productId: $productId,
                requested: $quantity,
                available: $available
            );
        }

        // Proceed with consumption
        $this->processConsumption($product, $quantity);
    }
}
```

### Exception Usage in Observers

```php
<?php

namespace App\Domains\Management\Attendance\Observers;

use App\Domains\Management\Attendance\Models\Attendance;
use Carbon\Carbon;
use Illuminate\Validation\ValidationException;

class AttendanceObserver
{
    public function saving(Attendance $attendance): void
    {
        if ($attendance->check_in && $attendance->check_out) {
            $startTime = Carbon::parse($attendance->check_in);
            $endTime = Carbon::parse($attendance->check_out);

            if ($endTime < $startTime) {
                throw ValidationException::withMessages([
                    'check_out' => [__('Check Out Time is Invalid.')],
                ]);
            }
        }
    }

    public function deleting(Attendance $attendance): bool
    {
        if ($attendance->is_approved) {
            throw ValidationException::withMessages([
                'attendance' => [__('Cannot delete approved attendance')],
            ]);
        }

        return true;
    }
}
```

## Global Exception Handler

**Location**: `bootstrap/app.php`

```php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withExceptions(function (Exceptions $exceptions) {
        // Render all exceptions as JSON for API
        $exceptions->shouldRenderJsonWhen(function ($request) {
            return $request->is('api/*') || $request->expectsJson();
        });

        // Custom rendering for specific exceptions
        $exceptions->render(function (InsufficientBalanceException $e, $request) {
            return response()->json([
                'success' => false,
                'code' => 422,
                'message' => $e->getMessage(),
                'errors' => [
                    'balance' => [$e->getMessage()]
                ]
            ], 422);
        });

        $exceptions->render(function (ModelNotFoundException $e, $request) {
            return response()->json([
                'success' => false,
                'code' => 404,
                'message' => __('Resource not found'),
            ], 404);
        });
    })
    ->create();
```

## Error Response Formats

### Standard Error Response

```json
{
    "success": false,
    "code": 422,
    "message": "The given data was invalid",
    "errors": {
        "field_name": ["Error message 1", "Error message 2"]
    }
}
```

### Validation Error (422)

```json
{
    "success": false,
    "code": 422,
    "message": "Validation failed",
    "errors": {
        "email": ["The email field is required."],
        "password": ["The password must be at least 8 characters."]
    }
}
```

### Authentication Error (401)

```json
{
    "success": false,
    "code": 401,
    "message": "Unauthenticated"
}
```

### Authorization Error (403)

```json
{
    "success": false,
    "code": 403,
    "message": "You do not have permission to perform this action"
}
```

### Not Found Error (404)

```json
{
    "success": false,
    "code": 404,
    "message": "Resource not found"
}
```

### Server Error (500)

```json
{
    "success": false,
    "code": 500,
    "message": "An unexpected error occurred"
}
```

## Response Trait

**Location**: `app/Core/Traits/InteractWithResponse.php`

```php
<?php

namespace App\Core\Traits;

use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

trait InteractWithResponse
{
    /**
     * Send a success response
     */
    public function sendSuccessResponse(
        $data = [],
        ?string $message = null,
        ?int $code = 200,
        $to = null
    ): JsonResponse {
        return response()->json([
            'success' => true,
            'code' => $code,
            'message' => $message,
            'to' => $to,
            'result' => $data,
        ], $code);
    }

    /**
     * Send a failed response
     */
    public function sendFailedResponse(
        ?string $message = null,
        int $code = 400,
        $to = null
    ): JsonResponse {
        return response()->json([
            'success' => false,
            'code' => $code,
            'message' => $message,
            'to' => $to,
            'result' => null,
        ], $code);
    }

    /**
     * Send a paginated response
     */
    public function sendPaginatedResponse(
        $resource,
        int $code = Response::HTTP_OK,
        ?string $message = null
    ): JsonResponse {
        if (is_null($message)) {
            $message = Response::$statusTexts[$code];
        }

        $paginator = new CustomPaginator(
            items: $resource->items(),
            total: $resource->total(),
            perPage: $resource->perPage(),
            currentPage: $resource->currentPage(),
            options: [
                'code' => $code,
                'message' => $message,
            ]
        );

        return response()->json($paginator, $code);
    }

    /**
     * Send a validation error response
     */
    public function sendValidationError(array $errors): JsonResponse
    {
        return response()->json([
            'success' => false,
            'code' => 422,
            'message' => __('Validation failed'),
            'errors' => $errors,
        ], 422);
    }

    /**
     * Send a not found response
     */
    public function sendNotFoundResponse(?string $message = null): JsonResponse
    {
        return response()->json([
            'success' => false,
            'code' => 404,
            'message' => $message ?? __('Resource not found'),
        ], 404);
    }
}
```

## Error Logging

### Logging Best Practices

```php
<?php

use Illuminate\Support\Facades\Log;

// Log with context
Log::error('Payment processing failed', [
    'order_id' => $order->id,
    'amount' => $order->total,
    'gateway' => $gateway,
    'error' => $exception->getMessage(),
    'trace' => $exception->getTraceAsString(),
]);

// Log levels
Log::emergency('System is down');
Log::alert('Action required immediately');
Log::critical('Critical error occurred');
Log::error('Runtime error');
Log::warning('Potential issue');
Log::notice('Normal but significant');
Log::info('General information');
Log::debug('Debug information');

// Channel-specific logging
Log::channel('payments')->error('Payment failed', $context);
Log::channel('audit')->info('User action', $context);
```

### Exception Logging in Services

```php
<?php

class PaymentService
{
    public function process(Order $order): PaymentResult
    {
        try {
            $result = $this->gateway->charge($order);

            Log::info('Payment successful', [
                'order_id' => $order->id,
                'transaction_id' => $result->transactionId,
            ]);

            return $result;
        } catch (PaymentException $e) {
            Log::error('Payment failed', [
                'order_id' => $order->id,
                'error' => $e->getMessage(),
                'code' => $e->getCode(),
            ]);

            throw $e;
        } catch (\Exception $e) {
            Log::critical('Unexpected payment error', [
                'order_id' => $order->id,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw new PaymentException('Payment processing failed', 0, $e);
        }
    }
}
```

## Best Practices

### Do

- Use Form Request validation for HTTP requests
- Create domain-specific exceptions
- Provide meaningful error messages
- Log errors with context
- Use appropriate HTTP status codes
- Translate error messages

### Don't

- Catch exceptions silently
- Expose internal errors to users in production
- Use generic exception classes for specific errors
- Return HTML error pages for API endpoints
- Log sensitive data (passwords, tokens)
- Throw exceptions for flow control
