Jump to content

newbie question regarding inline form validation


dudleylearning
Go to solution Solved by Psycho,

Recommended Posts

Hi All,
 
a bit new to PHP. I've been reading Kevin Yank's book PHP Novice to Ninja and got way through part of the book so far but have come to a little stumbling block. I have two files, the first is a index,php which acts as the controller file and the other is form.php which has the form to add data. What I am trying to do is add an inline error validation for missing fields within form.php when the user submits the form. 
 
Index.php:

<?php
include '../includes/dbconn.php';

# add joke link pressed
if (isset($_GET['add_joke']))
{
	$textError = '';
	
	// Build the list of authors for drop-down list
	try
	{
		$result = $dbConnection->query('SELECT id, name FROM author');
	}
	catch (PDOException $e)
	{
		$error = 'Error fetching list of authors.' . '<br />' . $e -> getMessage();
		include '../includes/error.php';
		exit();
	}

	foreach ($result as $row)
	{
		$authors_in_db[] = array(
		'id' => $row['id'], 
		'name' => $row['name']
		);
	}
	include 'form.php';
	exit();
}

# add joke to the database
if (isset($_GET['add_joke_to_db']))
{	
	# if author/joke is blank
	if ($_POST['joke_text'] == '' || $_POST['author'] == '')
	{
		$textError = 'You must add text to this field.';
	}

	# continue with adding joke to the database
	try
	{
		$sql = 'INSERT INTO joke SET
		joke_text = :joke_text,
		joke_date = CURDATE(),
		author_id = :author_id';
		
		$s = $dbConnection -> prepare($sql);
		$s -> bindValue(':joke_text', $_POST['joke_text']);
		$s -> bindValue(':author_id', $_POST['author']);
		$s -> execute();
	}
	catch (PDOException $e)
	{
		$error = 'Error adding joke.' . '<br />' . $e -> getMessage();
		include '../includes/error.php';
		exit();
	}
	
header('Location: .');
exit();
}

# delete joke from the database
if (isset($_GET['delete_joke']))
{
	try
	{
		$sql = 'DELETE FROM joke WHERE id = :id';
		$s = $dbConnection -> prepare($sql);
		$s -> bindValue(':id', $_POST['id']);
		$s -> execute();
	}
	catch (PDOException $e)
	{
		$error = 'Error deleting joke.' . '<br />' . $e -> getMessage();
		include '../includes/error.php';
		exit();
	}

header ('Location: .');
exit();
}

# select all jokes from the database
try
{
	$sql = 'SELECT joke.id, joke.joke_text, joke.joke_date, author.name, author.email 
	FROM joke INNER JOIN author
	ON author_id = author.id';
	$result = $dbConnection -> query($sql);
}
catch (PDOException $e)
{
	$error = 'Error fetching jokes.' . '<br />' . $e -> getMessage();
	include '../includes/error.php';
	exit();
}

# add each data item within an array
foreach ($result as $row)
{
	$jokes_in_db[] = array(
	'joke.id' => $row['id'], 
	'joke.joke_text' => $row['joke_text'], 
	'joke.joke_date' => $row['joke_date'],
	'author.name' => $row['name'],
	'author.email' => $row['email']
	);
}

include 'jokes.php';
?>

 
form.php:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Add Joke</title>
		<link rel="stylesheet" type="text/css" href="../includes/styles.css" />
	</head>
	<body>
		<h1>Add Joke</h1>
		<form action="?add_joke_to_db" method="post">
			<div>
				<label for="joke_text">Type your joke here:</label>
				<textarea id="joke_text" name="joke_text" rows="3"></textarea>
				<span class="error">* <?php echo $textError;?></span>
			</div>
			<div>
				<label for="author">Author:</label>
				<select name="author" id="author">
					<option value="">Select one</option>
					<?php foreach ($authors_in_db as $data): ?>
					<option value="<?php echo htmlspecialchars($data['id'], ENT_QUOTES, 'UTF-8'); ?>">
						<?php echo htmlspecialchars($data['name'], ENT_QUOTES, 'UTF-8'); ?>
					</option>
					<?php endforeach; ?>
				</select>
				<span class="error">* <?php echo $textError;?></span>
			</div>
			<div>
            	<input type="submit" value="Add">
            </div>
		</form>
	</body>
</html>

database:

-- phpMyAdmin SQL Dump
-- version 4.6.3
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1:3306
-- Generation Time: Nov 30, 2016 at 01:45 PM
-- Server version: 5.6.31
-- PHP Version: 7.0.8

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `jokesdb`
--

-- --------------------------------------------------------

--
-- Table structure for table `author`
--

CREATE TABLE `author` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Dumping data for table `author`
--

INSERT INTO `author` (`id`, `name`, `email`) VALUES
(1, 'Author1', 'Author1@email.com'),
(2, 'Author2', 'Author2@email.com'),
(3, 'Author3', 'Author3@email.com'),
(6, 'Author4', 'Author4@email.com');

-- --------------------------------------------------------

--
-- Table structure for table `joke`
--

