I learned of the Database Access Object design pattern from a friend and decided to implement it recently to my MySQL database. The code uses MySQLi to connect to the Database, and uses prepared statements to execute all queries. I have tried to apply OOP principles in creating these series of classes (encapsulation, code reuse).
Below is the CustomerValueObject
class I simply use as a container for a Customer
that I retrieve from a query. It contains simply a constructor and various setters and getters.
<?php
class CustomerValueObject {
private $customerUserName;
private $firstName;
private $middleName;
private $lastName;
public function __construct( $customerUserName, $firstName, $middleName, $lastName ) {
$this -> customerUserName = $customerUserName;
$this -> firstName = $firstName;
$this -> middleName = $middleName;
$this -> lastName = $lastName;
}
public function getCustomerUserName( ) {
return $this -> customerUserName;
}
public function setCustomerUserName( $customerUserName ) {
$this -> customerUserName = $customerUserName;
}
public function getFirstName( ) {
return $this -> firstName;
}
public function setFirstName( $firstName ) {
$this -> firstName = $firstName;
}
public function getMiddleName( ) {
return $this -> middleName;
}
public function setMiddleName( $middleName ) {
$this -> middleName = $middleName;
}
public function getLastName( ) {
return $this -> lastName;
}
public function setLastName( $lastName ) {
$this -> lastName = $lastName;
}
}
?>
Below is my parent DatabaseAccessObject
class. Each class that inherits from it is suppose to act on one table of my Database alone.
<?php
//Not the actual values for my DB below
abstract class DatabaseAccessObject{
private $DATABASE_HOST = '*';
private $DATABASE_USERNAME = '*';
private $DATABASE_PASSWORD = '*';
private $DATABASE_NAME = '*';
protected $connection;
public function __construct( ) {
$this -> connection = new mysqli( $this -> DATABASE_HOST, $this -> DATABASE_USERNAME,
$this -> DATABASE_PASSWORD, $this -> DATABASE_NAME );
}
/*
@$sqlStatement - takes in a SQL prepared statement (or partial statement)
@$sqlBindParams - array of parameters to bind SQL statement - if element in null - not concatenated
@$sqlClauses - array of clauses appended to $sqlStatement
@$paramTypes - array of types for each variable in $sqlBindParams
@$sqlStatementBinder - string that is appended to each element in $sqlClauses after the first
Concatenates together a sqlStatement on non-null values of $sqlBindParams together
and returns an array of $sqlParameters - with first element as $types followed by the bind parameters
*/
protected function setUpQuery( &$sqlStatement, &$sqlBindParams, &$sqlClauses, $paramTypes, $sqlStatementBinder = ' and' ) {
$types = '';
$sqlParameters = array( );
$sqlStatementClauses = array( );
for( $i = 0; $i < count( $sqlBindParams ); ++$i ) {
if( isset( $sqlBindParams[ $i ] ) ) {
$sqlStatementClauses[ ] = &$sqlClauses[ $i ];
$types .= $paramTypes[ $i ];
$sqlParameters[ ] = &$sqlBindParams[ $i ];
}
}
array_unshift( $sqlParameters, '' );
$sqlParameters[ 0 ] = &$types;
$sqlStatement = $this -> concatSQLStatement( $sqlStatement, $sqlStatementClauses, $sqlStatementBinder );
return $sqlParameters;
}
/*
Concatenates a sqlStatement together
$sqlStatement is a complete or partial sql statement.
$sqlSegments is an array of parts of a sql statement to be appended to the $sqlStatment.
$sqlStatementBinder is a string that is appended to a $sqlSegment after the first $sqlSegement is added
returns a complete $sqlStatement
*/
protected function concatSQLStatement( $sqlStatement, $sqlSegments, $sqlStatementBinder ) {
$added = 0;
foreach( $sqlSegments as $segments ) {
if( $added > 0 ) {
$sqlStatement .= $sqlStatementBinder;
}
$sqlStatement = $sqlStatement . ' ' . $segments;
++$added;
}
$sqlStatement .= ';';
return $sqlStatement;
}
abstract protected function executeStatement( $sqlQuery, $parameters );
public function __destruct( ) {
$this -> connection -> close();
}
}
?>
Finally - below is a CustomerDatabaseAccessObject
class. This inherits from the DAO above.
<?php
require_once( dirname( __FILE__ ) . '\DatabaseAccessObject.class.php' );
require_once( dirname( __FILE__ ) . '\..\ValueObjects\CustomerValueObject.class.php' );
class CustomerDatabaseAccessObject extends DatabaseAccessObject {
public function __construct( ) {
parent::__construct( );
}
public function getAllCustomers( ) {
$sqlQuery = 'select * from Customer;';
return $this -> executeStatement( $sqlQuery ) ;
}
public function getCustomerByUserName( $userName ) {
$sqlQuery = 'select * from Customer where customer_username = ?;';
$sqlParameters = array( );
$paramType = 's';
$sqlParameters[ 0 ] = &$paramType;
$sqlParameters[ 1 ] = &$userName;
return $this -> executeStatement( $sqlQuery, $sqlParameters );
}
public function getCustomerByName( $firstName = null, $middleName = null, $lastName = null ) {
$sqlQuery = 'select * from Customer where';
$names = [ $firstName, $middleName, $lastName ];
$sqlClauses = [ 'first_name = ?', 'middle_name = ?', 'last_name = ?' ];
$paramTypes = [ 's', 's', 's' ];
$sqlParameters = $this -> setUpQuery( $sqlQuery, $names, $sqlClauses, $paramTypes );
return $this -> executeStatement( $sqlQuery, $sqlParameters );
}
public function addCustomer( $userName, $firstName, $middleName, $lastName ) {
$sqlStatement = 'insert into Customer ( customer_username, first_name, middle_name, last_name ) values ( ?, ?, ?, ? );';
$paramTypes = 'ssss';
$sqlParameters = array( );
$sqlParameters[ 0 ] = &$paramTypes;
$sqlParameters[ 1 ] = &$userName;
$sqlParameters[ 2 ] = &$firstName;
$sqlParameters[ 3 ] = &$middleName;
$sqlParameters[ 4 ] = &$lastName;
return $this -> executeStatement( $sqlStatement, $sqlParameters );
}
public function updateCustomer( $userName, $firstName = null , $middleName = null, $lastName = null ) {
$sqlStatement = 'update Customer set';
$customerData = [ $firstName, $middleName, $lastName, $userName ];
$sqlClauses = [ 'first_name = ?', 'middle_name = ?', 'last_name = ?', 'where customer_username = ?' ];
$paramTypes = [ 's', 's', 's', 's' ];
$sqlParameters = $this -> setUpQuery( $sqlStatement, $customerData, $sqlClauses, $paramTypes, '' );
return $this -> executeStatement( $sqlStatement, $sqlParameters );
}
public function deleteCustomer( $userName ) {
$sqlStatement = 'delete from Customer where customer_username = ?;';
$sqlParameters = array( );
$paramType = 's';
$sqlParameters[ 0 ] = &$paramType;
$sqlParameters[ 1 ] = &$userName;
return $this -> executeStatement( $sqlStatement, $sqlParameters );
}
protected function executeStatement( $sqlQuery, $parameters = array( ) ) {
$sqlStatement = $this -> connection -> prepare( $sqlQuery );
if( $sqlStatement ) {
if( count( $parameters ) > 0 ) {
call_user_func_array( array( $sqlStatement, 'bind_param' ), $parameters );
}
$resultSet = array();
if( $sqlStatement -> execute() ) {
$sqlStatement -> store_result( );
if( $sqlStatement -> num_rows > 0 ) {
$sqlStatement -> bind_result( $customerUserName, $firstName, $middleName, $lastName );
while( $sqlStatement -> fetch( ) ) {
array_push( $resultSet, new CustomerValueObject( $customerUserName, $firstName, $middleName, $lastName ) );
}
}
}
else {
$error = 'Failed Execute Query: ' . $this -> connection -> error . "in function: executeQuery( \$sqlQuery, \$parameters )\n";
throw Exception ( $error );
}
$sqlStatement -> close();
return $resultSet;
}
else {
$error = 'Failed Prepared Query: ' . $this -> connection -> error . "in function: executeQuery( \$sqlQuery, \$parameters )\n";
throw new Exception( $error );
}
}
}
?>
In terms of my CustomerDAO if throwing an exception for executeStatement
was the right way to fail it.
Test code to show that it is working:
<?php
print( "getAllCustomers( ): <br> " );
$customerDAO = new CustomerDatabaseAccessObject( );
$customers = $customerDAO -> getAllCustomers( );
print_r( $customers );
print( "<br><br> getCustomerByUserName( 'user0' ): <br> " );
$customers = $customerDAO -> getCustomerByUserName( 'user0' );
print_r( $customers );
print( "<br><br> getCustomerByName( 'Frank' ): <br> " );
$customers = $customerDAO -> getCustomerByName( 'Frank' );
print_r( $customers );
print( "<br><br> getCustomerByName( null, null, 'Sinatra' ): <br> " );
$customers = $customerDAO -> getCustomerByName( null, null, 'Sinatra' );
print_r( $customers );
print( "<br><br> getCustomerByName( null, null, null ): <br> " );
try {
$customers = $customerDAO -> getCustomerByName( null, null, null );
print_r( $customers );
}
catch( Exception $exception ) {
print( $exception -> getTraceAsString() );
}
print( "<br><br> addCustomer( 'user1', 'Robert', 'E', 'Lee'): <br> " );
try {
$customers = $customerDAO -> addCustomer( 'user1', 'Robert', 'E', 'Lee');
print_r( $customers );
}
catch( Exception $exception ) {
print( $exception -> getTraceAsString( ) );
}
print( "<br><br> addCustomer( 'user1', 'Robert', 'E', 'Lee'): <br> " );
try {
$customerDAO -> updateCustomer( 'user1', 'Hell' );
}
catch( Exception $exception ) {
print( $exception -> getTraceAsString( ) );
}
$customers = $customerDAO -> getAllCustomers( );
print_r( $customers );
print( "<br><br> deleteCustomer( 'user1' ): <br>" );
try {
$customerDAO -> deleteCustomer( 'user1' );
}
catch( Exception $exception ) {
print( $exception -> getTraceAsString( ) );
}
$customers = $customerDAO -> getAllCustomers( );
print_r( $customers );
?>
I want to know if:
- I'm applying the design pattern correctly
- If my classes or functions need any refactoring
- Any concerns that these classes raise (performance, security, etc.)