<?php

//-------------------------------------------------------------------
// gRESTbookData.php: handle the data for gRESTbook
//-------------------------------------------------------------------
// For more info, see: http://muellerware.org/projects/gRESTbook/
//-------------------------------------------------------------------
// Copyright (c) 2007 Patrick Mueller
// Using the MIT license: 
//    http://www.opensource.org/licenses/mit-license.php
//-------------------------------------------------------------------
// 2007/03/xx - Patrick Mueller - pmuellr@yahoo.com
//-------------------------------------------------------------------

// json encoder/decoder from Zend Framework
require_once 'Zend/Json.php';

//-------------------------------------------------------------------
// This class models the data associated with the gRESTbook
// application.
//-------------------------------------------------------------------
class GRESTbookEntry {
    
// first three fields are meta-data; end up in the URL or
    // headers of HTTP requests/responses
    
public $id;            // integer: id of the entry
    
public $dateModified;  // integer: epoch seconds when entry modified
    
public $eTag;          // string:  hash of author::message

    // next two fields are the business data for the class
    
public $author;        // string:  author of the entry
    
public $message;       // string:  message of the entry
    
    //---------------------------------------------------------------
    // Create from a JSON string - return a new instance of this
    // class given a JSON string, but only sets the author and
    // message fields; remaining fields from HTTP request info.
    // Throw exception if invalid.
    //---------------------------------------------------------------
    
static public function fromJSON($json) {
        
$map Zend_Json::decode($json);
        
        if (!isset(
$map['author']))  throw new Exception('author property is not set');
        if (!isset(
$map['message'])) throw new Exception('message property is not set');
        
        
$result = new GRESTbookEntry();
        
        
$result->author  $map['author'];
        
$result->message $map['message'];

        
$result->validate();
                
        return 
$result;
    }
    
    
//---------------------------------------------------------------
    // Throw an exception if the business data is invalid.
    //---------------------------------------------------------------
    
public function validate() {
        if (!
is_string($this->author))  throw new Exception('author is not a string');
        if (!
is_string($this->message)) throw new Exception('message is not a string');

        if (
$this->author == '')  throw new Exception('author is an empty string');
        if (
$this->message == '') throw new Exception('message is an empty string');
        
        return 
'';
    }

    
//---------------------------------------------------------------
    // Return a hash of the object (used for eTag)
    //---------------------------------------------------------------
    
public function hash() {
        return 
md5($this->author '::' $this->message);
    }
    
    
//---------------------------------------------------------------
    // Return JSON string, consisting of the author and message
    // fields (remaining fields available via HTTP request or response).
    //---------------------------------------------------------------
    
public function toJSON() {
        
$map = array();
        
        
$map['author']  = $this->author;
        
$map['message'] = $this->message;
        
        return 
Zend_Json::encode($map);
    }
    
    
//---------------------------------------------------------------
    // Return a human readable string representation
    //---------------------------------------------------------------
    
public function __toString() {
        
$result  __CLASS__ '{' "\n";
        
$result .= '   id:           ' $this->id "\n";
        
$result .= '   dateModified: ' $this->dateModified "\n";
        
$result .= '   eTag:         ' $this->eTag "\n";
        
$result .= '   author:       ' $this->author "\n";
        
$result .= '   message:      ' $this->message "\n";
        
$result .= '}';
        
        return 
$result;
    }
}