CREATE TABLE `joke` (
  `id` int(11) NOT NULL,
  `joke_text` text,
  `joke_date` date NOT NULL,
  `author_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Dumping data for table `joke`
--

INSERT INTO `joke` (`id`, `joke_text`, `joke_date`, `author_id`) VALUES
(1, 'Why did the chicken cross the road? To get to the other side!', '2016-07-11', 1),
(2, 'Knock-knock! Who\'s there? Boo! "Boo" who? Don\'t cry; it\'s only a joke!', '2016-06-30', 1),
(3, 'Knock, knock. Who’s there? Canoe! Canoe who? Canoe come out and play with me today?', '2016-08-05', 1),
(4, 'Knock, knock. Who’s there? Lettuce. Lettuce who? Lettuce in, it’s cold out here.', '2016-08-05', 2);

--
-- Indexes for dumped tables
--

--
-- Indexes for table `author`
--
ALTER TABLE `author`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `joke`
--
ALTER TABLE `joke`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `author`
--
ALTER TABLE `author`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
--
-- AUTO_INCREMENT for table `joke`
--
ALTER TABLE `joke`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=46;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Could anyone be of assistance?

 

Thanks

Edited by dudleylearning
Link to comment
Share on other sites

Lot's of ways to solve this. With the current configuration you have here is one:

 

Instead of saving errors to the string variable $texterror, save the validation errors for each field to an array such as $formErrors using the field name as the index. E.g.

   $formErrors = array();
    if ($_POST['joke_text'] == '')
    {
        $formErrors['joke_text'] = '* Joke text field cannot be empty.';
    }
    if ($_POST['joke_text'] == '')
    {
        $formErrors['author'] = '* Author field cannot be empty.';
    }

Then, after all validations are completed, check to see if $formErrors is empty. If no, continue with saving the data. If yes, include the form script.

 

Lastly, on the form page, add logic on each field to check for any validation errors and show any errors in-line with the fields

                <label for="joke_text">Type your joke here:</label>
                <textarea id="joke_text" name="joke_text" rows="3"></textarea>
                <span class="error"><?php echo $formErrors['joke_text'];?></span>

Note: since you don't want to show the asterisk if there is no error, include it as part of the error text or add logic to dynamically add it if there is an error for a particular field. The former is much easier to implement.

 

Also, be sure to trim() inputs before checking if they are empty. Else, someone can enter only a space (or other non-printable characters) and the value will pass validation.

Edited by Psycho
Link to comment
Share on other sites

Thank you for replying Physco. I've tried what you have mentioned, to the best of my ability. This is what I have done to the index.php file:

<?php
include '../includes/dbconn.php';

# add joke link pressed
if (isset($_GET['add_joke']))
{	
$formErrors = '';
	// Build the list of authors for drop-down list
	try
	{
		$result = $dbConnection->query('SELECT id, name FROM author');
	}
	catch (PDOException $e)
	{
		$error = 'Error fetching list of authors.' . '<br />' . $e -> getMessage();
		include '../includes/error.php';
		exit();
	}

	foreach ($result as $row)
	{
		$authors_in_db[] = array(
		'id' => $row['id'], 
		'name' => $row['name']
		);
	}
	include 'form.php';
	exit();
}

# add joke to the database
if (isset($_GET['add_joke_to_db']))
{	
	$formErrors = array();
    if ($_POST['joke_text'] == '')
    {
        $formErrors['joke_text'] = '* Joke text field cannot be empty.';
    }
    if ($_POST['joke_text'] == '')
    {
        $formErrors['author'] = '* Author field cannot be empty.';
    }
	
	if (!empty($formErrors)) 
	{
    include 'form.php';
exit();
	} 
	else 
	{
		# continue with adding joke to the database
		try
		{
			$sql = 'INSERT INTO joke SET
			joke_text = :joke_text,
			joke_date = CURDATE(),
			author_id = :author_id';
			
			$s = $dbConnection -> prepare($sql);
			$s -> bindValue(':joke_text', $_POST['joke_text']);
			$s -> bindValue(':author_id', $_POST['author']);
			$s -> execute();
		}
		catch (PDOException $e)
		{
			$error = 'Error adding joke.' . '<br />' . $e -> getMessage();
			include '../includes/error.php';
			exit();
		}
	
		header('Location: .');
		exit();
	}
}

# delete joke from the database
if (isset($_GET['delete_joke']))
{
	try
	{
		$sql = 'DELETE FROM joke WHERE id = :id';
		$s = $dbConnection -> prepare($sql);
		$s -> bindValue(':id', $_POST['id']);
		$s -> execute();
	}
	catch (PDOException $e)
	{
		$error = 'Error deleting joke.' . '<br />' . $e -> getMessage();
		include '../includes/error.php';
		exit();
	}

header ('Location: .');
exit();
}

# select all jokes from the database
try
{
	$sql = 'SELECT joke.id, joke.joke_text, joke.joke_date, author.name, author.email 
	FROM joke INNER JOIN author
	ON author_id = author.id
	ORDER BY joke.id';
	$result = $dbConnection -> query($sql);
}
catch (PDOException $e)
{
	$error = 'Error fetching jokes.' . '<br />' . $e -> getMessage();
	include '../includes/error.php';
	exit();
}

# add each data item within an array
foreach ($result as $row)
{
	$jokes_in_db[] = array(
	'joke.id' => $row['id'], 
	'joke.joke_text' => $row['joke_text'], 
	'joke.joke_date' => $row['joke_date'],
	'author.name' => $row['name'],
	'author.email' => $row['email']
	);
}

include 'jokes.php';
?>

and this to the form.php page:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Add Joke</title>
		<link rel="stylesheet" type="text/css" href="../includes/styles.css" />
	</head>
	<body>
		<h1>Add Joke</h1>
		<form action="?add_joke_to_db" method="post">
			<div>
				<label for="joke_text">Type your joke here:</label>
				<textarea id="joke_text" name="joke_text" rows="3"></textarea>
				 <span class="error"><?php echo $formErrors['joke_text'];?></span>
			</div>
			<div>
				<label for="author">Author:</label>
				<select name="author" id="author">
					<option value="">Select one</option>
					<?php foreach ($authors_in_db as $data): ?>
					<option value="<?php echo htmlspecialchars($data['id'], ENT_QUOTES, 'UTF-8'); ?>">
						<?php echo htmlspecialchars($data['name'], ENT_QUOTES, 'UTF-8'); ?>
					</option>
					<?php endforeach; ?>
				</select>
				<span class="error"><?php echo $formErrors['author'];?></span>
			</div>
			<div>
            	<input type="submit" value="Add">
            </div>
		</form>
	</body>
</html>

I get the errors shown inline now which is great.

 

I've seen that I get an additional issue where the author drop-down list is empty when form.php is shown. If I add this before I include form.php:

try
	{
		$result = $dbConnection->query('SELECT id, name FROM author');
	}
	catch (PDOException $e)
	{
		$error = 'Error fetching list of authors.' . '<br />' . $e -> getMessage();
		include '../includes/error.php';
		exit();
	}

	foreach ($result as $row)
	{
		$authors_in_db[] = array(
		'id' => $row['id'], 
		'name' => $row['name']
		);
	}

it works as expected. But, seeing as it is a repeat of what I have under 

if (isset($_GET['add_joke']))

how would I go about reusing the command without copying it out again?

Edited by dudleylearning
Link to comment
Share on other sites

  • Solution

You should create functions (or classes) rather than putting everything in-line.

 

 

function showForm($dbConnection)
{
    //Get list of authors
    try
    {
        $result = $dbConnection->query('SELECT id, name FROM author');
    }
    catch (PDOException $e)
    {
        $error = 'Error fetching list of authors.' . '<br />' . $e -> getMessage();
        include '../includes/error.php';
        exit();
    }
    //Put values into array
    foreach ($result as $row)
    {
        $authors_in_db[] = $row;
    }
    //Call the form script
    include 'form.php';
    exit();
}

 

Now, wherever you need to include the form call the function above.

Link to comment
Share on other sites

You can make the code a lot shorter and safer:

  • Remove all this try-catch clutter. It's useless, repetitive and potentially dangerous, because printing internal error messages on the screen gives an attacker lots of information about your system. Instead, cover all errors (not just exceptions) with a central error handler. PHP actually has a pretty good system built in: If you turn display_errors off (which is what you should do in production), fatal errors lead to a blank page and a 500 response code. Your webserver should then be able to show a custom error page. See The mystery of errors and exceptions.
  • Take advantage of standard PDO fetch methods instead of reinventing the wheel. For example, if you need all rows, just call PDO::fetchAll().
  • Consider using a template engine like Twig to get a clean interface between the PHP code and the HTML markup.

So this:

try
{
    $result = $dbConnection->query('SELECT id, name FROM author');
}
catch (PDOException $e)
{
    $error = 'Error fetching list of authors.' . '<br />' . $e -> getMessage();
    include '../includes/error.php';
    exit();
}

foreach ($result as $row)
{
    $authors_in_db[] = array(
        'id' => $row['id'],
        'name' => $row['name']
    );
}
include 'form.php';
exit();

should be replaced with this:

$authors = $dbConnection->query('SELECT id, name FROM author')->fetchAll();

// now pass $authors to the template; or use your old form.php script

See the difference?

Link to comment
Share on other sites

  • 2 weeks later...

@Physco, so I tried the function suggested above. Can't see what have I done to stop the inline errors from showing. When its written inline it works as expected, when I add it to function it stops the form from processing. there are no errors showing in the logs either:

index.zip

Edited by dudleylearning
Link to comment
Share on other sites

The function I provided was only for the purpose of outputting the form since there may be different branches of logic needing to output the form.  There was other functionality I provided for the logic of the in-line errors. As it states in my signature, I don't always test the code I provide - it is provided as a guideline for the recipient to write their final code and properly test.

 

So, I'm not going to test the code. But, the only thing that jumps out at me as a possibility is variable scope. Since the form.php script is included in the function, perhaps the execution of that script has the same scope as the function. If, that's the case you could resolve it by passing the $formErrors array to the function

 

 

showForm($dbConnection, $formErrors)

 

Be sure to pass the variable when calling the function and adding the parameter where you define the function.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.