-1

How can I alter this statement to add another search parameter such as " AND username LIKE ? " ?

Currently, searching for something on my website gives the expected results beautifully, but I'd like to search within the listing usernames too, not just the listing titles. This is the part that's giving me trouble.

The code is rather complex but I'll try to minimize the code in question only:

    $fetch_paginated_listings = "SELECT * FROM table WHERE end_date >= CURDATE() ";
    $search_param = $_GET['search'];
    $search_param = (strlen($search_param) > 175) ? substr($search_param,0,175) : $search_param; //Limit the uesr input. If users fill up the search bar, they can crash the db!

    /*Following code is to dynamically populate a prepared statement. Reference: https://www.pontikis.net/blog/dynamically-bind_param-array-mysqli*/
    $a_bind_params = explode(' ', $search_param);
    $a_param_type = array();
    foreach ($a_bind_params as $item) {
      array_push($a_param_type, 's');
      $fetch_paginated_listings .= " AND l_title LIKE ? "; //here, try to add this: " AND l_username LIKE ? " and array_push another 's' to $a_param_type
    }
    /* Bind parameters. Types: s = string, i = integer, d = double,  b = blob */
    $a_params = array();
    $param_type = '';
    $n = count($a_param_type);
    for($i = 0; $i < $n; $i++) {
      $param_type .= $a_param_type[$i];
    }
    /* with call_user_func_array, array params must be passed by reference */
    $a_params[] = & $param_type;
    for($i = 0; $i < $n; $i++) {
      /* with call_user_func_array, array params must be passed by reference */
      $a_bind_params[$i] = "%".$a_bind_params[$i]."%";
      $a_params[] = & $a_bind_params[$i];
    }

    $fetch_paginated_listings .= ' ORDER BY l_title ';    
    $fetch_paginated_listings .= ' 
    LIMIT
        ' . (($pagination->get_page() - 1) * $records_per_page) . ', ' . $records_per_page . '';

    $stmt_fetch_paginated_listings = $conn->prepare($fetch_paginated_listings);
    if($stmt_fetch_paginated_listings === false) {
      trigger_error('Wrong SQL: ' . $fetch_paginated_listings . ' Error: ' . $conn->errno . ' ' . $conn->error, E_USER_ERROR);
    }

    /* use call_user_func_array, as $stmt->bind_param('s', $param); does not accept params array */
    call_user_func_array(array($stmt_fetch_paginated_listings, 'bind_param'), $a_params);

    $stmt_fetch_paginated_listings->execute();

My attempts so far:

    foreach ($a_bind_params as $item) {
      array_push($a_param_type, 's');
      array_push($a_param_type, 's');
      $fetch_paginated_listings .= " AND l_title LIKE ? ";
      $fetch_paginated_listings .= " AND l_username LIKE ? ";
    }

^ The error it gives is "Undefined offset 3, Undefined offset 4, Undefined offset 5" perhaps because I cannot find the correct place to add the extra fields.

Lame attempt, I know, but I'm stumped. How should I do it?

EDIT:

Let's say a user types in "foo bar" in the search input field, the words "foo" and "bar" are then placed in an array with a length of 2.

1 - The query checks a column called 'title' to see if there are any matches,

2 - It also checks a column called 'username' to see if there are any matches

3 - The query is done using prepared statements for security purposes

4 - In order to achieve this type of statement (varied amount of array strings) you have to build a dynamic multiple keyword prepared statement. I used this as reference: https://www.pontikis.net/blog/dynamically-bind_param-array-mysqli

5 - I've achieved the first part of the query i.e. the query is successful for a single placeholder

6 - I'm stuck trying to search the username column, since it requires another placeholder

7 - When a user searches for "foo bar" the query should end up like this, but with placeholders: SELECT * FROM table WHERE title LIKE %foo% AND title LIKE %bar% AND username LIKE %foo% AND username LIKE %bar%