//-------------------------------------------------------------------
// This class handles the database access for the GRESTbook data.
//-------------------------------------------------------------------
class GRESTbookDB {

    
// our PDO database handle
    
private $db;
    
    
//---------------------------------------------------------------
    // Connect to the database when the object is instantiated
    //---------------------------------------------------------------
    
public function __construct() {
        
$this->db = new PDO('sqlite:gRESTbook.sq3''''');
    }
    
    
//---------------------------------------------------------------
    // Disconnect from the database when object is GC'd
    //---------------------------------------------------------------
    
public function __destruct() {
        
$this->db null;
    }

    
//---------------------------------------------------------------
    // CREATE a new GRESTbook entry.  Expects the author and message
    // fields to be set; dateModified, eTag and id will be available
    // in the object returned, which will be the same object passed
    // in.
    // 
    // GRESTbookEntry on input:
    //    id:           ignored
    //    dateModified: ignored
    //    eTag          ignored
    //    author:       value to set in DB
    //    message:        value to set in DB
    // 
    // GRESTbookEntry on output:
    //    id:           id of new entry
    //    dateModified: current date
    //    eTag          hash of author::message
    //    author:       unchanged from input
    //    message:        unchanged from output
    //
    // Returns the input object, suitably updated, on success,
    // and null on failure.
    //
    // Can also throw an exception, including if the data is invalid.
    //---------------------------------------------------------------
    
public function create($gRESTbookEntry) {

        
// validate input
        
$gRESTbookEntry->validate();

        
$db $this->db;
        
        
// set up the SQL statement
        
$cmd = <<<CMD
            insert into ENTRIES 
                (ID, DATE_MODIFIED, ETAG, AUTHOR, MESSAGE)
                values (?,?,?,?,?)
CMD;
        
$stmt $db->prepare($cmd);
        if (
false == $stmt) {
            
$err $db->errorInfo();
            throw new 
Exception("Error creating insert statement: " $err[2]);
        }

        
// set the dateModified and eTag fields
        
$newDate time();
        
$newETag $gRESTbookEntry->hash();

        
// parameters for prepared statement
        
$vars = array(
            
NULL,
            
$newDate,
            
$newETag,
            
$gRESTbookEntry->author,
            
$gRESTbookEntry->message
        
);
        
        
// execute, returning early if failure
        
$result $stmt->execute($vars);
        if (!
$result) {
            return 
null;
        }
        
        
// if succcess, update fields in our data
        
$gRESTbookEntry->id           $db->lastInsertId();
        
$gRESTbookEntry->dateModified $newDate;
        
$gRESTbookEntry->eTag         $newETag;
        
        return 
$gRESTbookEntry;
    }
    
    
//---------------------------------------------------------------
    // READ a GRESTbook entry given an id.  
    //
    // GRESTbookEntry on output:
    //    id:           same as input $id
    //    dateModified: value from DB
    //    eTag          value from DB
    //    author:       value from DB
    //    message:        value from DB
    //
    // Returns a fully populated object if found, otherwise null.
    //
    // Can also throw an exception.
    //---------------------------------------------------------------
    
public function read($id) {
        
$db $this->db;
        
        
// setup the SQL statement
        
$cmd = <<<CMD
            select 
                DATE_MODIFIED, ETAG, AUTHOR, MESSAGE
            from ENTRIES
            where ID = ?
CMD;
        
$stmt $db->prepare($cmd);
        if (
false == $stmt) {
            
$err $db->errorInfo();
            throw new 
Exception("Error creating select statement: " $err[2]);
        }
        
        
// execute the SQL
        
$stmt->execute(array($id));
        
$fetchResult $stmt->fetch(PDO::FETCH_NUM);
        
        
// early return if we didn't find it
        
if (!$fetchResult) {
            return 
null;
        }

        
// found the entry!  Set it up.
        
$result = new GRESTbookEntry();
    
        
$result->id           $id;
        
$result->dateModified $fetchResult[0];
        
$result->eTag         $fetchResult[1];
        
$result->author       $fetchResult[2];
        
$result->message      $fetchResult[3];

        return 
$result;
    }
    
    
//---------------------------------------------------------------
    // UPDATE a GRESTbook entry.  Note that to prevent entries
    // from inadvertantly being updated, the eTag value passed in
    // must equal the eTag value of the entry currently in the
    // database.
    // 
    // GRESTbookEntry on input:
    //    id:           id of entry to update
    //    dateModified: ignored
    //    eTag          eTag of previous version of GRESTbookEntry
    //    author:       value to set in DB
    //    message:        value to set in DB
    // 
    // GRESTbookEntry on input:
    //    id:           unchanged from input
    //    dateModified: current date
    //    eTag          hash of new author::message
    //    author:       unchanged from input
    //    message:        unchanged from input
    //
    // Returns the input object, suitably updated, on success,
    // and null on failure.
    //
    // Can also throw an exception, including if the data is invalid.
    //---------------------------------------------------------------
    
public function update($gRESTbookEntry) {
        
        
// validate the data
        
$gRESTbookEntry->validate();
                
        
$db $this->db;
        
        
// new eTag and dateModified fields
        
$newETag $gRESTbookEntry->hash();
        
$newDate time();
        
        
// setup the SQL 
        
$cmd = <<<CMD
            update ENTRIES SET
                DATE_MODIFIED = ?,
                ETAG          = ?,
                AUTHOR        = ?,
                MESSAGE       = ?
            where 
                (ID == ?) AND (ETAG == ?)
CMD;
        
$stmt $db->prepare($cmd);
        if (
false == $stmt) {
            
$err $db->errorInfo();
            throw new 
Exception("Error creating update statement: " $err[2]);
        }
        
        
// execute the SQL
        
$vars = array(
            
$newDate,
            
$newETag,
            
$gRESTbookEntry->author,
            
$gRESTbookEntry->message,
            
$gRESTbookEntry->id,
            
$gRESTbookEntry->eTag,
            );
            
        
$result $stmt->execute($vars);
        
        
// return early if not found.
        
if (== $result) {
            return 
null;
        }
        if (
== $stmt->rowCount()) {
            return 
null;
        }
        

        
// update the dateModified and eTag fiels in the object
        
$gRESTbookEntry->dateModified time();
        
$gRESTbookEntry->eTag         $newETag;
        
        return 
$gRESTbookEntry;
    }

    
//---------------------------------------------------------------
    // DELETE a GRESTbook entry.  Note that to prevent entries
    // from inadvertantly being updated, the eTag value passed in
    // must equal the eTag value of the entry currently in the
    // database.
    //
    // Returns true if successful, false on failure.
    //---------------------------------------------------------------
    
public function delete($id$eTag) {
        
$db $this->db;
        
        
// setup SQL
        
$cmd = <<<CMD
            delete from ENTRIES 
            where (ID = ?) AND (ETAG = ?)
CMD;
        
$stmt $db->prepare($cmd);
        if (
false == $stmt) {
            
$err $db->errorInfo();
            throw new 
Exception("Error creating delete statement: " $err[2]);
        }
        
        
// execute SQL
        
$result $stmt->execute(array($id$eTag));
        
        
// return early on error
        
if (!$result) {
            return 
false;
        }
        if (
== $stmt->rowCount()) {
            return 
false;
        }

        return 
true;
    }

    
//---------------------------------------------------------------
    // READ the GRESTbook entries.  
    //
    // Returns an array of id's to the GRESTBook entries.
    //---------------------------------------------------------------
    
public function query() {
        
$db $this->db;
        
        
// setup the SQL
        
$cmd = <<<CMD
            select 
                ID 
            from ENTRIES
CMD;
        
$stmt $db->prepare($cmd);
        if (
false == $stmt) {
            
$err $db->errorInfo();
            throw new 
Exception("Error creating select statement: " $err[2]);
        }
        
        
// execute the SQL, return the output
        
$stmt->execute();
        
$result $stmt->fetchAll(PDO::FETCH_COLUMN0);

        return 
$result;
    }
    
}