MIF_E31231430/app/Models/Kamera.php

212 lines
5.9 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
class Kamera extends Model
{
public const DEFAULT_STOCK = 2;
private const IMAGE_DIRECTORY = 'images/kameras';
private const IMAGE_OVERRIDES = [
'canon1100d' => '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,
};
}
}