fix: uses symfony de-normalizer to map tmdb data to objects
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Base\Framework\Controller;
|
||||
use App\Monitor\Action\Command\MonitorTvShowCommand;
|
||||
use App\Monitor\Action\Handler\MonitorTvShowHandler;
|
||||
use App\Tmdb\Tmdb;
|
||||
use App\Tmdb\TmdbClient;
|
||||
use App\User\Framework\Entity\User;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -16,20 +17,18 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
final class IndexController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Tmdb $tmdb,
|
||||
private readonly TmdbClient $tmdb,
|
||||
private readonly MonitorTvShowHandler $monitorTvShowHandler,
|
||||
) {}
|
||||
|
||||
#[Route('/', name: 'app_index')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
return $this->render('index/index.html.twig', [
|
||||
'active_downloads' => $this->getUser()->getActiveDownloads(),
|
||||
'recent_downloads' => $this->getUser()->getDownloads(),
|
||||
'popular_movies' => $this->tmdb->popularMovies(1, 6),
|
||||
'popular_tvshows' => $this->tmdb->popularTvShows(1, 6),
|
||||
'popular_movies' => $this->tmdb->popularMovies(),
|
||||
'popular_tvshows' => $this->tmdb->popularTvShows(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
14
src/Tmdb/HydrationListener.php
Normal file
14
src/Tmdb/HydrationListener.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tmdb;
|
||||
|
||||
use Tmdb\Event\HydrationEvent;
|
||||
|
||||
class HydrationListener extends \Tmdb\Event\Listener\HydrationListener
|
||||
{
|
||||
public function hydrateSubject(HydrationEvent $event)
|
||||
{
|
||||
dump($event);
|
||||
dd('hre');
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,12 @@ use App\ValueObject\ResultFactory;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\ObjectMapper\ObjectMapper;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Tmdb\Api\Find;
|
||||
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;
|
||||
@@ -62,8 +64,12 @@ class Tmdb
|
||||
'uri_factory' => null,
|
||||
],
|
||||
'hydration' => [
|
||||
'event_listener_handles_hydration' => false,
|
||||
'only_for_specified_models' => []
|
||||
'event_listener_handles_hydration' => true,
|
||||
'only_for_specified_models' => [
|
||||
Movie::class,
|
||||
Tv::class,
|
||||
Tv\Episode::class,
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
@@ -92,6 +98,9 @@ class Tmdb
|
||||
$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);
|
||||
}
|
||||
@@ -247,19 +256,21 @@ class Tmdb
|
||||
return $series;
|
||||
}
|
||||
|
||||
public function relatedMedia(string $tmdbId, string $mediaType, int $maxResults = 6)
|
||||
public function relatedMedia(string $tmdbId, string $mediaType, int $maxResults = 20)
|
||||
{
|
||||
$repos = [
|
||||
'movies' => $this->movieRepository,
|
||||
'tvshows' => $this->tvRepository,
|
||||
];
|
||||
$results = $repos[$mediaType]->getRecommendations($tmdbId, ['append_to_response' => 'external_ids']);
|
||||
|
||||
$results = $repos[$mediaType]->getRecommendations($tmdbId);
|
||||
return Map::from(array_values($results->toArray()))
|
||||
->slice(0, 6)
|
||||
$results = Map::from(array_values($results->toArray()))
|
||||
->take($maxResults)
|
||||
->map(function ($result) use ($mediaType) {
|
||||
return $this->parseResult($result, $mediaType);
|
||||
})->toArray();
|
||||
|
||||
dd($results);
|
||||
}
|
||||
|
||||
public function mediaDetails(string $id, string $type)
|
||||
|
||||
166
src/Tmdb/TmdbClient.php
Normal file
166
src/Tmdb/TmdbClient.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,22 @@
|
||||
|
||||
namespace App\Tmdb;
|
||||
|
||||
use Symfony\Component\Serializer\Attribute\SerializedPath;
|
||||
|
||||
class TmdbResult
|
||||
{
|
||||
public function __construct(
|
||||
#[SerializedPath('[external_ids][imdb_id]')]
|
||||
public ?string $imdbId = "",
|
||||
public ?string $tmdbId = "",
|
||||
#[SerializedPath('[id]')]
|
||||
public ?int $tmdbId = null,
|
||||
#[SerializedPath('[title]')]
|
||||
public ?string $title = "",
|
||||
public ?string $poster = "",
|
||||
#[SerializedPath('[overview]')]
|
||||
public ?string $description = "",
|
||||
public ?string $year = "",
|
||||
public ?string $poster = "",
|
||||
public ?\DateTimeInterface $premiereDate = null,
|
||||
public ?string $year = null,
|
||||
public ?string $mediaType = "",
|
||||
public ?array $episodes = null,
|
||||
public ?string $episodeAirDate = null,
|
||||
|
||||
108
src/Tmdb/TmdbResultDenormalizer.php
Normal file
108
src/Tmdb/TmdbResultDenormalizer.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tmdb;
|
||||
|
||||
use App\Base\Enum\MediaType;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
class TmdbResultDenormalizer implements DenormalizerInterface
|
||||
{
|
||||
const POSTER_IMG_PATH = "https://image.tmdb.org/t/p/w500";
|
||||
|
||||
public function __construct(
|
||||
#[Autowire(service: 'serializer.normalizer.object')]
|
||||
private readonly NormalizerInterface $normalizer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): TmdbResult|array|null
|
||||
{
|
||||
$parsers = [
|
||||
MediaType::Movie->value => 'parseMovie',
|
||||
MediaType::TvShow->value => 'parseTvShow',
|
||||
'tvepisodes' => 'parseEpisode',
|
||||
];
|
||||
|
||||
$denormalized = $this->normalizer->denormalize($data, TmdbResult::class, $format, $context);
|
||||
return $this->{$parsers[$context['media_type']]}($data, $denormalized);
|
||||
}
|
||||
|
||||
private function parseTvShow(array $data, TmdbResult $result): TmdbResult
|
||||
{
|
||||
if (!in_array($data['first_air_date'], ['', null,])) {
|
||||
$airDate = (new \DateTime($data['first_air_date']));
|
||||
} else {
|
||||
$airDate = null;
|
||||
}
|
||||
|
||||
$result->premiereDate = $airDate;
|
||||
$result->poster = (null !== $data['poster_path']) ? self::POSTER_IMG_PATH . $data['poster_path'] : null;
|
||||
$result->year = (null !== $airDate) ? $airDate->format('Y') : null;
|
||||
$result->mediaType = "tvshows";
|
||||
if (array_key_exists('episodes', $data)) {
|
||||
$result->episodes = $data['episodes'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function parseMovie(array $data, TmdbResult $result): TmdbResult
|
||||
{
|
||||
if (!in_array($data['release_date'], ['', null,])) {
|
||||
$airDate = (new \DateTime($data['release_date']));
|
||||
} else {
|
||||
$airDate = null;
|
||||
}
|
||||
|
||||
$result->premiereDate = $airDate;
|
||||
$result->poster = (null !== $data['poster_path']) ? self::POSTER_IMG_PATH . $data['poster_path'] : null;
|
||||
$result->year = (null !== $airDate) ? $airDate->format('Y') : null;
|
||||
$result->mediaType = "movies";
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function parseEpisode(array $data, TmdbResult $result): TmdbResult
|
||||
{
|
||||
if (!in_array($data['air_date'], ['', null,])) {
|
||||
$airDate = (new \DateTime($data['air_date']));
|
||||
} else {
|
||||
$airDate = null;
|
||||
}
|
||||
|
||||
$result->premiereDate = $airDate;
|
||||
$result->episodeAirDate = $airDate;
|
||||
$result->poster = (null !== $data['still_path']) ? self::POSTER_IMG_PATH . $data['still_path'] : null;
|
||||
$result->year = (null !== $airDate) ? $airDate->format('Y') : null;
|
||||
$result->mediaType = "tvshows";
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supportsDenormalization(
|
||||
mixed $data,
|
||||
string $type,
|
||||
?string $format = null,
|
||||
array $context = []
|
||||
): bool {
|
||||
return $type === TmdbResult::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSupportedTypes(?string $format): array
|
||||
{
|
||||
return [
|
||||
TmdbResult::class => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user