Code examples from class
Web Design Considerations
- Make a page scannable.
- indicators, visual cues, hints (links, clickable, buttons, etc)
- not overbearing with color and content
- forms that are easy to use
- users like conventions, don’t stray too far
- make the back-button work properly
- no horizontal scrolling unless it’s a large table containing tabular data or an image
- Omit needless words
Web Development Best Practices & Tips
Writing Code:
- Make incremental changes.
- Develop locally, not on Heroku (production).
- Commit often, use a descriptive commit message.
- Refactor often. Don't leave broken windows.
- Start with user experience. This is why we designed the interface first.
- Keep HTML, JavaScript, PHP, and CSS completely separate. The only exception is PHP in your HTML view code.
Performance:
- The slowest part of loading a page is typically initiating socket and downloading a resource, like an image or a JS file.
- Put all JavaScript in one file and minified at download time.
- Place JavaScript script tags files at the bottom, and wrap the actual code in an onLoad event so they load after the page has started rendering.
- Image sprites for toggles or icons.
- Don’t resize images in HTML or CSS. If the image is the wrong size, then change the actual image in software like Photoshop.
Security:
- Don’t use $_POST or $_GET directly. Instead use another data struct like $sanitized for trusted data.
- Anything that is dynamically displayed to the screen needs to be considered for sanitization. Not just input from forms.
- Obfuscate ids in the query string.
- Anything that can be done with Apache, should. Like access controls.
- Use prepared statements and bind parameters for all database queries.
- Be precise on file permissions, like execute-only for the apache user.
- Require HTTPS for all communications containing private data.
- Don't display sensitive data, like a stack trace, in production.
- Don't limit a password's max length (you should be hashing and salting).
- Require manual approval (or spam detection) on all user comments.
Web Typography
PHP configuration information
<?php phpinfo(); ?>
PHP array tips
<?php
// PHP arrays can contain almost any data type
$stuff = array("Hello world!", 'C', new Thing(), array(1, 2, 3), 3.14);>
// can be numerically indexed or associative (indexed with strings)
$fruit = array("red" => array("cherry", "strawberry", "raspberry"),
"purple" => array("grape", "eggplant"),
"yellow" => array("banana", "lemon", "pineapple"));
// can be treated like an array, hashmap, linked list, stack, etc
array_pop($fruit); // removes and returns the "yellow" element
// There are like a bajillion built-in array functions.
// For example: sorts an array, maintaining key association.
asort($fruit);
// checks if the given key or index exists
array_key_exists("blue", $fruit);
// Shorthand for adding an element onto the end of an array.
// I use this ALL THE TIME, especially in loops.
$stuff[] = "more stuff";
Read from a file and output to an HTML table
conrad|32|rocky road
robby|29|chocolate fudge
adam|23|vanilla
clayton|25|red bean
ben|12|phish food
<html>
<head>HTML table example</head>
<body>
<?php
$data = file("data.txt"); // read file lines into an array ?>
<table border="1"> <!-- add a border using a style sheet instead -->
<?php
foreach ($data as $student) { // loop over lines ?>
<tr> <!-- begin table row -->
<?php
$line = explode("|", $student); // split line into array
foreach ($line as $item) { // loop over items found in each line ?>
<td><?php echo $item; ?></td> <!-- column containing data -->
<?php } ?>
</tr> <!-- end table row -->
<?php } ?>
</table>
</body>
</html>
PHP default function arguments
<?php
function student ($name, $major = "Computer Science") {
echo "$name\n";
echo "$major\n"; // will echo 'Computer Science'
}
student("Conrad Kennington");
PHP class example with static variables shared between instances
<?php
class Lockbox {
private $key = false;
static private $available = true;
static private $locked = false;
public function __construct () {
// no-op
}
public function getKey () {
if (self::$available) {
$this->key = true;
self::$available = false;
echo "I now have the key!\n";
} else {
echo "Sombody else already has the key!\n";
}
}
public function unlock () {
if ($this->key) {
self::$locked = false;
echo "I unlocked it!\n";
} else {
echo "I can't inlock it, I don't have the key!\n";
}
}
public function lock () {
if ($this->key) {
self::$locked = true;
echo "I locked it!\n";
} else {
echo "I can't unlock it, I need the key!\n";
}
}
} // end Lockbox
// sample usage
$alice = new Lockbox();
$bob = new Lockbox();
$alice->getKey(); // I now have the key!
$bob->getKey(); // Sombody else already has the key!
$bob->lock(); // I can't lock it, I need the key!
$alice->lock(); // I locked it!
$bob->unlock(); // I can't unlock it. I don't have the key!
$alice->unlock(); // I unlocked it!
PHP - check user permissions on a "members only" page using serialized User object from a file
O:4:"User":4:{s:10:"^@User^@name";s:6:"Conrad";s:11:"^@User^@email";s:31:"conradkennington@boisestate.edu";s:17:"^@User^@permissions";a:2:{i:0;s:6:"MEMBER";i:1;s:5:"GUEST";}s:8:"^@User^@id";N;}
O:4:"User":4:{s:10:"^@User^@name";s:7:"Gandalf";s:11:"^@User^@email";s:23:"thegray@middleearth.edu";s:17:"^@User^@permissions";a:3:{i:0;s:6:"MEMBER";i:1;s:5:"GUEST";i:2;s:5:"ADMIN";}s:8:"^@User^@id";N;}
O:4:"User":4:{s:10:"^@User^@name";s:6:"Sauron";s:11:"^@User^@email";s:17:"theeye@mordor.net";s:17:"^@User^@permissions";a:1:{i:0;s:5:"GUEST";}s:8:"^@User^@id";N;}
<?php
class User {
const GUEST = "GUEST";
const MEMBER = "MEMBER";
const ADMIN = "ADMIN";
private $name;
private $email;
private $permissions;
public function __construct ($name, $email, $permissions = array()) {
$this->name = $name;
$this->email = $email;
$this->permissions = $permissions;
}
public function getEmail() {
return $this->email;
}
public function hasPermission ($permission){
// return true if permission is found
return in_array($permission, $this->permissions);
}
} // end User
<?php
require_once "User.php";
class Dao {
// file containing serialized User objects
private $usersFile = "users.txt";
public function getUser ($email) {
$users = file($this->usersFile);
foreach ($users as $user) {
// recreate User object
$user = unserialize($user);
if ($email == trim($user->getEmail())) {
// user email found, return the object
return $user;
}
}
throw new Exception("User not found");
}
} // end Dao
<?php
// this page requires a user with the MEMBER permission to view
require_once "Dao.php";
$dao = new Dao();
try {
// get the user object from the data store
$user = $dao->getUser("theeye@mordor.net");
if ($user->hasPermission(User::MEMBER)) {
echo "User has the permission";
} else {
echo "User does NOT have the permission";
}
} catch (Exception $e) {
echo $e->getMessage();
}
?>
<html>
<head></head>
<body>
<!-- page content for members only-->
</body>
</html>
Superhero Database
create table person (id int not null auto_increment primary key, name varchar(32) not null,
gender varchar(1), alter_ego varchar(32), good BIT, date_entered date);
create table power (id int not null auto_increment primary key, power varchar(16) not null);
create table person_power (person_id int not null, power_id int not null, primary key (person_id, power_id);
insert into person (name, gender, alter_ego, good) values ('Ironman', 'M', 'Tony Stark', 1);
insert into person (name, gender, alter_ego, good) values ('Hulk', 'M', 'Bruce Banner', 1);
insert into person (name, gender, alter_ego, good) values ('Storm', 'F', 'Ororo Munroe', 1);
insert into power (power) values ('Fly');
insert into power (power) values ('Strength');
insert into power (power) values ('Laser Vision');
select * from person where gender = 'M';
update person set gender = 'F', name = 'She-Hulk' where name = 'Hulk';
delete from person where name = 'She-Hulk';
insert into person_power (person_id, power_id) values (1, 1);
insert into person_power (person_id, power_id) values (1, 2);
insert into person_power (person_id, power_id) values (2, 2);
select name from person join person_power on person.id =
person_power.person_id join power on power.id = person_power.power_id where power.power = 'Strength';
PHP/MySQL save and display comments
<?php
// index.php
// This page has a comment form for posting, and a list of comments from MySQL
require_once "Dao.php";
$dao = new Dao();
?>
<html>
<head>
<title>List of Products</title>
<link rel="stylesheet" type="text/css" href="comment.css">
</head>
<body>
<form name="commentForm" action="handler.php" method="POST">
<div>
Leave a comment: <input type="text" name="comment">
</div>
<div>
<input type="submit" name="commentButton" value="Submit">
</div>
<input type="hidden" name="form" value="comment">
</form>
<?php
$comments = $dao->getComments();
echo "<table>";
foreach ($comments as $comment) {
echo "<tr>";
echo "<td>" . $comment["comment"] . "</td>";
echo "<td>" . $comment["created"] . "</td>";
echo "</tr>";
}
echo "</table>";
?>
</body>
</html>
<?php
// handler.php
// handle comment posts, saving to MySQL and redirecting back to the list
require_once "Dao.php";
if (isset($_POST["commentButton"])) {
$comment = $_POST["comment"];
try {
$dao = new Dao();
$dao->saveComment($comment);
} catch (Exception $e) {
var_dump($e);
die;
}
}
header("Location:index.php");
<?php
// Dao.php
// class for saving and getting comments from MySQL
class Dao {
private $host = "localhost";
private $db = "ckenning";
private $user = "ckenning";
private $pass = "password";
public function getConnection () {
return
new PDO("mysql:host={$this->host};dbname={$this->db}", $this->user,
$this->pass);
}
public function saveComment ($comment) {
$conn = $this->getConnection();
$saveQuery =
"INSERT INTO comment
(comment)
VALUES
(:comment)";
$q = $conn->prepare($saveQuery);
$q->bindParam(":comment", $comment);
$q->execute();
}
public function getComments () {
$conn = $this->getConnection();
return $conn->query("SELECT * FROM comment");
}
} // end Dao
PHP/MySQL upload an image, and generate links to details
<?php
// products.php
// This page lists products and generates links with an id in the query string
require_once "Dao.php";
$dao = new Dao();
$products = $dao->getProducts();
?>
<html>
<body>
<ul>
<?php foreach ($products as $product) {
echo "<li><a href='product/details.php?id=" . $product["id"] . "'>" .
$product["name"] . "</a></li>";
} ?>
</ul>
<a href="/product/add.html">Add a product</a>
</body>
</html>
<!-- product/add.html -->
<html>
<head><title>Add a product</title></head>
<body>
<form action="upload.php" method="POST" enctype="multipart/form-data">
<div>
<label for="name">Product Name:</label>
<input type="text" name="name"/>
</div>
<div>
<label for="name">Product Description:</label>
<input type="text" name="description"/>
</div>
<div>
<label for="file">Image:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="Submit">
</div>
</form>
</body>
</html>
<?php
// product/upload.php
require_once "../Dao.php";
$dao = new Dao();
// save a product, including name, description, and an image path
$name = (isset($_POST["name"])) ? $_POST["name"] : "";
$description = (isset($_POST["description"])) ? $_POST["description"] : "";
$imagePath = "";
if (count($_FILES) > 0) {
if ($_FILES["file"]["error"] > 0) {
throw new Exception("Error: " . $_FILES["file"]["error"]);
} else {
$basePath = "/Users/crk/projects/cs497/src/www";
$imagePath = "/product/images/" . $_FILES["file"]["name"];
if (!move_uploaded_file($_FILES["file"]["tmp_name"], $basePath . $imagePath)) {
throw new Exception("File move failed");
}
}
}
$dao->saveProduct($name, $description, $imagePath);
header("location:http://cs497/products.php");
<?php
// products/detail.php
// Gets an id from the query string to look up the product in MySQL
require_once "../Dao.php";
$dao = new Dao();
$id = $_GET["id"];
$product = $dao->getProduct($id);
echo "<h2>Details for " . $product["name"] . ": " . $product["description"] .
"</h2>";?>
<img src="<?php echo $product["image_path"]; ?>" />
<div>
<a href="/products.php">Back to Product List</a>
</div>
<?php
// Dao.php
// class for getting products in MySQL
class Dao {
private $host = "localhost";
private $db = "ckenning";
private $user = "ckenning";
private $pass = "password";
public function getConnection () {
return
new PDO("mysql:host={$this->host};dbname={$this->db}", $this->user,
$this->pass);
}
public function getProducts () {
$conn = $this->getConnection();
return $conn->query("SELECT id, name FROM product");
}
public function getProduct ($id) {
$conn = $this->getConnection();
$getQuery = "SELECT id, name, description, image_path FROM product WHERE id = :id";
$q = $conn->prepare($getQuery);
$q->bindParam(":id", $id);
$q->execute();
return reset($q->fetchAll());
}
public function saveProduct ($name, $description, $imagePath) {
$conn = $this->getConnection();
$saveQuery =
"INSERT INTO product
(name, description, image_path)
VALUES
(:name, :description, :imagePath)";
$q = $conn->prepare($saveQuery);
$q->bindParam(":name", $name);
$q->bindParam(":description", $description);
$q->bindParam(":imagePath", $imagePath);
$q->execute();
}
} // end Dao
PHP login session, form preset, and status
<?php
// login.php
session_start();
if (isset($_SESSION["access_granted"]) && $_SESSION["access_granted"]) {
header("Location:granted.php");
}
$email = "";
if (isset($_SESSION["email_preset"])) {
$email = $_SESSION["email_preset"];
}
?>
<html>
<head></head>
<body>
<h3>Login to my Secret System</h3>
<?php
if (isset($_SESSION["status"])) {
echo "<div id="status">" . $_SESSION["status"] . "</div>";
unset($_SESSION["status"]);
}
?>
<form action="login_handler.php" method="POST">
<div>
<label for="email">Email</label>
<input type="text" name="email" id="email" value="<?php echo $email; ?>"/>
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password" value=""/>
</div>
<div>
<input type="submit" name="submit" id="login" value="Login"/>
</div>
</form>
</body>
</html>
<?php
// login_handler.php
session_start();
// For simplification Lets pretend I got these login credentials from an SQL table.
if ("ckennington@gmail.com" == $_POST["email"] &&
"lollipop" == $_POST["password"]) {
$_SESSION["access_granted"] = true;
header("Location:granted.php");
} else {
$status = "Invalid username or password";
$_SESSION["status"] = $status;
$_SESSION["email_preset"] = $_POST["email"];
$_SESSION["access_granted"] = false;
header("Location:login.php");
}
?>
<?php
// granted.php
session_start();
if (isset($_SESSION["access_granted"]) && !$_SESSION["access_granted"] ||
!isset($_SESSION["access_granted"])) {
$_SESSION["status"] = "You need to log in first";
header("Location:login.php");
}
echo "ACCESS GRANTED";
?>
<a href="logout.php">Logout</a>
<?php
// logout.php
session_start();
session_destroy();
header("Location:login.php");
?>
AJAX using jQuery
// ajax.js file
$(function() {
$("#form").submit(function(){
var values = $("form").serialize();
var comment = $("#comment").val();
console.log(values);
$.ajax({
type: "POST",
url: "handlerAjax.php",
data: values,
success: function() {
$("#comments tbody").prepend("<tr><td>" +
comment + "</td><td>Just now</td></tr>");
$("#comment").val("");
$("#email").val("");
$("#age").val("");
},
error: function () {
alert("FAILURE");
}
});
return false;
});
});
<?php
// ajax.php file
session_start();
require_once "Dao.php";
$dao = new Dao();
$comments = $dao->getComments();
?>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="js/ajax.js" type="text/javascript"></script>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<img src="moon.jpg" />
<form id="form" method="POST"> <!-- notice that the form has no action -->
<div class="pair">
<label for="email">Enter your email:</label>
<input type="text" id="email"
<?php if (isset($_SESSION["email"])) {
echo "value='{$_SESSION['email']}'";
} ?>
name="email"/>
</div>
<div class="pair">
<label for="comment">Leave a comment:</label>
<input type="text" id="comment"
<?php if (isset($_SESSION["comment"])) {
echo "value='{$_SESSION['comment']}'";
} ?>
name="comment"/>
</div>
<div class="pair">
<label for="age">Enter your age:</label>
<input type="text" id="age"
<?php if (isset($_SESSION['age'])) {
echo "value='{$_SESSION['age']}'";
} ?>
name="age"/>
</div>
<div class="pair">
<label for="age"></label>
<input id="submit" type="submit"/><small>(You must be 18 to post)</small>
</div>
</form>
</div>
<table id="comments">
<?php
foreach ($comments as $comment) {
echo "<tr>";
echo "<td>" . $comment["comment"] . "</td>";
echo "<td>" . $comment["created"] . "</td>";
echo "</tr>";
}
?>
</table>
</body>
</html>
<?php
// handlerAjax.php file
require_once "Dao.php";
$dao = new Dao();
$dao->saveComment($_POST['comment'], $_POST['email']);
?>
Hashing passwords with a SHA256 + salt
<?php echo hash("sha256", "password" . "fKd93Vmz!k*dAv5029Vkf9$3Aa");