Undefined variable in included file

Hello,

I’m trying to create a web page that supplies a form for the user to fill out, then queries those values against a MySQL database. I started by simply building a page that displayed the results of a hard coded SQL query that I’d written. This all worked fine until I added the option of user input.

Here’s the HTML for the form:

<h1>Please enter a search term:</h1>
<form action="query.php" method="post">
    <div><p>First Name:
        <input type="text" name="firstname"></p>
    </div>
    <div><p>Last Name:
        <input type="text" name="lastname"></p>
    </div>
    <div><input type="submit" value="Search"></div>
</form>

Here’s the query.php file:

<?php
if(isset($_POST['submit'])){
$fname=$_POST['firstname'];
$lname=$_POST['lastname'];
try
{
        $pdo = new PDO('mysql:host=localhost;dbname=employees', 'phpuser', 'p@$$w0rd');
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->exec('SET NAMES "utf8"');
}
catch (PDOException $e)
{
        $error = 'Unable to connect to the database server.';
        include 'error_pages/error.html.php';
        exit();
}

try
{
        $sql = "SELECT first_name, last_name FROM employees WHERE first_name like '%" . $fname . "%' and last_name like '%" . $lname . "%'";
        $result = $pdo->query($sql);
}
catch (PDOException $e)
{
        $error = 'Error fetching employee names: ' . $e->getMessage();
        include 'error_pages/error.html.php';
        exit();
}

while ($row = $result->fetch())
{
        $fname[] = $row['first_name'];
        $lname[] = $row['last_name'];
}}
include 'result.html.php';
?>

Here’s the code in the result.html.php file to display the results:

<h1>Here are all the employees in the database that match your selection criteria: <br></h1>
<p><?php echo ($fname)?><?php echo ($lname)?></p>
</body>
</html>

This results in an ‘Undefined variable’ error for the $fname and $lname values.

I won’t lie that I’m not a PHP expert and this is a project I’ve created for myself to learn. I’ve researched it and searched the error on these forums, but please forgive me, it’s not clicking in.

If someone can assist me with what I’m doing wrong here it would be much appreciated.

<input type="submit" value="Search">

Don’t you need to put a name value on this form field, if you’re going to check it exists using this?

