fix: uses symfony de-normalizer to map tmdb data to objects

This commit is contained in:
2025-09-01 21:01:10 -05:00
parent 662e2600f6
commit b8b71fa5b3
9 changed files with 396 additions and 15 deletions

166
src/Tmdb/TmdbClient.php Normal file
View File

@@ -0,0 +1,166 @@
<?php
namespace App\Tmdb;
use Aimeos\Map;
use App\Base\Enum\MediaType;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\ObjectMapper\ObjectMapper;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Tmdb\Client;
use Tmdb\Event\BeforeRequestEvent;
use Tmdb\Event\HydrationEvent;
use Tmdb\Event\Listener\Psr6CachedRequestListener;
use Tmdb\Event\Listener\Request\AcceptJsonRequestListener;
use Tmdb\Event\Listener\Request\ApiTokenRequestListener;
use Tmdb\Event\Listener\Request\ContentTypeJsonRequestListener;
use Tmdb\Event\Listener\Request\UserAgentRequestListener;
use Tmdb\Event\RequestEvent;
use Tmdb\Model\Movie;
use Tmdb\Model\Tv;
use Tmdb\Repository\MovieRepository;
use Tmdb\Repository\TvRepository;
use Tmdb\Token\Api\ApiToken;
use Tmdb\Token\Api\BearerToken;
class TmdbClient
{
protected Client $client;
protected MovieRepository $movieRepository;
protected TvRepository $tvRepository;
protected ObjectMapper $objectMapper;
const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500";
public function __construct(
private readonly SerializerInterface $serializer,
private readonly CacheItemPoolInterface $cache,
private readonly EventDispatcherInterface $eventDispatcher,
#[Autowire(env: 'TMDB_API')] string $apiKey,
) {
$this->client = new Client(
[
/** @var ApiToken|BearerToken */
'api_token' => new BearerToken($apiKey),
'secure' => true,
'base_uri' => Client::TMDB_URI,
'event_dispatcher' => [
'adapter' => $this->eventDispatcher,
],
// We make use of PSR-17 and PSR-18 auto discovery to automatically guess these, but preferably set these explicitly.
'http' => [
'client' => null,
'request_factory' => null,
'response_factory' => null,
'stream_factory' => null,
'uri_factory' => null,
],
'hydration' => [
'event_listener_handles_hydration' => true,
'only_for_specified_models' => [
Movie::class,
Tv::class,
Tv\Episode::class,
]
]
]
);
/**
* Required event listeners and events to be registered with the PSR-14 Event Dispatcher.
*/
$requestListener = new Psr6CachedRequestListener(
$this->client->getHttpClient(),
$this->eventDispatcher,
$cache,
$this->client->getHttpClient()->getPsr17StreamFactory(),
[]
);
$this->eventDispatcher->addListener(RequestEvent::class, $requestListener);
$apiTokenListener = new ApiTokenRequestListener($this->client->getToken());
$this->eventDispatcher->addListener(BeforeRequestEvent::class, $apiTokenListener);
$acceptJsonListener = new AcceptJsonRequestListener();
$this->eventDispatcher->addListener(BeforeRequestEvent::class, $acceptJsonListener);
$jsonContentTypeListener = new ContentTypeJsonRequestListener();
$this->eventDispatcher->addListener(BeforeRequestEvent::class, $jsonContentTypeListener);
$userAgentListener = new UserAgentRequestListener();
$this->eventDispatcher->addListener(BeforeRequestEvent::class, $userAgentListener);
// $hydrationListener = new HydrationListener($this->eventDispatcher);
// $this->eventDispatcher->addListener(HydrationEvent::class, $hydrationListener);
$this->movieRepository = new MovieRepository($this->client);
$this->tvRepository = new TvRepository($this->client);
}
public function search() {}
public function find() {}
public function movieDetails() {}
public function tvshowDetails() {}
public function tvEpisodeDetails() {}
public function relatedMedia() {}
public function popularMovies(int $resultCount = 6): Map
{
$results = $this->movieRepository->getApi()->getPopular();
return Map::from($results['results'])->map(function ($result) {
$result['external_ids'] = $this->getExternalIds($result['id'], MediaType::Movie->value);
return $result;
})->filter(function ($result) {
return array_key_exists('imdb_id', $result['external_ids']);
})->map(function ($result) {
$result = $this->serializer->denormalize($result, TmdbResult::class, context: ['media_type' => MediaType::Movie->value]);
return $result;
})->slice(0, $resultCount);
}
public function popularTvShows(int $resultCount = 6): Map
{
$results = $this->tvRepository->getApi()->getPopular();
return Map::from($results['results'])->map(function ($result) {
$result['external_ids'] = $this->getExternalIds($result['id'], MediaType::Movie->value);
return $result;
})->filter(function ($result) {
return array_key_exists('imdb_id', $result['external_ids']) && $result['external_ids']['imdb_id'] !== null && $result['external_ids']['imdb_id'] !== "";
})->map(function ($result) {
$result = $this->serializer->denormalize($result, TmdbResult::class, context: ['media_type' => MediaType::TvShow->value]);
return $result;
})->slice(0, $resultCount);
}
public function getExternalIds(string $tmdbId, $mediaType)
{
try {
switch (MediaType::tryFrom($mediaType)->value) {
case MediaType::Movie->value:
$externalIds = $this->movieRepository->getApi()->getExternalIds($tmdbId);
break;
case MediaType::TvShow->value:
$externalIds = $this->tvRepository->getApi()->getExternalIds($tmdbId);
break;
default:
$externalIds = null;
break;
}
} catch (\throwable $e) {
return [];
}
return $externalIds;
}
}