• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Php 102: Out with the Bad, In with the Good
 

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

on

  • 776 views

In this session, we'll look at a typical PHP application, review a few of the horrible mistakes the fictional developer made, and then refactor the app according to some best practices. Along the way ...

In this session, we'll look at a typical PHP application, review a few of the horrible mistakes the fictional developer made, and then refactor the app according to some best practices. Along the way you might even learn a thing or two about PHP you don't already know.

Statistics

Views

Total Views
776
Views on SlideShare
768
Embed Views
8

Actions

Likes
3
Downloads
13
Comments
0

2 Embeds 8

http://librosweb.es 7
https://twitter.com 1

Accessibility

Categories

Upload Details

Uploaded via as OpenOffice

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

    • PHP 102Out with the Bad,In with the Goodphp[tek] 2013
    • Who is this guy?Jeremy KendallI love to codeI love to take picturesIm terribly forgetfulI work at OpenSky
    • Following AlongYou can follow along with the presentationcode at github.com.https://github.com/jeremykendall/bookshelfBefore = oh-the-horrorAfter = much-better
    • Why Did YouStart Programming?
    • I wanted to solve problems, but . . .
    • . . . I frequently causedas many problemsas I solved.
    • “Always code as if the person who ends upmaintaining your code is a violent psychopathwho knows where you live.”http://c2.com/cgi/wiki?CodeForTheMaintainer
    • “Alternatively, always code and comment in sucha way that if someone a few notches junior picksup the code, they will take pleasure in readingand learning from it.”http://c2.com/cgi/wiki?CodeForTheMaintainer
    • Lets Solve a Problem Together• Well review a “typical” PHP application• Find horrible mistakes• Correct those mistakes• Make a cool improvement• Learn and apply better practices
    • Typical application?• Ive seen code similar to the samples Ill showin almost every app Ive ever touched.• Ive made the same mistakes in almost everyapp I wrote in my early days.• Im guessing youve seen and made similarmistakes too.
    • Whats the Problem?• Crappy application• CRUD bookshelf• What does it do?:– Display books– Add books– Edit books– Delete books
    • Whats 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 isnt filtered• Output isnt 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” onphp.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
    • ConsolidateLets throw all that duplicated code into aninclude file, say library/base.php.(Well add a few other handy items while were in there)
    • base.php// . . . snip . . .$db = mysql_connect(localhost, testuser, testpass);mysql_select_db(bookshelf, $db);
    • Replace db info with base.phpRemove the db connection code from eachfile with:require_once dirname(__FILE__) . /library/base.php;
    • STOP!
    • PDO• PHP Data Objects• Lightweight, consistent interface• Data access abstraction• Not database abstractionhttp://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 thedatabase.;}
    • 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 thedatabase.;}
    • 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 thedatabase.;}
    • 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 thedatabase.;}
    • 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 messageinternally, its processed and formatted all theway up to the fully formatted message that canbe outputted straight to the browser. Only justbefore it is displayed the error_reporting settingis 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 $authorrequire_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.phpUse filter_input() to guarantee $id iseither false or an int.$id = filter_input(INPUT_GET, id, FILTER_VALIDATE_INT);
    • process-book.phpMore 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.phpUse htmlspecialchars() to escapeoutputhtmlspecialchars($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 FROMbookshelf WHERE id = $id")->fetch();
    • book-form.php: After$statement = $dbh->prepare(SELECT title, author FROMbookshelf WHERE id = :id);$statement->bindParam(:id, $id);$statement->execute();$book = $statement->fetch();
    • book-form.php: After$statement = $dbh->prepare(SELECT title, author FROMbookshelf WHERE id = :id);$statement->bindParam(:id, $id);$statement->execute();$book = $statement->fetch();
    • book-form.php: After$statement = $dbh->prepare(SELECT title, author FROMbookshelf WHERE id = :id);$statement->bindParam(:id, $id);$statement->execute();$book = $statement->fetch();
    • book-form.php: After$statement = $dbh->prepare(SELECT title, author FROMbookshelf WHERE id = :id);$statement->bindParam(:id, $id);$statement->execute();$book = $statement->fetch();
    • book-form.php: After$statement = $dbh->prepare(SELECT title, author FROMbookshelf WHERE id = :id);$statement->bindParam(:id, $id);$statement->execute();$book = $statement->fetch();
    • process-book.php: Beforeif (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: Afterif ($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: Afterif ($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: Afterif ($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: Afterif ($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 LayerLets replace all that baked in DB stuffwith 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 filesystem)
    • BookshelfService.phpnamespace 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.phpprivate $dbh;public function __construct(PDO $dbh){$this->dbh = $dbh;}
    • BookshelfService.phppublic 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.phppublic function save(array $options){if ($options[id]) {$statement = $this->dbh->prepare("UPDATE bookshelfSET title = :title, author = :author WHERE id = :id");$statement->execute($options);} else {unset($options[id]);$statement = $this->dbh->prepare("INSERT INTObookshelf (title, author) VALUES (:title, :author)");$statement->execute($options);}}
    • A New Class Means . . .. . . we need to add a few things to base.php.
    • base.phpset_include_path(implode(PATH_SEPARATOR, array(get_include_path(),dirname(__FILE__))));
    • base.phpfunction 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 aftershots . . .
    • index.php: Beforerequire_once dirname(__FILE__) . /library/base.php;$books = $dbh->query("SELECT * FROM bookshelf ORDER BYtitle")->fetchAll();
    • index.php: Afterrequire_once dirname(__FILE__) . /library/base.php;$books = $bookshelf->findAll();
    • book-form.php: Beforeif ($id) {$statement = $dbh->prepare(SELECT title, author FROMbookshelf WHERE id = :id);$statement->bindParam(:id, $id);$statement->execute();$book = $statement->fetch();$title = $book[title];$author = $book[author];}
    • book-form.php: Afterif ($id) {$book = $bookshelf->find($id);$title = $book[title];$author = $book[author];}
    • process-book.php: Beforeif ($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 weve 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• Dont 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!@JeremyKendalljeremy@jeremykendall.nethttp://joind.in/8188