J Doe
  • 121
  • 2
  • 11
  • "I'll try to minimize the code" - Is that a joke? I miss a **clear** problem statement and the **shortest** code to reproduce the "problem". – Paul Spiegel Mar 16 '19 at 23:30
  • This is a case where OOP would definitely help tremendously. The first thing I would do is to make a wrapper for that hideous abomination that is mysqli. (Just my personal opinion.) Nevertheless, I would suggest making an array of terms `l_title LIKE ?` and then join them with ' and '. – Tim Morton Mar 16 '19 at 23:46
  • I'll put it in an answer. It'll take a bit... – Tim Morton Mar 16 '19 at 23:46
  • I'm new to php and mysql, doing my best to learn. For now, whatever works can later be improved. Thank you for stopping by @TimMorton – J Doe Mar 16 '19 at 23:48
  • Well, they went and closed it, so I can't post my answer. – Tim Morton Mar 17 '19 at 00:04
  • You should be able to find a useful solution in the three duplicates I've linked. The second answer to the first question (using the unpacking operator) is a good one. – Nick Mar 17 '19 at 00:04
  • @TimMorton sorry, but this question has been asked **many** times before. I've only listed the 3 questions with the best answers... – Nick Mar 17 '19 at 00:06
  • @Nick It's not the same. You could've waited a few extra seconds. – J Doe Mar 17 '19 at 00:08
  • @JDoe in what way is it not the same? If you can convince me I can reopen... – Nick Mar 17 '19 at 00:09
  • comment-sized answer: you only want to add that last bit just before the `ORDER` part, and add the username as the last element of your bind array. You're sticking it in the `foreach` which adds a great deal of confusion to the mix. – Tim Morton Mar 17 '19 at 00:14
  • @Nick My question isn't about how to dynamically bind multiple parameters into an sql query, it's about how to iterate through a single array for 2 placeholder parameters. I don't know how to explain clearer, but the answers to those questions do not solve my problem. – J Doe Mar 17 '19 at 00:14
  • @JDoe check out ub3rst4r answer to https://stackoverflow.com/questions/27913348/stmt-bind-param-dynamically-if-condition-query – Nick Mar 17 '19 at 00:18
  • @TimMorton Will that search the username column for each word in the search parameters? Remember that the `for` loop is there to separate each keyword, so searching for something like "bottle soda" would result in any of these: "a bottle of soda", "soda of a bottle", "abottleofsoda", "bottle123". Likewise it should also search the usernames for matches of "bottle soda". Not sure if I'm being clear – J Doe Mar 17 '19 at 00:18
  • oh... username is a search term. I misunderstood. Perhaps you could update your question with a sample search input and the query you want that to generate. – Tim Morton Mar 17 '19 at 00:25
  • Alright, I just edited the question to explain the process with some sample code. Can you understand what I'm trying to get at? – J Doe Mar 17 '19 at 00:34
  • @Nick I just tried the solution in that answer but it's in no way similar to my code, I find it impossible to implement. Also the answer does not show where the input comes from... There is a difference. – J Doe Mar 17 '19 at 00:36
  • 1
    @JDoe Yes, I get it. You were on the right track as far as you went. What you missed is how to get the right number of bind parameters. `$a_bind_params` has enough parameters for the title, but when you add username to it, it has to be doubled. i.e., if `$a_bind_params = ['bottle','soda']`, your new array needs to be `['bottle','soda','bottle','soda']` or `['bottle','bottle','soda','soda']` – Tim Morton Mar 17 '19 at 00:48
  • 1
    @JDoe Based on your edit, I think you should be looking at [full text search](https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html) instead. But I agree that the duplicates are not perfect. – Nick Mar 17 '19 at 00:49
  • 2
    @TimMorton question is reopened. Have at it! – Nick Mar 17 '19 at 00:57
  • That's it! Tim thank you for staying with me, it's working so perfectly... I can put the result in an answer if I should – J Doe Mar 17 '19 at 00:58

2 Answers2

1

You were on the right track as far as you went. What you missed is how to get the right number of bind parameters. $a_bind_params has enough parameters for the title, but when you add username to it, it has to be doubled. i.e., if $a_bind_params = ['bottle','soda'], your new array needs to be ['bottle','soda','bottle','soda'] or ['bottle','bottle','soda','soda']

Tim Morton
  • 2,614
  • 1
  • 15
  • 23
0

As @TimMorton's answer states, these are the edits I done (hacky, but works!)

foreach ($a_bind_params as $item) {
      array_push($a_param_type, 'ss'); //added another s
      $fetch_paginated_listings .= " AND l_title LIKE ? AND l_username LIKE ? "; //added the second parameter
    }

Then in the for loop:

for($i = 0; $i < $n; $i++) {
      $a_bind_params[$i] = "%".$a_bind_params[$i]."%";
      $a_params[] = & $a_bind_params[$i];
      $a_params[] = & $a_bind_params[$i]; //added this line
    }

And it works :)

J Doe
  • 121
  • 2
  • 11