views:

95

answers:

4

Hi. I'm trying to figure out how to write a statement in my first CI app (and only second PHP app ever) and I'm stuck.

I have a junction table that holds book_id and user_id from their respective tables. I want to find out who has read a book and echo that back out, but I need the formatting to make sense. Here is the query:

$readQuery = $this->db->get_where('books_users', array('book_id' => $isbn));

And here's my logic for one person

// Get my user info
$user = $this->ion_auth->get_user();
// Tell me who has read it.
$this->db->select('user_id');
$readQuery = $this->db->get_where('books_users', array('book_id' => $isbn));
// If only one person has read it
if ($readQuery->num_rows() == 1) {
    $readResult = $readQuery->row();
    // And if that person was me...
    if ($readResult->user_id == $user->id) {
        $message = 'You have read this.';
    // If it was someone else...
    } else {
        $reader = $this->ion_auth->get_user($readResult->user_id);
        $message = "$reader->first_name $reader->last_name has read this";
    }
// If two people have read it
}

So I'm good if only one person has read it. But when two people have read it, I want it to say "Name One and Name Two have read this." if the logged in person hasn't read it. If they're one of the two people, it should say "You and Name Two have read this".

And so on for 3-5. If 3-5 people have read it, the options would be "Name One, Name Two, and Name Three have read this" or "You, Name Two, and Name Three have read this." up to five people

And if it's 6 or more, it should just say the names of two of them, IE "You, Name Two, and 5 other people have read this."

I considered doing a conditional for each instance and then pushing all of the users to an array. Then I could use in_array() to see if the logged-in user has read it and try to report back, but I'm just having some trouble working up the logic.

Is there an easier way?

Thanks much in advance, Marcus

+2  A: 

I'd say use a switch statement with ranges. You'll have to do a bit of acrobatics to handle the issue of including "You" or not. The code below is untested but give you the general idea.

$message = "";
$readers = array();
$me_included = 0; // Counter increment if you're included in the reader list

$this->db->select('user_id');
$readQuery = $this->db->get_where('books_users', array('book_id' => $isbn));      
foreach ($readQuery->result() as $row) {
  if ($row->user_id == $user->id) {
    $message = "You";
    $me_included = 1;
  } else {
    $readers[] = $this->ion_auth->get_user($row->user_id);
  }
}

$reader_count = $sizeof($readers) + $me_included;
switch(TRUE) 
{
  // One reader, not you
  case( ($reader_count == 1) && ($me_included == 0) ):
    $message .= $readers[0]->first_name . " " . $readers[0]->last_name . " has read this.";
    break;
  // Only you
  case( ($reader_count == 1) && ($me_included == 1) ):
    $message .= " have read this.";
    break;
  // Two readers
  case( ($reader_count == 2) ):
    for ($i = 0; $i <= sizeof($readers); $i++) {
      if ($i == sizeof($readers)) {
        $message .= " and ";
      }
      $message .= $readers[i]->first_name . " " . $readers[i]->last_name;
    }
    $message .= " have read this.";
    break;
  case( ($reader_count > 3) && ($reader_count < 6) ):
    if ($me_included) {
      $message .= ", ";
    }
    for ($i = 0; $i <= sizeof($readers); $i++) {
      if ($i == sizeof($readers)) {
        $message .= " and ";
      }
      $message .= $readers[i]->first_name . " " . $readers[i]->last_name;
      if ($i != sizeof($readers)) {
        $message .= ", ";
      } else {
        $message .= " have read this.";
      }
    }
    break;
  case( ($reader_count > 6) ):
    if ($me_included) {
      $message .= ", " . $readers[0]->first_name . " " . $readers[0]->last_name . " and " . ($reader_count - 2) . " others have read this.";
    } else {
    for ($i = 0; $i <= 1; $i++) {
      $message .= $readers[0]->first_name . " " . $readers[0]->last_name . ", " . $readers[1]->first_name . " " . $readers[1]->last_name . " and " . ($reader_count - 2) . " others have read this.";
    }
    break;
  }
Hibiscus
I was able to successfully implement this after modifying it a bit. I don't have enough space to post the whole code here, sadly, but this does work once modified. Thanks for the help.
Marcus
I hate seeing useless logicical expressions :) At work I always have to deal with if(something) {} else { Do some work}... or if(true)... makes me furious
methodin
+2  A: 
$users = array();
while($row)
{
  if(currentuser)
  {
    array_unshift($users,'You')
  }
  else
  {
    $users[] = row[name]
  }
}

$count = $max = count($users);
if($max >= 6)
{
  $max = 2;
}

$str = '';
for($i=0;$i<$max;$i++)
{
  $str .= ($str == '' ? '' : ($i == $count-1 && $count < 6 ? ' And ' : ', ') ) . $users[$i];
}

if($count >= 6)
{
  $str .= ' and '.$count-2.' others'
}

This should help you along. It's pretty much pseudo-code but the logic is there to achieve what you want (I believe). You'd obviously want to not fetch all the rows if you are just showing a number of users but that can easily be accomplished.

methodin
I kind of prefer this setup more - it appears cleaner - but I just wasn't able to implement it for my scenario. Most of my confusion came from this line: $str .= ($str == '' ? '' : ($i == $count-1 Any suggestions? I'm not great at the ternary statements and don't understand what I'd put in the quotes in the first statement.
Marcus
The ternary statements are just for concatenation - so if str is blank, dont append anything, otherwise append the result of (if we are the end of the str and we have less than 6, append And otherwise append a comma). I always use this when separating things with commas so you dont have an extra comma at the beginning or end
methodin
Nice. I was able to implement this after your explanation, and it's 40 lines shorter (30 lines vs 70 lines) and much more maintainable. Thanks again.
Marcus
+1  A: 

There are actually a couple places we can optimize this.

1) In the initial query do a JOIN with the user table so we don't have to query again multiple times just for each name:

$readers = $this->db->select('id', 'first_name', 'last_name')->from('books_users')->join('users', 'books_users.id =users.id', 'left')->where('book_id' => $isbn);

2) Additionally, we can avoid iterating over the entire (potentially large) result set just to see if you read it, by doing a separate query:

$you_read = $this->db->get_where('books_users', array('book_id' => $isbn, 'user_id' => $user->id));
Mitchell McKenna
A: 

Just for the sake of completeness, here is the final code I used. Hopefully it will be of use to someone.

    // Find out who has read this book
    // Get the users
    $current_user = $this->ion_auth->get_user();
    $users = $this->ion_auth->get_users();
    // Get the readers
    $this->db->select('user_id');
    $query = $this->db->get_where('books_users', array('book_id' => $isbn));
    // If there were results
    $readers = array();
    if ($query->num_rows() > 0) {
        foreach ($users as $user) {
            if ($current_user->id == $user->id) {
                array_unshift($readers, 'You');
            } else {
                $readers[] = $user->first_name . ' ' . $user->last_name;
            }
            $count = $max = count($readers);
            if ($max >= 6) {
                $max = 2;
            }
            $message = '';
            for ($i = 0; $i < $max; $i++) {
                $message .= ($message == '' ? '' : ($i == ($count - 1) && ($count < 6) ? ' and ' : ', ')) . $readers[$i];
            }
            if ($count >= 6) {
                $message .= ' and ' . ($count - 2) . ' others';
            }
            $message .= ($count == 1 && !$current_user->id ? ' has ' : ' have ') . 'read this.';
        }
    } else {
        $message = '<a href="' . base_url() . 'books/' . $bookResult->filename . '">Be the first to read this.</a>';
    }
Marcus