Adding Search: Controller & View
Creating the Search Page
We’ll make the page by making a subfolder called search, so we can keep our urls clean, then putting an index.php file inside of it. Then, we’ll include the config file, and set the page title and search variable to underline it in the nav when the page is selected. Then, we’ll include the header and footer files.
Now, we’ll add a new list item to the nav in the header for the search, updating the class and such for search.
Building the Search Form
Here we’ll add some html for the header and form. Note that for the text input he gave it the name s, which is common for search fields. If you search for something, notice how you get s=value you entered in the query string.
If you leave the form’s action attribute blank, it will redirect back to the current page. For the method attribute, if you give it no value the default value is GET. You should use POST when the form is creating records or taking some other action like sending an email. For retrieving or getting data, like a search, you should use GET. Still, it’s good practice to specify them anyway in the form element. To indicate the current folder, ie for a redirect, use ./, which is for the root of the current directory.
Handling the Form Submission
We’ll start by making a blank variable $search_term to eventually hold the value entered in the search field. Then we’ll make a conditionally based on whether a value is set for the s get variable. If it is, we’ll add that value to the $search_term variable, and at the same time trim it. Then, we’ll add another conditional that checks if the $search_term variable is not blank. If it’s not, we’ll pull in the product.php file (the model).
We’ll go to the model now to make a function to perform when the search is performed. We’ll call it get_products_search, and it will take one argument, the search term. We’ll start with an empty array called $results that we can add to, and make a variable $all equal to the return value of the function that gives us the main array. Then, we’ll add a foreach loop to go through those and give us the results. The conditional inside should look for matches, and add them to results. We’ll then return the $results variable so we can use it later.
Now we can go back and add this function and it’s return value to our search page, passing our $search_term variable as its argument. We’ll also add some troubleshooting code to display the code on the screen if there’s a search entry.
Displaying the Products
Now to actually have the products display on the screen. First we’ll remove the troubleshooting code. We’ll then add a conditional below the form checking if the $search_term is not set. If it is, we’ll add ul tags surrounding a foreach loop, which goes through the array and echo’s out the return value of the function get_list_view_html for each element.
Accounting For Empty Results
We’ll also make the search field retain the value entered, using the same method as before for the contact form error scenario with the htmlspecialchars() function to prevent people writing malicious code.
Now to account for what happens if there are no matches. We can simulate this by commenting out the part that adds to the array in the model.
We can then use the native php function empty() to check if an array has any elements. We’ll use this and the negation operator to make a if else block, where if there is nothing in the array, it displays a message saying no results were found.
Adding Search: Model
Introducing the strpos Function
Now to make the searching work with the conditional we set up in the products model file. The strpos (string position) function (looks for one piece of text inside of another. The first argument it takes is the piece of text you want to search in, the second is the one you want to search for. It will return the place in the position of text it occurred.
It can search variables as well. Here we have it search for something not there as well. It looks like it didn’t return anything, but it did.
Introducing Boolean Values
Boolean values can be true or false, and can be written without quotation marks. If we use var_dump instead of echo, we can see more info about the return values. You can see now that if the text isn’t found it returns a boolean value of false.
Introducing the PHP Manual
The PHP Manual contains the documentation for everything you need to know, including functions. For example, trim. It will tell you the version it was first available in, gives a description and technical info. Arguments listed in square brackets are optional. Info on the return value is displayed in front of the function name. The word before the argument tells you the data type it accepts for that argument, like string. If it says mixed, it accepts multiple types. It will also list out the return values you get depending on what you enter.
Introducing Identity Comparison
With strpos, the value we’re searching for is referred to as a needle, and the one we’re searching in is the haystack. It takes an optional third argument that lets you offset the search so you don’t start at the beginning.
Now, under the return values section they give you a warning that if it doesn’t find it it may return a non-boolean value which evaluates to FALSE. So, you should use the === operator, as that checks for the data type as well as the value.
Type juggling means that php will convert the two values to the same type, and then see if they match. So, 5 == “5” would be true. But, so would 0 == false. So, for booleans its good to use === instead. 0 === false would be false. To check if things are not equal and not of the same type, you’d use !== (not !=== because the ! takes the place of the first = sign).
The stripos function is slightly different in that it ignores if the characters are upper or lower case, so we should use that instead.
Performing the Search
Now back to our code. We’ll put the stripos function in the conditional, and have the the thing it searches be the array element that gets loaded into it by the foreach loop, $product, with the name key set so it only searches that value. We’ll have it search the $s variable, which is the search term argument that’s passed to the main function. We’ll use the !== operator to check if it returned a value that is not boolean false. If it didn’t, we’ll add it to the list to be displayed. If no matches are found, we’ll display the message that we couldn’t find any.
Paginating a List: Controller
What currently happens:
1. Someone clicks the shirts link, and our rewrite rule redirects them to shirts/shirts.php.
2. The controller code in that file requests all the shirts from the product.php model page.
3. The view code, also on the shirt.php page, then displays all the shirts at once.
What will happen with pagination
1. When someone goes to /shirts/, the controller will have to determine which of the shirts pages is being requested (1,2,3 or 4), then which subset of shirts it needs for that page.
2. The controller requests just those shirts from the model products page.
3. The view will then display just those 8 shirts. We’ll add the pagination links above and below the shirts, and the view needs to know which page its on so we can apply the class to that element.
Getting the Count of Products
We’ll need a function that returns the total number of products in the array. To do that, we’ll define a new one. We’ll set a variable equal to the return of get_products_all, then set another variable equal to the count of that, then return that second variable.
Retrieving the GET Variable
We can use a GET variable for a page number without any rewrite rules because the items available and their order may change, and it makes it easy for people to navigate from page to page by changing the URL. If you’re on the first page though, the url should just be domain.com/shirts/.
First, we’ll set a variable $current_page equal to the get value of the variable pg. We’ll add some test code to check if this worked.
Now, if there is no pg variable, the $current_page variable will be blank, so we’ll add a conditional to check for this. If the pg GET variable is empty, we’ll set the $current_page variable to 1.
Adjusting Larger Page Numbers
What if someone enters a pg number higher than the number of pages we have? Lets redirect to the last page, but we need to know how many pages we have. To do this, we’ll ask the model how many pages we have, then calculate the number of pages from that.
We’ll get the total number by setting a variable equal to the return value of our get_products_count variable. We’ll also put the number of products per page in a variable as well, in case that changes later. We want to divide them, but what if we had an uneven result? We can use the ceil() function, which always rounds floats up.
We’ll then add a conditional which checks if the current page is larger than the current page. If it is, we’ll redirect to the last page.
Adjusting Invalid Page Numbers
What if someone puts a string in the query string, or not a number? We can use the inval() function to convert whatever what entered to become an integer. For most strings, this will set them to 0.
Let’s add another conditional for if that happens and $current_value gets set to 0.
So, if someone were to enter words now, it would just redirect them to the main shirts page.
Calculating Shirts From Page Number
Right now our shirts page is showing all the shirts due to the variable $products. We want this to instead show a subset, which takes a starting and ending number.
We’ll calculate the ending shirt by multiply the page number by the number of shirts per page. To calculate the starting shirt, will take the the current page number and subtract one from it, to get the previous page’s number, then multiply that by the number of pages to get the last shirt of the last page.. Then, we’ll take that and add one to it.
Now, the last page might not have the full 8 shirts on it, like if we had 31 products total. So, we’ll add a conditional that checks if the $ending shirt if greater than the total number of shirts, and if it is we’ll just set it equal to the total number of shirts.
Paginating a List: Model and View
Creating the Model Function
We now need to make a function that returns the subset of products for the appropriate page. We’ll go to products.php and make a new function called get_product_subset, which will take two arguments, the position start and the end. We’ll set it up similar to the other functions where we needed a subset, where we declare an empty array, make a variable of the products array, and adding a forearch loop for that. We’ll save the conditional for later, but if it’s true have it add that element to the empty $subset array.
Limiting the Products
Now, for the conditional. We’ll need a position variable that increases by one for each element in the array. Then, we’ll need to check if that falls within our desired shirt range. We want to only add elements whose position is greater than or equal to the starting position, and less than or equal to the ending one.
We need to add 1 to our position variable each time we loop. We could write $position = $position + 1;, but you can also instead write $position += 1;
Testing Model Code
We could test on our controller page, but right now there can only be 4 different pages, and 8 items per page. If we test on our models page we can try out some other scenarios. We’ll make a new file called model-test.php, include the products.php file, and var_dump the function. We’ll check for some ranges, like 1 to 2.
Now to try this on the actual shirts page. The get_list_view_html function is taking the variable $product, so at the top we’ll set that equal to our new subset function and pass in the $start and $end variables.
Introducing While Loops
Now to add the pagination links. A while loop is like a conditional except it runs multiple times, until its condition is no longer met. You first set a counter variable outside of it, usually named $i and set to 0. Inside the loop, you’ll add a line to increase $i by one for each loop run. For the condition, we’ll test if $i is less than the total number of pages. Here, we’ll echo out $i to show it increasing with each loop.
To make it easier to mingle the php and html, we can change the top curly bracket of the while loop to a colon (:), and the ending one to endwhile. This makes it easy to wrap each section in its own php tags.
Adding HTML Markup
First, we’ll wrap the echoed $i in an anchor tags. For the link, we’ll use php to set it equal to the current directory (./) plus ?pg= plus the looped page value for this loop run. This adds the links, and they work!
Now, we want to make it so that for the page we’re on, that that link isn’t a link and is instead a span. You can do something similar with if conditionals by replacing the curly braces with a colon and endif. We’ll check if $i is equal to the current page. If it is, we’ll add a span. If it’s not, we’ll add a link. Note: you can write an else block after an if block using the colon method with else :.
We also want the pagination links below our shirts, but we don’t want to copy and paste code. So, we’ll make another include file called list-navigation.html.php. The .html is a common naming practice because this is view code that exists simply to display something on the page. This is aka as a partial. We’ll copy our code into there, then include it when needed in our shirts.php page.
We should do the same thing for the get_list_view_html code.
Comments are essential in describing what bits of code do in case someone else needs to come in on the project or you need to come back and work on this at a later date. Here are some we added for the shirts.php controller code, and the products.php model page. For functions, you should describe what it does, the arguments it receives and what its return value is. The extra stars are like bullet points and are there just for formatting.
For functions, people often use a standard format called phpdoc, Arguments are listed as @param, and return values are marked as @return.