I'm displaying star ratings based on input from an API (note: only displaying, not receiving ratings at all). The following code works exactly as I need it to, but it's a very large piece of logic for something that feels like it could be massively simplified.
I read a few other tickets here that suggest that using an array might be more effective than a switch case. But because the logic depends on each case between true within a range of numbers (like more than 2.5 but less than 3) I'm not sure an array would work at all in this case.
Bottom line is this: Can this code be massively simplified somehow?
$stars = 3.5;
switch ($stars) {
case ($stars > 1 && $stars <= 1.5):
$star2 = 'dashicons-star-half';
$star3 = 'dashicons-star-empty';
$star4 = 'dashicons-star-empty';
$star5 = 'dashicons-star-empty';
break;
case($stars > 1.5 && $stars <= 2):
$star2 = 'dashicons-star-filled';
$star3 = 'dashicons-star-empty';
$star4 = 'dashicons-star-empty';
$star5 = 'dashicons-star-empty';
break;
case($stars > 2 && $stars <= 2.5):
$star2 = 'dashicons-star-filled';
$star3 = 'dashicons-star-half';
$star4 = 'dashicons-star-empty';
$star5 = 'dashicons-star-empty';
break;
case($stars > 2.5 && $stars <= 3):
$star2 = 'dashicons-star-filled';
$star3 = 'dashicons-star-filled';
$star4 = 'dashicons-star-empty';
$star5 = 'dashicons-star-empty';
break;
case($stars > 3 && $stars <= 3.5):
$star2 = 'dashicons-star-filled';
$star3 = 'dashicons-star-filled';
$star4 = 'dashicons-star-half';
$star5 = 'dashicons-star-empty';
break;
case($stars > 3.5 && $stars <= 4):
$star2 = 'dashicons-star-filled';
$star3 = 'dashicons-star-filled';
$star4 = 'dashicons-star-filled';
$star5 = 'dashicons-star-empty';
break;
case($stars > 4 && $stars <= 4.5):
$star2 = 'dashicons-star-filled';
$star3 = 'dashicons-star-filled';
$star4 = 'dashicons-star-filled';
$star5 = 'dashicons-star-half';
break;
case($stars > 4.5):
$star2 = 'dashicons-star-filled';
$star3 = 'dashicons-star-filled';
$star4 = 'dashicons-star-filled';
$star5 = 'dashicons-star-filled';
break;
default:
$star2 = 'dashicons-star-empty';
$star3 = 'dashicons-star-empty';
$star4 = 'dashicons-star-empty';
$star5 = 'dashicons-star-empty';
}
?>
<div class="wporg-ratings" title="<?php echo $stars; ?> out of 5 stars" style="color:#e6b800;">
<span class="dashicons dashicons-star-filled"></span>
<span class="dashicons <?php echo $star2; ?>"></span>
<span class="dashicons <?php echo $star3; ?>"></span>
<span class="dashicons <?php echo $star4; ?>"></span>
<span class="dashicons <?php echo $star5; ?>"></span>
</div>
I don't think an array or a switch are needed. You can use a for loop, and check the $stars
value against your loop variable to see which icon should be used.
<div class="wporg-ratings" title="<?php echo $stars; ?> out of 5 stars" style="color:#e6b800;">
<?php
// for loop: one iteration for each of five possible stars
// (for loops are generally best for repeating code a specific number of times)
for ($i=0; $i < 5; $i++) {
if ($stars <= $i ) {
// empty stars are displayed when the iterator (i) is >= the star value
echo '<span class="dashicons dashicons-star-empty"></span>';
} elseif ($stars <= $i + 0.5) {
// half stars are displayed for star values between i and i+0.5
echo '<span class="dashicons dashicons-star-half"></span>';
} else {
// whole stars are displayed when the star value is > i+0.5
echo '<span class="dashicons dashicons-star-filled"></span>';
}
}
?>
</div>
To help understand why this works, take a theoretical value through the loop. We can use the one from your question, 3.5
.
here is quick version:
$stars = 3.5;
$d = array(
($stars >= 1.0) ? 'dashicons-star-filled' : (($stars >= 0.5) ? 'dashicons-star-half' : 'dashicons-star-empty'),
($stars >= 2.0) ? 'dashicons-star-filled' : (($stars >= 1.5) ? 'dashicons-star-half' : 'dashicons-star-empty'),
($stars >= 3.0) ? 'dashicons-star-filled' : (($stars >= 2.5) ? 'dashicons-star-half' : 'dashicons-star-empty'),
($stars >= 4.0) ? 'dashicons-star-filled' : (($stars >= 3.5) ? 'dashicons-star-half' : 'dashicons-star-empty'),
($stars >= 5.0) ? 'dashicons-star-filled' : (($stars >= 4.5) ? 'dashicons-star-half' : 'dashicons-star-empty')
);
echo '<div class="wporg-ratings" title="' . $stars . ' out of 5 stars" style="color:#e6b800;">';
foreach ($d as $value) echo '<span class="dashicons ' . $value . '"></span>';
echo '</div>';
it is not the same as your original logic (when first star is always filled), but you can use it as direction
next step could be moving repeated code into function
Another possibility is to define an array of hashes. Let's say that that each element in the array contains "minimum value, and three strings," and that the array is sorted in ascending order. Your if/then logic could be replaced by a loop through that array, breaking-out as soon as it can.
However: "Clarity trumps all other concerns." If the code works and is reasonably maintainable, who cares if it stinks?
Your existing code could be simplified to eliminate the possibility of any value "falling through the cracks" by noticing that the first half of the if-condition can be omitted: if you reach the second case, it must be true that the value is greater than 1.5, and so on.