I've been learning PHP and I've created a phone book webpage as an exercise. I am satisfied with the way it works -- it seems to handle all possible input cases correctly, and the interface feels nice and solid. I would appreciate feedback on the code style, as well as suggestions for what would be a better or more idiomatic way to organize it.
<html>
<head>
<title>Phone Book</title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<link rel="stylesheet"
href="//fonts.googleapis.com/css?family=Ubuntu+Mono:300,400,600,700&lang=en" />
<style>
body {
font-family: Ubuntu Mono;
background-color: #F2F1F0;
}
.form-control {
border-radius: 0;
border: 1px solid #AEA79F;
}
.btn-default {
background-color: #EEEDEB;
}
.form-control:focus {
border: 1px solid #E95420 !important;
box-shadow: none !important;
}
.btn:focus, .btn:active {
outline: none !important;
box-shadow: none;
}
.header {
text-shadow: 0px 1px #666;
box-shadow: 0 0px 0 #F6BBA6 inset;
color: #F6F6F5;
background-color: #E95420;
font-size: 28px;
font-weight: bold;
padding: 1px;
padding-left: 20px;
border: 1px solid #555;
margin-top: 12px;
}
.c2 {
background-color: #FFF;
border: 1px solid #666;
border-top: none;
padding: 18px;
}
.msg {
border: 1px solid #AEA79F;
border-style: dashed;
padding: 8px;
margin-bottom: 18px;
}
.table td, th {
border: 1px solid #AEA79F !important;
border-style: dotted !important;
}
.spc {
padding: 18px;
}
.inv {
display: none;
}
input[type="radio"] {
margin: 5px;
margin-top: 4px;
vertical-align: middle;
}
input:invalid {
border: 1px solid red;
}
</style>
</head>
<body>
<div class="container">
<div class="header">Phone Book</div>
<div class="c2">
<form action="PhoneForm.php" method="post">
<div class="col-xs-5" style="padding: 1px">
<input type="text" class="form-control" name="name" id="name"
placeholder="Name" maxlength="36">
</div>
<div class="col-xs-5" style="padding: 1px;">
<input type="text" class="form-control" name="phone" id="phone"
placeholder="Phone Number" maxlength="17">
<div class="phonetypes" class="inv">
<input type="radio" name="phonetype" value="other" CHECKED>Other<input
type="radio" name="phonetype" value="home">Home <input
type="radio" name="phonetype" value="work">Work <input
type="radio" name="phonetype" value="cell">Cell <input
type="radio" name="phonetype" value="fax">Fax
</div>
</div>
<div class="col-xs-1" style="padding: 1px">
<select class="form-control" id="actions" name="actions">
<option value="add">Add</option>
<option value="find" selected>Find</option>
<option value="delete">Delete</option>
<option value="update">Update</option>
<option value="viewall">View All</option>;
</select>
</div>
<div class="col-xs-1" style="padding: 1px">
<input type="submit" class="btn btn-default form-control"
name="btn" value="Go">
</div>
<BR>
<div class="spc"></div>
</form>
<script>
var actions = document.getElementById('actions');
var phonetypes = document.querySelector('.phonetypes');
var phone = document.getElementById('phone');
var name = document.getElementById('name');
//var pattern_phone = phone.getAttribute("pattern");
//var pattern_name = name.getAttribute("pattern");
phonetypes.classList.add('inv');
actions.addEventListener('change', function() {
if (actions.value == 'update') {
name.placeholder = "Old Name";
phone.placeholder = "New Name";
// phone.setAttribute("pattern", pattern_name);
} else {
name.placeholder = "Name";
phone.placeholder = "Phone Number";
//phone.setAttribute("pattern", pattern_phone);
}
if (actions.value == 'add') {
phonetypes.classList.remove('inv');
} else {
phonetypes.classList.add('inv');
}
});
</script>
<?php
$host = "localhost";
$user = "user";
$pass = "pass";
$db = "db";
$mysqli = new mysqli($host, $user, $pass, $db);
if (mysqli_connect_errno()) {
die("Unable to connect.");
}
$show_all = "select Name, PhoneNumber, PhoneType, person.PersonID from person,phone where person.PersonID = phone.personID";
$listquery = $show_all;
msg_info("Displaying all entries.");
$name = trim($_POST["name"]);
$phone = trim($_POST["phone"]);
$phonetype = trim($_POST["phonetype"]);
$action = trim($_POST['actions']);
if ($btn = $_POST['btn']) {
switch ($action) {
case ('viewall'):
$listquery = $show_all;
msg_info("Displaying all entries.");
break;
/* --------------------------------------------------------------- */
case ('add'):
if (! ($name or $phone)) {
msg_warn("Enter a name and/or number to add.");
break;
}
if (! ($name)) {
msg_warn("Enter a name to be associated with this number.");
break;
}
// is person already there?
$query = "select PersonID from person where Name like ('" . $name .
"')";
if ($id = $mysqli->query($query)->fetch_object()->PersonID) {
// if person is there and we have an input phone number, assign
// the number to him.
if ($phone) {
// check to see if he already has this number
if ($result = $mysqli->query(
"select * from phone where PhoneNumber like '" .
$phone . "' and PersonID = " . $id . "")->fetch_object()) {
msg_warn(
"Person '" . $name . "' already has number '" .
$phone . "'.");
break;
}
// add new phone
$q1 = "insert into phone (PersonID, PhoneNumber, PhoneType) values (" .
$id . ", '" . $phone . "', '" . $phonetype . "')";
// get rid of dummy phone if there is one
$q2 = "delete from phone where PersonID = " . $id .
" and PhoneNumber = 'none'";
// queries okay?
if ($mysqli->query($q1) && $mysqli->query($q2)) {
msg_success(
"Added " . $phonetype . " number '" . $phone .
"' for person '" . $name . "'.");
} else {
msg_error(
"Error adding number '" . $phone .
"' for person '" . $name . "': " . $mysqli->error);
}
break;
} // otherwise, do nothing
else {
msg_warn("'" . $name . "' is already in the phone book.");
break;
}
}
// if the person is not there, add him
// and create dummy phone if none specified
$newphone = $phone ? $phone : "none";
$q1 = "insert into person (Name) values ('" . $name . "')";
$q2 = "set @id = (select PersonID from person where Name like ('" .
$name . "'))";
$q3 = "insert into phone (PersonID, PhoneNumber, PhoneType) values (@id, '" .
$newphone . "', '" . $phonetype . "')";
// queries okay?
if (! ($mysqli->query($q1) and $mysqli->query($q2) and
$mysqli->query($q3))) {
msg_error(
"Error adding '" . $name . "' to the phone book: " .
$mysqli->error);
break;
}
// good; we are done
msg_success(
"Added '" . $name . "' to the phone book" . ($newphone ==
'none' ? "." : " with " . $phonetype . " number " . $newphone .
"."));
break;
/* --------------------------------------------------------------- */
case ('find'):
// does exactly one of the fields have input?
if (! ($name xor $phone)) {
msg_warn("Enter either a name or a number to find.");
break;
}
// pick query based on which field has input
if ($name) {
$info = $name;
// includes wild cards
$q1 = "select Name, PhoneNumber, PhoneType from phone, person where phone.PersonID = person.PersonID and Name like '%" .
$name . "%'";
}
if ($phone) {
$info = $phone;
$q1 = "select Name, PhoneNumber, PhoneType from phone, person where phone.PersonID = person.PersonID and PhoneNumber like '" .
$phone . "'";
}
// query okay?
if (! ($result = $mysqli->query($q1))) {
$msg = "Error searching for '" . $info . "': " . $mysqli->error;
$msg_color = $col_error;
break;
}
// good; we are done
if (($nr = $result->num_rows) > 0) {
msg_info(
"Displaying " . $nr . " result" . ($nr > 1 ? "s" : "") .
" for '" . $info . "'.");
} else {
msg_info("No results for '" . $info . "'.");
}
$listquery = $q1;
break;
/* --------------------------------------------------------------- */
case ('delete'):
if (! ($name || $phone)) {
msg_warn("Enter a name and/or a number to delete.");
break;
}
if ($phone && $name) {
// if both a phone and a person are entered, delete the phone
// for that person only
$q1 = "set @id = (select PersonID from person where Name like ('" .
$name . "'))";
$q2 = "delete from phone where PhoneNumber like '" . $phone .
"' and PersonID = @id";
if (! ($mysqli->query($q1) && $mysqli->query($q2))) {
msg_error(
"Error deleting number '" . $phone . " for person '" .
$name . "': " . $mysqli->error);
break;
}
if ($mysqli->affected_rows > 0) {
msg_success(
"Removed number '" . $phone . "' for person '" .
$name . "'.");
} else {
msg_warn(
"No name '" . $name . "' associated with number '" .
$phone . "'.");
}
break;
} else {
if ($name) {
$info = $name;
$q1 = "delete from person where Name like '" . $name . "'";
}
// if just a phone is entered, delete all instances of it
if ($phone) {
$info = $phone;
// unfortunately we have to make sure everyone affected by
// the phone delete has at least a dummy phone number
// otherwise they won't show up.
// working around a poor design choice
$q1 = "select phone.PersonID from phone, person where phone.PersonID = person.PersonID and PhoneNumber like '" .
$phone . "'";
if ($result = $mysqli->query($q1)) {
while ($person_with_this_number = $result->fetch_array()) {
$id = $person_with_this_number[0];
$cnt = $mysqli->query(
"select count(*) as c from phone where PersonID = " .
$id)->fetch_object()->c;
// is this someone's only number?
if ($cnt == '1') {
// if so, then replace it with a dummy
if (! ($mysqli->query(
"insert into phone values (" . $id .
", 'none', 'none')"))) {
msg_error($mysqli->error);
break;
}
}
}
}
$q1 = "delete from phone where PhoneNumber like '" . $phone .
"'";
}
if (! ($mysqli->query($q1))) {
msg_error(
"Error deleting " .
($info == $name ? "person" : "phone") . " '" . $info .
"': " . $mysqli->error);
break;
}
}
if ($mysqli->affected_rows > 0) {
msg_success("Removed '" . $info . "' from the phone book.");
} else {
msg_warn("'" . $info . "' is not in the phone book.");
}
break;
/* --------------------------------------------------------------- */
case ('update'):
// there is some javascript that changes the form placeholders
// if 'update' is selected
$oldname = $name;
$newname = $phone;
// is a name entered in both fields?
if (! ($oldname && $newname)) {
msg_warn("Enter a name and a new name to replace it with.");
break;
}
// is the person in the phone book?
$q1 = "select * from person where name like '" . $oldname . "'";
if (! ($mysqli->query($q1)->num_rows > 0)) {
msg_warn("'" . $oldname . "' is not in the phone book.");
break;
}
// is the new name different from the old one?
if ($oldname == $newname) {
msg_info(
"New name is the same as old name; no information was changed.");
break;
}
//
if ($mysqli->query(
"update person set Name = '" . $newname . "' where name = '" .
$oldname . "'")) {
msg_success(
"Changed name '" . $oldname . "' to '" . $newname . "'.");
} else {
msg_error("Error updating name: " . $mysqli->error);
}
break;
}
}
display($listquery);
/* --------------------------------------------------------------- */
function msg_error ($msg)
{
$GLOBALS['msg'] = $msg;
$GLOBALS['msg_color'] = "#ffe3db";
}
function msg_success ($msg)
{
$GLOBALS['msg'] = $msg;
$GLOBALS['msg_color'] = "#deffdb";
}
function msg_info ($msg)
{
$GLOBALS['msg'] = $msg;
$GLOBALS['msg_color'] = "#f7fffe";
}
function msg_warn ($msg)
{
$GLOBALS['msg'] = $msg;
$GLOBALS['msg_color'] = "#fff9d4";
}
function display ($query)
{
echo '<div class="msg" style="background-color: ' . $GLOBALS['msg_color'] . ';">
' . $GLOBALS['msg'] . '
</div>';
if ($result = $GLOBALS['mysqli']->query($query)) {
if ($result->num_rows > 0) {
echo '<table class="table table-bordered table-striped">
<tr>
<th> Name </th>
<th> Number </th>
<th> Type </th>';
$last = "";
$name = "";
$phone = "";
$type = "";
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
echo '<tr>';
if ($row["Name"] != $last) {
$last = $row["Name"];
$name = $last;
} else {
$name = "";
}
if ($row["PhoneNumber"] != "none") {
$phone = $row["PhoneNumber"];
$type = $row["PhoneType"];
} else {
$phone = "(No number)";
$type = "";
}
echo "<td> $name </td>
<td> $phone </td>
<td> $type </td>
</tr>";
}
echo "</table>";
}
$result->close();
} else {
echo "Error in query: " . $GLOBALS['mysqli']->error;
}
}
/* --------------------------------------------------------------- */
?>
</div>
</div>
</body>
</html>