'Canon_100D.png', ]; private static ?array $cachedImagePaths = null; protected $table = 'kameras'; protected $fillable = [ 'kode_kamera', 'nama', 'merek', 'harga_sewa', 'stok', 'price', 'iso', 'af_point', 'sensor', 'sensor_score', 'low_light', 'portabilitas', 'af_speed', ]; public function getImagePathAttribute(): ?string { $images = self::availableImages(); if ($images === []) { return null; } $brand = is_string($this->merek) ? $this->merek : ''; $name = is_string($this->nama) ? $this->nama : ''; if ($overridePath = self::overrideImagePath($name)) { return $overridePath; } $sourceName = Str::startsWith(Str::lower($name), Str::lower($brand)) ? $name : trim($brand.' '.$name); $fullCompactVariants = self::compactVariants($sourceName); $modelCompactVariants = self::compactVariants($name); $bestMatch = null; $bestScore = 0; foreach ($images as $image) { $score = self::scoreImageMatch($image, $fullCompactVariants, $modelCompactVariants); if ($score > $bestScore) { $bestScore = $score; $bestMatch = $image['path']; } } return $bestScore >= 70 ? $bestMatch : null; } private static function availableImages(): array { if (self::$cachedImagePaths !== null) { return self::$cachedImagePaths; } $directory = public_path(self::IMAGE_DIRECTORY); if (! File::isDirectory($directory)) { return self::$cachedImagePaths = []; } return self::$cachedImagePaths = collect(File::files($directory)) ->filter(fn ($file) => in_array(strtolower($file->getExtension()), ['jpg', 'jpeg', 'png', 'webp'], true)) ->map(function ($file): array { return [ 'path' => self::IMAGE_DIRECTORY.'/'.$file->getFilename(), 'compact' => self::compactString(pathinfo($file->getFilename(), PATHINFO_FILENAME)), 'priority' => self::extensionPriority($file->getExtension()), 'length' => strlen($file->getFilename()), ]; }) ->sortBy([ ['priority', 'asc'], ['length', 'asc'], ['path', 'asc'], ]) ->values() ->all(); } private static function scoreImageMatch(array $image, array $fullCompactVariants, array $modelCompactVariants): int { $score = 0; $fileCompact = $image['compact']; foreach ($fullCompactVariants as $variant) { if ($variant !== '' && $fileCompact === $variant) { return 160; } if ($variant !== '' && Str::contains($fileCompact, $variant)) { $score = max($score, 125); } } foreach ($modelCompactVariants as $variant) { if ($variant !== '' && $fileCompact === $variant) { return 150; } if ($variant !== '' && Str::contains($fileCompact, $variant)) { $score = max($score, 115); } } $meaningfulTokens = self::meaningfulTokens($modelCompactVariants[0] ?? ''); if ($meaningfulTokens !== [] && collect($meaningfulTokens)->every(fn ($token) => Str::contains($fileCompact, $token))) { $score = max($score, 90); } return $score; } private static function overrideImagePath(string $name): ?string { $overrideFile = self::IMAGE_OVERRIDES[self::compactString($name)] ?? null; if (! $overrideFile) { return null; } $relativePath = self::IMAGE_DIRECTORY.'/'.$overrideFile; return File::exists(public_path($relativePath)) ? $relativePath : null; } private static function compactVariants(string $value): array { $compact = self::compactString($value); if ($compact === '') { return []; } $variants = []; $queue = [$compact]; while ($queue !== []) { $variant = array_shift($queue); if ($variant === '' || in_array($variant, $variants, true)) { continue; } $variants[] = $variant; foreach (['canon', 'sony', 'fujifilm', 'nikon'] as $brand) { if (Str::startsWith($variant, $brand)) { $queue[] = Str::after($variant, $brand); } } if (Str::contains($variant, 'mark')) { $queue[] = str_replace('mark', 'mk', $variant); } if (Str::contains($variant, 'mk')) { $queue[] = str_replace('mk', 'mark', $variant); } } return $variants; } private static function compactString(string $value): string { return (string) preg_replace('/[^a-z0-9]+/', '', Str::lower(Str::ascii($value))); } private static function meaningfulTokens(string $compact): array { preg_match_all('/[a-z]+|\d+/', $compact, $matches); return array_values(array_filter( $matches[0] ?? [], fn ($token) => strlen($token) >= 2 || ctype_digit($token) )); } private static function extensionPriority(string $extension): int { return match (strtolower($extension)) { 'png' => 1, 'jpg', 'jpeg' => 2, 'webp' => 3, default => 9, }; } }