119 lines
4.5 KiB
PHP
119 lines
4.5 KiB
PHP
<?php
|
||
|
||
namespace App\Modules\Movies\Services;
|
||
|
||
use App\Modules\Movies\Models\{Movie, Genre, Actor, Director, Studio, Country, Language};
|
||
use Illuminate\Support\Facades\DB;
|
||
use App\Modules\Movies\Services\Contracts\UpsertMovieServiceInterface;
|
||
|
||
class UpsertMovieFromProvider implements UpsertMovieServiceInterface
|
||
{
|
||
/**
|
||
* Persist a movie and all relations from normalized provider details.
|
||
*
|
||
* @param array $details Output of MovieProvider::details
|
||
* @param string $mode 'overwrite'|'duplicate'
|
||
* @return Movie
|
||
*/
|
||
public function handle(array $details, string $mode = 'overwrite'): Movie
|
||
{
|
||
return DB::transaction(function () use ($details, $mode) {
|
||
$provider = $details['provider'] ?? null;
|
||
$providerId = $details['provider_id'] ?? null;
|
||
|
||
$movie = null;
|
||
if ($mode === 'overwrite' && $provider && $providerId) {
|
||
$movie = Movie::query()
|
||
->where('provider', $provider)
|
||
->where('provider_id', $providerId)
|
||
->first();
|
||
}
|
||
|
||
if (!$movie) {
|
||
$movie = new Movie();
|
||
}
|
||
|
||
// Fill scalar fields
|
||
$movie->fill([
|
||
'provider' => $provider,
|
||
'provider_id' => $providerId,
|
||
'external_ids' => $details['external_ids'] ?? null,
|
||
'title' => $details['title'] ?? '',
|
||
'original_title' => $details['original_title'] ?? null,
|
||
'description' => $details['description'] ?? null,
|
||
'poster_url' => $details['poster_url'] ?? null,
|
||
'backdrop_url' => $details['backdrop_url'] ?? null,
|
||
'rating' => $details['rating'] ?? null,
|
||
'release_date' => $details['release_date'] ?? null,
|
||
'year' => $details['year'] ?? null,
|
||
'runtime' => $details['runtime'] ?? null,
|
||
]);
|
||
|
||
$movie->save();
|
||
|
||
// Sync relations using names from details payload
|
||
$this->syncByNames($movie, Genre::class, 'genres', $details['genres'] ?? []);
|
||
$this->syncByNames($movie, Actor::class, 'actors', $details['actors'] ?? []);
|
||
$this->syncByNames($movie, Director::class, 'directors', $details['directors'] ?? []);
|
||
$this->syncByNames($movie, Studio::class, 'studios', $details['studios'] ?? []);
|
||
$this->syncByNames($movie, Country::class, 'countries', $details['countries'] ?? []);
|
||
$this->syncByNames($movie, Language::class, 'languages', $details['languages'] ?? []);
|
||
|
||
return $movie->refresh();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param Movie $movie
|
||
* @param class-string $modelClass
|
||
* @param string $relation
|
||
* @param array $names Names to sync
|
||
*/
|
||
protected function syncByNames(Movie $movie, string $modelClass, string $relation, array $names): void
|
||
{
|
||
// Create or fetch IDs by name (case-insensitive normalization)
|
||
$ids = collect($names)
|
||
->filter()
|
||
->map(function ($name) use ($modelClass) {
|
||
$original = (string) $name;
|
||
$normalized = $this->normalizeName($original);
|
||
if ($normalized === '') return null;
|
||
|
||
// Find existing row in a case-insensitive way
|
||
/** @var \Illuminate\Database\Eloquent\Model|null $existing */
|
||
$existing = $modelClass::query()
|
||
->whereRaw('lower(name) = ?', [mb_strtolower($normalized)])
|
||
->first();
|
||
|
||
if ($existing) {
|
||
return $existing->getKey();
|
||
}
|
||
|
||
/** @var \Illuminate\Database\Eloquent\Model $model */
|
||
$model = new $modelClass();
|
||
$model->setAttribute('name', $normalized);
|
||
$model->save();
|
||
return $model->getKey();
|
||
})
|
||
->filter()
|
||
->values()
|
||
->all();
|
||
|
||
// Sync relation if method exists
|
||
if (method_exists($movie, $relation)) {
|
||
$movie->{$relation}()->sync($ids);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Normalize a person/genre/studio/etc name for de-duplication.
|
||
*/
|
||
protected function normalizeName(string $name): string
|
||
{
|
||
// Trim, collapse internal whitespace to single spaces
|
||
$name = trim(preg_replace('/\s+/u', ' ', $name) ?? '');
|
||
// Leave original casing (provider’s) but comparisons will be lowercase.
|
||
return $name;
|
||
}
|
||
}
|