Refactor to a UseCase and some value objects

This commit is contained in:
Oliver Davies 2020-05-22 21:52:04 +01:00
parent ba75e22439
commit 5aa1f79279
8 changed files with 315 additions and 94 deletions

View file

@ -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'];
});
}
}

View file

@ -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();
}
}

View file

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

View file

@ -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'];
}
}

View file

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

44
src/ValueObject/Event.php Normal file
View file

@ -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();
}
}

View file

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

View file

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