feat: search results
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Enum;
|
||||
|
||||
enum MediaType
|
||||
enum MediaType: string
|
||||
{
|
||||
|
||||
case Movie = 'movies';
|
||||
case TvShow = 'tvshows';
|
||||
}
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
namespace App\Search\Action\Command;
|
||||
|
||||
class SearchCommand
|
||||
{
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
}
|
||||
class SearchCommand implements CommandInterface
|
||||
{
|
||||
/**
|
||||
* @param string $term
|
||||
* @implements CommandInterface<SearchCommand>
|
||||
*/
|
||||
public function __construct(
|
||||
public string $term
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,24 @@
|
||||
|
||||
namespace App\Search\Action\Handler;
|
||||
|
||||
class SearchHandler
|
||||
{
|
||||
use App\Search\Action\Result\SearchResult;
|
||||
use App\Tmdb\Tmdb;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
}
|
||||
class SearchHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Tmdb $tmdb,
|
||||
) {}
|
||||
|
||||
/*** @implements HandlerInterface<SearchResult> */
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
return new SearchResult(
|
||||
term: $command->term,
|
||||
results: $this->tmdb->search($command->term)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,25 @@
|
||||
|
||||
namespace App\Search\Action\Input;
|
||||
|
||||
class SearchInput
|
||||
{
|
||||
use App\Search\Action\Command\SearchCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceQuery;
|
||||
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
|
||||
}
|
||||
/**
|
||||
* @implements InputInterface<SearchCommand>
|
||||
*/
|
||||
class SearchInput implements InputInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[SourceQuery('term')]
|
||||
#[SourceRequest('term')]
|
||||
public string $term
|
||||
){}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new SearchCommand($this->term);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
namespace App\Search\Action\Result;
|
||||
|
||||
class SearchResult
|
||||
{
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
}
|
||||
class SearchResult implements ResultInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $term = "",
|
||||
public array $results = []
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,27 @@
|
||||
|
||||
namespace App\Search\Framework\Controller;
|
||||
|
||||
use App\Search\Action\Handler\SearchHandler;
|
||||
use App\Search\Action\Input\SearchInput;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
final class SearchController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private SearchHandler $searchHandler,
|
||||
) {}
|
||||
|
||||
#[Route('/search', name: 'app_search', methods: ['GET'])]
|
||||
public function index(): Response
|
||||
public function index(
|
||||
SearchInput $searchInput,
|
||||
): Response
|
||||
{
|
||||
$results = $this->searchHandler->handle($searchInput->toCommand());
|
||||
|
||||
return $this->render('search/results.html.twig', [
|
||||
'controller_name' => 'SearchController',
|
||||
'results' => $results,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Tmdb;
|
||||
|
||||
use App\ValueObject\MediaResult;
|
||||
use App\Enum\MediaType;
|
||||
use App\Tmdb\TmdbResult;
|
||||
use App\ValueObject\ResultFactory;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -31,7 +32,7 @@ use Tmdb\Repository\TvSeasonRepository;
|
||||
use Tmdb\Token\Api\ApiToken;
|
||||
use Tmdb\Token\Api\BearerToken;
|
||||
|
||||
class Client
|
||||
class Tmdb
|
||||
{
|
||||
protected Client $client;
|
||||
|
||||
@@ -178,8 +179,8 @@ class Client
|
||||
throw new \Exception("A media type must be set when parsing from an array.");
|
||||
}
|
||||
|
||||
function parseTvShow(array $data, string $posterBasePath): MediaResult {
|
||||
return new MediaResult(
|
||||
function parseTvShow(array $data, string $posterBasePath): TmdbResult {
|
||||
return new TmdbResult(
|
||||
imdbId: $data['external_ids']['imdb_id'],
|
||||
tvdbId: $data['id'],
|
||||
title: $data['name'],
|
||||
@@ -191,8 +192,8 @@ class Client
|
||||
);
|
||||
}
|
||||
|
||||
function parseMovie(array $data, string $posterBasePath): MediaResult {
|
||||
return new MediaResult(
|
||||
function parseMovie(array $data, string $posterBasePath): TmdbResult {
|
||||
return new TmdbResult(
|
||||
imdbId: $data['external_ids']['imdb_id'],
|
||||
tvdbId: $data['id'],
|
||||
title: $data['title'],
|
||||
@@ -209,67 +210,80 @@ class Client
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function parseFromObject($result)
|
||||
private function parseFromObject($result): TmdbResult
|
||||
{
|
||||
$result->mediaType = $result instanceof Movie ? 'movies' : 'tvshows';
|
||||
$mediaType = $result instanceof Movie ? MediaType::Movie->value : MediaType::TvShow->value;
|
||||
$tmdbResult = new TmdbResult();
|
||||
$tmdbResult->mediaType = $mediaType;
|
||||
$tmdbResult->imdbId = $this->getImdbId($result->getId(), $mediaType);
|
||||
$tmdbResult->title = $this->getTitle($result, $mediaType);
|
||||
$tmdbResult->poster = self::POSTER_IMG_PATH . $result->getPosterImage();
|
||||
$tmdbResult->year = $this->getReleaseDate($result, $mediaType);
|
||||
$tmdbResult->description = $result->getOverview();
|
||||
return $tmdbResult;
|
||||
}
|
||||
|
||||
$externalIds = $this->cache->get("tmdb.externalIds.{$result->getId()}", function (ItemInterface $item) use ($result) {
|
||||
if ($result instanceof Movie) {
|
||||
$externalIds = $this->movieRepository->getExternalIds($result->getId());
|
||||
} else {
|
||||
$externalIds = $this->tvRepository->getExternalIds($result->getId());
|
||||
}
|
||||
return $externalIds;
|
||||
public function getImdbId(string $tmdbId, $mediaType)
|
||||
{
|
||||
$externalIds = $this->cache->get("tmdb.externalIds.{$tmdbId}",
|
||||
function (ItemInterface $item) use ($tmdbId, $mediaType) {
|
||||
switch (MediaType::tryFrom($mediaType)->value) {
|
||||
case MediaType::Movie->value:
|
||||
return $this->movieRepository->getExternalIds($tmdbId);
|
||||
case MediaType::TvShow->value:
|
||||
return $this->tvRepository->getExternalIds($tmdbId);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
$images = $this->cache->get("tmdb.images.{$result->getId()}", function (ItemInterface $item) use ($result) {
|
||||
if ($result instanceof Movie) {
|
||||
$images = $this->movieRepository->getImages($result->getId());
|
||||
} else {
|
||||
$images = $this->tvRepository->getImages($result->getId());
|
||||
}
|
||||
return $images;
|
||||
if (null === $externalIds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $externalIds->getImdbId() !== "" ? $externalIds->getImdbId() : "null";
|
||||
}
|
||||
|
||||
public function getImages($tmdbId, $mediaType)
|
||||
{
|
||||
return $this->cache->get("tmdb.images.{$tmdbId}",
|
||||
function (ItemInterface $item) use ($tmdbId, $mediaType) {
|
||||
switch (MediaType::tryFrom($mediaType)->value) {
|
||||
case MediaType::Movie->value:
|
||||
return $this->movieRepository->getImages($tmdbId);
|
||||
case MediaType::TvShow->value:
|
||||
return $this->tvRepository->getImages($tmdbId);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (null !== $externalIds) {
|
||||
$imdbId = $externalIds->getImdbId() !== "" ? $externalIds->getImdbId() : "null";
|
||||
|
||||
if ("movies" === $result->mediaType) {
|
||||
$result->setImdbId($imdbId);
|
||||
} else {
|
||||
$result->imdbId = $imdbId;
|
||||
$result->title = $result->getName();
|
||||
}
|
||||
} else {
|
||||
if ("movies" === $result->mediaType) {
|
||||
$result->setImdbId("null");
|
||||
} else {
|
||||
$result->imdbId = "null";
|
||||
}
|
||||
private function getReleaseDate($result, $mediaType): string
|
||||
{
|
||||
switch (MediaType::tryFrom($mediaType)->value) {
|
||||
case MediaType::Movie->value:
|
||||
return ($result->getReleaseDate() instanceof \DateTime)
|
||||
? $result->getReleaseDate()->format('Y')
|
||||
: $result->getReleaseDate();
|
||||
case MediaType::TvShow->value:
|
||||
return ($result->getFirstAirDate() instanceof \DateTime)
|
||||
? $result->getFirstAirDate()->format('Y')
|
||||
: $result->getFirstAirDate();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
if ("movies" === $result->mediaType) {
|
||||
if ($result->getReleaseDate() instanceof \DateTime) {
|
||||
$result->year = $result->getReleaseDate()->format("Y");
|
||||
} else {
|
||||
$result->year = (new \DateTime($result->getReleaseDate()))->format('Y');
|
||||
}
|
||||
} else {
|
||||
if ($result->getFirstAirDate() instanceof \DateTime) {
|
||||
$result->year = $result->getFirstAirDate()->format("Y");
|
||||
} else {
|
||||
$result->year = (new \DateTime($result->getFirstAirDate()))->format('Y');
|
||||
}
|
||||
private function getTitle($result, $mediaType): string
|
||||
{
|
||||
switch (MediaType::tryFrom($mediaType)->value) {
|
||||
case MediaType::Movie->value:
|
||||
return $result->getTitle();
|
||||
case MediaType::TvShow->value:
|
||||
return $result->getName();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
/** @var Movie $result */
|
||||
$result->setExternalIds($externalIds);
|
||||
|
||||
$result->setImages($images);
|
||||
$result->getPosterImage()->setFilePath(
|
||||
self::POSTER_IMG_PATH . $result->getPosterImage()->getFilePath()
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\ValueObject;
|
||||
namespace App\Tmdb;
|
||||
|
||||
class MediaResult
|
||||
class TmdbResult
|
||||
{
|
||||
public function __construct(
|
||||
public string $imdbId,
|
||||
public string $tvdbId,
|
||||
public string $title,
|
||||
public string $poster,
|
||||
public string $excerpt,
|
||||
public string $year,
|
||||
public string $mediaType,
|
||||
public ?string $imdbId = "",
|
||||
public ?string $tvdbId = "",
|
||||
public ?string $title = "",
|
||||
public ?string $poster = "",
|
||||
public ?string $description = "",
|
||||
public ?string $year = "",
|
||||
public ?string $mediaType = "",
|
||||
public ?array $episodes = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
10
src/Twig/Components/SearchResult.php
Normal file
10
src/Twig/Components/SearchResult.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Twig\Components;
|
||||
|
||||
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||
|
||||
#[AsTwigComponent]
|
||||
final class SearchResult
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user