Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refactoring the Domain, Guided by Tests

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for pelshoff pelshoff
February 20, 2018

Refactoring the Domain, Guided by Tests

Slides of my first showing, tonight in Veenendaal
https://www.meetup.com/Web-Apps-Veenendaal/events/246607957/

Avatar for pelshoff

pelshoff

February 20, 2018
Tweet

More Decks by pelshoff

Other Decks in Programming

Transcript

  1. final class Meeting { private $meetingId; private $title; private $description;

    private $code; private $duration; private $isPublished; private $subTitle; private $program; public function __construct(Uuid $meetingId, string $title, string $description, string $code, MeetingDuration $duration, bool $isPublished, string $subTitle, Program $program) { $this->meetingId = $meetingId; $this->title = $title; $this->description = $description; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->subTitle = $subTitle; $this->program = $program; } }
  2. final class MeetingDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { $this->start = $start; $this->end = $end; $this->meetingCannotEndBeforeStart (); } private function meetingCannotEndBeforeStart (): void { if ($this->start > $this->end) { throw InvalidMeetingDuration::becauseDurationEndsBeforeStarting() ; } } }
  3. final class Program { private $program; /** @param Slot[] $program

    */ public function __construct(array $program) { $this->program = $program; $this->programSlotsCannotOccurInTheSameRoomAtTheSameTime (); } private function programSlotsCannotOccurInTheSameRoomAtTheSameTime (): void { foreach ($this->slotsStartingAt (0) as $index => $thisSlot) { foreach ($this->slotsStartingAt ($index + 1) as $thatSlot) { if ($thisSlot->overlapsWith($thatSlot)) { throw InvalidProgram:: becauseProgramSlotsOverlap(); } } } } /** * @param int $index * @return Slot[] */ private function slotsStartingAt (int $index): array { return array_slice($this->program, $index); } }
  4. final class Slot { private $duration; private $title; private $room;

    public function __construct(SlotDuration $duration, string $title, string $room) { $this->duration = $duration; $this->title = $title; $this->room = $room; } public function overlapsWith(Slot $that): bool { return $this->room === $that->room && $this->duration->overlapsWith($that->duration); } }
  5. final class SlotDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { $this->start = $start; $this->end = $end; $this->slotCannotEndBeforeStart (); $this->slotMustStartAndEndOnTheSameDay (); } private function slotCannotEndBeforeStart (): void { /**/ } private function slotMustStartAndEndOnTheSameDay (): void { /**/ } public function overlapsWith(SlotDuration $that): bool { return !$this->before($that) && !$that->before($this); } private function before(SlotDuration $that): bool { return $that->start >= $this->end; } }
  6. final class Meeting { private $meetingId; private $title; private $description;

    private $code; private $duration; private $isPublished; private $subTitle; private $program; public function __construct(Uuid $meetingId, string $title, string $description, string $code, MeetingDuration $duration, bool $isPublished, string $subTitle, Program $program) { $this->meetingId = $meetingId; $this->title = $title; $this->description = $description; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->subTitle = $subTitle; $this->program = $program; } // ... }
  7. final class MeetingTest extends TestCase { public function testThatValidMeetingsCanBeInstantiated ()

    { $this->assertInstanceOf (Meeting::class, new Meeting( Uuid:: uuid4(), 'This is a test' , 'This is a test description' , 'M01', new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) ) , false, 'This is a test sub title' , new Program([]) )); } } $ vendor/bin/phpunit -c phpunit.xml.dist tests/ PHPUnit 5.7.27 by Sebastian Bergmann and contributors. ........ 8 / 8 (100%) Time: 25 ms, Memory: 4.00MB OK (8 tests, 8 assertions)
  8. final class MeetingTest extends TestCase { public function testThatValidMeetingsCanBeInstantiated ()

    { $this->assertInstanceOf (Meeting::class, new Meeting( Uuid:: uuid4(), new MeetingPresentation( 'This is a test' , 'This is a test description' , 'This is a test sub title' ), 'M01', new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) ) , false, new Program([]) )); } } ........ 8 / 8 (100%) Time: 24 ms, Memory: 4.00MB OK (8 tests, 8 assertions)
  9. final class MeetingPresentationTest extends TestCase { public function testThatPresentationMustHaveTitle ()

    { $this->expectException (InvalidMeetingPresentation:: class); new MeetingPresentation( '', '', ''); } } ..F...... 9 / 9 (100%) Time: 25 ms, Memory: 4.00MB There was 1 failure: 1) Pelshoff\Meeting\test\MeetingPresentationTest::testThatPresentationMustHaveTitle Failed asserting that exception of type "Pelshoff\Meeting\InvalidMeetingPresentation" is thrown. FAILURES! Tests: 9, Assertions: 9, Failures: 1.
  10. final class MeetingPresentation { private $title; private $description; private $subTitle;

    public function __construct(string $title, string $description, string $subTitle){ $this->title = $title; $this->description = $description; $this->subTitle = $subTitle; $this->presentationMustHaveTitle (); } private function presentationMustHaveTitle (): void { if (mb_strlen($this->title) < 5) { throw InvalidMeetingPresentation:: becauseTitleIsMissing(); } } } ......... 9 / 9 (100%) Time: 26 ms, Memory: 4.00MB OK (9 tests, 9 assertions)
  11. final class Meeting { private $meetingId; private $presentation; private $code;

    private $duration; private $isPublished; private $program; public function __construct(UuidInterface $meetingId, MeetingPresentation $presentation, string $code, MeetingDuration $duration, bool $isPublished, Program $program) { $this->meetingId = $meetingId; $this->presentation = $presentation; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->program = $program; } }
  12. final class Meeting { private $meetingId; private $presentation; private $code;

    private $duration; private $isPublished; private $program; public function __construct(UuidInterface $meetingId, MeetingPresentation $presentation, string $code, MeetingDuration $duration, bool $isPublished, Program $program) { $this->meetingId = $meetingId; $this->presentation = $presentation; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->program = $program; } }
  13. final class Meeting { private $meetingId; private $presentation; private $code;

    private $isPublished; private $program; public function __construct(UuidInterface $meetingId, MeetingPresentation $presentation, string $code, bool $isPublished, Program $program) { $this->meetingId = $meetingId; $this->presentation = $presentation; $this->code = $code; $this->isPublished = $isPublished; $this->program = $program; }// final class Program { private $duration; private $program; public function __construct(MeetingDuration $duration, array $program) { $this->duration = $duration; $this->program = $program; $this->programSlotsCannotOccurInTheSameRoomAtTheSameTime (); }// OK (9 tests, 9 assertions)
  14. public function testThatMeetingCanBeRescheduled () { $meeting = new Meeting($meetingId =

    Uuid::uuid4(), /**/ new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) )/**/ new SlotDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 20:00' ) ) /**/; $expected = new Meeting($meetingId, /**/ new MeetingDuration( new DateTimeImmutable( '2018-02-21 07:00' ), new DateTimeImmutable( '2018-02-21 10:00' ) )/**/ new SlotDuration( new DateTimeImmutable( '2018-02-21 07:00' ), new DateTimeImmutable( '2018-02-21 08:00' ) ) ; $meeting->reschedule(new DateTimeImmutable( '2018-02-21 07:00' )); $this->assertEquals($expected, $meeting); } Error: Call to undefined method Pelshoff\Meeting\Meeting::reschedule()
  15. final class Meeting { /**/ public function reschedule(DateTimeImmutable $newStartDate): void

    { $this->program = $this->program->rescheduled($newStartDate); } }
  16. final class Program { /**/ public function rescheduled(DateTimeImmutable $newStartDate): Program

    { $newDuration = $this->duration->rescheduled($newStartDate); $diff = $this->duration->intervalToStartOf ($newDuration); $newProgram = array_map(function (Slot $slot) use ($diff) { return $slot->movedBy($diff); }, $this->program); return new self($newDuration, $newProgram); } }
  17. final class MeetingDuration { /**/ public function rescheduled(DateTimeImmutable $newStartDate): MeetingDuration

    { $newEndDate = $this->end->add($this->start->diff($newStartDate)); return new self($newStartDate, $newEndDate); } public function intervalToStartOf (MeetingDuration $that): DateInterval { return $this->start->diff($that->start); } }
  18. final class Slot { /**/ public function movedBy(DateInterval $diff): Slot

    { return new self( $this->duration->movedBy($diff), $this->title, $this->room ); } }
  19. final class SlotDuration { /**/ public function movedBy(DateInterval $diff): SlotDuration

    { return new self($this->start->add($diff), $this->end->add($diff)); } } .......... 10 / 10 (100%) Time: 29 ms, Memory: 4.00MB OK (10 tests, 10 assertions)
  20. final class TicketService { /** @var TicketRepository */ private $ticketRepository

    ; public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, []) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, $currentSoldPerTicket ) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  21. final class TicketService { /** @var TicketRepository */ private $ticketRepository

    ; public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, []) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, $currentSoldPerTicket ) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  22. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { if (!$ticket->getIsPublic())

    { return false; } if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { return false; } if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()) { return false; } if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { return false; } return true; }
  23. final class Ticket { private $id; private $isPublic; private $numberOfTickets

    ; private $startDate; private $endDate; public function __construct(int $id, bool $isPublic, int $numberOfTickets , ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate) { $this->id = $id; $this->isPublic = $isPublic; $this->numberOfTickets = $numberOfTickets ; $this->startDate = $startDate; $this->endDate = $endDate; }
  24. interface TicketRepository { /** * @param Meeting $meeting * @return

    Ticket[] * * Do a query */ public function getMeetingTickets (Meeting $meeting): array; /** * @param Meeting $meeting * @return array * * Do a query */ public function getNumberOfCurrentSoldTickets (Meeting $meeting): array; /** * @param Ticket $ticket * @param User $user * @return bool * * allowed by user invitation? * allowed by group? * allowed by selection? * Anyway, do a query */ public function checkAllowed(Ticket $ticket, User $user): bool; }
  25. final class TicketServiceTest extends TestCase { public function testThatGetAvailableTicketsGivesTheOnlyTicketAvailable ()

    { $meeting = $this->givenAnUpcomingMeeting (); $ticket = $this->givenAPublicTicket (); $user = $this->givenAUser(); $this->whenTicketsForMeeting ([$ticket], $meeting); $this->whenNoTicketsSold (); $expected = [$ticket->getId() => $ticket]; $actual = $this->ticketService->getAvailableTickets ($meeting, false, $user, false); $this->assertEquals($expected, $actual); } ........... 11 / 11 (100%) Time: 32 ms, Memory: 4.00MB OK (11 tests, 11 assertions)
  26. private function givenAnUpcomingMeeting () { return new Meeting(/**/); } private

    function givenAPublicTicket () { return new Ticket(mt_rand() , true, 0, null, null); } private function whenTicketsForMeeting (array $tickets, Meeting $meeting) { $this->ticketRepository ->expects($this->any()) ->method('getMeetingTickets' ) ->with($meeting) ->will($this->returnValue($tickets)); } private function whenNoTicketsSold () { $this->ticketRepository ->expects($this->any()) ->method('getNumberOfCurrentSoldTickets' ) ->will($this->returnValue([])); } private function givenAUser() { return new User(); } protected function setUp() { $this->ticketRepository = $this->getMockBuilder (TicketRepository:: class) ->getMock(); $this->ticketService = new TicketService( $this->ticketRepository ); }
  27. public function testThatGetAvailableTicketsFiltersDisallowedTickets () { $meeting = $this->givenAnUpcomingMeeting (); $allTickets

    = [ $this->givenAPublicTicket (), $this->givenAPublicTicket (), $this->givenAPublicTicket (), $allowedTicket = $this->givenAPublicTicket (), ]; $user = $this->givenAUser(); $this->whenTicketsForMeeting ($allTickets, $meeting); $this->whenUserOnlyAllowedForTicket ($allowedTicket , $user); $this->whenNoTicketsSold (); $expected = [$allowedTicket ->getId() => $allowedTicket ]; $actual = $this->ticketService->getAvailableTickets ($meeting, true, $user, false); $this->assertEquals($expected, $actual); } ............ 12 / 12 (100%) Time: 33 ms, Memory: 4.00MB OK (12 tests, 12 assertions)
  28. private function whenUserOnlyAllowedForTicket (Ticket $ticket, User $user) { $this->ticketRepository ->expects($this->any())

    ->method('checkAllowed' ) ->withConsecutive ($this->anything(), $this->anything(), $this->anything(), [$ticket, $user]) ->willReturnOnConsecutiveCalls (false, false, false, true); }
  29. public function testThatSoldOutTicketsAreNotAvailable () { $meeting = $this->givenAnUpcomingMeeting (); $ticket

    = $this->givenALimitedTicket (); $user = $this->givenAUser(); $this->whenTicketsForMeeting ([$ticket], $meeting); $this->whenTicketsAreAllowed (); $this->whenTicketIsSoldOut ($ticket); $expected = []; $actual = $this->ticketService->getAvailableTickets ($meeting, true, $user, false); $this->assertEquals($expected, $actual); } public function testThatSoldOutTicketsAreAvailableForReserve () { $meeting = $this->givenAnUpcomingMeeting (); $ticket = $this->givenALimitedTicket (); $user = $this->givenAUser(); $this->whenTicketsForMeeting ([$ticket], $meeting); $this->whenTicketsAreAllowed (); $this->whenTicketIsSoldOut ($ticket); $expected = [$ticket->getId() => $ticket]; $actual = $this->ticketService->getAvailableTickets ($meeting, true, $user, true); $this->assertEquals($expected, $actual); } OK (14 tests, 14 assertions)
  30. final class TicketService { /** @var TicketRepository */ private $ticketRepository

    ; public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, []) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, $currentSoldPerTicket ) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  31. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; foreach ($tickets as $ticket) { $ticketIsAvailableForReserve [$ticket->getId()] = $this->checkAvailability ($ticket, []); $ticketIsAvailableOtherwise [$ticket->getId()] = $this->checkAvailability ($ticket, $currentSoldPerTicket ); $ticketIsAllowedForUser [$ticket->getId()] = $this->ticketRepository ->checkAllowed ($ticket, $user); } /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailableForReserve [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailableOtherwise [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  32. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; foreach ($tickets as $ticket) { $ticketIsAvailableForReserve [$ticket->getId()] = $this->checkAvailability ($ticket, []); $ticketIsAvailableOtherwise [$ticket->getId()] = $this->checkAvailability ($ticket, $currentSoldPerTicket ); $ticketIsAllowedForUser [$ticket->getId()] = $this->ticketRepository ->checkAllowed ($ticket, $user); } $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise ; /** @var Ticket[] $availableTickets */ $availableTickets = []; foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailable [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } .............. 14 / 14 (100%) Time: 32 ms, Memory: 4.00MB OK (14 tests, 14 assertions)
  33. private function checkAvailability (Ticket $ticket, array $currentSoldPerTicket ): bool {

    if (!$ticket->isPublic()) { return false; } if ($ticket->getNumberOfTickets () && isset($currentSoldPerTicket [$ticket->getId()]) && $currentSoldPerTicket [$ticket->getId()] >= $ticket->getNumberOfTickets ()) { return false; } if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()){ return false; } if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { return false; } return true; }
  34. private function checkAvailability (Ticket $ticket, array $currentSoldPerTicket ): bool {

    return $this->ticketIsOpenForSale ($ticket) && $this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket ); } private function ticketIsOpenForSale (Ticket $ticket): bool { if (!$ticket->isPublic ()) { return false; } if ($ticket->getStartDate () && new DateTimeImmutable() < $ticket->getStartDate ()) { return false; } if ($ticket->getEndDate () && new DateTimeImmutable() > $ticket->getEndDate ()) { return false; } return true; } private function thereAreTicketsLeftForSale (Ticket $ticket, array $currentSoldPerTicket ): bool { if ($ticket->getNumberOfTickets () && isset($currentSoldPerTicket [$ticket->getId()]) && $currentSoldPerTicket [$ticket->getId()] >= $ticket->getNumberOfTickets ()) { return false; } return true; }
  35. private function checkAvailability (Ticket $ticket, array $currentSoldPerTicket ): bool {

    return $ticket->ticketIsOpenForSaleOn (new DateTimeImmutable()) && $this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket ); } private function thereAreTicketsLeftForSale (Ticket $ticket, array $currentSoldPerTicket ): bool { if ($ticket->getNumberOfTickets () && isset($currentSoldPerTicket [$ticket->getId()]) && $currentSoldPerTicket [$ticket->getId()] >= $ticket->getNumberOfTickets ()) { return false; } return true; } final class Ticket { public function ticketIsOpenForSaleOn (DateTimeImmutable $date): bool { if (!$this->isPublic ()) { return false; } if ($this->getStartDate () && $date < $this->getStartDate ()) { return false; } if ($this->getEndDate () && $date > $this->getEndDate ()) { return false; } return true; }
  36. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; $today = new DateTimeImmutable() ; foreach ($tickets as $ticket) { $ticketIsAvailableForReserve [$ticket->getId()] = $ticket->ticketIsOpenForSaleOn ($today); $ticketIsAvailableOtherwise [$ticket->getId()] = $ticket->ticketIsOpenForSaleOn ($today) && $this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket ); $ticketIsAllowedForUser [$ticket->getId()] = $this->ticketRepository ->checkAllowed ($ticket, $user); } $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise ; /** @var Ticket[] $availableTickets */ $availableTickets = []; foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailable [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } private function thereAreTicketsLeftForSale (Ticket $ticket, array $currentSoldPerTicket ): bool { /**/ }
  37. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { if (!$publicOnly ) { return $this->ticketRepository ->getMeetingTickets ($meeting ); } // ... Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ Array ( - 1623135705 => Pelshoff\Meeting\Ticket Object (...) + 0 => Pelshoff\Meeting\Ticket Object (...) ) /data/presentatie/tests/TicketServiceTest.php:77 FAILURES! Tests: 14, Assertions: 14, Failures: 3.
  38. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { if ($publicOnly ) { return $this->getPubliclyAvailableTickets ($meeting , $user, $forReserve ); } return $this->getPrivatelyAvailableTickets ($meeting ); } public function getPrivatelyAvailableTickets (Meeting $meeting ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); foreach ($tickets as $ticket) { $availableTickets [$ticket->getId()] = $ticket; } return $availableTickets ; } public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve ): array { /**/ /** @var Ticket[] $availableTickets */ $availableTickets = []; foreach ($tickets as $ticket) { if ($ticketIsAvailable [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()]) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } OK (14 tests, 14 assertions)
  39. public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve

    ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $today = new DateTimeImmutable() ; $openTickets = []; foreach ($tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($today)) { $openTickets [$ticket->getId()] = $ticket; } } $leftTickets = []; if ($forReserve ) { $leftTickets = $openTickets ; } else { foreach ($openTickets as $ticket) { if ($this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket )) { $leftTickets [$ticket->getId()] = $ticket; } } } $allowedTickets = []; foreach ($leftTickets as $ticket) { if ($this->ticketRepository ->checkAllowed ($ticket, $user)) { $allowedTickets [$ticket->getId()] = $ticket; } } return $allowedTickets ; }
  40. public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve

    ): array { $today = new DateTimeImmutable() ; $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $openTickets = []; foreach ($tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($today)) { $openTickets [$ticket->getId()] = $ticket; } } $allowedTickets = []; foreach ($openTickets as $ticket) { if ($this->ticketRepository ->checkAllowed ($ticket, $user)) { $allowedTickets [$ticket->getId()] = $ticket; } } if ($forReserve ) { return $allowedTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $leftTickets = []; foreach ($allowedTickets as $ticket) { if ($this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket )) { $leftTickets [$ticket->getId()] = $ticket; } } return $leftTickets ; }
  41. public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve

    ): array { if ($forReserve ) { return $this->getTicketsUserCanReserve ($meeting , $user); } return $this->getTicketsUserCanPurchase ($meeting , $user); } public function getTicketsUserCanReserve (Meeting $meeting , User $user): array { $tickets = $this->getTicketsOpenForSaleOn ($meeting , new DateTimeImmutable()) ; $allowedTickets = []; foreach ($tickets as $ticket) { if ($this->ticketRepository ->checkAllowed ($ticket, $user)) { $allowedTickets [$ticket->getId()] = $ticket; } } return $allowedTickets ; } public function getTicketsUserCanPurchase (Meeting $meeting , User $user): array { $tickets = $this->getTicketsUserCanReserve ($meeting , $user); $leftTickets = []; $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if ($this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket )) { $leftTickets [$ticket->getId()] = $ticket; } } return $leftTickets ; } OK (14 tests, 14 assertions)
  42. private function getTicketsOpenForSaleOn (Meeting $meeting, DateTimeImmutable $date): array { $tickets

    = $this->ticketRepository ->getMeetingTickets ($meeting); $openTickets = []; foreach ($tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($date)) { $openTickets[$ticket->getId()] = $ticket; } } return $openTickets; }
  43. final class Meeting { /**/ public function getTickets(): array {

    return $this->tickets; } public function getTicketsOpenForSaleOn (DateTimeImmutable $date): array { $ticketsOpenForSale = []; foreach ($this->tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($date)) { $ticketsOpenForSale [$ticket->getId()] = $ticket; } } return $ticketsOpenForSale ; } } .............. 14 / 14 (100%) Time: 43 ms, Memory: 4.00MB OK (14 tests, 14 assertions)
  44. final class TicketService { /** @deprecated Use getPrivatelyAvailableTickets, getTicketsUserCanReserve or

    getTicketsUserCanPurchase */ public function getAvailableTickets (Meeting $meeting, bool $publicOnly, User $user, bool $forReserve): array { /**/ } public function getPrivatelyAvailableTickets (Meeting $meeting): array { /**/ } /** @deprecated Use getTicketsUserCanReserve or getTicketsUserCanPurchase */ public function getPubliclyAvailableTickets (Meeting $meeting, User $user, bool $forReserve): array {/**/} public function getTicketsUserCanReserve (Meeting $meeting, User $user): array { /**/ } public function getTicketsUserCanPurchase (Meeting $meeting, User $user): array { /**/ }
  45. public function testThatTicketsCannotBeSoldWhenMeetingHasStarted () { $meeting = new Meeting( Uuid::uuid4(),

    new MeetingPresentation( 'This is a test' , '', ''), 'M01', false, new Program( new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) ), [] ), [new Ticket(1, true, 0, null, null)] ); $expected = []; $actual = $meeting->getTicketsOpenForSaleOn ( new DateTimeImmutable( '2018-02-20 20:00' )); $this->assertEquals($expected, $actual); } --- Expected +++ Actual @@ @@ Array ( + 1 => Pelshoff\Meeting\Ticket Object (...) )
  46. public function getTicketsOpenForSaleOn (DateTimeImmutable $date): array { if ($this->program->hasStartedOn($date)) {

    return []; } $ticketsOpenForSale = []; foreach ($this->tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($date)) { $ticketsOpenForSale [$ticket->getId()] = $ticket; } } return $ticketsOpenForSale ; } ............... 15 / 15 (100%) Time: 84 ms, Memory: 4.00MB OK (15 tests, 15 assertions)