SlideShare uma empresa Scribd logo
1 de 95
PHP 102
Out with the Bad,
In with the Good
php[tek] 2013
Who is this guy?
Jeremy Kendall
I love to code
I love to take pictures
I'm terribly forgetful
I work at OpenSky
Following Along
You can follow along with the presentation
code at github.com.
https://github.com/jeremykendall/bookshelf
Before = oh-the-horror
After = much-better
Why Did You
Start Programming?
I wanted to solve problems, but . . .
. . . I frequently caused
as many problems
as I solved.
“Always code as if the person who ends up
maintaining your code is a violent psychopath
who knows where you live.”
http://c2.com/cgi/wiki?CodeForTheMaintainer
“Alternatively, always code and comment in such
a way that if someone a few notches junior picks
up the code, they will take pleasure in reading
and learning from it.”
http://c2.com/cgi/wiki?CodeForTheMaintainer
Let's Solve a Problem Together
• We'll review a “typical” PHP application
• Find horrible mistakes
• Correct those mistakes
• Make a cool improvement
• Learn and apply better practices
Typical application?
• I've seen code similar to the samples I'll show
in almost every app I've ever touched.
• I've made the same mistakes in almost every
app I wrote in my early days.
• I'm guessing you've seen and made similar
mistakes too.
What's the Problem?
• Crappy application
• CRUD bookshelf
• What does it do?:
– Display books
– Add books
– Edit books
– Delete books
What's There?
• Database (MySQL)
• View (index.php)
• Form (book-form.php)
• Form processor (process-form.php)
• Delete (delete-book.php)
index.php
book-form.php
index.php – db connection
$db = mysql_connect('localhost', 'testuser', 'testpass');
mysql_select_db('bookshelf', $db);
$query = "SELECT * FROM bookshelf ORDER BY title";
$result = mysql_query($query);
index.php – db connection
$db = mysql_connect('localhost', 'testuser', 'testpass');
mysql_select_db('bookshelf', $db);
$query = "SELECT * FROM bookshelf ORDER BY title";
$result = mysql_query($query);
index.php – db connection
$db = mysql_connect('localhost', 'testuser', 'testpass');
mysql_select_db('bookshelf', $db);
$query = "SELECT * FROM bookshelf ORDER BY title";
$result = mysql_query($query);
index.php – db connection
$db = mysql_connect('localhost', 'testuser', 'testpass');
mysql_select_db('bookshelf', $db);
$query = "SELECT * FROM bookshelf ORDER BY title";
$result = mysql_query($query);
index.php – books table
<?php while ($book = mysql_fetch_assoc($result)): ?>
<tr>
<td>
<a href="book-form.php?id=<?php echo $book['id']; ?>">
<?php echo $book['title']; ?>
</a>
</td>
<td>
<?php echo $book['author']; ?>
</td>
</tr>
<?php endwhile; ?>
index.php – books table
<?php while ($book = mysql_fetch_assoc($result)): ?>
<tr>
<td>
<a href="book-form.php?id=<?php echo $book['id']; ?>">
<?php echo $book['title']; ?>
</a>
</td>
<td>
<?php echo $book['author']; ?>
</td>
</tr>
<?php endwhile; ?>
book-form.php
// Database connection code
$id = empty($_GET['id']) ? null : $_GET['id'];
if ($id) {
// Database connection code
$query = "SELECT title, author "
. "FROM bookshelf WHERE id = $id";
$result = mysql_query($query);
$book = mysql_fetch_assoc($result);
$title = $book['title'];
$author = $book['author'];
}
book-form.php
$id = empty($_GET['id']) ? null : $_GET['id'];
if ($id) {
// Database connection code
$query = "SELECT title, author "
. "FROM bookshelf WHERE id = $id";
$result = mysql_query($query);
$book = mysql_fetch_assoc($result);
$title = $book['title'];
$author = $book['author'];
}
book-form.php
$id = empty($_GET['id']) ? null : $_GET['id'];
if ($id) {
// Database connection code
$query = "SELECT title, author "
. "FROM bookshelf WHERE id = $id";
$result = mysql_query($query);
$book = mysql_fetch_assoc($result);
$title = $book['title'];
$author = $book['author'];
}
book-form.php
<input type="hidden" name="id" value="<?php echo $id; ?>" />
<input type="text" name="title" value="<?php echo $title; ?>" />
<input type="text" name="author" value="<?php echo $author; ?>" />
book-form.php
<input type="hidden" name="id" value="<?php echo $id; ?>" />
<input type="text" name="title" value="<?php echo $title; ?>" />
<input type="text" name="author" value="<?php echo $author; ?>" />
process-book.php
// Database connection code
$ins = "INSERT INTO bookshelf (title, author) "
."VALUES ('{$_POST['title']}', '{$_POST['author']}')";
$up = "UPDATE bookshelf SET title = '{$_POST['title']}', "
. "author = '{$_POST['author']}' WHERE id = {$_POST['id']}";
if (empty($_POST['id'])) {
mysql_query($ins);
} else {
mysql_query($up);
}
process-book.php
// Database connection code
$ins = "INSERT INTO bookshelf (title, author) "
."VALUES ('{$_POST['title']}', '{$_POST['author']}')";
$up = "UPDATE bookshelf SET title = '{$_POST['title']}', "
. "author = '{$_POST['author']}' WHERE id = {$_POST['id']}";
if (empty($_POST['id'])) {
mysql_query($ins);
} else {
mysql_query($up);
}
process-book.php
// Database connection code
$ins = "INSERT INTO bookshelf (title, author) "
."VALUES ('{$_POST['title']}', '{$_POST['author']}')";
$up = "UPDATE bookshelf SET title = '{$_POST['title']}', "
. "author = '{$_POST['author']}' WHERE id = {$_POST['id']}";
if (empty($_POST['id'])) {
mysql_query($ins);
} else {
mysql_query($up);
}
process-book.php
// Database connection code
$ins = "INSERT INTO bookshelf (title, author) "
."VALUES ('{$_POST['title']}', '{$_POST['author']}')";
$up = "UPDATE bookshelf SET title = '{$_POST['title']}', "
. "author = '{$_POST['author']}' WHERE id = {$_POST['id']}";
if (empty($_POST['id'])) {
mysql_query($ins);
} else {
mysql_query($up);
}
process-book.php
// Database connection code
$ins = "INSERT INTO bookshelf (title, author) "
."VALUES ('{$_POST['title']}', '{$_POST['author']}')";
$up = "UPDATE bookshelf SET title = '{$_POST['title']}', "
. "author = '{$_POST['author']}' WHERE id = {$_POST['id']}";
if (empty($_POST['id'])) {
mysql_query($ins);
} else {
mysql_query($up);
}
Glaring Problems?
• Code duplication
• Input isn't filtered
• Output isn't escaped
• Vendor specific functions
• User-provided data used in SQL
• Did you see any others?
Deprecated!
• mysql extension deprecated as of 5.5.0
• Use mysqli or PDO
• Read the MySQL “Choosing an API” on
php.net: http://bit.ly/13zur2e
Code Duplication
$db = mysql_connect('localhost', 'testuser', 'testpass');
mysql_select_db('bookshelf', $db);
• Violates DRY principle
• Maintenance nightmare
• The next developer will want to kill you
• And your family
• And your pets
Consolidate
Let's throw all that duplicated code into an
include file, say library/base.php.
(We'll add a few other handy items while we're in there)
base.php
// . . . snip . . .
$db = mysql_connect('localhost', 'testuser', 'testpass');
mysql_select_db('bookshelf', $db);
Replace db info with base.php
Remove the db connection code from each
file with:
require_once dirname(__FILE__) . '/library/base.php';
STOP!
PDO
• PHP Data Objects
• Lightweight, consistent interface
• Data access abstraction
• Not database abstraction
http://www.php.net/manual/en/intro.pdo.php
base.php
// . . . snip . . .
$options = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$dsn =
'mysql:host=localhost;dbname=bookshelf;charset=utf8';
$username = 'testuser';
$password = 'testpass';
try {
$dbh = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
error_log($e->getMessage(), 3, '../logs/errors.log');
echo 'An error occurred while trying to connect to the
database.';
}
base.php
// . . . snip . . .
$options = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$dsn =
'mysql:host=localhost;dbname=bookshelf;charset=utf8';
$username = 'testuser';
$password = 'testpass';
try {
$dbh = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
error_log($e->getMessage(), 3, '../logs/errors.log');
echo 'An error occurred while trying to connect to the
database.';
}
base.php
// . . . snip . . .
$options = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$dsn =
'mysql:host=localhost;dbname=bookshelf;charset=utf8';
$username = 'testuser';
$password = 'testpass';
try {
$dbh = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
error_log($e->getMessage(), 3, '../logs/errors.log');
echo 'An error occurred while trying to connect to the
database.';
}
base.php
// . . . snip . . .
$options = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$dsn =
'mysql:host=localhost;dbname=bookshelf;charset=utf8';
$username = 'testuser';
$password = 'testpass';
try {
$dbh = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
error_log($e->getMessage(), 3, '../logs/errors.log');
echo 'An error occurred while trying to connect to the
database.';
}
Now start replacing mysql_*
// index.php
$books = $dbh->query($sql)->fetchAll();
// book-form.php
$book = $dbh->query($sql)->fetch();
// process-book.php and delete-book.php
$dbh->exec($sql);
Now on to the “handy items” I promised
base.php
$env = getenv('APPLICATION_ENVIRONMENT');
if (!$env) {
$env = "production";
}
date_default_timezone_set('America/Chicago');
if ($env === "develop") {
error_reporting(-1);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
}
// . . . snip . . .
base.php
$env = getenv('APPLICATION_ENVIRONMENT');
if (!$env) {
$env = "production";
}
date_default_timezone_set('America/Chicago');
if ($env === "develop") {
error_reporting(-1);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
}
// . . . snip . . .
base.php
$env = getenv('APPLICATION_ENVIRONMENT');
if (!$env) {
$env = "production";
}
date_default_timezone_set('America/Chicago');
if ($env === "develop") {
error_reporting(-1);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
}
// . . . snip . . .
base.php
$env = getenv('APPLICATION_ENVIRONMENT');
if (!$env) {
$env = "production";
}
date_default_timezone_set('America/Chicago');
if ($env === "develop") {
error_reporting(-1);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
}
// . . . snip . . .
Duplication removed, but . . .
Besides getting shot . . .
“Whenever PHP generates an error message
internally, it's processed and formatted all the
way up to the fully formatted message that can
be outputted straight to the browser. Only just
before it is displayed the error_reporting setting
is checked.”
http://derickrethans.nl/five-reasons-why-the-shutop-operator-should-be-avoided.html
We echo $title and $author
<input type="hidden" name="id" value="<?php echo $id; ?>" />
<input type="text" name="title" value="<?php echo $title; ?>" />
<input type="text" name="author" value="<?php echo $author; ?>" />
Without defining $title and $author
require_once dirname(__FILE__) . '/library/base.php';
$id = empty($_GET['id']) ? null : $_GET['id'];
if ($id) {
$book = $dbh->query( . . . )->fetch();
$title = $book['title'];
$author = $book['author'];
}
Super easy to fix
$id = empty($_GET['id']) ? null : $_GET['id'];
$title = null;
$author = null;
if ($id) {
$book = $dbh->query( . . . )->fetch();
$title = $book['title'];
$author = $book['author'];
}
FIEO
• Filter input
– Your users want to destroy your app
– Prevent SQL injection
• Escape output
– Or just destroy your app yourself
– Defend against XSS
http://xkcd.com/327/
book-form.php
Use filter_input() to guarantee $id is
either false or an int.
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
process-book.php
More filter_input()
$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
$title = filter_input(INPUT_POST, 'title',
FILTER_SANITIZE_STRING);
$author = filter_input(INPUT_POST, 'author',
FILTER_SANITIZE_STRING);
index.php
Use htmlspecialchars() to escape
output
htmlspecialchars($book['title'], ENT_COMPAT, 'UTF-8'); ?>
htmlspecialchars($book['author'], ENT_COMPAT, 'UTF-8'); ?>
Prepared Statements
• Uses fewer resources
• Runs faster
• Protects against SQL injection
• Easier to read and maintain
book-form.php: Before
$book = $dbh->query("SELECT title, author FROM
bookshelf WHERE id = $id")->fetch();
book-form.php: After
$statement = $dbh->prepare('SELECT title, author FROM
bookshelf WHERE id = :id');
$statement->bindParam(':id', $id);
$statement->execute();
$book = $statement->fetch();
book-form.php: After
$statement = $dbh->prepare('SELECT title, author FROM
bookshelf WHERE id = :id');
$statement->bindParam(':id', $id);
$statement->execute();
$book = $statement->fetch();
book-form.php: After
$statement = $dbh->prepare('SELECT title, author FROM
bookshelf WHERE id = :id');
$statement->bindParam(':id', $id);
$statement->execute();
$book = $statement->fetch();
book-form.php: After
$statement = $dbh->prepare('SELECT title, author FROM
bookshelf WHERE id = :id');
$statement->bindParam(':id', $id);
$statement->execute();
$book = $statement->fetch();
book-form.php: After
$statement = $dbh->prepare('SELECT title, author FROM
bookshelf WHERE id = :id');
$statement->bindParam(':id', $id);
$statement->execute();
$book = $statement->fetch();
process-book.php: Before
if (empty($_POST['id'])) {
$sql = "INSERT INTO bookshelf (title, author) "
."VALUES ('{$_POST['title']}',
'{$_POST['author']}')";
$dbh->exec($sql);
} else {
$sql = "UPDATE bookshelf SET title =
'{$_POST['title']}', "
. "author = '{$_POST['author']}' WHERE id =
{$_POST['id']}";
$dbh->exec($sql);
}
process-book.php: After
if ($id) {
$statement = $dbh->prepare("UPDATE bookshelf SET title
= :title, author = :author WHERE id = :id");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->bindParam(':id', $id);
$statement->execute();
} else {
$statement = $dbh->prepare("INSERT INTO bookshelf
(title, author) VALUES (:title, :author)");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->execute();
}
process-book.php: After
if ($id) {
$statement = $dbh->prepare("UPDATE bookshelf SET title
= :title, author = :author WHERE id = :id");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->bindParam(':id', $id);
$statement->execute();
} else {
$statement = $dbh->prepare("INSERT INTO bookshelf
(title, author) VALUES (:title, :author)");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->execute();
}
process-book.php: After
if ($id) {
$statement = $dbh->prepare("UPDATE bookshelf SET title
= :title, author = :author WHERE id = :id");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->bindParam(':id', $id);
$statement->execute();
} else {
$statement = $dbh->prepare("INSERT INTO bookshelf
(title, author) VALUES (:title, :author)");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->execute();
}
process-book.php: After
if ($id) {
$statement = $dbh->prepare("UPDATE bookshelf SET title
= :title, author = :author WHERE id = :id");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->bindParam(':id', $id);
$statement->execute();
} else {
$statement = $dbh->prepare("INSERT INTO bookshelf
(title, author) VALUES (:title, :author)");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->execute();
}
Service Layer
Let's replace all that baked in DB stuff
with a Service Layer
Why?
• Introduce some OOP principles
• High ROI
– Maintainability
– Testability
What? Where?
• Class name: BookshelfService
• Namespace: BookshelfService
• Location: library/Bookshelf/Service
• Filename: BookshelfService.php
• PSR-0 compliant
(The class name should mirror its location in the file
system)
BookshelfService.php
namespace BookshelfService;
class BookshelfService
{
private $dbh;
public function __construct(PDO $dbh) {}
public function find($id) {}
public function findAll() {}
public function save(array $options) {}
public function delete($id) {}
}
BookshelfService.php
private $dbh;
public function __construct(PDO $dbh)
{
$this->dbh = $dbh;
}
BookshelfService.php
public function find($id)
{
$sql = 'SELECT * FROM bookshelf WHERE id = :id';
$statement = $this->dbh->prepare($sql);
$statement->bindParam(':id', $id);
$statement->execute();
return $statement->fetch();
}
public function findAll()
{
$sql = 'SELECT * FROM bookshelf ORDER BY title';
return $this->dbh->query($sql)->fetchAll();
}
BookshelfService.php
public function save(array $options)
{
if ($options['id']) {
$statement = $this->dbh->prepare("UPDATE bookshelf
SET title = :title, author = :author WHERE id = :id");
$statement->execute($options);
} else {
unset($options['id']);
$statement = $this->dbh->prepare("INSERT INTO
bookshelf (title, author) VALUES (:title, :author)");
$statement->execute($options);
}
}
A New Class Means . . .
. . . we need to add a few things to base.php.
base.php
set_include_path(
implode(PATH_SEPARATOR, array(
get_include_path(),
dirname(__FILE__)
)
)
);
base.php
function autoload($className)
{
// PSR-0 autoloader
}
spl_autoload_register('autoload');
https://gist.github.com/jwage/221634
base.php
// database connection code
$bookshelf = new BookshelfServiceBookshelfService($dbh);
And now for some before and after
shots . . .
index.php: Before
require_once dirname(__FILE__) . '/library/base.php';
$books = $dbh->query("SELECT * FROM bookshelf ORDER BY
title")->fetchAll();
index.php: After
require_once dirname(__FILE__) . '/library/base.php';
$books = $bookshelf->findAll();
book-form.php: Before
if ($id) {
$statement = $dbh->prepare('SELECT title, author FROM
bookshelf WHERE id = :id');
$statement->bindParam(':id', $id);
$statement->execute();
$book = $statement->fetch();
$title = $book['title'];
$author = $book['author'];
}
book-form.php: After
if ($id) {
$book = $bookshelf->find($id);
$title = $book['title'];
$author = $book['author'];
}
process-book.php: Before
if ($id) {
$statement = $dbh->prepare("UPDATE bookshelf SET title
= :title, author = :author WHERE id = :id");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->bindParam(':id', $id);
$statement->execute();
} else {
$statement = $dbh->prepare("INSERT INTO bookshelf
(title, author) VALUES (:title, :author)");
$statement->bindParam(':title', $title);
$statement->bindParam(':author', $author);
$statement->execute();
}
process-book.php: After
$book = array(
'id' => $id,
'title' => $title,
'author' => $author
);
$bookshelf->save($book);
What Have We Done?
• Discovered we've been handed a disaster
• Iteratively improved on the nightmare
• Seen some nice features of PHP
– PDO
– Filtering and escaping
– OOP
• Learned something?
What more could we do?
• Global config file
• Environment config file
• Unit and functional tests
• Don't roll your own!
• Add Composer, add libraries as needed
Want More?
• Presentation source code: https://github.com/jeremykendall/bookshelf
• PHP Manual: http://www.php.net/manual/en/index.php
• PHP The Right Way: http://www.phptherightway.com/
• PHP 101 Suggestions:
http://csiphp.com/blog/2011/07/19/stop-doing-it-wrong-and-learn-to-code-good-too/
• PHP 101: PHP for the Absolute Beginner:
http://devzone.zend.com/6/php-101-php-for-the-absolute-beginner/
• PHPDeveloper.org http://phpdeveloper.org/
• php|architect: http://www.phparch.com/
• Composer http://getcomposer.org
Questions?
Thanks!
@JeremyKendall
jeremy@jeremykendall.net
http://joind.in/8188

Mais conteúdo relacionado

Mais procurados

(Ab)Using the MetaCPAN API for Fun and Profit
(Ab)Using the MetaCPAN API for Fun and Profit(Ab)Using the MetaCPAN API for Fun and Profit
(Ab)Using the MetaCPAN API for Fun and Profit
Olaf Alders
 
NoSQL を Ruby で実践するための n 個の方法
NoSQL を Ruby で実践するための n 個の方法NoSQL を Ruby で実践するための n 個の方法
NoSQL を Ruby で実践するための n 個の方法
Tomohiro Nishimura
 
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
ichikaway
 
Hd insight programming
Hd insight programmingHd insight programming
Hd insight programming
Casear Chu
 
jQuery%20on%20Rails%20Presentation
jQuery%20on%20Rails%20PresentationjQuery%20on%20Rails%20Presentation
jQuery%20on%20Rails%20Presentation
guestcf600a
 

Mais procurados (18)

Potential Friend Finder
Potential Friend FinderPotential Friend Finder
Potential Friend Finder
 
(Ab)Using the MetaCPAN API for Fun and Profit
(Ab)Using the MetaCPAN API for Fun and Profit(Ab)Using the MetaCPAN API for Fun and Profit
(Ab)Using the MetaCPAN API for Fun and Profit
 
NoSQL を Ruby で実践するための n 個の方法
NoSQL を Ruby で実践するための n 個の方法NoSQL を Ruby で実践するための n 個の方法
NoSQL を Ruby で実践するための n 個の方法
 
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
 
Hd insight programming
Hd insight programmingHd insight programming
Hd insight programming
 
Fantom and Tales
Fantom and TalesFantom and Tales
Fantom and Tales
 
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
 
PHP Tutorial (funtion)
PHP Tutorial (funtion)PHP Tutorial (funtion)
PHP Tutorial (funtion)
 
Current state-of-php
Current state-of-phpCurrent state-of-php
Current state-of-php
 
How to use MongoDB with CakePHP
How to use MongoDB with CakePHPHow to use MongoDB with CakePHP
How to use MongoDB with CakePHP
 
jQuery%20on%20Rails%20Presentation
jQuery%20on%20Rails%20PresentationjQuery%20on%20Rails%20Presentation
jQuery%20on%20Rails%20Presentation
 
Fazendo mágica com ElasticSearch
Fazendo mágica com ElasticSearchFazendo mágica com ElasticSearch
Fazendo mágica com ElasticSearch
 
PHP Basics and Demo HackU
PHP Basics and Demo HackUPHP Basics and Demo HackU
PHP Basics and Demo HackU
 
My First Ruby
My First RubyMy First Ruby
My First Ruby
 
How else can you write the code in PHP?
How else can you write the code in PHP?How else can you write the code in PHP?
How else can you write the code in PHP?
 
[WLDN] Supercharging word press development in 2018
[WLDN] Supercharging word press development in 2018[WLDN] Supercharging word press development in 2018
[WLDN] Supercharging word press development in 2018
 
Fun with Python
Fun with PythonFun with Python
Fun with Python
 
Wsomdp
WsomdpWsomdp
Wsomdp
 

Destaque

Tdd in php a brief example
Tdd in php   a brief exampleTdd in php   a brief example
Tdd in php a brief example
Jeremy Kendall
 

Destaque (9)

PHP Data Objects
PHP Data ObjectsPHP Data Objects
PHP Data Objects
 
PHP 102: Out with the Bad, In with the Good
PHP 102: Out with the Bad, In with the GoodPHP 102: Out with the Bad, In with the Good
PHP 102: Out with the Bad, In with the Good
 
Django a whirlwind tour
Django   a whirlwind tourDjango   a whirlwind tour
Django a whirlwind tour
 
Tdd in php a brief example
Tdd in php   a brief exampleTdd in php   a brief example
Tdd in php a brief example
 
PHP and MySQL
PHP and MySQLPHP and MySQL
PHP and MySQL
 
PHP - PDO Objects
PHP - PDO ObjectsPHP - PDO Objects
PHP - PDO Objects
 
Php 101: PDO
Php 101: PDOPhp 101: PDO
Php 101: PDO
 
PHP+DB
PHP+DBPHP+DB
PHP+DB
 
TDC SP 2015 - PHP7: melhor e mais rápido
TDC SP 2015 - PHP7: melhor e mais rápidoTDC SP 2015 - PHP7: melhor e mais rápido
TDC SP 2015 - PHP7: melhor e mais rápido
 

Semelhante a Php 102: Out with the Bad, In with the Good

Propel sfugmd
Propel sfugmdPropel sfugmd
Propel sfugmd
iKlaus
 
PHP and Rich Internet Applications
PHP and Rich Internet ApplicationsPHP and Rich Internet Applications
PHP and Rich Internet Applications
elliando dias
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutos
Edgar Suarez
 
Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB
jhchabran
 

Semelhante a Php 102: Out with the Bad, In with the Good (20)

The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
DBIx::Class introduction - 2010
DBIx::Class introduction - 2010DBIx::Class introduction - 2010
DBIx::Class introduction - 2010
 
Propel sfugmd
Propel sfugmdPropel sfugmd
Propel sfugmd
 
My shell
My shellMy shell
My shell
 
Php
PhpPhp
Php
 
Adventures in Optimization
Adventures in OptimizationAdventures in Optimization
Adventures in Optimization
 
PHP and Rich Internet Applications
PHP and Rich Internet ApplicationsPHP and Rich Internet Applications
PHP and Rich Internet Applications
 
DBIx::Class beginners
DBIx::Class beginnersDBIx::Class beginners
DBIx::Class beginners
 
Mongo Presentation by Metatagg Solutions
Mongo Presentation by Metatagg SolutionsMongo Presentation by Metatagg Solutions
Mongo Presentation by Metatagg Solutions
 
Why Hacking WordPress Search Isn't Some Big Scary Thing
Why Hacking WordPress Search Isn't Some Big Scary ThingWhy Hacking WordPress Search Isn't Some Big Scary Thing
Why Hacking WordPress Search Isn't Some Big Scary Thing
 
php part 2
php part 2php part 2
php part 2
 
Zend Certification Preparation Tutorial
Zend Certification Preparation TutorialZend Certification Preparation Tutorial
Zend Certification Preparation Tutorial
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday Developer
 
PHP Tips & Tricks
PHP Tips & TricksPHP Tips & Tricks
PHP Tips & Tricks
 
Open Source Search: An Analysis
Open Source Search: An AnalysisOpen Source Search: An Analysis
Open Source Search: An Analysis
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutos
 
Not Really PHP by the book
Not Really PHP by the bookNot Really PHP by the book
Not Really PHP by the book
 
Drupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary EditionDrupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary Edition
 
PHP POWERPOINT SLIDES
PHP POWERPOINT SLIDESPHP POWERPOINT SLIDES
PHP POWERPOINT SLIDES
 
Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB
 

Mais de Jeremy Kendall

Game Changing Dependency Management
Game Changing Dependency ManagementGame Changing Dependency Management
Game Changing Dependency Management
Jeremy Kendall
 
Keeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro FrameworkKeeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro Framework
Jeremy Kendall
 
Keeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro frameworkKeeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro framework
Jeremy Kendall
 
Intro to #memtech PHP 2011-12-05
Intro to #memtech PHP   2011-12-05Intro to #memtech PHP   2011-12-05
Intro to #memtech PHP 2011-12-05
Jeremy Kendall
 
TDD in PHP - Memphis PHP 2011-08-25
TDD in PHP - Memphis PHP 2011-08-25TDD in PHP - Memphis PHP 2011-08-25
TDD in PHP - Memphis PHP 2011-08-25
Jeremy Kendall
 
Zend_Form to the Rescue - A Brief Introduction to Zend_Form
Zend_Form to the Rescue - A Brief Introduction to Zend_FormZend_Form to the Rescue - A Brief Introduction to Zend_Form
Zend_Form to the Rescue - A Brief Introduction to Zend_Form
Jeremy Kendall
 
Zero to Zend Framework in 10 minutes
Zero to Zend Framework in 10 minutesZero to Zend Framework in 10 minutes
Zero to Zend Framework in 10 minutes
Jeremy Kendall
 

Mais de Jeremy Kendall (11)

5 Ways to Awesome-ize Your (PHP) Code
5 Ways to Awesome-ize Your (PHP) Code5 Ways to Awesome-ize Your (PHP) Code
5 Ways to Awesome-ize Your (PHP) Code
 
Game Changing Dependency Management
Game Changing Dependency ManagementGame Changing Dependency Management
Game Changing Dependency Management
 
Keeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro frameworkKeeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro framework
 
Keeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro FrameworkKeeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro Framework
 
Keeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro frameworkKeeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro framework
 
Intro to #memtech PHP 2011-12-05
Intro to #memtech PHP   2011-12-05Intro to #memtech PHP   2011-12-05
Intro to #memtech PHP 2011-12-05
 
TDD in PHP - Memphis PHP 2011-08-25
TDD in PHP - Memphis PHP 2011-08-25TDD in PHP - Memphis PHP 2011-08-25
TDD in PHP - Memphis PHP 2011-08-25
 
Zend_Form to the Rescue - A Brief Introduction to Zend_Form
Zend_Form to the Rescue - A Brief Introduction to Zend_FormZend_Form to the Rescue - A Brief Introduction to Zend_Form
Zend_Form to the Rescue - A Brief Introduction to Zend_Form
 
Zero to ZF in 10 Minutes
Zero to ZF in 10 MinutesZero to ZF in 10 Minutes
Zero to ZF in 10 Minutes
 
A Brief Introduction to Zend_Form
A Brief Introduction to Zend_FormA Brief Introduction to Zend_Form
A Brief Introduction to Zend_Form
 
Zero to Zend Framework in 10 minutes
Zero to Zend Framework in 10 minutesZero to Zend Framework in 10 minutes
Zero to Zend Framework in 10 minutes
 

Último

Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Último (20)

Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Manulife - Insurer Innovation Award 2024
Manulife - Insurer Innovation Award 2024Manulife - Insurer Innovation Award 2024
Manulife - Insurer Innovation Award 2024
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 

Php 102: Out with the Bad, In with the Good

  • 1. PHP 102 Out with the Bad, In with the Good php[tek] 2013
  • 2. Who is this guy? Jeremy Kendall I love to code I love to take pictures I'm terribly forgetful I work at OpenSky
  • 3. Following Along You can follow along with the presentation code at github.com. https://github.com/jeremykendall/bookshelf Before = oh-the-horror After = much-better
  • 4. Why Did You Start Programming?
  • 5. I wanted to solve problems, but . . .
  • 6. . . . I frequently caused as many problems as I solved.
  • 7. “Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live.” http://c2.com/cgi/wiki?CodeForTheMaintainer
  • 8. “Alternatively, always code and comment in such a way that if someone a few notches junior picks up the code, they will take pleasure in reading and learning from it.” http://c2.com/cgi/wiki?CodeForTheMaintainer
  • 9. Let's Solve a Problem Together • We'll review a “typical” PHP application • Find horrible mistakes • Correct those mistakes • Make a cool improvement • Learn and apply better practices
  • 10. Typical application? • I've seen code similar to the samples I'll show in almost every app I've ever touched. • I've made the same mistakes in almost every app I wrote in my early days. • I'm guessing you've seen and made similar mistakes too.
  • 11. What's the Problem? • Crappy application • CRUD bookshelf • What does it do?: – Display books – Add books – Edit books – Delete books
  • 12. What's There? • Database (MySQL) • View (index.php) • Form (book-form.php) • Form processor (process-form.php) • Delete (delete-book.php)
  • 15. index.php – db connection $db = mysql_connect('localhost', 'testuser', 'testpass'); mysql_select_db('bookshelf', $db); $query = "SELECT * FROM bookshelf ORDER BY title"; $result = mysql_query($query);
  • 16. index.php – db connection $db = mysql_connect('localhost', 'testuser', 'testpass'); mysql_select_db('bookshelf', $db); $query = "SELECT * FROM bookshelf ORDER BY title"; $result = mysql_query($query);
  • 17. index.php – db connection $db = mysql_connect('localhost', 'testuser', 'testpass'); mysql_select_db('bookshelf', $db); $query = "SELECT * FROM bookshelf ORDER BY title"; $result = mysql_query($query);
  • 18. index.php – db connection $db = mysql_connect('localhost', 'testuser', 'testpass'); mysql_select_db('bookshelf', $db); $query = "SELECT * FROM bookshelf ORDER BY title"; $result = mysql_query($query);
  • 19. index.php – books table <?php while ($book = mysql_fetch_assoc($result)): ?> <tr> <td> <a href="book-form.php?id=<?php echo $book['id']; ?>"> <?php echo $book['title']; ?> </a> </td> <td> <?php echo $book['author']; ?> </td> </tr> <?php endwhile; ?>
  • 20. index.php – books table <?php while ($book = mysql_fetch_assoc($result)): ?> <tr> <td> <a href="book-form.php?id=<?php echo $book['id']; ?>"> <?php echo $book['title']; ?> </a> </td> <td> <?php echo $book['author']; ?> </td> </tr> <?php endwhile; ?>
  • 21. book-form.php // Database connection code $id = empty($_GET['id']) ? null : $_GET['id']; if ($id) { // Database connection code $query = "SELECT title, author " . "FROM bookshelf WHERE id = $id"; $result = mysql_query($query); $book = mysql_fetch_assoc($result); $title = $book['title']; $author = $book['author']; }
  • 22. book-form.php $id = empty($_GET['id']) ? null : $_GET['id']; if ($id) { // Database connection code $query = "SELECT title, author " . "FROM bookshelf WHERE id = $id"; $result = mysql_query($query); $book = mysql_fetch_assoc($result); $title = $book['title']; $author = $book['author']; }
  • 23. book-form.php $id = empty($_GET['id']) ? null : $_GET['id']; if ($id) { // Database connection code $query = "SELECT title, author " . "FROM bookshelf WHERE id = $id"; $result = mysql_query($query); $book = mysql_fetch_assoc($result); $title = $book['title']; $author = $book['author']; }
  • 24. book-form.php <input type="hidden" name="id" value="<?php echo $id; ?>" /> <input type="text" name="title" value="<?php echo $title; ?>" /> <input type="text" name="author" value="<?php echo $author; ?>" />
  • 25. book-form.php <input type="hidden" name="id" value="<?php echo $id; ?>" /> <input type="text" name="title" value="<?php echo $title; ?>" /> <input type="text" name="author" value="<?php echo $author; ?>" />
  • 26. process-book.php // Database connection code $ins = "INSERT INTO bookshelf (title, author) " ."VALUES ('{$_POST['title']}', '{$_POST['author']}')"; $up = "UPDATE bookshelf SET title = '{$_POST['title']}', " . "author = '{$_POST['author']}' WHERE id = {$_POST['id']}"; if (empty($_POST['id'])) { mysql_query($ins); } else { mysql_query($up); }
  • 27. process-book.php // Database connection code $ins = "INSERT INTO bookshelf (title, author) " ."VALUES ('{$_POST['title']}', '{$_POST['author']}')"; $up = "UPDATE bookshelf SET title = '{$_POST['title']}', " . "author = '{$_POST['author']}' WHERE id = {$_POST['id']}"; if (empty($_POST['id'])) { mysql_query($ins); } else { mysql_query($up); }
  • 28. process-book.php // Database connection code $ins = "INSERT INTO bookshelf (title, author) " ."VALUES ('{$_POST['title']}', '{$_POST['author']}')"; $up = "UPDATE bookshelf SET title = '{$_POST['title']}', " . "author = '{$_POST['author']}' WHERE id = {$_POST['id']}"; if (empty($_POST['id'])) { mysql_query($ins); } else { mysql_query($up); }
  • 29. process-book.php // Database connection code $ins = "INSERT INTO bookshelf (title, author) " ."VALUES ('{$_POST['title']}', '{$_POST['author']}')"; $up = "UPDATE bookshelf SET title = '{$_POST['title']}', " . "author = '{$_POST['author']}' WHERE id = {$_POST['id']}"; if (empty($_POST['id'])) { mysql_query($ins); } else { mysql_query($up); }
  • 30. process-book.php // Database connection code $ins = "INSERT INTO bookshelf (title, author) " ."VALUES ('{$_POST['title']}', '{$_POST['author']}')"; $up = "UPDATE bookshelf SET title = '{$_POST['title']}', " . "author = '{$_POST['author']}' WHERE id = {$_POST['id']}"; if (empty($_POST['id'])) { mysql_query($ins); } else { mysql_query($up); }
  • 31.
  • 32. Glaring Problems? • Code duplication • Input isn't filtered • Output isn't escaped • Vendor specific functions • User-provided data used in SQL • Did you see any others?
  • 33. Deprecated! • mysql extension deprecated as of 5.5.0 • Use mysqli or PDO • Read the MySQL “Choosing an API” on php.net: http://bit.ly/13zur2e
  • 34. Code Duplication $db = mysql_connect('localhost', 'testuser', 'testpass'); mysql_select_db('bookshelf', $db); • Violates DRY principle • Maintenance nightmare • The next developer will want to kill you • And your family • And your pets
  • 35. Consolidate Let's throw all that duplicated code into an include file, say library/base.php. (We'll add a few other handy items while we're in there)
  • 36. base.php // . . . snip . . . $db = mysql_connect('localhost', 'testuser', 'testpass'); mysql_select_db('bookshelf', $db);
  • 37. Replace db info with base.php Remove the db connection code from each file with: require_once dirname(__FILE__) . '/library/base.php';
  • 38. STOP!
  • 39. PDO • PHP Data Objects • Lightweight, consistent interface • Data access abstraction • Not database abstraction http://www.php.net/manual/en/intro.pdo.php
  • 40. base.php // . . . snip . . . $options = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ); $dsn = 'mysql:host=localhost;dbname=bookshelf;charset=utf8'; $username = 'testuser'; $password = 'testpass'; try { $dbh = new PDO($dsn, $username, $password, $options); } catch (PDOException $e) { error_log($e->getMessage(), 3, '../logs/errors.log'); echo 'An error occurred while trying to connect to the database.'; }
  • 41. base.php // . . . snip . . . $options = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ); $dsn = 'mysql:host=localhost;dbname=bookshelf;charset=utf8'; $username = 'testuser'; $password = 'testpass'; try { $dbh = new PDO($dsn, $username, $password, $options); } catch (PDOException $e) { error_log($e->getMessage(), 3, '../logs/errors.log'); echo 'An error occurred while trying to connect to the database.'; }
  • 42. base.php // . . . snip . . . $options = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ); $dsn = 'mysql:host=localhost;dbname=bookshelf;charset=utf8'; $username = 'testuser'; $password = 'testpass'; try { $dbh = new PDO($dsn, $username, $password, $options); } catch (PDOException $e) { error_log($e->getMessage(), 3, '../logs/errors.log'); echo 'An error occurred while trying to connect to the database.'; }
  • 43. base.php // . . . snip . . . $options = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ); $dsn = 'mysql:host=localhost;dbname=bookshelf;charset=utf8'; $username = 'testuser'; $password = 'testpass'; try { $dbh = new PDO($dsn, $username, $password, $options); } catch (PDOException $e) { error_log($e->getMessage(), 3, '../logs/errors.log'); echo 'An error occurred while trying to connect to the database.'; }
  • 44. Now start replacing mysql_* // index.php $books = $dbh->query($sql)->fetchAll(); // book-form.php $book = $dbh->query($sql)->fetch(); // process-book.php and delete-book.php $dbh->exec($sql);
  • 45. Now on to the “handy items” I promised
  • 46. base.php $env = getenv('APPLICATION_ENVIRONMENT'); if (!$env) { $env = "production"; } date_default_timezone_set('America/Chicago'); if ($env === "develop") { error_reporting(-1); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); } // . . . snip . . .
  • 47. base.php $env = getenv('APPLICATION_ENVIRONMENT'); if (!$env) { $env = "production"; } date_default_timezone_set('America/Chicago'); if ($env === "develop") { error_reporting(-1); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); } // . . . snip . . .
  • 48. base.php $env = getenv('APPLICATION_ENVIRONMENT'); if (!$env) { $env = "production"; } date_default_timezone_set('America/Chicago'); if ($env === "develop") { error_reporting(-1); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); } // . . . snip . . .
  • 49. base.php $env = getenv('APPLICATION_ENVIRONMENT'); if (!$env) { $env = "production"; } date_default_timezone_set('America/Chicago'); if ($env === "develop") { error_reporting(-1); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); } // . . . snip . . .
  • 51.
  • 52. Besides getting shot . . . “Whenever PHP generates an error message internally, it's processed and formatted all the way up to the fully formatted message that can be outputted straight to the browser. Only just before it is displayed the error_reporting setting is checked.” http://derickrethans.nl/five-reasons-why-the-shutop-operator-should-be-avoided.html
  • 53. We echo $title and $author <input type="hidden" name="id" value="<?php echo $id; ?>" /> <input type="text" name="title" value="<?php echo $title; ?>" /> <input type="text" name="author" value="<?php echo $author; ?>" />
  • 54. Without defining $title and $author require_once dirname(__FILE__) . '/library/base.php'; $id = empty($_GET['id']) ? null : $_GET['id']; if ($id) { $book = $dbh->query( . . . )->fetch(); $title = $book['title']; $author = $book['author']; }
  • 55. Super easy to fix $id = empty($_GET['id']) ? null : $_GET['id']; $title = null; $author = null; if ($id) { $book = $dbh->query( . . . )->fetch(); $title = $book['title']; $author = $book['author']; }
  • 56. FIEO • Filter input – Your users want to destroy your app – Prevent SQL injection • Escape output – Or just destroy your app yourself – Defend against XSS
  • 58. book-form.php Use filter_input() to guarantee $id is either false or an int. $id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
  • 59. process-book.php More filter_input() $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT); $title = filter_input(INPUT_POST, 'title', FILTER_SANITIZE_STRING); $author = filter_input(INPUT_POST, 'author', FILTER_SANITIZE_STRING);
  • 60. index.php Use htmlspecialchars() to escape output htmlspecialchars($book['title'], ENT_COMPAT, 'UTF-8'); ?> htmlspecialchars($book['author'], ENT_COMPAT, 'UTF-8'); ?>
  • 61. Prepared Statements • Uses fewer resources • Runs faster • Protects against SQL injection • Easier to read and maintain
  • 62. book-form.php: Before $book = $dbh->query("SELECT title, author FROM bookshelf WHERE id = $id")->fetch();
  • 63. book-form.php: After $statement = $dbh->prepare('SELECT title, author FROM bookshelf WHERE id = :id'); $statement->bindParam(':id', $id); $statement->execute(); $book = $statement->fetch();
  • 64. book-form.php: After $statement = $dbh->prepare('SELECT title, author FROM bookshelf WHERE id = :id'); $statement->bindParam(':id', $id); $statement->execute(); $book = $statement->fetch();
  • 65. book-form.php: After $statement = $dbh->prepare('SELECT title, author FROM bookshelf WHERE id = :id'); $statement->bindParam(':id', $id); $statement->execute(); $book = $statement->fetch();
  • 66. book-form.php: After $statement = $dbh->prepare('SELECT title, author FROM bookshelf WHERE id = :id'); $statement->bindParam(':id', $id); $statement->execute(); $book = $statement->fetch();
  • 67. book-form.php: After $statement = $dbh->prepare('SELECT title, author FROM bookshelf WHERE id = :id'); $statement->bindParam(':id', $id); $statement->execute(); $book = $statement->fetch();
  • 68. process-book.php: Before if (empty($_POST['id'])) { $sql = "INSERT INTO bookshelf (title, author) " ."VALUES ('{$_POST['title']}', '{$_POST['author']}')"; $dbh->exec($sql); } else { $sql = "UPDATE bookshelf SET title = '{$_POST['title']}', " . "author = '{$_POST['author']}' WHERE id = {$_POST['id']}"; $dbh->exec($sql); }
  • 69. process-book.php: After if ($id) { $statement = $dbh->prepare("UPDATE bookshelf SET title = :title, author = :author WHERE id = :id"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->bindParam(':id', $id); $statement->execute(); } else { $statement = $dbh->prepare("INSERT INTO bookshelf (title, author) VALUES (:title, :author)"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->execute(); }
  • 70. process-book.php: After if ($id) { $statement = $dbh->prepare("UPDATE bookshelf SET title = :title, author = :author WHERE id = :id"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->bindParam(':id', $id); $statement->execute(); } else { $statement = $dbh->prepare("INSERT INTO bookshelf (title, author) VALUES (:title, :author)"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->execute(); }
  • 71. process-book.php: After if ($id) { $statement = $dbh->prepare("UPDATE bookshelf SET title = :title, author = :author WHERE id = :id"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->bindParam(':id', $id); $statement->execute(); } else { $statement = $dbh->prepare("INSERT INTO bookshelf (title, author) VALUES (:title, :author)"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->execute(); }
  • 72. process-book.php: After if ($id) { $statement = $dbh->prepare("UPDATE bookshelf SET title = :title, author = :author WHERE id = :id"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->bindParam(':id', $id); $statement->execute(); } else { $statement = $dbh->prepare("INSERT INTO bookshelf (title, author) VALUES (:title, :author)"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->execute(); }
  • 73. Service Layer Let's replace all that baked in DB stuff with a Service Layer
  • 74. Why? • Introduce some OOP principles • High ROI – Maintainability – Testability
  • 75. What? Where? • Class name: BookshelfService • Namespace: BookshelfService • Location: library/Bookshelf/Service • Filename: BookshelfService.php • PSR-0 compliant (The class name should mirror its location in the file system)
  • 76. BookshelfService.php namespace BookshelfService; class BookshelfService { private $dbh; public function __construct(PDO $dbh) {} public function find($id) {} public function findAll() {} public function save(array $options) {} public function delete($id) {} }
  • 77. BookshelfService.php private $dbh; public function __construct(PDO $dbh) { $this->dbh = $dbh; }
  • 78. BookshelfService.php public function find($id) { $sql = 'SELECT * FROM bookshelf WHERE id = :id'; $statement = $this->dbh->prepare($sql); $statement->bindParam(':id', $id); $statement->execute(); return $statement->fetch(); } public function findAll() { $sql = 'SELECT * FROM bookshelf ORDER BY title'; return $this->dbh->query($sql)->fetchAll(); }
  • 79. BookshelfService.php public function save(array $options) { if ($options['id']) { $statement = $this->dbh->prepare("UPDATE bookshelf SET title = :title, author = :author WHERE id = :id"); $statement->execute($options); } else { unset($options['id']); $statement = $this->dbh->prepare("INSERT INTO bookshelf (title, author) VALUES (:title, :author)"); $statement->execute($options); } }
  • 80. A New Class Means . . . . . . we need to add a few things to base.php.
  • 82. base.php function autoload($className) { // PSR-0 autoloader } spl_autoload_register('autoload'); https://gist.github.com/jwage/221634
  • 83. base.php // database connection code $bookshelf = new BookshelfServiceBookshelfService($dbh);
  • 84. And now for some before and after shots . . .
  • 85. index.php: Before require_once dirname(__FILE__) . '/library/base.php'; $books = $dbh->query("SELECT * FROM bookshelf ORDER BY title")->fetchAll();
  • 86. index.php: After require_once dirname(__FILE__) . '/library/base.php'; $books = $bookshelf->findAll();
  • 87. book-form.php: Before if ($id) { $statement = $dbh->prepare('SELECT title, author FROM bookshelf WHERE id = :id'); $statement->bindParam(':id', $id); $statement->execute(); $book = $statement->fetch(); $title = $book['title']; $author = $book['author']; }
  • 88. book-form.php: After if ($id) { $book = $bookshelf->find($id); $title = $book['title']; $author = $book['author']; }
  • 89. process-book.php: Before if ($id) { $statement = $dbh->prepare("UPDATE bookshelf SET title = :title, author = :author WHERE id = :id"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->bindParam(':id', $id); $statement->execute(); } else { $statement = $dbh->prepare("INSERT INTO bookshelf (title, author) VALUES (:title, :author)"); $statement->bindParam(':title', $title); $statement->bindParam(':author', $author); $statement->execute(); }
  • 90. process-book.php: After $book = array( 'id' => $id, 'title' => $title, 'author' => $author ); $bookshelf->save($book);
  • 91. What Have We Done? • Discovered we've been handed a disaster • Iteratively improved on the nightmare • Seen some nice features of PHP – PDO – Filtering and escaping – OOP • Learned something?
  • 92. What more could we do? • Global config file • Environment config file • Unit and functional tests • Don't roll your own! • Add Composer, add libraries as needed
  • 93. Want More? • Presentation source code: https://github.com/jeremykendall/bookshelf • PHP Manual: http://www.php.net/manual/en/index.php • PHP The Right Way: http://www.phptherightway.com/ • PHP 101 Suggestions: http://csiphp.com/blog/2011/07/19/stop-doing-it-wrong-and-learn-to-code-good-too/ • PHP 101: PHP for the Absolute Beginner: http://devzone.zend.com/6/php-101-php-for-the-absolute-beginner/ • PHPDeveloper.org http://phpdeveloper.org/ • php|architect: http://www.phparch.com/ • Composer http://getcomposer.org