dudleylearning Posted November 30, 2016 Share Posted November 30, 2016 (edited) 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 November 30, 2016 by dudleylearning Quote Link to comment Share on other sites More sharing options...
dudleylearning Posted November 30, 2016 Author Share Posted November 30, 2016 attached are the php files. add_author_to_jokes.zip Quote Link to comment Share on other sites More sharing options...
Psycho Posted November 30, 2016 Share Posted November 30, 2016 (edited) 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 November 30, 2016 by Psycho Quote Link to comment Share on other sites More sharing options...
dudleylearning Posted November 30, 2016 Author Share Posted November 30, 2016 (edited) 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 November 30, 2016 by dudleylearning Quote Link to comment Share on other sites More sharing options...
Solution Psycho Posted November 30, 2016 Solution Share Posted November 30, 2016 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. Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted November 30, 2016 Share Posted November 30, 2016 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? Quote Link to comment Share on other sites More sharing options...
dudleylearning Posted December 2, 2016 Author Share Posted December 2, 2016 Many thanks for the responses OrpheanBeholderScryDoubt and Psycho Quote Link to comment Share on other sites More sharing options...
dudleylearning Posted December 12, 2016 Author Share Posted December 12, 2016 (edited) @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 December 12, 2016 by dudleylearning Quote Link to comment Share on other sites More sharing options...
Psycho Posted December 12, 2016 Share Posted December 12, 2016 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. Quote Link to comment Share on other sites More sharing options...
dudleylearning Posted December 12, 2016 Author Share Posted December 12, 2016 yea that was it, the parameter needed to be passed. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.