Refactoring the Domain, Guided by Tests v2

Refactoring the Domain, Guided by Tests v2

B84af63b07f297643ab1fd943c9ac59c?s=128

pelshoff

April 26, 2018
Tweet

Transcript

  1. None
  2. Refactoring the Domain Guided by tests

  3. Pim Elshoff developer.procurios.com @pelshoff

  4. Introduction Case study Minor feature request Major feature request

  5. “Make the change easy, then make the easy change” --

    Kent Beck
  6. Introduction Case study Minor feature request Major feature request

  7. MeetInc. Competitor of Meetup.com Hosts meetings Sells tickets Bad code

  8. Meeting Program Slot SlotDuration MeetingDuration

  9. 1. final class Meeting { 2. private $meetingId; 3. private

    $title; 4. private $description; 5. private $code; 6. private $duration; 7. private $isPublished; 8. private $subTitle; 9. private $program; 10. 11. public function __construct(UuidInterface $meetingId, string $title, string 12. $description, string $code, MeetingDuration $duration, bool $isPublished, 13. string $subTitle, Program $program) { 14. $this->meetingId = $meetingId; 15. $this->title = $title; 16. $this->description = $description; 17. $this->code = $code; 18. $this->duration = $duration; 19. $this->isPublished = $isPublished; 20. $this->subTitle = $subTitle; 21. $this->program = $program; 22. } 23. }
  10. 1. final class MeetingDuration { 2. private $start; 3. private

    $end; 4. 5. public function __construct(DateTimeImmutable $start, DateTimeImmutable $end) { 6. $this->start = $start; 7. $this->end = $end; 8. $this->meetingCannotEndBeforeStart(); 9. } 10. 11. private function meetingCannotEndBeforeStart(): void { 12. if ($this->start > $this->end) { 13. throw InvalidMeetingDuration::becauseDurationEndsBeforeStarting(); 14. } 15. } 16. }
  11. 1. final class Program { 2. private $program; 3. 4.

    /** @param Slot[] $program */ 5. public function __construct(array $program) { 6. $this->program = $program; 7. $this->programSlotsCannotOccurInTheSameRoomAtTheSameTime(); 8. } 9. 10. private function programSlotsCannotOccurInTheSameRoomAtTheSameTime(): void { 11. foreach ($this->slotsStartingAt(0) as $index => $thisSlot) { 12. foreach ($this->slotsStartingAt($index + 1) as $thatSlot) { 13. if ($thisSlot->overlapsWith($thatSlot)) { 14. throw InvalidProgram::becauseProgramSlotsOverlap(); 15. } 16. } 17. } 18. } 19. 20. /** @return Slot[] */ 21. private function slotsStartingAt(int $index): array { 22. return array_slice($this->program, $index); 23. } 24. }
  12. 1. final class Slot { 2. private $duration; 3. private

    $title; 4. private $room; 5. 6. public function __construct(SlotDuration $duration, string $title, string $room) { 7. $this->duration = $duration; 8. $this->title = $title; 9. $this->room = $room; 10. } 11. 12. public function overlapsWith(Slot $that): bool { 13. return $this->room === $that->room 14. && $this->duration->overlapsWith($that->duration); 15. } 16. }
  13. 1. final class SlotDuration { 2. private $start; 3. private

    $end; 4. 5. public function __construct(DateTimeImmutable $start, DateTimeImmutable $end) { 6. $this->start = $start; 7. $this->end = $end; 8. $this->slotCannotEndBeforeStart(); 9. $this->slotMustStartAndEndOnTheSameDay(); 10. } 11. 12. private function slotCannotEndBeforeStart(): void {...} 13. 14. private function slotMustStartAndEndOnTheSameDay(): void {...} 15. 16. public function overlapsWith(SlotDuration $that): bool { 17. return !$this->before($that) && !$that->before($this); 18. } 19. 20. private function before(SlotDuration $that): bool { 21. return $that->start >= $this->end; 22. } 23. }
  14. Introduction Case study Minor feature request Major feature request

  15. A meeting should always have a title

  16. 1. final class Meeting { 2. private $meetingId; 3. private

    $title; 4. private $description; 5. private $code; 6. private $duration; 7. private $isPublished; 8. private $subTitle; 9. private $program; 10. 11. public function __construct(UuidInterface $meetingId, string $title, string 12. $description, string $code, MeetingDuration $duration, bool $isPublished, 13. string $subTitle, Program $program) { 14. $this->meetingId = $meetingId; 15. $this->title = $title; 16. $this->description = $description; 17. $this->code = $code; 18. $this->duration = $duration; 19. $this->isPublished = $isPublished; 20. $this->subTitle = $subTitle; 21. $this->program = $program; 22. } 23. }
  17. 1. final class MeetingTest extends TestCase { 2. public function

    testThatValidMeetingsCanBeInstantiated() { 3. $this->assertInstanceOf(Meeting::class, 4. new Meeting( 5. Uuid::uuid4(), 6. 'This is a test', 7. 'This is a test description', 8. 'M01', 9. new MeetingDuration( 10. new DateTimeImmutable('2018-02-20 19:00'), 11. new DateTimeImmutable('2018-02-20 22:00') 12. ), 13. false, 14. 'This is a test sub title', 15. new Program([]) 16. ) 17. ); 18. } 19. } $ 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)
  18. 1. final class MeetingTest extends TestCase { 2. public function

    testThatValidMeetingsCanBeInstantiated() { 3. $this->assertInstanceOf(Meeting::class, 4. new Meeting( 5. Uuid::uuid4(), 6. new MeetingPresentation( 7. 'This is a test', 8. 'This is a test description', 9. 'This is a test sub title' 10. ), 11. 'M01', 12. new MeetingDuration( 13. new DateTimeImmutable('2018-02-20 19:00'), 14. new DateTimeImmutable('2018-02-20 22:00') 15. ), 16. false, 17. 'This is a test sub title', 18. new Program([]) 19. ) 20. ); 21. } 22. } OK (8 tests, 8 assertions)
  19. 1. final class MeetingPresentationTest extends TestCase { 2. public function

    testThatPresentationMustHaveTitle() { 3. $this->expectException(InvalidMeetingPresentation::class); 4. new MeetingPresentation('', '', ''); 5. } 6. } ..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.
  20. 1. final class MeetingPresentation { 2. private $title; 3. private

    $description; 4. private $subTitle; 5. 6. public function __construct(string $title, string $description, string $subTitle) { 7. $this->title = $title; 8. $this->description = $description; 9. $this->subTitle = $subTitle; 10. $this->presentationMustHaveTitle(); 11. } 12. 13. private function presentationMustHaveTitle(): void { 14. if (mb_strlen($this->title) < 5) { 15. throw InvalidMeetingPresentation::becauseTitleIsMissing(); 16. } 17. } 18. } OK (9 tests, 9 assertions)
  21. 1. final class Meeting { 2. private $meetingId; 3. private

    $presentation; 4. private $code; 5. private $duration; 6. private $isPublished; 7. private $program; 8. 9. public function __construct(UuidInterface $meetingId, MeetingPresentation 10. $presentation, string $code, MeetingDuration $duration, bool $isPublished, 11. Program $program) { 12. $this->meetingId = $meetingId; 13. $this->presentation = $presentation; 14. $this->code = $code; 15. $this->duration = $duration; 16. $this->isPublished = $isPublished; 17. $this->program = $program; 18. } 19. }
  22. Reschedule a meeting

  23. 1. final class Meeting { 2. private $meetingId; 3. private

    $presentation; 4. private $code; 5. private $duration; 6. private $isPublished; 7. private $program; 8. 9. public function __construct(UuidInterface $meetingId, MeetingPresentation 10. $presentation, string $code, MeetingDuration $duration, bool $isPublished, 11. Program $program) { 12. $this->meetingId = $meetingId; 13. $this->presentation = $presentation; 14. $this->code = $code; 15. $this->duration = $duration; 16. $this->isPublished = $isPublished; 17. $this->program = $program; 18. } 19. }
  24. 1. final class Meeting { 2. private $meetingId; 3. private

    $presentation; 4. private $code; 5. private $duration; 6. private $isPublished; 7. private $program; 8. 9. public function __construct(UuidInterface $meetingId, MeetingPresentation 10. $presentation, string $code, MeetingDuration $duration, bool $isPublished, 11. Program $program) { 12. $this->meetingId = $meetingId; 13. $this->presentation = $presentation; 14. $this->code = $code; 15. $this->duration = $duration; 16. $this->isPublished = $isPublished; 17. $this->program = $program; 18. } 19. }
  25. 1. final class Program { 2. private $duration; 3. private

    $program; 4. 5. /** @param Slot[] $program */ 6. public function __construct(MeetingDuration $duration, array $program) { 7. $this->duration = $duration; 8. $this->program = $program; 9. $this->programSlotsCannotOccurInTheSameRoomAtTheSameTime(); 10. } 11. 12. private function programSlotsCannotOccurInTheSameRoomAtTheSameTime(): void {...} 13. 14. /** @return Slot[] */ 15. private function slotsStartingAt(int $index): array {...} 16. } OK (9 tests, 9 assertions)
  26. 1. public function testThatMeetingCanBeRescheduled() { 2. $meeting = new Meeting($meetingId

    = Uuid::uuid4(), 3. ... 4. new MeetingDuration( 5. new DateTimeImmutable('2018-02-20 19:00'), 6. new DateTimeImmutable('2018-02-20 22:00') 7. )... 8. new SlotDuration( 9. new DateTimeImmutable('2018-02-20 20:00'), 10. new DateTimeImmutable('2018-02-20 21:00') 11. ),...; 12. $expected = new Meeting($meetingId, 13. ... 14. new MeetingDuration( 15. new DateTimeImmutable('2018-02-21 07:00'), 16. new DateTimeImmutable('2018-02-21 10:00') 17. )... 18. new SlotDuration( 19. new DateTimeImmutable('2018-02-21 08:00'), 20. new DateTimeImmutable('2018-02-21 09:00') 21. )...; 22. $meeting->reschedule(new DateTimeImmutable('2018-02-21 07:00')); 23. $this->assertEquals($expected, $meeting); 24. } Error: Call to undefined method Pelshoff\Meeting\Meeting::reschedule()
  27. 1. final class Meeting { 2. ... 3. public function

    reschedule(DateTimeImmutable $newStartDate): void { 4. $this->program = $this->program->rescheduled($newStartDate); 5. } 6. }
  28. 1. final class Program { 2. ... 3. public function

    rescheduled(DateTimeImmutable $newStartDate): Program { 4. $newDuration = $this->duration->rescheduled($newStartDate); 5. $diff = $this->duration->intervalToStartOf($newDuration); 6. $newProgram = array_map(function (Slot $slot) use ($diff) { 7. return $slot->movedBy($diff); 8. }, $this->program); 9. return new self($newDuration, $newProgram); 10. } 11. }
  29. 1. final class MeetingDuration { 2. ... 3. public function

    rescheduled(DateTimeImmutable $newStartDate): MeetingDuration { 4. $newEndDate = $this->end->add($this->start->diff($newStartDate)); 5. return new self($newStartDate, $newEndDate); 6. } 7. 8. public function intervalToStartOf(MeetingDuration $that): DateInterval { 9. return $this->start->diff($that->start); 10. }}
  30. 1. final class Slot { 2. ... 3. public function

    movedBy(DateInterval $diff): Slot { 4. return new self( 5. $this->duration->movedBy($diff), 6. $this->title, 7. $this->room 8. ); 9. } 10. }
  31. 1. final class SlotDuration { 2. ... 3. public function

    movedBy(DateInterval $diff): SlotDuration { 4. return new self($this->start->add($diff), $this->end->add($diff)); 5. } 6. } .......... 10 / 10 (100%) Time: 29 ms, Memory: 4.00MB OK (10 tests, 10 assertions)
  32. Introduction Case study Minor feature request Major feature request

  33. Meeting Program Slot MeetingDuration SlotDuration TicketService TicketRepository Meeting Presentation Ticket

  34. 1. final class TicketService { 2. /** @var TicketRepository */

    3. private $ticketRepository; 4. 5. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 6. bool $forReserve): array { 7. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 8. 9. $availableTickets = []; 10. if ($forReserve) { 11. foreach ($tickets as $ticket) { 12. if (!$publicOnly || ($this->checkAvailability($ticket, []) 13. && $this->ticketRepository->checkAllowed($ticket, $user))) { 14. $availableTickets[$ticket->getId()] = $ticket; 15. } 16. } 17. return $availableTickets; 18. } 19. 20. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 21. 22. foreach ($tickets as $ticket) { 23. if (!$publicOnly || ($this->checkAvailability($ticket, $currentSoldPerTicket) 24. && $this->ticketRepository->checkAllowed($ticket, $user))) { 25. $availableTickets[$ticket->getId()] = $ticket; 26. } 27. } 28. 29. return $availableTickets; 30. }
  35. Prevent sale of tickets when meeting has started

  36. None
  37. 1. final class TicketService { 2. /** @var TicketRepository */

    3. private $ticketRepository; 4. 5. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 6. bool $forReserve): array { 7. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 8. 9. $availableTickets = []; 10. if ($forReserve) { 11. foreach ($tickets as $ticket) { 12. if (!$publicOnly || ($this->checkAvailability($ticket, []) 13. && $this->ticketRepository->checkAllowed($ticket, $user))) { 14. $availableTickets[$ticket->getId()] = $ticket; 15. } 16. } 17. return $availableTickets; 18. } 19. 20. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 21. 22. foreach ($tickets as $ticket) { 23. if (!$publicOnly || ($this->checkAvailability($ticket, $currentSoldPerTicket) 24. && $this->ticketRepository->checkAllowed($ticket, $user))) { 25. $availableTickets[$ticket->getId()] = $ticket; 26. } 27. } 28. 29. return $availableTickets; 30. }
  38. 1. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { 2.

    if (!$ticket->getIsPublic()) { 3. return false; 4. } 5. 6. if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) 7. && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { 8. return false; 9. } 10. 11. if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()) { 12. return false; 13. } 14. 15. if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { 16. return false; 17. } 18. 19. return true; 20. }
  39. 1. final class Ticket { 2. private $id; 3. private

    $isPublic; 4. private $numberOfTickets; 5. private $startDate; 6. private $endDate; 7. 8. public function __construct(int $id, bool $isPublic, int $numberOfTickets, 9. ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate) { 10. $this->id = $id; 11. $this->isPublic = $isPublic; 12. $this->numberOfTickets = $numberOfTickets; 13. $this->startDate = $startDate; 14. $this->endDate = $endDate; 15. }
  40. 1. interface TicketRepository 2. { 3. /** 4. * @param

    Meeting $meeting 5. * @return Ticket[] 6. * Do a query 7. */ 8. public function getMeetingTickets(Meeting $meeting): array; 9. /** 10. * @param Meeting $meeting 11. * @return array 12. * Do a query 13. */ 14. public function getNumberOfCurrentSoldTickets(Meeting $meeting): array; 15. /** 16. * @param Ticket $ticket 17. * @param User $user 18. * @return bool 19. * allowed by user invitation? 20. * allowed by group? 21. * allowed by selection? 22. * Anyway, do a query 23. */ 24. public function checkAllowed(Ticket $ticket, User $user): bool; 25. }
  41. 1. final class TicketServiceTest extends TestCase { 2. public function

    testThatGetAvailableTicketsGivesTheOnlyTicketAvailable() { 3. $meeting = $this->givenAnUpcomingMeeting(); 4. $ticket = $this->givenAPublicTicket(); 5. $user = $this->givenAUser(); 6. $this->whenTicketsForMeeting([$ticket], $meeting); 7. $this->whenNoTicketsSold(); 8. 9. $expected = [$ticket->getId() => $ticket]; 10. $actual = $this->ticketService->getAvailableTickets($meeting, false, $user, 11. false); 12. 13. $this->assertEquals($expected, $actual); 14. } ........... 11 / 11 (100%) Time: 32 ms, Memory: 4.00MB OK (11 tests, 11 assertions)
  42. 1. private function givenAnUpcomingMeeting() { 2. return new Meeting(...); 3.

    } 4. private function givenAPublicTicket() { 5. return new Ticket(mt_rand(), true, 0, null, null); 6. } 7. private function whenTicketsForMeeting(array $tickets, Meeting $meeting) { 8. $this->ticketRepository->expects($this->any()) 9. ->method('getMeetingTickets') 10. ->with($meeting) 11. ->will($this->returnValue($tickets)); 12. } 13. private function whenNoTicketsSold() { 14. $this->ticketRepository->expects($this->any()) 15. ->method('getNumberOfCurrentSoldTickets') 16. ->will($this->returnValue([])); 17. } 18. private function givenAUser() { 19. return new User(); 20. } 21. protected function setUp() { 22. $this->ticketRepository = $this->getMockBuilder(TicketRepository::class) 23. ->getMock(); 24. $this->ticketService = new TicketService($this->ticketRepository); 25. }
  43. 1. public function testThatGetAvailableTicketsFiltersDisallowedTickets() { 2. $meeting = $this->givenAnUpcomingMeeting(); 3.

    $allTickets = [ 4. $this->givenAPublicTicket(), 5. $this->givenAPublicTicket(), 6. $this->givenAPublicTicket(), 7. $allowedTicket = $this->givenAPublicTicket(), 8. ]; 9. $user = $this->givenAUser(); 10. $this->whenTicketsForMeeting($allTickets, $meeting); 11. $this->whenUserOnlyAllowedForTicket($allowedTicket, $user); 12. $this->whenNoTicketsSold(); 13. 14. $expected = [$allowedTicket->getId() => $allowedTicket]; 15. $actual = $this->ticketService->getAvailableTickets($meeting, true, $user, false); 16. 17. $this->assertEquals($expected, $actual); 18. } ............ 12 / 12 (100%) Time: 33 ms, Memory: 4.00MB OK (12 tests, 12 assertions)
  44. 1. private function whenUserOnlyAllowedForTicket(Ticket $ticket, User $user) { 2. $this->ticketRepository->expects($this->any())

    3. ->method('checkAllowed') 4. ->withConsecutive($this->anything(), $this->anything(), $this->anything(), 5. [$ticket, $user]) 6. ->willReturnOnConsecutiveCalls(false, false, false, true); 7. }
  45. 1. public function testThatSoldOutTicketsAreNotAvailable() { 2. $meeting = $this->givenAnUpcomingMeeting(); 3.

    $ticket = $this->givenALimitedTicket(); 4. $user = $this->givenAUser(); 5. $this->whenTicketsForMeeting([$ticket], $meeting); 6. $this->whenTicketsAreAllowed(); 7. $this->whenTicketIsSoldOut($ticket); 8. 9. $expected = []; 10. $actual = $this->ticketService->getAvailableTickets($meeting, true, $user, false); 11. 12. $this->assertEquals($expected, $actual); 13. } 14. public function testThatSoldOutTicketsAreAvailableForReserve() { 15. $meeting = $this->givenAnUpcomingMeeting(); 16. $ticket = $this->givenALimitedTicket(); 17. $user = $this->givenAUser(); 18. $this->whenTicketsForMeeting([$ticket], $meeting); 19. $this->whenTicketsAreAllowed(); 20. $this->whenTicketIsSoldOut($ticket); 21. 22. $expected = [$ticket->getId() => $ticket]; 23. $actual = $this->ticketService->getAvailableTickets($meeting, true, $user, true); 24. 25. $this->assertEquals($expected, $actual); 26. } OK (14 tests, 14 assertions)
  46. None
  47. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. 5. $availableTickets = []; 6. if ($forReserve) { 7. foreach ($tickets as $ticket) { 8. if (!$publicOnly || ($this->checkAvailability($ticket, []) 9. && $this->ticketRepository->checkAllowed($ticket, $user))) { 10. $availableTickets[$ticket->getId()] = $ticket; 11. } 12. } 13. return $availableTickets; 14. } 15. 16. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 17. 18. foreach ($tickets as $ticket) { 19. if (!$publicOnly || ($this->checkAvailability($ticket, $currentSoldPerTicket) 20. && $this->ticketRepository->checkAllowed($ticket, $user))) { 21. $availableTickets[$ticket->getId()] = $ticket; 22. } 23. } 24. 25. return $availableTickets; 26. }
  48. 1. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 2. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 3. $ticketIsAvailableForReserve

    = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 4. foreach ($tickets as $ticket) { 5. $ticketIsAvailableForReserve[$ticket->getId()] = $this->checkAvailability($ticket, []); 6. $ticketIsAvailableOtherwise[$ticket->getId()] = 7. $this->checkAvailability($ticket, $currentSoldPerTicket); 8. $ticketIsAllowedForUser[$ticket->getId()] = 9. $this->ticketRepository->checkAllowed($ticket, $user); 10. } 11. 12. $availableTickets = []; 13. if ($forReserve) { 14. foreach ($tickets as $ticket) { 15. if (!$publicOnly || ($ticketIsAvailableForReserve[$ticket->getId()] 16. && $ticketIsAllowedForUser[$ticket->getId()])) { 17. $availableTickets[$ticket->getId()] = $ticket; 18. } 19. } 20. return $availableTickets; 21. } 22. 23. foreach ($tickets as $ticket) { 24. if (!$publicOnly || ($ticketIsAvailableOtherwise[$ticket->getId()] 25. && $ticketIsAllowedForUser[$ticket->getId()])) { 26. $availableTickets[$ticket->getId()] = $ticket; 27. } 28. } 29. 30. return $availableTickets;
  49. 1. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 2. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 3. $ticketIsAvailableForReserve

    = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 4. foreach ($tickets as $ticket) { 5. $ticketIsAvailableForReserve[$ticket->getId()] = $this->checkAvailability($ticket, []); 6. $ticketIsAvailableOtherwise[$ticket->getId()] = 7. $this->checkAvailability($ticket, $currentSoldPerTicket); 8. $ticketIsAllowedForUser[$ticket->getId()] = 9. $this->ticketRepository->checkAllowed($ticket, $user); 10. } 11. 12. $availableTickets = []; 13. if ($forReserve) { 14. foreach ($tickets as $ticket) { 15. if (!$publicOnly || ($ticketIsAvailableForReserve[$ticket->getId()] 16. && $ticketIsAllowedForUser[$ticket->getId()])) { 17. $availableTickets[$ticket->getId()] = $ticket; 18. } 19. } 20. return $availableTickets; 21. } 22. 23. foreach ($tickets as $ticket) { 24. if (!$publicOnly || ($ticketIsAvailableOtherwise[$ticket->getId()] 25. && $ticketIsAllowedForUser[$ticket->getId()])) { 26. $availableTickets[$ticket->getId()] = $ticket; 27. } 28. } 29. 30. return $availableTickets;
  50. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. foreach ($tickets as $ticket) { 7. $ticketIsAvailableForReserve[$ticket->getId()] = $this->checkAvailability($ticket, []); 8. $ticketIsAvailableOtherwise[$ticket->getId()] = 9. $this->checkAvailability($ticket, $currentSoldPerTicket); 10. $ticketIsAllowedForUser[$ticket->getId()] = 11. $this->ticketRepository->checkAllowed($ticket, $user); 12. } 13. 14. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 15. 16. $availableTickets = []; 17. foreach ($tickets as $ticket) { 18. if (!$publicOnly || ($ticketIsAvailable[$ticket->getId()] 19. && $ticketIsAllowedForUser[$ticket->getId()])) { 20. $availableTickets[$ticket->getId()] = $ticket; 21. } 22. } 23. 24. return $availableTickets; 25. } OK (14 tests, 14 assertions)
  51. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. foreach ($tickets as $ticket) { 7. $ticketIsAvailableForReserve[$ticket->getId()] = $this->checkAvailability($ticket, []); 8. $ticketIsAvailableOtherwise[$ticket->getId()] = 9. $this->checkAvailability($ticket, $currentSoldPerTicket); 10. $ticketIsAllowedForUser[$ticket->getId()] = 11. $this->ticketRepository->checkAllowed($ticket, $user); 12. } 13. 14. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 15. 16. $availableTickets = []; 17. foreach ($tickets as $ticket) { 18. if (!$publicOnly || ($ticketIsAvailable[$ticket->getId()] 19. && $ticketIsAllowedForUser[$ticket->getId()])) { 20. $availableTickets[$ticket->getId()] = $ticket; 21. } 22. } 23. 24. return $availableTickets; 25. }
  52. 1. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { 2.

    if (!$ticket->getIsPublic()) { 3. return false; 4. } 5. 6. if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) 7. && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { 8. return false; 9. } 10. 11. if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()) { 12. return false; 13. } 14. 15. if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { 16. return false; 17. } 18. 19. return true; 20. }
  53. 1. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { 2.

    return $this->ticketIsOpenForSale($ticket) 3. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 4. } 5. private function thereAreTicketsLeftForSale(Ticket $ticket, array $currentSoldPerTicket): 6. bool { 7. if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) 8. && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { 9. return false; 10. } 11. return true; 12. } 13. private function ticketIsOpenForSale(Ticket $ticket): bool { 14. if (!$ticket->isPublic()) { 15. return false; 16. } 17. if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()) { 18. return false; 19. } 20. if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { 21. return false; 22. } 23. return true; 24. }
  54. 1. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { 2.

    return $this->ticketIsOpenForSale($ticket) 3. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 4. } 5. private function thereAreTicketsLeftForSale(Ticket $ticket, array $currentSoldPerTicket): 6. bool { 7. if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) 8. && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { 9. return false; 10. } 11. return true; 12. } 13. private function ticketIsOpenForSale(Ticket $ticket): bool { 14. if (!$ticket->isPublic()) { 15. return false; 16. } 17. if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()) { 18. return false; 19. } 20. if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { 21. return false; 22. } 23. return true; 24. }
  55. 1. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { 2.

    return $ticket->isOpenForSaleOn(new DateTimeImmutable()) 3. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 4. } 5. private function thereAreTicketsLeftForSale(Ticket $ticket, array $currentSoldPerTicket): 6. bool { 7. if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) 8. && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { 9. return false; 10. } 11. return true; 12. } 13. final class Ticket { 14. public function isOpenForSaleOn(DateTimeImmutable $date): bool { 15. if (!$this->isPublic()) { 16. return false; 17. } 18. if ($this->getStartDate() && $date < $this->getStartDate()) { 19. return false; 20. } 21. if ($this->getEndDate() && $date > $this->getEndDate()) { 22. return false; 23. } 24. return true; 25. }
  56. 1. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { 2.

    return $ticket->isOpenForSaleOn(new DateTimeImmutable()) 3. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 4. } 5. private function thereAreTicketsLeftForSale(Ticket $ticket, array $currentSoldPerTicket): 6. bool { 7. if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) 8. && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { 9. return false; 10. } 11. return true; 12. } 13. final class Ticket { 14. public function isOpenForSaleOn(DateTimeImmutable $date): bool { 15. if (!$this->isPublic()) { 16. return false; 17. } 18. if ($this->getStartDate() && $date < $this->getStartDate()) { 19. return false; 20. } 21. if ($this->getEndDate() && $date > $this->getEndDate()) { 22. return false; 23. } 24. return true; 25. }
  57. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. foreach ($tickets as $ticket) { 7. $ticketIsAvailableForReserve[$ticket->getId()] = $this->checkAvailability($ticket, []); 8. $ticketIsAvailableOtherwise[$ticket->getId()] = 9. $this->checkAvailability($ticket, $currentSoldPerTicket); 10. $ticketIsAllowedForUser[$ticket->getId()] = 11. $this->ticketRepository->checkAllowed($ticket, $user); 12. } 13. 14. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 15. 16. $availableTickets = []; 17. foreach ($tickets as $ticket) { 18. if (!$publicOnly || ($ticketIsAvailable[$ticket->getId()] 19. && $ticketIsAllowedForUser[$ticket->getId()])) { 20. $availableTickets[$ticket->getId()] = $ticket; 21. } 22. } 23. 24. return $availableTickets; 25. }
  58. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 9. && $this->thereAreTicketsLeftForSale($ticket, []); 10. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 11. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 12. $ticketIsAllowedForUser[$ticket->getId()] = 13. $this->ticketRepository->checkAllowed($ticket, $user); 14. } 15. 16. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 17. 18. $availableTickets = []; 19. foreach ($tickets as $ticket) { 20. if (!$publicOnly || ($ticketIsAvailable[$ticket->getId()] 21. && $ticketIsAllowedForUser[$ticket->getId()])) { 22. $availableTickets[$ticket->getId()] = $ticket; 23. } 24. } 25. 26. return $availableTickets; 27. }
  59. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. $ticketIsAllowedForUser[$ticket->getId()] = 12. $this->ticketRepository->checkAllowed($ticket, $user); 13. } 14. 15. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 16. 17. $availableTickets = []; 18. foreach ($tickets as $ticket) { 19. if (!$publicOnly || ($ticketIsAvailable[$ticket->getId()] 20. && $ticketIsAllowedForUser[$ticket->getId()])) { 21. $availableTickets[$ticket->getId()] = $ticket; 22. } 23. } 24. 25. return $availableTickets; 26. } OK (14 tests, 14 assertions)
  60. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. $ticketIsAllowedForUser[$ticket->getId()] = 12. $this->ticketRepository->checkAllowed($ticket, $user); 13. } 14. 15. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 16. 17. $availableTickets = []; 18. foreach ($tickets as $ticket) { 19. if (!$publicOnly || ($ticketIsAvailable[$ticket->getId()] 20. && $ticketIsAllowedForUser[$ticket->getId()])) { 21. $availableTickets[$ticket->getId()] = $ticket; 22. } 23. } 24. 25. return $availableTickets; 26. }
  61. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. if (!$publicOnly) { 4. return $this->ticketRepository->getMeetingTickets($meeting); 5. } 6. // ... 7. 8. Failed asserting that two arrays are equal. 9. --- Expected 10. +++ Actual 11. @@ @@ 12. Array ( 13. - 1623135705 => Pelshoff\Meeting\Ticket Object (...) 14. + 0 => Pelshoff\Meeting\Ticket Object (...) 15. ) 16. 17. /data/presentatie/tests/TicketServiceTest.php:77 18. 19. FAILURES! 20. Tests: 14, Assertions: 14, Failures: 3.
  62. 1. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 2.

    bool $forReserve): array { 3. if ($publicOnly) { 4. return $this->getPubliclyAvailableTickets($meeting, $user, $forReserve); 5. } 6. return $this->getPrivatelyAvailableTickets($meeting); 7. } 8. 9. public function getPrivatelyAvailableTickets(Meeting $meeting): array { 10. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 11. foreach ($tickets as $ticket) { 12. $availableTickets[$ticket->getId()] = $ticket; 13. } 14. return $availableTickets; 15. } 16. 17. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, 18. bool $forReserve): array { 19. /**/ 20. } OK (14 tests, 14 assertions)
  63. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve):array {

    2. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 3. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 4. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 5. $today = new DateTimeImmutable(); 6. foreach ($tickets as $ticket) { 7. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 8. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 9. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 10. $ticketIsAllowedForUser[$ticket->getId()] = 11. $this->ticketRepository->checkAllowed($ticket, $user); 12. } 13. 14. 15. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 16. 17. $availableTickets = []; 18. foreach ($tickets as $ticket) { 19. if ($ticketIsAvailable[$ticket->getId()] && $ticketIsAllowedForUser[$ticket->getId()]) { 20. $availableTickets[$ticket->getId()] = $ticket; 21. } 22. } 23. 24. return $availableTickets; 25. }
  64. 1. final class TicketService { 2. 3. /** @deprecated Use

    getPrivatelyAvailableTickets or getPubliclyAvailableTickets */ 4. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 5. bool $forReserve): array {...} 6. 7. public function getPrivatelyAvailableTickets(Meeting $meeting): array {...} 8. 9. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, 10. bool $forReserve): array {...} 11. 12. ... 13. }
  65. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. $ticketIsAllowedForUser[$ticket->getId()] = 12. $this->ticketRepository->checkAllowed($ticket, $user); 13. } 14. 15. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 16. 17. $availableTickets = []; 18. foreach ($tickets as $ticket) { 19. if ($ticketIsAvailable[$ticket->getId()] && $ticketIsAllowedForUser[$ticket->getId()]) { 20. $availableTickets[$ticket->getId()] = $ticket; 21. } 22. } 23. 24. return $availableTickets; 25. }
  66. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. $ticketIsAllowedForUser[$ticket->getId()] = 12. $this->ticketRepository->checkAllowed($ticket, $user); 13. } 14. 15. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 16. 17. $leftTickets = []; 18. foreach ($tickets as $ticket) { 19. if ($ticketIsAvailable[$ticket->getId()]) { 20. $leftTickets[$ticket->getId()] = $ticket; 21. } 22. } 23. 24. $allowedTickets = []; 25. foreach ($leftTickets as $ticket) { 26. if ($ticketIsAllowedForUser[$ticket->getId()]) { 27. $allowedTickets[$ticket->getId()] = $ticket; 28. } 29. } 30. 31. return $allowedTickets; 32. }
  67. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. $ticketIsAllowedForUser[$ticket->getId()] = 12. $this->ticketRepository->checkAllowed($ticket, $user); 13. } 14. 15. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 16. 17. $leftTickets = []; 18. foreach ($tickets as $ticket) { 19. if ($ticketIsAvailable[$ticket->getId()]) { 20. $leftTickets[$ticket->getId()] = $ticket; 21. } 22. } 23. 24. $allowedTickets = []; 25. foreach ($leftTickets as $ticket) { 26. if ($ticketIsAllowedForUser[$ticket->getId()]) { 27. $allowedTickets[$ticket->getId()] = $ticket; 28. } 29. } 30. 31. return $allowedTickets; 32. }
  68. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. } 12. 13. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 14. 15. $leftTickets = []; 16. foreach ($tickets as $ticket) { 17. if ($ticketIsAvailable[$ticket->getId()]) { 18. $leftTickets[$ticket->getId()] = $ticket; 19. } 20. } 21. 22. $allowedTickets = []; 23. foreach ($leftTickets as $ticket) { 24. if ($this->ticketRepository->checkAllowed($ticket, $user)) { 25. $allowedTickets[$ticket->getId()] = $ticket; 26. } 27. } 28. 29. return $allowedTickets; 30. }
  69. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. } 12. 13. $openTickets = []; 14. foreach ($tickets as $ticket) { 15. if ($ticketIsAvailableForReserve[$ticket->getId()]) { 16. $openTickets[$ticket->getId()] = $ticket; 17. } 18. } 19. 20. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 21. 22. $leftTickets = []; 23. foreach ($openTickets as $ticket) { 24. if ($ticketIsAvailable[$ticket->getId()]) { 25. $leftTickets[$ticket->getId()] = $ticket; 26. } 27. } 28. 29. $allowedTickets = []; 30. ...
  70. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. } 12. 13. $openTickets = []; 14. foreach ($tickets as $ticket) { 15. if ($ticketIsAvailableForReserve[$ticket->getId()]) { 16. $openTickets[$ticket->getId()] = $ticket; 17. } 18. } 19. 20. $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise; 21. 22. $leftTickets = []; 23. foreach ($openTickets as $ticket) { 24. if ($ticketIsAvailable[$ticket->getId()]) { 25. $leftTickets[$ticket->getId()] = $ticket; 26. } 27. } 28. 29. $allowedTickets = []; 30. ...
  71. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. } 12. 13. $openTickets = []; 14. foreach ($tickets as $ticket) { 15. if ($ticketIsAvailableForReserve[$ticket->getId()]) { 16. $openTickets[$ticket->getId()] = $ticket; 17. } 18. } 19. 20. $leftTickets = $openTickets; 21. if (!$forReserve) { 22. $leftTickets = []; 23. foreach ($openTickets as $ticket) { 24. if ($ticketIsAvailableOtherwise[$ticket->getId()]) { 25. $leftTickets[$ticket->getId()] = $ticket; 26. } 27. } 28. } 29. 30. $allowedTickets = []; 31. ...
  72. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): 2.

    array { 3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 5. $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = []; 6. $today = new DateTimeImmutable(); 7. foreach ($tickets as $ticket) { 8. $ticketIsAvailableForReserve[$ticket->getId()] = $ticket->isOpenForSaleOn($today); 9. $ticketIsAvailableOtherwise[$ticket->getId()] = $ticket->isOpenForSaleOn($today) 10. && $this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket); 11. } 12. 13. $openTickets = []; 14. foreach ($tickets as $ticket) { 15. if ($ticketIsAvailableForReserve[$ticket->getId()]) { 16. $openTickets[$ticket->getId()] = $ticket; 17. } 18. } 19. 20. $leftTickets = $openTickets; 21. if (!$forReserve) { 22. $leftTickets = []; 23. foreach ($openTickets as $ticket) { 24. if ($ticketIsAvailableOtherwise[$ticket->getId()]) { 25. $leftTickets[$ticket->getId()] = $ticket; 26. } 27. } 28. } 29. 30. $allowedTickets = []; 31. ...
  73. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 3. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 4. $today = new DateTimeImmutable(); 5. 6. $openTickets = []; 7. foreach ($tickets as $ticket) { 8. if ($ticket->isOpenForSaleOn($today)) { 9. $openTickets[$ticket->getId()] = $ticket; 10. } 11. } 12. 13. $leftTickets = $openTickets; 14. if (!$forReserve) { 15. $leftTickets = []; 16. foreach ($openTickets as $ticket) { 17. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 18. $leftTickets[$ticket->getId()] = $ticket; 19. } 20. } 21. } 22. 23. $allowedTickets = []; 24. foreach ($leftTickets as $ticket) { 25. if ($this->ticketRepository->checkAllowed($ticket, $user)) { 26. $allowedTickets[$ticket->getId()] = $ticket; 27. } 28. } 29. 30. return $allowedTickets; 31. }
  74. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 3. $today = new DateTimeImmutable(); 4. 5. $openTickets = []; 6. foreach ($tickets as $ticket) { 7. if ($ticket->isOpenForSaleOn($today)) { 8. $openTickets[$ticket->getId()] = $ticket; 9. } 10. } 11. 12. $allowedTickets = []; 13. foreach ($openTickets as $ticket) { 14. if ($this->ticketRepository->checkAllowed($ticket, $user)) { 15. $allowedTickets[$ticket->getId()] = $ticket; 16. } 17. } 18. 19. if ($forReserve) { 20. return $allowedTickets; 21. } 22. 23. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 24. $leftTickets = []; 25. foreach ($allowedTickets as $ticket) { 26. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 27. $leftTickets[$ticket->getId()] = $ticket; 28. } 29. } 30. 31. return $leftTickets; 32. }
  75. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 3. $today = new DateTimeImmutable(); 4. 5. $openTickets = []; 6. foreach ($tickets as $ticket) { 7. if ($ticket->isOpenForSaleOn($today)) { 8. $openTickets[$ticket->getId()] = $ticket; 9. } 10. } 11. 12. $allowedTickets = []; 13. foreach ($openTickets as $ticket) { 14. if ($this->ticketRepository->checkAllowed($ticket, $user)) { 15. $allowedTickets[$ticket->getId()] = $ticket; 16. } 17. } 18. 19. if ($forReserve) { 20. return $allowedTickets; 21. } 22. 23. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 24. $leftTickets = []; 25. foreach ($allowedTickets as $ticket) { 26. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 27. $leftTickets[$ticket->getId()] = $ticket; 28. } 29. } 30. 31. return $leftTickets; 32. }
  76. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. $openTickets = $this->getTicketsOpenForSaleOn($meeting, new DateTimeImmutable()); 3. 4. $allowedTickets = []; 5. foreach ($openTickets as $ticket) { 6. if ($this->ticketRepository->checkAllowed($ticket, $user)) { 7. $allowedTickets[$ticket->getId()] = $ticket; 8. } 9. } 10. 11. if ($forReserve) { 12. return $allowedTickets; 13. } 14. 15. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 16. $leftTickets = []; 17. foreach ($allowedTickets as $ticket) { 18. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 19. $leftTickets[$ticket->getId()] = $ticket; 20. } 21. } 22. 23. return $leftTickets; 24. } 25. 26. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...}
  77. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. $openTickets = $this->getTicketsOpenForSaleOn($meeting, new DateTimeImmutable()); 3. 4. $allowedTickets = []; 5. foreach ($openTickets as $ticket) { 6. if ($this->ticketRepository->checkAllowed($ticket, $user)) { 7. $allowedTickets[$ticket->getId()] = $ticket; 8. } 9. } 10. 11. if ($forReserve) { 12. return $allowedTickets; 13. } 14. 15. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 16. $leftTickets = []; 17. foreach ($allowedTickets as $ticket) { 18. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 19. $leftTickets[$ticket->getId()] = $ticket; 20. } 21. } 22. 23. return $leftTickets; 24. } 25. 26. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...}
  78. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. $allowedTickets = $this->getTicketsUserCanReserve($meeting, $user); 3. 4. if ($forReserve) { 5. return $allowedTickets; 6. } 7. 8. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 9. $leftTickets = []; 10. foreach ($allowedTickets as $ticket) { 11. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 12. $leftTickets[$ticket->getId()] = $ticket; 13. } 14. } 15. 16. return $leftTickets; 17. } 18. 19. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...} 20. 21. private function getTicketsUserCanReserve(Meeting $meeting, User $user): array {...}
  79. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. $allowedTickets = $this->getTicketsUserCanReserve($meeting, $user); 3. 4. if ($forReserve) { 5. return $allowedTickets; 6. } 7. 8. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 9. $leftTickets = []; 10. foreach ($allowedTickets as $ticket) { 11. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 12. $leftTickets[$ticket->getId()] = $ticket; 13. } 14. } 15. 16. return $leftTickets; 17. } 18. 19. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...} 20. 21. private function getTicketsUserCanReserve(Meeting $meeting, User $user): array {...}
  80. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. if ($forReserve) { 3. return $this->getTicketsUserCanReserve($meeting, $user); 4. } 5. 6. $allowedTickets = $this->getTicketsUserCanReserve($meeting, $user); 7. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 8. $leftTickets = []; 9. foreach ($allowedTickets as $ticket) { 10. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 11. $leftTickets[$ticket->getId()] = $ticket; 12. } 13. } 14. 15. return $leftTickets; 16. } 17. 18. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...} 19. 20. private function getTicketsUserCanReserve(Meeting $meeting, User $user): array {...}
  81. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. if ($forReserve) { 3. return $this->getTicketsUserCanReserve($meeting, $user); 4. } 5. 6. $allowedTickets = $this->getTicketsUserCanReserve($meeting, $user); 7. $currentSoldPerTicket = $this->ticketRepository->getNumberOfCurrentSoldTickets($meeting); 8. $leftTickets = []; 9. foreach ($allowedTickets as $ticket) { 10. if ($this->thereAreTicketsLeftForSale($ticket, $currentSoldPerTicket)) { 11. $leftTickets[$ticket->getId()] = $ticket; 12. } 13. } 14. 15. return $leftTickets; 16. } 17. 18. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...} 19. 20. private function getTicketsUserCanReserve(Meeting $meeting, User $user): array {...}
  82. 1. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array

    { 2. if ($forReserve) { 3. return $this->getTicketsUserCanReserve($meeting, $user); 4. } 5. return $this->getTicketsUserCanPurchase($meeting, $user); 6. } 7. 8. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...} 9. 10. private function getTicketsUserCanReserve(Meeting $meeting, User $user): array {...} 11. 12. private function getTicketsUserCanPurchase(Meeting $meeting, User $user): array {...}
  83. 1. /** @deprecated Use getTicketsUserCanReserve or getTicketsUserCanPurchase */ 2. public

    function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array { 3. if ($forReserve) { 4. return $this->getTicketsUserCanReserve($meeting, $user); 5. } 6. return $this->getTicketsUserCanPurchase($meeting, $user); 7. } 8. 9. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): array {...} 10. 11. public function getTicketsUserCanReserve(Meeting $meeting, User $user): array {...} 12. 13. public function getTicketsUserCanPurchase(Meeting $meeting, User $user): array {...} OK (14 tests, 14 assertions)
  84. Prevent sale of tickets when meeting has started

  85. 1. private function getTicketsOpenForSaleOn(Meeting $meeting, DateTimeImmutable $date): 2. array {

    3. $tickets = $this->ticketRepository->getMeetingTickets($meeting); 4. $openTickets = []; 5. foreach ($tickets as $ticket) { 6. if ($ticket->isOpenForSaleOn($today)) { 7. $openTickets[$ticket->getId()] = $ticket; 8. } 9. } 10. return $openTickets; 11. }
  86. 1. final class Meeting { 2. ... 3. public function

    getTickets(): array { 4. return $this->tickets; 5. } 6. 7. public function getTicketsOpenForSaleOn(DateTimeImmutable $date): array { 8. $openTickets = []; 9. foreach ($this->tickets as $ticket) { 10. if ($ticket->ticketIsOpenForSaleOn($date)) { 11. $openTickets[$ticket->getId()] = $ticket; 12. } 13. } 14. return $openTickets; 15. } 16. } .............. 14 / 14 (100%) Time: 43 ms, Memory: 4.00MB OK (14 tests, 14 assertions)
  87. 1. final class TicketService { 2. /** @deprecated Use getPrivatelyAvailableTickets,

    getTicketsUserCanReserve or getTicketsUserCanPurchase */ 3. public function getAvailableTickets(Meeting $meeting, bool $publicOnly, User $user, 4. bool $forReserve): array {...} 5. 6. public function getPrivatelyAvailableTickets(Meeting $meeting): array {...} 7. 8. /** @deprecated Use getTicketsUserCanReserve or getTicketsUserCanPurchase */ 9. public function getPubliclyAvailableTickets(Meeting $meeting, User $user, bool $forReserve): array {...} 10. 11. public function getTicketsUserCanReserve(Meeting $meeting, User $user): array {...} 12. 13. private function getTicketsUserCanPurchase(Meeting $meeting, User $user): array {...} 14. 15. private function thereAreTicketsLeftForSale(Ticket $ticket, array $currentSoldPerTicket): bool {...} 16. }
  88. 1. public function testThatTicketsCannotBeSoldWhenMeetingHasStarted() { 2. $meeting = new Meeting(

    3. ..., 4. new Program( 5. new MeetingDuration( 6. new DateTimeImmutable('2018-02-20 19:00'), 7. new DateTimeImmutable('2018-02-20 22:00') 8. ), 9. [] 10. ), 11. [new Ticket(1, true, 0, null, null)] 12. ); 13. 14. $expected = []; 15. $actual = $meeting->getTicketsOpenForSaleOn( 16. new DateTimeImmutable('2018-02-20 20:00') 17. ); 18. 19. $this->assertEquals($expected, $actual); 20. } --- Expected +++ Actual @@ @@ Array ( + 1 => Pelshoff\Meeting\Ticket Object (...) )
  89. 1. public function getTicketsOpenForSaleOn(DateTimeImmutable $date): array { 2. if ($this->program->hasStartedOn($date))

    { 3. return []; 4. } 5. $openTickets = []; 6. foreach ($this->tickets as $ticket) { 7. if ($ticket->isOpenForSaleOn($date)) { 8. $openTickets[$ticket->getId()] = $ticket; 9. } 10. } 11. return $openTickets; 12. } ............... 15 / 15 (100%) Time: 84 ms, Memory: 4.00MB OK (15 tests, 15 assertions)
  90. None
  91. Pim Elshoff developer.procurios.com @pelshoff https://speakerdeck.com/pelshoff/refactoring-the-domain-guided-by-tests

  92. Workshop dates 2018-05-22 DevDays Vilnius 2018 2018-06-28 DDDNL 2018-? 010PHP