- Add `FileOwnership` to encapsulate file owner and group information. - Add `ProcessUser` to represent and manage system process user details. - Enhance ownership matching and debugging with structured data objects. - Include new documentation on file ownership handling and permission improvements. - Prepare infrastructure for enriched error handling in filesystem operations.
279 lines
9.4 KiB
PHP
279 lines
9.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Discovery\Exceptions;
|
|
|
|
use App\Framework\Exception\ErrorCode;
|
|
use App\Framework\Exception\ExceptionContext;
|
|
use App\Framework\Exception\FrameworkException;
|
|
|
|
/**
|
|
* Base exception for Discovery system operations
|
|
*
|
|
* Provides specialized error handling for discovery-related
|
|
* operations with appropriate error codes and recovery hints.
|
|
*/
|
|
final class DiscoveryException extends FrameworkException
|
|
{
|
|
/**
|
|
* Discovery process failed due to memory constraints
|
|
*/
|
|
public static function memoryExhausted(string $operation, int $currentMemory, int $memoryLimit): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.memory_exhausted', 'DiscoveryService')
|
|
->withData([
|
|
'operation' => $operation,
|
|
'current_memory_mb' => round($currentMemory / 1024 / 1024, 2),
|
|
'memory_limit_mb' => round($memoryLimit / 1024 / 1024, 2),
|
|
'memory_usage_percent' => round(($currentMemory / $memoryLimit) * 100, 1),
|
|
])
|
|
->withDebug([
|
|
'current_memory_bytes' => $currentMemory,
|
|
'memory_limit_bytes' => $memoryLimit,
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Discovery operation '{$operation}' failed due to memory exhaustion",
|
|
$context,
|
|
ErrorCode::MEMORY_LIMIT_EXCEEDED
|
|
)->withRetryAfter(300); // Suggest retry after 5 minutes
|
|
}
|
|
|
|
/**
|
|
* Directory scanning failed
|
|
*/
|
|
public static function scanFailed(string $directory, string $reason, ?\Throwable $previous = null): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.scan_failed', 'FileScanner')
|
|
->withData([
|
|
'directory' => $directory,
|
|
'reason' => $reason,
|
|
'is_readable' => is_readable($directory),
|
|
'exists' => file_exists($directory),
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Failed to scan directory '{$directory}': {$reason}",
|
|
$context,
|
|
ErrorCode::FILESYSTEM_READ_ERROR,
|
|
$previous
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Cache operation failed
|
|
*/
|
|
public static function cacheFailed(string $operation, string $key, ?\Throwable $previous = null): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.cache_failed', 'DiscoveryCacheManager')
|
|
->withData([
|
|
'cache_operation' => $operation,
|
|
'cache_key' => $key,
|
|
])
|
|
->withDebug([
|
|
'key_hash' => md5($key),
|
|
'operation_timestamp' => time(),
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Discovery cache operation '{$operation}' failed for key '{$key}'",
|
|
$context,
|
|
ErrorCode::CACHE_OPERATION_FAILED,
|
|
$previous
|
|
);
|
|
}
|
|
|
|
/**
|
|
* File processing failed
|
|
*/
|
|
public static function fileProcessingFailed(string $file, string $reason, ?\Throwable $previous = null): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.file_processing_failed', 'FileProcessor')
|
|
->withData([
|
|
'file_path' => $file,
|
|
'file_size' => file_exists($file) ? filesize($file) : null,
|
|
'file_extension' => pathinfo($file, PATHINFO_EXTENSION),
|
|
'reason' => $reason,
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Failed to process file '{$file}': {$reason}",
|
|
$context,
|
|
ErrorCode::FILE_PROCESSING_FAILED,
|
|
$previous
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Attribute reflection failed
|
|
*/
|
|
public static function attributeReflectionFailed(string $className, string $attributeType, ?\Throwable $previous = null): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.attribute_reflection_failed', 'AttributeProcessor')
|
|
->withData([
|
|
'class_name' => $className,
|
|
'attribute_type' => $attributeType,
|
|
'class_exists' => class_exists($className),
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Failed to reflect attribute '{$attributeType}' on class '{$className}'",
|
|
$context,
|
|
ErrorCode::REFLECTION_FAILED,
|
|
$previous
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Memory guard emergency stop
|
|
*/
|
|
public static function emergencyStop(string $reason, array $memoryStats): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.emergency_stop', 'MemoryGuard')
|
|
->withData([
|
|
'reason' => $reason,
|
|
'memory_usage_mb' => round($memoryStats['current_usage'] / 1024 / 1024, 2),
|
|
'memory_limit_mb' => round($memoryStats['memory_limit'] / 1024 / 1024, 2),
|
|
'memory_pressure' => $memoryStats['memory_pressure'] ?? 'unknown',
|
|
])
|
|
->withDebug($memoryStats);
|
|
|
|
return self::fromContext(
|
|
"Discovery process stopped by memory guard: {$reason}",
|
|
$context,
|
|
ErrorCode::EMERGENCY_STOP_TRIGGERED
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Configuration validation failed
|
|
*/
|
|
public static function configurationInvalid(string $field, string $reason): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.configuration_invalid', 'DiscoveryConfiguration')
|
|
->withData([
|
|
'invalid_field' => $field,
|
|
'validation_error' => $reason,
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Discovery configuration invalid - {$field}: {$reason}",
|
|
$context,
|
|
ErrorCode::VAL_CONFIGURATION_INVALID
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Discovery timeout
|
|
*/
|
|
public static function timeout(int $timeoutSeconds, int $actualSeconds): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.timeout', 'DiscoveryService')
|
|
->withData([
|
|
'timeout_seconds' => $timeoutSeconds,
|
|
'actual_seconds' => $actualSeconds,
|
|
'exceeded_by_seconds' => $actualSeconds - $timeoutSeconds,
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Discovery operation timed out after {$actualSeconds}s (limit: {$timeoutSeconds}s)",
|
|
$context,
|
|
ErrorCode::OPERATION_TIMEOUT
|
|
)->withRetryAfter(60); // Suggest retry after 1 minute
|
|
}
|
|
|
|
/**
|
|
* Concurrent discovery conflict
|
|
*/
|
|
public static function concurrentDiscovery(string $lockId): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.concurrent_conflict', 'DiscoveryService')
|
|
->withData([
|
|
'lock_id' => $lockId,
|
|
'conflict_type' => 'concurrent_discovery',
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Discovery already in progress (lock: {$lockId})",
|
|
$context,
|
|
ErrorCode::RESOURCE_CONFLICT
|
|
)->withRetryAfter(30); // Suggest retry after 30 seconds
|
|
}
|
|
|
|
/**
|
|
* Corrupted discovery data
|
|
*/
|
|
public static function corruptedData(string $source, string $reason): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.corrupted_data', 'DiscoveryService')
|
|
->withData([
|
|
'data_source' => $source,
|
|
'corruption_reason' => $reason,
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Discovery data corrupted in {$source}: {$reason}",
|
|
$context,
|
|
ErrorCode::DATA_CORRUPTION_DETECTED
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Insufficient permissions
|
|
*/
|
|
public static function insufficientPermissions(string $path, string $requiredPermission): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.insufficient_permissions', 'FileScanner')
|
|
->withData([
|
|
'path' => $path,
|
|
'required_permission' => $requiredPermission,
|
|
'current_permissions' => file_exists($path) ? substr(sprintf('%o', fileperms($path)), -4) : null,
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Insufficient permissions to access '{$path}' (required: {$requiredPermission})",
|
|
$context,
|
|
ErrorCode::PERMISSION_DENIED
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resource limit exceeded
|
|
*/
|
|
public static function resourceLimitExceeded(string $resource, int $current, int $limit): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.resource_limit_exceeded', 'DiscoveryService')
|
|
->withData([
|
|
'resource_type' => $resource,
|
|
'current_usage' => $current,
|
|
'resource_limit' => $limit,
|
|
'usage_percentage' => round(($current / $limit) * 100, 1),
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Resource limit exceeded for {$resource}: {$current}/{$limit}",
|
|
$context,
|
|
ErrorCode::RESOURCE_LIMIT_EXCEEDED
|
|
)->withRetryAfter(60);
|
|
}
|
|
|
|
/**
|
|
* Dependency missing
|
|
*/
|
|
public static function dependencyMissing(string $dependency, string $requiredFor): self
|
|
{
|
|
$context = ExceptionContext::forOperation('discovery.dependency_missing', 'DiscoveryService')
|
|
->withData([
|
|
'missing_dependency' => $dependency,
|
|
'required_for' => $requiredFor,
|
|
]);
|
|
|
|
return self::fromContext(
|
|
"Missing dependency '{$dependency}' required for {$requiredFor}",
|
|
$context,
|
|
ErrorCode::DEPENDENCY_MISSING
|
|
);
|
|
}
|
|
}
|