if(isset($_POST['submit'])){

Your results page is included whether or not that form field exists, but if it does not, then you don’t create $fname and $lname because they’re inside your main if() clause. Hence when that field does not exist (because you didn’t name it ‘submit’) you still try to display the variables and get the error.

So either change the line to

<input type="submit" value="Search" name="submit">

or use the alternative means of checking for form submission, which I forget now.

A few points.

Here you are creating two arrays, so to echo those out you would need a foreach loop.

Why are the variables in brackets?

Don’t ever put un-sanitised user input directly into an sql query like that, that’s saying “Please Hack Me Now!”.

Validate and sanitise all input, use prepared statements, stay safe.

1 Like
if($_SERVER['REQUEST_METHOD'] == 'POST'){ ... }
2 Likes

Some peeps treat echo as though it were a function - same with include.

It is in a way if you’re talking about the literal source code to PHP, but it should never be treated as one.

Thank you all for your input. I added ‘name=“submit”’ into the HTML form, which resulted in this error:
‘Fatal error: operator not supported for strings’

<h1>Please enter a search term:</h1>
<form action="query.php" method="post">
    <div><p>First Name:
        <input type="text" name="firstname"></p>
    </div>
    <div><p>Last Name:
        <input type="text" name="lastname"></p>
    </div>
    <div><input type="submit" value="Search" name="submit"></div>
</form>

Researching that indicated that I needed to define those variables specifically as arrays. You can see how I did so at the bottom of the query.php and result.html.php files below.

Is this a good practice? Seemed kind of klugy to me…

<?php
if(isset($_POST['submit'])){
$fname=$_POST['firstname'];
$lname=$_POST['lastname'];
try
{
        $pdo = new PDO('mysql:host=localhost;dbname=employees', 'phpuser', 'p@$$w0rd');
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->exec('SET NAMES "utf8"');
}
catch (PDOException $e)
{
        $error = 'Unable to connect to the database server.';
        include 'error_pages/error.html.php';
        exit();
}

try
{
        $sql = "SELECT first_name, last_name FROM employees WHERE first_name like '%" . $fname . "%' and last_name like '%" . $lname . "%'";
        $result = $pdo->query($sql);
}
catch (PDOException $e)
{
        $error = 'Error fetching employee names: ' . $e->getMessage();
        include 'error_pages/error.html.php';
        exit();
}

while ($row = $result->fetch())
{
        $fname = array();
        $lname = array();
        $fname[] = $row['first_name'];
        $lname[] = $row['last_name'];
}}
include 'result.html.php';
?>
<h1>Here are all the employees in the database that match your selection criteria: <br></h1>
<p><?php
        $fnames=array();
                foreach ($fname as $fnames) {
                        echo "$fnames";
}
        $lnames=array();
                foreach ($lname as $lnames) {
                        echo "$lnames";
}?></p>

This works, but I don’t think the results are correct. If I query a specific name, it returns it, but if I try a specific first name and just an ‘F’ in the last name field it only displays the last row returned. As far as I know, the SQL query’s correct using ‘like’ operators and wildcards so I think I’ve done foreach loop incorrectly.

Any ideas?

Thank you, SamA74!

I’m researching “validate and sanitize” now.[

Also look at Prepared Statements.
You may want to read about SQL Injection too, as this is the type of hack we are guarding against, which your current code vulnerable to.

1 Like

Well, don’t forget that you use those names twice in the code. First off, right at the start you define them as strings:

if(isset($_POST['submit'])){
$fname=$_POST['firstname'];
$lname=$_POST['lastname'];

and then a bit later on, you start using the same names as arrays:

while ($row = $result->fetch())
{
        $fname[] = $row['first_name'];
        $lname[] = $row['last_name'];

I suspect the biggest problem with the new code:

while ($row = $result->fetch())
{
        $fname = array();
        $lname = array();
        $fname[] = $row['first_name'];
        $lname[] = $row['last_name'];
}}

might be that the array gets cleared out every time you fetch a new result. When you call array() it creates a new, empty array.

My thought would be either (a) use different variable names, or (b) redefine them as arrays before you open the while loop, not inside it. Actually my thought would be (a), use different variable names.

2 Likes

Can you elaborate a bit on this? You mention using different variable names as a possible solution, but show code from two different files so I’m not sure where exactly I should use different names.

Also, I know there’s a belief that no one truly learns when someone just does the job for them, but I tend to learn very well by example so if you’re so inclined, an example of something you would try in this case would be much appreciated. It’s like learning a new language and I only know a few verbs so I need all the help I can get. :slight_smile:

Thank you, though. I really appreciate the help.

Love that comic!!

Oh, I didn’t realise these were from two different files. It’s not at all clear in your post #8 that the PHP in the second code segment isn’t all one big file. I notice that you separately include “result.html.php”, but that doesn’t count as a separate file as it’s merged into the main file at run-time and any variable names would continue from one to the other - which you rely upon in order to display the results. But both the code snippets I quoted are from the second code segment in your post, which I thought was all one file. If it is not, you should show clearly where the break is, and how you share the variables from the first file to the second.

Looking at the code, I see this (in the added comments)

<?php
if(isset($_POST['submit'])){
$fname=$_POST['firstname'];  // ** here, you create $fname as a plain string
$lname=$_POST['lastname'];  // ** and here you create $lname as a plain string
try
{
        $pdo = new PDO('mysql:host=localhost;dbname=employees', 'phpuser', 'p@$$w0rd');
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->exec('SET NAMES "utf8"');
}
catch (PDOException $e)
{
        $error = 'Unable to connect to the database server.';
        include 'error_pages/error.html.php';
        exit();
}
try
{
       // below, you use those plain strings in your query
        $sql = "SELECT first_name, last_name FROM employees WHERE first_name like '%" . $fname . "%' and last_name like '%" . $lname . "%'"; 
        $result = $pdo->query($sql);
}
catch (PDOException $e)
{
        $error = 'Error fetching employee names: ' . $e->getMessage();
        include 'error_pages/error.html.php';
        exit();
}
while ($row = $result->fetch())
{
  // here, you create two blank arrays, using the same names you used above
        $fname = array();
        $lname = array();
        $fname[] = $row['first_name']; // and here, you assign values to those blank arrays.
        $lname[] = $row['last_name'];
}}
include 'result.html.php';
?>

What I believe you want to do is this:

$fnames = array();
$lnames = array();
while ($row = $result->fetch())
{
        $fnames[] = $row['first_name'];
        $lnames[] = $row['last_name'];
}}

I’ve moved the array creation to before the loop, so it only gets created once, before the loop starts. Every time you run through the loop, you then add an element to each array for each result that comes from your query. I’ve changed the variable names as well, to avoid confusion. I’ve just added an “s” on the end of the name, because it makes sense to me that as there will be more than one value, the name reflects that.

To clarify, the only problem in this particular section was that you define the array inside the loop. This means that every time you retrieve the next result, you define the array (which also empties the array) and then add an element to it. So of course every time you retrieve a result, you throw away the previous ones. Moving the array definition to before the loop fixes this.

Moving on to the separately included code:

<h1>Here are all the employees in the database that match your selection criteria: <br></h1>
<p><?php
        $fnames=array(); // what does this achieve?
                foreach ($fname as $fnames) {
                        echo "$fnames";
}
        $lnames=array();  // or this?
                foreach ($lname as $lnames) {
                        echo "$lnames";
}?></p>

There’s no point doing those array definitions at this point. (Actually if you use the code change I made above, it will cause you a big problem because they match the names I used.) But you’re achieving nothing by creating an array at that point. The very next line in each case is a foreach loop that iterates through your results array and uses that same array that you just created, but as a plain string, which is only fine because the foreach will overwrite the array you created. So there’s no harm, but there’s also no point creating the array.

If the aim of the code is to run a query, get all the results into an array and then display them, I’d probably do something like this instead:

<?php
if(isset($_POST['submit'])){
$fname=$_POST['firstname'];  
$lname=$_POST['lastname'];  
try
{
  $pdo = new PDO('mysql:host=localhost;dbname=employees', 'phpuser', 'p@$$w0rd');
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $pdo->exec('SET NAMES "utf8"');
}
catch (PDOException $e)
{
  $error = 'Unable to connect to the database server.';
  include 'error_pages/error.html.php';
  exit();
}
try
{
  // below, you use those plain strings in your query
  $sql = "SELECT first_name, last_name FROM employees WHERE first_name like :firstname and last_name like :lastname"; 
  $prep = $pdo->prepare($sql); // use a prepared statement here
  $prep->bindParam(":firstname", "%" . $fname . "%"); // first name
  $prep->bindParam(":lastname", "%" . $lname . "%"); // last name
  $result = $prep-execute();
}
catch (PDOException $e)
{
  $error = 'Error fetching employee names: ' . $e->getMessage();
  include 'error_pages/error.html.php';
  exit();
}

// Get the results
if ($result) { 
  $results = $prep->fetchAll();
  }
else { 
  echo "There was a problem executing the query";
  exit();
  } // obviously you'll do something better here.

}
include 'result.html.php';
?>

and in your display

<h1>Here are all the employees in the database that match your selection criteria: <br></h1>
<p><?php
foreach ($results as $employee) { 
  echo $employee['first_name'] . " " . $employee['last_name'] . "\n";
  }
?>
</p>

There are things to watch out for. If your result set is going to be massive, using fetchAll() might be a problem, you might need to loop through the results (as you were originally doing) but instead of storing them in an array, display each one as it is found. That might prove to be an easier approach anyway, but as all your loop does is retrieve every result and stick it in an array, you might as well use the built in fetchAll() to do it for you.

Also - I don’t whether all those try-catch blocks are the best way to deal with any errors. I know there’s been a thread recently about PDO error trapping. I’ve left them in so as to not change too much. But basically I’ve changed:

  • Use a prepared statement. This will help with the sanitising of user input
  • Use fetchAll() to get all the results into a single array

You also might want to consider folding things to upper case for the search, or lower case, for consistency. If the database stores “Hugh Cornwell” you don’t want to be throwing “not found” errors (or in this case just displaying an empty list) because your user typed in “UG ORN”. You should also look at how many results come back before displaying them - use rowCount() to decide whether you should say “none found” or your current display.

droopsnoot, THANK YOU!! This is great!

I’m going to pore over this and do my best to LEARN from it.

Really, I can’t thank you enough!

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.