Lesson 1
Potluck Dinner Organization System Using PHP
Introduction

Welcome to today's lesson, where we will explore a common challenge in software engineering: introducing complex features while preserving backward compatibility. Our focus will be on a Potluck Dinner organization system, where we will manage participants and their respective dishes for each round. Prepare for an exciting journey through PHP programming, step-by-step analysis, and strategic thinking. Let's dive into our adventure!

Starter Task Review

Initially, our Potluck Dinner organization system allows us to add and remove participants and manage their respective dishes for each round. There are three essential methods:

  • addParticipant(string $memberId): bool: This method adds a participant. If a participant with the given $memberId already exists, it won't create a new one but will return false. Otherwise, it will add the member and return true.
  • removeParticipant(string $memberId): bool: This method removes a participant with the given $memberId. If the participant exists, the system will remove them and return true. Otherwise, it will return false. When removing a participant, you need to remove their dish if they brought one.
  • addDish(string $memberId, string $dishName): bool: This method enables each participant to add their dish for every round. If a participant has already added a dish for this round OR if the $memberId isn't valid, the method will return false. Otherwise, it will add the dish for the respective participant's round and return true.

Let's write our PHP code, which implements these functions as per our initial state:

php
1<?php 2 3class Potluck { 4 private array $participants = []; 5 private array $dishes = []; 6 7 public function addParticipant(string $memberId): bool { 8 if (isset($this->participants[$memberId])) { 9 return false; 10 } else { 11 $this->participants[$memberId] = true; 12 return true; 13 } 14 } 15 16 public function removeParticipant(string $memberId): bool 17 { 18 if (!isset($this->participants[$memberId])) { 19 return false; 20 } else { 21 unset($this->participants[$memberId]); 22 unset($this->dishes[$memberId]); 23 return true; 24 } 25 } 26 27 public function addDish(string $memberId, string $dishName): bool 28 { 29 if (!isset($this->participants[$memberId]) || isset($this->dishes[$memberId])) { 30 return false; 31 } else { 32 $this->dishes[$memberId] = $dishName; 33 return true; 34 } 35 } 36} 37?>

In this code, we used PHP arrays to store unique participant IDs and associative arrays to store the participant's ID and their respective dish name. With this foundation laid, let's introduce some advanced functionalities.

Introducing Advanced Functionalities

Our Potluck Dinner organization system is currently simple but practical. To make it even more exciting, we're going to introduce a "Dish of the Day" feature. This feature will enable participants to vote, and the dish receiving the most votes will be declared the "Dish of the Day."

To add this feature, we will define two new methods:

  • vote(string $memberId, string $voteId): bool: This method will allow a participant to cast a vote for a dish. Each participant can cast a vote only once per round. If a participant tries to vote again or if the $memberId isn't valid, it should return false.
  • dishOfTheDay(): ?string: This method will calculate and return the "Dish of the Day" based on the votes received. If multiple dishes tie for the highest number of votes, the dish brought by the participant who joined first acquires precedence. If there are no votes, the function returns null.
Building Advanced Features. Step 1: Implementing the `vote` method
php
1<?php 2private array $votes = []; 3 4public function vote(string $memberId, string $voteId): bool 5{ 6 if (isset($this->participants[$memberId]) && !isset($this->votes[$memberId])) { 7 $this->votes[$memberId] = $voteId; 8 return true; 9 } else { 10 return false; 11 } 12} 13?>

In the vote function, we check whether the $memberId exists and whether the vote hasn't been cast before. If both conditions are fulfilled, we add the vote to our votes and return true; otherwise, we return false.

Building Advanced Features. Step 2: Implementing the `dishOfTheDay` method
php
1<?php 2 3public function dishOfTheDay(): ?string 4{ 5 if (count($this->votes) == 0) { 6 return null; 7 } 8 9 $voteCount = []; 10 foreach ($this->votes as $vote) { 11 if (isset($voteCount[$vote])) { 12 $voteCount[$vote]++; 13 } else { 14 $voteCount[$vote] = 1; 15 } 16 } 17 18 $maxVotes = max($voteCount); 19 $maxVoteDishes = array_keys($voteCount, $maxVotes); 20 21 $earliestJoinTime = PHP_INT_MAX; 22 $dishOfTheDayMember = null; 23 foreach ($maxVoteDishes as $memberId) { 24 if ($this->participants[$memberId] < $earliestJoinTime) { 25 $earliestJoinTime = $this->participants[$memberId]; 26 $dishOfTheDayMember = $memberId; 27 } 28 } 29 30 return "Participant: '" . $dishOfTheDayMember . "', Dish: '" . $this->dishes[$dishOfTheDayMember] . "'"; 31} 32?>

