diff --git a/src/Collection/EventCollection.php b/src/Collection/EventCollection.php deleted file mode 100644 index 7a6357d..0000000 --- a/src/Collection/EventCollection.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace App\Collection; - -use Illuminate\Support\Arr; -use Illuminate\Support\Collection; - -final class EventCollection extends Collection -{ - public function excludeEventHosts(): self - { - return (new self($this->items))->filter(function (array $rsvp): bool { - return !$rsvp['member']['event_context']['host']; - }); - } -} diff --git a/src/Collection/RsvpCollection.php b/src/Collection/RsvpCollection.php new file mode 100644 index 0000000..a164821 --- /dev/null +++ b/src/Collection/RsvpCollection.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +namespace App\Collection; + +use Illuminate\Support\Arr; +use Illuminate\Support\Collection; + +final class RsvpCollection extends Collection +{ + private const RESPONSE_ATTENDING = 'yes'; + + public function excludeEventHosts(): self + { + return (new self($this->items))->filter( + function (array $rsvp): bool { + return !$rsvp['member']['event_context']['host']; + } + ); + } + + public function onlyAttending(): self + { + return (new self($this->items))->filter( + function (array $rsvp): bool { + return $rsvp['response'] == self::RESPONSE_ATTENDING; + } + ); + } + + public function getNames(): self + { + return (new self($this->items))->pluck('member.name')->sort(); + } +} diff --git a/src/Command/GetRaffleWinnerCommand.php b/src/Command/GetRaffleWinnerCommand.php index 27fb28c..c95a1d1 100644 --- a/src/Command/GetRaffleWinnerCommand.php +++ b/src/Command/GetRaffleWinnerCommand.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace App\Command; -use App\Collection\EventCollection; +use App\Collection\RsvpCollection; +use App\UseCase\FindTheWinner; +use App\ValueObject\Winner; use DateInterval; use Illuminate\Support\Collection; use Symfony\Component\Console\Command\Command; @@ -17,6 +19,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; final class GetRaffleWinnerCommand extends Command { + protected static $defaultName = 'app:get-raffle-winner'; private HttpClientInterface $client; @@ -58,91 +61,39 @@ final class GetRaffleWinnerCommand extends Command OutputInterface $output ): int { $io = new SymfonyStyle($input, $output); - $eventId = (int) $input->getArgument('event_id'); + $eventId = (int)$input->getArgument('event_id'); - $this->retrieveEventData($eventId); - $this->retrieveRsvps($eventId); - $this->pickWinner(); + $result = (new FindTheWinner( + $this->client, + $this->cache, + $eventId + ))->__invoke(); - $io->title(sprintf( - '%s - %s', - $this->eventData['group']['name'], - $this->eventData['name'] - )); + $event = $result->getEvent(); + $io->title($event->getName()); + $io->text($event->getLink()); - $io->text(rtrim($this->eventData['link'], '/')); - - $io->section(sprintf('%s \'yes\' RSVPs (excluding hosts)', $this->yesRsvps->count())); - $io->listing($this->yesRsvps->pluck('member.name')->sort()->toArray()); - $io->writeln( - sprintf('Winner: %s', $this->winner['member']['name']) + $io->section( + sprintf( + '%s \'yes\' RSVPs (excluding hosts)', + $result->getRsvps()->count() + ) ); - $this->openWinnerPhoto($io); + $io->listing($result->getRsvps()->getNames()->toArray()); + + $io->writeln( + sprintf('Winner: %s', $result->getWinner()->getName()) + ); + + $this->openWinnerPhoto($result->getWinner(), $io); return 0; } - private function retrieveEventData(int $eventId): void + private function openWinnerPhoto(Winner $winner, SymfonyStyle $io): void { - $eventData = $this->cache->getItem(sprintf('event.%d', $eventId)); - - if (!$eventData->isHit()) { - $response = $this->client->request( - 'GET', - sprintf( - 'https://api.meetup.com/%s/events/%d', - 'php-south-wales', - $eventId - ) - ); - - $eventData->expiresAfter(DateInterval::createFromDateString('1 hour')); - $this->eventData = $response->toArray(); - $this->cache->save($eventData->set($this->eventData)); - } else { - $this->eventData = $eventData->get(); - } - } - - private function retrieveRsvps(int $eventId): void - { - $rsvps = $this->cache->getItem(sprintf('rsvps.%d', $eventId)); - - if (!$rsvps->isHit()) { - $response = $this->client->request( - 'GET', - vsprintf( - 'https://api.meetup.com/%s/events/%d/rsvps', - [ - 'php-south-wales', - $eventId, - ] - ) - ); - - $this->rsvps = EventCollection::make($response->toArray()) - ->excludeEventHosts(); - - $rsvps->expiresAfter(DateInterval::createFromDateString('1 hour')); - $this->cache->save($rsvps->set($this->rsvps)); - } else { - $this->rsvps = $rsvps->get(); - } - - $this->yesRsvps = $this->rsvps->filter(function (array $rsvp): bool { - return $rsvp['response'] == 'yes'; - }); - } - - private function pickWinner(): void - { - $this->winner = $this->yesRsvps->random(1)->first(); - } - - private function openWinnerPhoto(SymfonyStyle $io): void - { - if ($photo = $this->winner['member']['photo']['photo_link'] ?? NULL) { + if ($photo = $winner->getPhoto()) { $io->write($photo); } } diff --git a/src/UseCase/FindTheWinner.php b/src/UseCase/FindTheWinner.php new file mode 100644 index 0000000..f7990e8 --- /dev/null +++ b/src/UseCase/FindTheWinner.php @@ -0,0 +1,106 @@ +<?php + +declare(strict_types=1); + +namespace App\UseCase; + +use App\Collection\RsvpCollection; +use App\ValueObject\Event; +use App\ValueObject\Result; +use App\ValueObject\Winner; +use DateInterval; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class FindTheWinner implements UseCaseInterface +{ + + private int $eventId; + + private HttpClientInterface $client; + + private CacheInterface $cache; + + private RsvpCollection $yesRsvps; + + public function __construct( + HttpClientInterface $client, + CacheInterface $cache, + int $eventId + ) { + $this->eventId = $eventId; + $this->client = $client; + $this->cache = $cache; + } + + public function __invoke(): Result + { + $eventData = $this->retrieveEventData(); + $rsvps = $this->retrieveRsvps(); + $winner = $this->pickWinner($rsvps); + + return new Result( + Winner::createFromArray($winner), + Event::createFromArray($eventData), + $rsvps + ); + } + + private function retrieveEventData(): array + { + $eventData = $this->cache->getItem(sprintf('event.%d', $this->eventId)); + + if (!$eventData->isHit()) { + $response = $this->client->request( + 'GET', + sprintf( + 'https://api.meetup.com/%s/events/%d', + 'php-south-wales', + $this->eventId + ) + ); + + $eventData->expiresAfter( + DateInterval::createFromDateString('1 hour') + ); + $return = $response->toArray(); + $this->cache->save($eventData->set($return)); + + return $return; + } else { + return $eventData->get(); + } + } + + private function retrieveRsvps(): RsvpCollection + { + $rsvps = $this->cache->getItem(sprintf('rsvps.%d', $this->eventId)); + + if (!$rsvps->isHit()) { + $response = $this->client->request( + 'GET', + sprintf( + 'https://api.meetup.com/%s/events/%d/rsvps', + 'php-south-wales', + $this->eventId + ) + ); + + $filteredRsvps = RsvpCollection::make($response->toArray()) + ->excludeEventHosts() + ->onlyAttending(); + + $rsvps->expiresAfter(DateInterval::createFromDateString('1 hour')); + $this->cache->save($rsvps->set($filteredRsvps)); + + return $filteredRsvps; + } else { + return $rsvps->get(); + } + } + + private function pickWinner(RsvpCollection $rsvps): array + { + return $rsvps->random(1)->first()['member']; + } +} diff --git a/src/UseCase/UseCaseInterface.php b/src/UseCase/UseCaseInterface.php new file mode 100644 index 0000000..1ceadfe --- /dev/null +++ b/src/UseCase/UseCaseInterface.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +namespace App\UseCase; + +use App\ValueObject\Result; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +interface UseCaseInterface +{ + + public function __construct( + HttpClientInterface $client, + CacheInterface $cache, + int $eventId + ); + + public function __invoke(): Result; +} diff --git a/src/ValueObject/Event.php b/src/ValueObject/Event.php new file mode 100644 index 0000000..c5023a2 --- /dev/null +++ b/src/ValueObject/Event.php @@ -0,0 +1,44 @@ +<?php + +namespace App\ValueObject; + +use App\Collection\RsvpCollection; +use Tightenco\Collect\Support\Collection; + +class Event +{ + private string $name; + + private string $link; + + public static function createFromArray(array $data) + { + return new static($data); + } + + public function getName(): string + { + return $this->name; + } + + public function getLink(): string + { + return rtrim($this->link, '/'); + } + + protected function __construct(array $data) + { + [ + 'name' => $name, + 'link' => $link, + ] = $data; + + $this->name = $name; + $this->link = $link; + } + + public function getRsvps(): Collection + { + return new Collection(); + } +} diff --git a/src/ValueObject/Result.php b/src/ValueObject/Result.php new file mode 100644 index 0000000..6d3e267 --- /dev/null +++ b/src/ValueObject/Result.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + +namespace App\ValueObject; + +use App\Collection\RsvpCollection; +use Tightenco\Collect\Support\Collection; + +final class Result +{ + private Winner $winner; + + private Event $event; + + private Collection $rsvps; + + public function __construct( + Winner $winner, + Event $event, + RsvpCollection $rsvps + ) { + $this->winner = $winner; + $this->event = $event; + $this->rsvps = $rsvps; + } + + public function getWinner(): Winner + { + return $this->winner; + } + + public function getEvent(): Event + { + return $this->event; + } + + public function getRsvps(): RsvpCollection + { + return $this->rsvps; + } +} diff --git a/src/ValueObject/Winner.php b/src/ValueObject/Winner.php new file mode 100644 index 0000000..51416e1 --- /dev/null +++ b/src/ValueObject/Winner.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); + +namespace App\ValueObject; + +final class Winner +{ + + private string $name; + + private string $photo; + + public static function createFromArray(array $data): self + { + return new static($data); + } + + public function getName(): string + { + return $this->name; + } + + protected function __construct(array $data) + { + [ + 'name' => $name, + 'photo' => $photo, + ] = $data; + + $this->name = $name; + $this->photo = $photo['photo_link']; + } + + public function getPhoto(): ?string + { + return $this->photo ?? null; + } +}