pepper = $pepper; } /** * يحسب الفهرس لقيمة داخل حقل محدد. * * $scope يشمل الجدول والحقل (مثل "driver.phone") عمداً: بدونه يكون فهرس * نفس الرقم متطابقاً في جدول السائقين والركاب، فيستطيع من يقرأ القاعدة * ربط الحسابات ببعضها دون فك أي تشفير. */ public function index(string $scope, ?string $value): ?string { $normalized = self::normalize($scope, $value); if ($normalized === null || $normalized === '') { return null; } return hash_hmac('sha256', $scope . ':' . $normalized, $this->pepper); } /** * فهرس مبتور للبحث الجزئي (مثل الأسماء). * * البتر مقصود: يُنتج تطابقات كاذبة تُصفّى بعد فك التشفير، وهذه الضبابية * هي ما يمنع استخدام الفهرس نفسه في تحليل التكرارات. */ public function bucket(string $scope, ?string $value, int $length = 8): ?string { $full = $this->index($scope, $value); return $full === null ? null : substr($full, 0, $length); } /** * التطبيع قبل الحساب — بدونه يُنتج 0791234567 و+962791234567 فهرسين * مختلفين ويفشل البحث. */ public static function normalize(string $scope, ?string $value): ?string { if ($value === null) return null; $value = trim($value); if ($value === '') return null; if (str_contains($scope, 'phone')) { $digits = preg_replace('/\D+/', '', $value); // توحيد الصيغة المحلية والدولية على شكل واحد $digits = preg_replace('/^00/', '', $digits); if (str_starts_with($digits, '0')) { $cc = getenv('DEFAULT_COUNTRY_CODE') ?: '962'; $digits = $cc . substr($digits, 1); } return $digits; } if (str_contains($scope, 'email')) { return mb_strtolower($value, 'UTF-8'); } // الأسماء: توحيد حالة الأحرف والمسافات، وتوحيد أشكال الألف والياء // والتاء المربوطة العربية حتى لا يتوقف البحث على شكل الكتابة. $value = mb_strtolower($value, 'UTF-8'); $value = preg_replace('/\s+/u', ' ', $value); $value = str_replace( ['أ', 'إ', 'آ', 'ٱ', 'ى', 'ة', 'ؤ', 'ئ'], ['ا', 'ا', 'ا', 'ا', 'ي', 'ه', 'و', 'ي'], $value ); // إزالة التشكيل $value = preg_replace('/[\x{064B}-\x{0652}\x{0640}]/u', '', $value); return trim($value); } }