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;
|
|||
|
|
}
|
|||
|
|
}
|