Planning and Developing the Core Framework [ 32 ] Connecting to the database and managing connections In order to connect to multiple databases, we need to maintain a record of the different connections. This can be done by storing each connection resource in an array, keeping a record as to which of the items in this array is the active connection. When a query is executed, it will perform the query against the currently active connection. <?php /** * Database management / access class: basic abstraction * * @author Michael Peacock * @version 1.0 */ class Mysqldb { /** * Allows multiple database connections * each connection is stored as an element in the array, and the active connection is maintained in a variable (see below) */ private $connections = array(); /** * Tells the DB object which connection to use * setActiveConnection($id) allows us to change this */ private $activeConnection = 0; /** * Queries which have been executed and the results cached for later, primarily for use within the template engine */ private $queryCache = array(); /** * Data which has been prepared and then cached for later usage, primarily within the template engine */ private $dataCache = array(); /** * Number of queries made during execution process */ private $queryCounter = 0; Download from Wow! eBook <www.wowebook.com> Chapter 2 [ 33 ] /** * Record of the last query */ private $last; /** * Reference to the registry object */ private $registry; /** * Construct our database object */ public function __construct( Registry $registry ) { $this->registry = $registry; } To connect to the database, we pass the database server host, username, and password and of course the name of the database we wish to connect to. The resulting connection is stored in our connections array, and the connection ID (Array key) is returned. /** * Create a new database connection * @param String database hostname * @param String database username * @param String database password * @param String database we are using * @return int the id of the new connection */ public function newConnection( $host, $user, $password, $database ) { $this->connections[] = new mysqli( $host, $user, $password, $database ); $connection_id = count( $this->connections )-1; if( mysqli_connect_errno() ) { trigger_error('Error connecting to host. '.$this- >connections[$connection_id]->error, E_USER_ERROR); } return $connection_id; } Download from Wow! eBook <www.wowebook.com> Planning and Developing the Core Framework [ 34 ] When we need to swap between connections, for example, to look up data from an external source, or authenticate against another system, we need to tell the database object to use this different connection. This is achieved through the setActiveConnection method. /** * Change which database connection is actively used for the next operation * @param int the new connection id * @return void */ public function setActiveConnection( int $new ) { $this->activeConnection = $new; } Executing queries After a query is executed, we may wish to get the rows from the result of the query; to allow us to do this, we simply store the result of the query in the classes $last variable, so that it can be accessed by other methods. /** * Execute a query string * @param String the query * @return void */ public function executeQuery( $queryStr ) { if( !$result = $this->connections[$this->activeConnection]- >query( $queryStr ) ) { trigger_error('Error executing query: ' . $queryStr .' - '.$this->connections[$this->activeConnection]->error, E_USER_ERROR); } else { $this->last = $result; } } Download from Wow! eBook <www.wowebook.com> Chapter 2 [ 35 ] When we do need to get the results from a query, we simply call the MySQLi fetch_ array method on the result stored in the last variable. /** * Get the rows from the most recently executed query, excluding cached queries * @return array */ public function getRows() { return $this->last->fetch_array(MYSQLI_ASSOC); } Simplifying common queries Common queries such as INSERT, UPDATE, and DELETE are often very repetitive; however, they are quite easy to abstract the basics of into our database management class. This won't work for all situations, but should make our lives easier for the bulk of these operations. We can abstract select queries to this class too. However, these are much more complicated, particularly, as we will more often than not, need to utilize more complicated logic, such as sub-queries, joins, and aliases. This more complicated logic would need to be developed into the code. Deleting records can be done simply using the table name, conditions, and a limit. In some cases, a limit may not be required, so if a non-empty string is passed, we need to add the LIMIT keyword to the query. /** * Delete records from the database * @param String the table to remove rows from * @param String the condition for which rows are to be removed * @param int the number of rows to be removed * @return void */ public function deleteRecords( $table, $condition, $limit ) { $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit; $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}"; $this->executeQuery( $delete ); } Download from Wow! eBook <www.wowebook.com> Planning and Developing the Core Framework [ 36 ] Updating and inserting records are tasks I nd to be the most cumbersome; however, we can easily abstract these simply by passing the table name, an array of eld names and eld value pairs, and in the case of update operations, a condition. /** * Update records in the database * @param String the table * @param array of changes field => value * @param String the condition * @return bool */ public function updateRecords( $table, $changes, $condition ) { $update = "UPDATE " . $table . " SET "; foreach( $changes as $field => $value ) { $update .= "`" . $field . "`='{$value}',"; } // remove our trailing , $update = substr($update, 0, -1); if( $condition != '' ) { $update .= "WHERE " . $condition; } $this->executeQuery( $update ); return true; } /** * Insert records into the database * @param String the database table * @param array data to insert field => value * @return bool */ public function insertRecords( $table, $data ) { // setup some variables for fields and values $fields = ""; $values = ""; // populate them Download from Wow! eBook <www.wowebook.com> Chapter 2 [ 37 ] foreach ($data as $f => $v) { $fields .= "`$f`,"; $values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',"; } // remove our trailing , $fields = substr($fields, 0, -1); // remove our trailing , $values = substr($values, 0, -1); $insert = "INSERT INTO $table ({$fields}) VALUES({$values})"; //echo $insert; $this->executeQuery( $insert ); return true; } Sanitizing data Depending on the exact PHP setup, data needs to be sanitized slightly differently, to prevent characters being escaped too many times. This is often the result of magic_quotes_gpc setting. To make things easier, and to provide a single place for changes to be made depending on our server's conguration, we can centralize our data sanitization. /** * Sanitize data * @param String the data to be sanitized * @return String the sanitized data */ public function sanitizeData( $value ) { // Stripslashes if ( get_magic_quotes_gpc() ) { $value = stripslashes ( $value ); } // Quote value if ( version_compare( phpversion(), "4.3.0" ) == "-1" ) { $value = $this->connections[$this->activeConnection]- Download from Wow! eBook <www.wowebook.com> Planning and Developing the Core Framework [ 38 ] >escape_string( $value ); } else { $value = $this->connections[$this->activeConnection]- >real_escape_string( $value ); } return $value; } Wrapping other MySQLi functions This leaves us with a few other common MySQLi functions to wrap into our class, including fetching the data from the executed query, fetching the number of rows returned by a query, and getting the number of rows affected by a query. /** * Get the rows from the most recently executed query, excluding cached queries * @return array */ public function getRows() { return $this->last->fetch_array(MYSQLI_ASSOC); } public function numRows() { return $this->last->num_rows; } /** * Gets the number of affected rows from the previous query * @return int the number of affected rows */ public function affectedRows() { return $this->last->affected_rows; } Download from Wow! eBook <www.wowebook.com> Chapter 2 [ 39 ] Disconnecting When the database object is no longer required, we should disconnect from all of the connections we have made to various databases. This can be done through a simple foreach loop in the deconstructor. /** * Deconstruct the object * close all of the database connections */ public function __deconstruct() { foreach( $this->connections as $connection ) { $connection->close(); } } } ?> Template management Template management is another set of core tasks that will need to be accessed by almost every aspect of our social network code. Every page request needs to display something to the user, and for each user the page will normally be different, and contain dynamic data from our database. For example, when any user views their friends list, they will all see the same page layout; however, the list of friends will be different. When they view a prole, all proles will have the same layout, with different data, and in some cases, some additional sections to the page, depending on how complete their prole is. Our template manager should take a series of template les, which contain the HTML to be sent to the browser, and manage data, which should be inserted into it, as well as process this dynamic replacement of data. Additional templates should be able to be included within a template, should they be required—for instance when viewing the prole of a user who has comments enabled, a comments list and form should be displayed, whereas a user without this would not see a list of a comments form. The data and template contents will be stored in a Page object; the management of this object and its processing will be handled by the template object. Let's go through what we need in our template class ( registry/template.class.php). Download from Wow! eBook <www.wowebook.com> Planning and Developing the Core Framework [ 40 ] Firstly, we need to create the object, which involves assigning our registry to a variable, including the page class, and instantiating a page object. /** * Include our page class, and build a page object to manage the content and structure of the page * @param Object our registry object */ public function __construct( Registry $registry ) { $this->registry = $registry; include( FRAMEWORK_PATH . '/registry/page.class.php'); $this->page = new Page( $this->registry ); } Since the views are made up of a number of template les, we need to be able to include these les and send them to our page object. Certain pages might be made up of two templates, others may be made up of three or more. To make this exible, instead of dening parameters for this method, we instead take however many templates are passed as parameters and include them, in order, to our page object. /** * Set the content of the page based on a number of templates * pass template file locations as individual arguments * @return void */ public function buildFromTemplates() { $bits = func_get_args(); $content = ""; foreach( $bits as $bit ) { if( strpos( $bit, 'views/' ) === false ) { $bit = 'views/' . $this->registry->getSetting('view') . '/ templates/' . $bit; } if( file_exists( $bit ) == true ) { $content .= file_get_contents( $bit ); } } $this->page->setContent( $content ); } Download from Wow! eBook <www.wowebook.com> Chapter 2 [ 41 ] Within our template les, we may need to insert other templates. For instance, as we mentioned earlier, if one user has comments enabled on their prole, and another doesn't, then they will use the same main template, however, different templates will be inserted dynamically into them. We can do this by taking a $tag (which is something contained within the template already included), and a template $bit, which is included and placed within the main template where the $tag was found. /** * Add a template bit from a view to our page * @param String $tag the tag where we insert the template e.g. {hello} * @param String $bit the template bit (path to file, or just the filename) * @return void */ public function addTemplateBit( $tag, $bit ) { if( strpos( $bit, 'views/' ) === false ) { $bit = 'views/' . $this->registry->getSetting('view') . '/ templates/' . $bit; } $this->page->addTemplateBit( $tag, $bit ); } These templates bits that we insert into our page object need to actually be replaced into the current page, which is where the replaceBits method comes in. This iterates through the list of template bits, and performs the replacement. The replacement is done in order, so if we wanted to insert a template into a page, and then insert another template into that one, we can do, so long as they were added in order. The replacement is a simple str_replace to nd the tag, and replace it with the contents from the template. /** * Take the template bits from the view and insert them into our page content * Updates the pages content * @return void */ private function replaceBits() { $bits = $this->page->getBits(); // loop through template bits Download from Wow! eBook <www.wowebook.com> . $this->connections[$this->activeConnection ]- >query( $queryStr ) ) { trigger_error('Error executing query: ' . $queryStr .' - '.$this->connections[$this->activeConnection ]-& gt;error,. // Quote value if ( version_compare( phpversion(), "4.3.0" ) == " ;-1 " ) { $value = $this->connections[$this->activeConnection ]- Download from Wow! eBook <www.wowebook.com> Planning. array */ public function getRows() { return $this->last->fetch_array(MYSQLI_ASSOC); } public function numRows() { return $this->last->num_rows; } /** * Gets the number of affected