In the dishOfTheDay function, we ensure that we only perform the calculation if there are votes. We calculate the count of votes for each dish, determine the dish or dishes that received the maximum votes, and resolve ties by selecting the dish brought by the participant with the earliest join time.

Building Advanced Features. Step 3: Updating Previous Methods for Compatibility and Integration

As we introduce the two advanced functionalities, vote and dishOfTheDay, our existing system needs adept adjustments to ensure seamless integration and maintain backward compatibility.

The addParticipant method initially just added a participant. With the introduction of the "Dish of the Day" feature, we now store the participant's ID along with their join time in an associative array. This timestamp is crucial for tie-breaking in votes.

php
1<?php 2 3public function addParticipant(string $memberId): bool 4{ 5 if (isset($this->participants[$memberId])) { 6 return false; 7 } else { 8 $this->participants[$memberId] = microtime(true); 9 return true; 10 } 11} 12?>
Building Advanced Features. Step 4: Adjustments in `removeParticipant`

The original removeParticipant method did not consider the impact of removing a participant who might have already voted or added a dish. To maintain the integrity of voting and dish tracking, we now clean up all related entries across dishes and votes when a participant is removed.

php
1<?php 2 3public function removeParticipant(string $memberId): bool 4{ 5 if (!isset($this->participants[$memberId])) { 6 return false; 7 } else { 8 unset($this->participants[$memberId]); 9 unset($this->dishes[$memberId]); 10 unset($this->votes[$memberId]); 11 return true; 12 } 13} 14?>
Ensuring Backward Compatibility
  • Participants Data Structure Change: We transitioned the participants' storage to an associative array that captures IDs and join times, maintaining the unique identification trait while adding new functionality. This change is backward-compatible since it requires no modification to how participant IDs are provided or handled externally.

  • Method Signature Consistency: All method signatures (addParticipant, removeParticipant, addDish) remain unchanged. This ensures that existing integrations with our system interface do not require modifications from client code.

Final Implementation

Combining all our steps, this is the final implementation of our Potluck class with all the required methods in PHP:

php
1<?php 2 3class Potluck 4{ 5 private array $participants = []; 6 private array $dishes = []; 7 private array $votes = []; 8 9 public function addParticipant(string $memberId): bool 10 { 11 if (isset($this->participants[$memberId])) { 12 return false; 13 } else { 14 $this->participants[$memberId] = microtime(true); 15 return true; 16 } 17 } 18 19 public function removeParticipant(string $memberId): bool 20 { 21 if (!isset($this->participants[$memberId])) { 22 return false; 23 } else { 24 unset($this->participants[$memberId]); 25 unset($this->dishes[$memberId]); 26 unset($this->votes[$memberId]); 27 return true; 28 } 29 } 30 31 public function addDish(string $memberId, string $dishName): bool 32 { 33 if (!isset($this->participants[$memberId]) || isset($this->dishes[$memberId])) { 34 return false; 35 } else { 36 $this->dishes[$memberId] = $dishName; 37 return true; 38 } 39 } 40 41 public function vote(string $memberId, string $voteId): bool 42 { 43 if (isset($this->participants[$memberId]) && !isset($this->votes[$memberId])) { 44 $this->votes[$memberId] = $voteId; 45 return true; 46 } else { 47 return false; 48 } 49 } 50 51 public function dishOfTheDay(): ?string 52 { 53 if (count($this->votes) == 0) { 54 return null; 55 } 56 57 $voteCount = []; 58 foreach ($this->votes as $vote) { 59 if (isset($voteCount[$vote])) { 60 $voteCount[$vote]++; 61 } else { 62 $voteCount[$vote] = 1; 63 } 64 } 65 66 $maxVotes = max($voteCount); 67 $maxVoteDishes = array_keys($voteCount, $maxVotes); 68 69 $earliestJoinTime = PHP_INT_MAX; 70 $dishOfTheDayMember = null; 71 foreach ($maxVoteDishes as $memberId) { 72 if ($this->participants[$memberId] < $earliestJoinTime) { 73 $earliestJoinTime = $this->participants[$memberId]; 74 $dishOfTheDayMember = $memberId; 75 } 76 } 77 78 return "Participant: '" . $dishOfTheDayMember . "', Dish: '" . $this->dishes[$dishOfTheDayMember] . "'"; 79 } 80} 81?>
Lesson Summary

Congratulations! You've successfully completed the task of introducing complex features while preserving backward compatibility using PHP. This skill is invaluable in real-life software engineering scenarios, where existing codebases can be extensive, and breaking changes can be disruptive. Strengthen this ability with more practice and explore similar challenges. I'll see you in the next lesson! Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.