PHP - MySQL wrapper class advice - php

i am writing a MySQL wrapper class to:
a.) replace a current one
b.) make life easier in terms of custom functionality
c.) to learn and improve
I am looking on advice on how to improve my techniques and coding style, so i would appreciate any input you could provide.
DISCLAIMER:
I am aware there are other database abstraction methods such as PDO but i wanted to write my own for the above reasons
Thanks,
Lee
Test Call
#!/usr/bin/php
<?php
include('mysql.class.php');
$_mysql = new mysqli_ls( array( 'debug_log' => TRUE, 'query_log' => TRUE ) );
if ($_mysql->connect('host','user','password','db') === FALSE) print_r( $_mysql->get_error() );
else print "#1 connected\n";
if ($_mysql->set_database('auth_tracker_test') === FALSE) print_r( $_mysql->get_error() );
else print "#1 database changed\n";
/// Execute standard query
$sql = "SELECT * from user";
if ($_mysql->SQLQuery($sql) === TRUE) print "#1 SELECT Query worked\n";
else print print_r( $_mysql->get_error() );
#print_r($_mysql->getArray());
print_r($_mysql->getRow());
$_mysql->disconnect();
?>
<?php
class mysqli_ls
{
/**************************************************************************/
/* SETUP VARIABLES */
/**************************************************************************/
private $E_OK = TRUE;
private $E_ERROR = FALSE;
private $db_host;
private $db_user;
private $db_port;
private $db_name;
private $db_pass;
private $result;
private $link = FALSE;
private $errorArr = array();
private $config = array( // Location of exisiting
'config_load' => FALSE,
'config_path' => 'database.cfg.php',
// Record errors to a file
'debug_log' => FALSE,
'debug_log_path' => '/tmp/mysql.debug.log',
// Record queries to a file
'query_log' => FALSE,
'query_log_path' => '/tmp/mysql.debug.log' );
private $fh_debug = FALSE;
private $fh_query = FALSE;
private $fh_config = FALSE;
/**************************************************************************/
/* MAGIC FUNCTIONS */
/**************************************************************************/
public function __construct( $config = '' )
{
// Config vars
if ( !empty($config) && is_array($config) ) $this->set_config($config);
// Open file handles if logs are required
// Debug Log
if ($this->config['debug_log'] === TRUE)
{
if (! $this->fh_debug = fopen($this->config['debug_log_path'], 'a') )
{
$this->handle_error('#01A', 'could not open debug log');
return $this->E_ERROR;
}
}
// Query Log
if ($this->config['query_log'] === TRUE)
{
if (! $this->fh_query = fopen($this->config['query_log_path'], 'a') )
{
$this->handle_error('#01B', 'could not open query log');
return $this->E_ERROR;
}
}
// Check mysqli functions are available
if (!function_exists('mysqli_connect'))
{
$this->handle_error('#01C', 'mysqli not installed');
return $this->E_ERROR;
}
return $this->E_OK;
}
public function __deconstruct()
{
if ($this->link) $this->disconnect();
return $this->E_OK;
}
/**************************************************************************/
/* CONNECTION MANAGEMENT */
/**************************************************************************/
public function connect($db_host='', $db_user='', $db_pass='', $db_name, $db_port='3306')
{
if (empty($db_host) || empty($db_user) || empty($db_pass) || empty($db_name) || empty($db_port))
{
$this->handle_error('#02A', 'Missing connection variables');
return $this->E_ERROR;
}
$this->db_host = $db_host;
$this->db_user = $db_user;
$this->db_pass = $db_pass;
$this->db_name = $db_name;
$this->db_port = $db_port;
$this->link = #new mysqli($this->db_host, $this->db_user, $this->db_pass, $this->db_name, $this->db_port);
if (mysqli_connect_error($this->link))
{
$this->handle_error(mysqli_connect_errno($this->link), mysqli_connect_error($this->link));
return $this->E_ERROR;
}
return $this->E_OK;
}
public function disconnect()
{
if ($this->link)
{
if ($this->link->close() === TRUE) return $this->E_OK;
else
{
$this->handle_error($this->link->errno, $this->link->error);
return $this->E_ERROR;
}
}
$this->handle_error('#03A','no activate database connection');
return $this->E_ERROR;
}
public function connect_existing()
{
}
public function set_database($database)
{
if ( $this->link->select_db($database) === FALSE )
{
$this->handle_error($this->link->errno, $this->link->error);
return $this->E_ERROR;
}
$this->E_OK;
}
/**************************************************************************/
/* SQL INTERFACE */
/**************************************************************************/
public function insert()
{
}
public function update()
{
}
public function delete()
{
}
public function select()
{
}
public function query($sql)
{
// If the result set has cleaned up, do so before a new query
if ($this->result) $this->result->close();
// Record query
if ($this->config['query_log'] === TRUE) $this->write_log('query', $sql);
if ($result = $this->link->query($sql));
{
$this->result = $result;
return $this->E_OK;
}
// Clean up the result set
$result->close();
// Query failed, handle error
$this->handle_error($this->link->errno, $this->link->error);
return $this->E_ERROR;
}
/**************************************************************************/
/* RESULT FUNCTIONS */
/**************************************************************************/
public function getArray($type = 'assoc')
{
switch($type)
{
case 'num':
$type = MYSQLI_NUM;
break;
case 'assoc':
$type = MYSQLI_ASSOC;
break;
case 'both':
$type = MYSQLI_BOTH;
break;
default:
$this->handle_error('#12A','invalid field type. Options are include num, assoc, both');
return $this->E_ERROR;
break;
}
$resultArr = array();
while( $row = $this->result->fetch_array( $type ) )
{
$resultArr[] = $row;
}
return $resultArr;
}
public function getRow($type = 'assoc')
{
switch($type)
{
case 'num':
$type = MYSQLI_NUM;
break;
case 'assoc':
$type = MYSQLI_ASSOC;
break;
case 'both':
$type = MYSQLI_BOTH;
break;
default:
$this->handle_error('#13A','invalid field type. Options are include num, assoc, both');
return $this->E_ERROR;
break;
}
return $this->result->fetch_array( $type );
}
public function num_row()
{
return $this->result->num_rows;
}
public function insert_id()
{
return $this->link->insert_id;
}
public function affected_rows()
{
return $this->link->affected_rows;
}
/**************************************************************************/
/* LEGACY SUPPORT */
/**************************************************************************/
public function SQLQuery($sql='')
{
if (empty($sql))
{
$this->handle_error('#19A','missing query string');
return $this->E_ERROR;
}
// Check for a select statement
if ( preg_match("/^select/i",$sql) === 0)
{
$this->handle_error('#19A','incorrect query type, SELECT expected');
return $this->E_ERROR;
}
// Execute query
if ($this->query($sql) === $this->E_ERROR) return $this->E_ERROR;
// Return number of rows
return $this->num_row();
}
public function SQLModify($sql='')
{
if (empty($sql))
{
$this->handle_error('#19A','missing query string');
return $this->E_ERROR;
}
// Execute query
if ($this->query($sql) === $this->E_ERROR) return $this->E_ERROR;
// Return affected rows
$this->affected_rows();
}
public function numRow()
{
return $this->num_row();
}
/**************************************************************************/
/* LOGGING AND DEBUGGING */
/**************************************************************************/
private function write_log($type, $msg)
{
$msg = date('Y-m-d H:i:s') ."\t". $type ."\t". $msg ."\n";
switch($type)
{
case 'error':
fwrite($this->fh_debug, $msg);
break;
case 'query':
fwrite($this->fh_query, $msg);
break;
default:
return $this->E_ERROR;
break;
}
}
private function handle_error($errormsg, $errorno)
{
$this->errorArr[] = array( 'code' => $errorno,
'error' => $errormsg );
if ($this->config['debug_log'] === TRUE)
{
$msg = "($errorno) $errormsg";
$this->write_log('error', $msg);
}
return $this->E_OK;
}
public function get_error($type = 'string')
{
switch($string)
{
case 'string':
$error = end($this->errorArr);
return $error['error'] .' ('. $error['code'] .')';
break;
case 'array':
return end($this->errorArr);
break;
}
return false;
}
/**************************************************************************/
/* SET CONFIG VARS */
/**************************************************************************/
public function set_config($config)
{
foreach ($config as $key => &$value)
{
if ( ! isset($this->config[$key]) )
{
$this->handle_error('#19A','invalid field type');
return $this->E_ERROR;
}
$this->config[$key] = $value;
}
return $this->E_OK;
}
/**************************************************************************/
} // Class END
?>

I made one myself once and added another class named MySqlTable, which represented a table. I returned it in the __GET function, so you could call a table with
$sql->tablename->select();
Here's the code for the __get function:
function __GET($name)
{
return new MySqlTable($this, $name);
}
The class was like this:
class MySqlTable
{
private $table;
private $mySql;
function MySqlTable(&$oMySql, $sTable)
{
$this->mySql = $oMySql;
$this->table = $sTable;
}
function &select($sWhere = '')
{
if (empty($sWhere))
{
$data = $this->mySql->query("SELECT * FROM " . $this->table);
}
else
{
$data = $this->mySql->query("SELECT * FROM " . $this->table . " WHERE " . $sWhere);
}
return $this->mySql->resultToArray($data);
}
}

Currently, your mysqli_ls class contains the result of a query. This makes it impossible to do two queries and use the results of the first query after the second query ran.
A better way would be to let the SQLQuery() method return a result object, which contains the result handle and methods to retrieve rows from the result.

Related

Check All Array Without foreach Statement

How to check if there's no page information found in the database? If I were using foreach to check whether the page is exist or not, I will return false like the example below:
My router file:
$db = new PDO('mysql:host=127.0.0.1;dbname=project1', 'root', '');
$GetDirectory = $db->prepare('
SELECT *
FROM pages
');
$GetDirectory->execute();
$GetDirectory = $GetDirectory->fetchAll(PDO::FETCH_ASSOC);
if (Route::Request_URL()=='/') {
Route::Navigate_URL('home');
} else {
foreach ($GetDirectory as $Directory) {
switch (Route::Request_URL()) {
case strpos(Route::Request_URL(), $Directory['request']) :
if (Route::Verify_URL($Directory['pos1'], $Directory['pos2'], $Directory['request'])!==false) {
Route::Navigate_URL($Directory['name']);
$db = null;
} else {
//this will give an error for each time it didn't found
}
break;
}
}
}
My class file:
class Route {
public static function Request_URL() {
return $_SERVER['REQUEST_URI'];
}
public static function Verify_URL($pos1, $pos2, $url) {
if (strpos(substr(Route::Request_URL(), $pos1, $pos2), $url)!==false) {
return true;
} else {
return false;
}
}
public static function Navigate_URL($RequestURL) {
$db = new PDO('mysql:host=127.0.0.1;dbname=project1', 'root', '');
$ListDirectory = $db->prepare('
SELECT *
FROM pages
');
$ListDirectory->execute();
$ListDirectory = $ListDirectory->fetchAll(PDO::FETCH_ASSOC);
foreach ($ListDirectory as $Directory) {
switch ($RequestURL) {
case $Directory['name'] :
return require_once SERVE_PATH.$Directory['path'];
break;
}
}
}
}
Sorry for the long code. Wanna ask a simpler question, but don't know how to explain.

How to retrieve values from another class initialization without function to return them manually

I'm not sure how to go about this.
I have two classes, LairEngine and RespondEngine.
RespondEngine has certain methods in it that require values that are inside an initialization of LairEngine.
How do I make an initialization of RespondEngine automatically grab those values other than having to write methods inside class LairEngine to return values and then pass those as arguments to RespondEngine.
I'm trying to build a framework for telegram bots.
index.php
require_once('init_lairfw.php');
//Load up LairEngine and RespondEngine
$engine = new LairEngine;
$respond = new RespondEngine;
$respond->sendText("HEY");
init_lairfw.php
spl_autoload_register(null, false);
spl_autoload_extensions('.mod.php, .lair.php');
function ModuleLoader($class) {
if(file_exists(strtolower('modules/'.$class.'.mod.php'))) {
include(strtolower('modules/'.$class.'.mod.php'));
}
}
function EngineLoader($class) {
if(file_exists(strtolower('engine/'.$class.'.lair.php'))) {
include(strtolower('engine/'.$class.'.lair.php'));
}
}
spl_autoload_register('ModuleLoader');
spl_autoload_register('EngineLoader');
engine/lairengine.php
class LairEngine {
private $config;
private $url;
private $data;
private $datatypes;
//The construct method is executed, and requrired information(incoming data, outgoing data)
//is processed and allocated for further use
function __construct()
{
$this->config = json_decode(file_get_contents('config/.Engine'), true);
$this->url = array(
"https://api.telegram.org/bot".$this->config['token'],
"https://api.telegram.org/bot".$this->config['token']."/getFile?file_id=",
"https://api.telegram.org/file/bot".$this->config['token']."/"
);
$this->data = file_get_contents("php://input");
$this->data = json_decode($this->data, TRUE);
$this->process();
}
//Process the message and find out what kind of message it is
private function process()
{
$k = array_keys($this->data['message']);
switch($k[4])
{
case('reply_to_message'):
$f=5; $this->dataTypes = array('msg_type' => 'reply');
$this->dataTypes['reply_to_content'] = array_keys($this->data['message']['reply_to_message'][4]);
break;
case('new_chat_title'):
$f=4; $this->dataTypes = array('msg_type' => 'group_name_change');
break;
case('new_chat_photo'):
$f=4; $this->dataTypes = array('msg_type' => 'group_pic_change');
break;
case('forward_from'):
$f=6; $this->dataTypes = array('msg_type' => 'forward');
break;
case('new_chat_participant'):
$f=null; $this->dataTypes = array('msg_type' => 'new_user');
break;
case('left_chat_participant'):
$f=null; $this->dataTypes = array('msg_type' => 'user_leave');
break;
default:
$f=4; $this->dataTypes = array('msg_type' => 'original');
}
$this->dataTypes['msg_content'] = ($f!==null) ? $k[$f] : null;
}
public function returnData()
{
$this->data['lair_return'] = $this->dataTypes;
return $this->data;
}
public function returnDataTypes()
{
return $this->datatypes;
}
public function returnURL()
{
return $this->url;
}
public function returnchatdata()
{
return self::$data['message']['chat'];
}
public function throwError($e,$t)
{
switch($t)
{
case(0): trigger_error($e, E_USER_NOTICE); break;
case(1): trigger_error($e, E_USER_WARNING); break;
case(2): trigger_error($e, E_USER_ERROR); break;
}
}
}
engine/respondengine.php
class RespondEngine {
public function sendText($response,$chatID=null,$url=null)
{
if(!$chatID)
{
//need the value from initalization of LairEngine
}
if(!$url)
{
//need the value from initalization of LairEngine
}
$request = $url."sendMessage?chat_id=".$chatID."&text=".urlencode($response);
print_r($request);
if(file_get_contents($request)) { return true; } else { return false; }
}
}

PDO Binding Sequence/MySQL Incompatibility?

I'm using MySQL(i)-community-5.3 if not mistaken. After finally getting the hang of PDO, I can now conclude that the infected sectore is that which processes binding, all other functions are fine (that I know of). Below I will present a function which works and also doesn't work (the sequence without anything to do with binding works flawlessly.
Function you() is as followes:
public function you($row) {
if(isset($_COOKIE["SK"])) {
$usr = $this->baseXencode($_SESSION["username"]);
$q = "SELECT $row FROM SN_users WHERE username=:USR";
$this->netCore->q($q);
$this->netCore->bind(":USR", $usr);
$result = $this->netCore->single();
$result = $result[$row];
} else {
$q = "SELECT $row FROM SN_users WHERE username='".$this->baseXencode("Anonymous")."' AND password='".$this->baseXencode("Anonymous")."'";
$result = $this->netCore->q($q);
$result = $this->netCore->single();
$result = $result[$row];
}
return $result;
}
}
(As you can see, when pre-allocating the username/password combo for Anonymous users, the function executes perfectly whereas binding within the if() section does not, returning the value 1.
Below is my binding function bind() (if you may require any other code, I will edit this post further~^^):
EDIT:
Below is the netCore class:
class netCore {
private $boot;
private $dbHost = "";
private $dbNAME = "";
private $dbPASS = "";
private $dbUSR = "";
private $err;
private $state;
public function __construct() {
$bootloadr = "mysql:host=".$this->dbHost.";dbname=".$this->dbNAME.";charset=UTF8";
$opt = array(PDO::ATTR_PERSISTENT => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
try {
$this->boot = new PDO($bootloadr, $this->dbUSR, $this->dbPASS, $opt);
} catch(PDOException $e) {
$this->err = "<b>Lebensborn® netCore™ Error:</b> An exception has been raised during Network-to-Database procedures.<br /><b>Message:</b> ".$e->getMessage().".";
}
}
public function bind($param, $value, $type = NULL) {
if(is_null($type)) {
switch(true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
}
}
$this->state->bindParam($param, $value, $type);
}
public function debug() {
return $this->state->debugDumpParams();
}
public function exe() {
return $this->state->execute();
}
public function count() {
$this->exe();
return $this->state->fetchColumn();
}
public function q($q) {
$this->state = $this->boot->prepare($q);
}
public function set() {
$this->exe();
return $this->state->fetchAll(PDO::FETCH_ASSOC);
}
public function single() {
$this->exe();
return $this->state->fetch(PDO::FETCH_ASSOC);
}
public function transBegin() {
return $this->boot->beginTransaction();
}
public function transCancel() {
return $this->boot->rollBack();
}
public function transEnd() {
return $this->boot->commit();
}
}

Checking which function is true and false

Sorry for the terrible headline, let me try to explain below.
I have written a bunch of small functions that either returns true or false.
validateName()
validateEmail()
validateAddr()
validateBirtd()
validateUsername()
Now I am looping through a lot of data imported with a CSV file, and checkin which data is valid or not (returns true or false).
I do it this way:
if (validateName($data[0]) == true AND validateEmail($data[1]) == true AND validateAddr($data[3]) == true AND validateBirtd($data[5]) == true AND validateUsername($data[6])==true) {
// create array to import etc etc
}else{
// create other array with data who failed validation, to show user later..etc etc
}
My question is - is there a more clever way to do this? Would it be possible to create a list of for each failed validation ? Say 3 entrys has fails the validateEmail() function, and 10 both fails validateEmail and validateName().
Would there be a way for me to sort this so I can tell the user "these entrys failed email validation" and "these entrys failed Name and email validation".
I thought about validating one field at a time, but this way I would have duplicates if one entry has more than one validation error.
Would be cool if there was some kind of logic that I don't know of where I could do this
You could create a function.
function validate($data) {
$errors = array();
$fields = array('Name', 'Email', 'Addr', 'Birtd', 'UserName');
foreach ($fields as $i => $field) {
$func = 'validate'.$field;
if (!$func($data[$i])) {
$errors[] = $field;
}
}
return $errors;
}
$errors = validate($data);
if (empty($errors)) {
// create array to import etc etc
} else {
// errors
echo 'There are errors with ' . implode(',', $errors);
}
You can use Iterators to get CSV content and filter at the same time. you can also add different callback to each CSV index
Example
$csv = new CSVFilter(new CSVIterator("log.txt"));
$csv->addFilter(0, "validateName"); //<------------ Means validate Index at 0
$csv->addFilter(1, "validateEmail");
$csv->addFilter(2, "validateAddr");
$csv->addFilter(3, "validateBirtd");
$csv->addFilter(4, "validateName");
$csv->addFilter(5, "validateUsername");
foreach ( $csv as $data ) {
var_dump($data);
}
//To get Errors
var_dump($csv->getErrors());
CSV Filter
class CSVFilter extends FilterIterator {
protected $filter = array();
protected $errors = array();
public function __construct(Iterator $iterator) {
parent::__construct($iterator);
}
public function addFilter($index, Callable $callable) {
$this->filter[$index] = $callable;
$this->errors[$callable] = 0;
}
public function getErrors() {
return $this->errors;
}
public function accept() {
$line = $this->getInnerIterator()->current();
$x = true;
foreach ($this->filter as $key => $var ) {
if (isset($line[$key])) {
$func = $this->filter[$key];
$func($var) or $this->errors[$func] ++ and $x = false;
}
}
return $x;
}
}
CSVIterator
class CSVIterator implements \Iterator {
protected $fileHandle;
protected $line;
protected $i;
public function __construct($fileName) {
if (! $this->fileHandle = fopen($fileName, 'r')) {
throw new \RuntimeException('Couldn\'t open file "' . $fileName . '"');
}
}
public function rewind() {
fseek($this->fileHandle, 0);
$this->line = fgetcsv($this->fileHandle);
$this->i = 0;
}
public function valid() {
return false !== $this->line;
}
public function current() {
return array_map("trim", $this->line);
}
public function key() {
return $this->i;
}
public function next() {
if (false !== $this->line) {
$this->line = fgetcsv($this->fileHandle);
$this->i ++;
}
}
public function __destruct() {
fclose($this->fileHandle);
}
}
Simple Random Functions
function validateName($var) {
return mt_rand(0, 5);
}
function validateEmail($var) {
return mt_rand(0, 5);
}
function validateAddr($var) {
return mt_rand(0, 5);
}
function validateBirtd($var) {
return mt_rand(0, 5);
}
function validateUsername($var) {
return mt_rand(0, 5);
}
If you something a little more encapsulated, you can try this. It allows you to write different validators for each CSV file you may be validating. Additionally, you could write methods in either class that would allow you to perform additional tasks on each row. I just find it a little cleaner and easier to maintain than having a bunch of globally-named functions.
Note: I'm obviously using some pretty basic validator examples and exceptions. The idea here is that I'm providing a layout for you to follow; you can customize any specific behaviors however you'd like.
usage
$c = new UserCsvValidator('user_data.csv');
try {
$c->validate();
}
catch (Exception $e) {
echo $e->getMessage();
}
implementation; parent class
<?php
class CsvValidator {
private $filename;
private $fh;
protected $fields = array();
public function validate__construct($filename, ) {
$this->filename = $filename;
// open file
if ( ($this->fh = fopen($this->filename, 'r')) === false) {
throw new Exception("could not open file: {$this->filename}");
}
}
public function validate() {
while( ($row=fgetcsv($this->fh)) !== false) {
// create hash
if ( ($hash = array_combine($this->fields, $row)) === false) {
throw new Exception("invalid row" . print_r($row, true));
}
// validate
foreach ($hash as $field => $value) {
// determine method call
$method = "validate_{$field}";
if (!method_exists($this, $method)) {
throw new Exception("validation method not defined: {$method}");
}
// validate the field
if (call_user_func(array($this, $method), $value) === false) {
throw new Exception("invalid value for {$field}: {$value}");
}
}
}
}
}
implementation; subclass
<?php
class UserCsvValidator extends CsvValidator {
protected $fields = array('name', 'email', 'address', 'birth_date', 'username');
// example functions for each field
protected function validate_name($value) {
return !empty($value);
}
protected function validate_email($value) {
return strpos($value, '#') !== false;
}
protected function validate_address($value) {
return !empty($value);
}
protected function validate_birth_date($value) {
return date('Y-m-d', strtotime($value)) == $value;
}
protected function validate_username($value) {
return !empty($value);
}
}

Multiple Tables in Zend based on the Quick Start Guide Example

I am new to Zend and have been attempting to follow the Zend Quick Start Guide's example of using Data Mappers and extending Zend_Db_Table_Abstract. I think I've grasped the general concepts, but I am now wondering, how I would go about modifying the guide's example code to allow for multiple tables.
Here is the part of the code I am currently interested in modifying:
protected $_dbTable;
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Guestbook');
}
return $this->_dbTable;
}
I have changed it to this:
protected $_dbTables;
public function setDbTable($dbTable, $tableName)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTables[$tableName] = $dbTable;
return $this;
}
public function getDbTables()
{
if (null === $this->_dbTables) {
$this->setDbTable('Application_Model_DbTable_Courses', 'courses');
$this->setDbTable('Application_Model_DbTable_CourseTimes', 'course_times');
}
return $this->_dbTables;
}
Is this a correct way to go about implementing multiple tables within the Data Mapper pattern or would you do it differently? Thanks for your help in advance!
Assuming that you want to return data from related tables, you should either add queries with joins to one Zend_Db_Table or use Zend_Db_Table_Relationships to fetch associated data. The benefit of using Joins is that you will only do one query over many when using Relationships. The drawback is that joined tables wont return Zend_Db_Table_Row objects (iirc), but since you are going to map them onto your Domain objects anyway, it's not that much of an issue.
Structurally, you can do like I suggested in How to change Zend_Db_Table name within a Model to insert in multiple tables. Whether you create a Gateway of Gateways or simply aggregate the Table Gateways in the DataMapper directly is really up to you. Just compose them as you see fit.
I'm not sure if it's the best practice, but, this is my abstract mapper:
<?php
abstract class Zf_Model_DbTable_Mapper
{
protected $_db;
protected $_dbTable = null;
protected $_systemLogger = null;
protected $_userLogger = null;
public function __construct()
{
$this->_systemLogger = Zend_Registry::get('systemLogger');
$this->_userLogger = Zend_Registry::get('userLogger');
// Set the adapter
if(null !== $this->_dbTable)
{
$tableName = $this->_dbTable;
$this->_db = $this->$tableName->getAdapter();
}
}
public function __get($value)
{
if(isset($this->$value))
{
return $this->$value;
}
$dbTable = 'Model_DbTable_' . $value;
$mapper = 'Model_' . $value;
if(class_exists($dbTable))
{
return new $dbTable;
}
elseif(class_exists($mapper))
{
return new $mapper;
}
else
{
throw new Exception("The property, DbTable or Mapper \"$value\" doesn't exists");
}
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function getById($id)
{
$resource = $this->getDefaultResource();
$id = (int)$id;
$row = $resource->fetchRow('id =' . $id);
if (!$row) {
throw new Exception("Count not find row $id");
}
return $row;
}
public function getAll()
{
$resource = $this->getDefaultResource();
return $resource->fetchAll()->toArray();
}
public function save(Zf_Model $Model)
{
$dbTable = $this->getDefaultResource();
$data = $Model->toArray();
if(false === $data) return false;
if(false === $Model->isNew())
{
if(1 == $dbTable->update($data, 'id =' . (int)$Model->getId()))
{
return $Model;
}
}
else
{
$id = $dbTable->insert($data);
if($id)
{
$Model->setId($id);
return $Model;
}
}
return false;
}
public function remove($id)
{
return $this->getDefaultResource()->delete('id =' . (int) $id);
}
protected function getDefaultResource()
{
if(empty($this->_dbTable))
{
throw new Exception('The $_dbTable property was not set.');
}
$classname = 'Model_DbTable_' . $this->_dbTable;
if(!class_exists($classname))
{
throw new Exception("The Model_DbTable_\"$classname\" class was not found.");
}
return new $classname;
}
protected function getDefaultModel()
{
return current($this->_models);
}
protected function getResources()
{
return $this->_resources;
}
}
And this is one for my implemented mappers:
<?php
class Model_TwitterPostsMapper extends Zf_Model_DbTable_Mapper
{
/*
* Data Source
* #var string Zend_Db_Table name
*/
protected $_dbTable = 'TwitterPosts';
public function recordExists($Item)
{
$row = $this->TwitterPosts->fetchRow($this->TwitterPosts->select()->where('status_id =?', $Item->getSource()->getStatusId()));
if($row)
{
return $row->id;
}
return false;
}
public function getLastUpdate($options)
{
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), 't.created_at')
->join(array('u' => 'twt_users'), 't.user_id = u.id', '')
->order('t.created_at DESC');
if($options['user_id'])
{
$select->where("t.user_id = ?", $options['user_id']);
}
if($options['terms'])
{
if(is_array($options['terms']))
{
$condition = '';
foreach($options['terms'] as $i => $term)
{
$condition .= ($i > 0) ? ' OR ' : '';
$condition .= $this->getAdapter()->quoteInto('content LIKE ?',"%$term%");
}
if($condition)
{
$select->where($condition);
}
}
}
return $this->TwitterPosts->fetchRow($select)->created_at;
}
public function getSinceId($term = null)
{
$select = $this->TwitterPosts->select()->setIntegrityCheck(false)
->from('twt_tweets_content', 'status_id')
->where('MATCH(content) AGAINST(? IN BOOLEAN MODE)', "$term")
->order('status_id ASC')
->limit(1);
//echo $select; exit;
$tweet = $this->TwitterPosts->fetchRow($select);
if(null !== $tweet) return $tweet->status_id;
return 0;
}
public function getAllByStatusId($statuses_id)
{
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('t.id', 't.user_id', 't.status_id','t.user_id'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', array('u.screen_name', 'u.profile_image'))
->where('status_id IN(?)', $statuses_id);
$rows = $this->TwitterPosts->fetchAll($select);
$Posts = array();
foreach($rows as $row)
{
// Here we populate the models only with the specific method return data
$data = $row->toArray();
$Post = new Model_TwitterPost($data['id']);
$Post->populate($data);
$User = new Model_TwitterUser($data['user_id']);
$User->populate($data);
$Post->setUser($User);
$Posts[] = $Post;
}
return $Posts;
}
public function getAllSince($since_id)
{
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('t.status_id','t.user_id'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', array('u.screen_name', 'u.profile_image'))
->where('status_id > ?', $since_id)
->order('t.datetime DESC');
$rows = $this->TwitterPosts->fetchAll($select);
$Posts = array();
foreach($rows as $row)
{
// Here we populate the models only with the specific method return data
// TODO: This is not a truly lazy instatiation, since there's no way to get the not setted properties
$data = $row->toArray();
$Post = new Model_TwitterPost($data);
$User = new Model_TwitterUser($data);
$Post->setUser($User);
$Posts[] = $Post;
}
return $Posts;
}
public function getTotalRatedItems($options)
{
$options = $this->prepareOptions($options);
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('COUNT(DISTINCT t.id) AS total','r.rate'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', '')
->join(array('r' => 'twt_tweets_rate'), 't.id = r.tweet_id', array('r.rate'))
->group('r.rate')
->order('t.datetime DESC');
$select = $this->prepareSelect($select, $options);
$rates = $this->TwitterPosts->fetchAll($select)->toArray();
$itemsRated = array('Green' => 0, 'Yellow' => 0, 'Orange' => 0, 'Red' => 0, 'Gray' => 0);
foreach ($rates as $rate)
{
$itemsRated[$rate['rate']] = $rate['total'];
}
return $itemsRated;
}
public function getUsersActivity($options)
{
$options = $this->prepareOptions($options);
$select = $this->TwitterPosts->select()
->setIntegrityCheck(false)
->from(array('t' => 'twt_tweets'), array('COUNT(DISTINCT t.id) AS total','DATE(t.datetime) AS datetime'))
->join(array('u' => 'twt_users'), 't.user_id = u.id', '')
->joinLeft(array('r' => 'twt_tweets_rate'), 't.id = r.tweet_id', '')
->group('t.user_id')
->order('t.datetime DESC');
$select = $this->prepareSelect($select, $options);
$activity = $this->TwitterPosts->fetchAll($select)->toArray();
return $activity;
}
public static function prepareOptions($options)
{
if(!is_array($options))
{
$options = array();
}
date_default_timezone_set('America/Sao_Paulo');
if(Zend_Date::isDate($options['start_date']))
{
$date = new Zend_Date($options['start_date']);
$date->setTime('00:00:00');
$date->setTimezone('UTC');
$options['start_date'] = $date->toString('yyyy-MM-dd HH:mm:ss');
}
if(Zend_Date::isDate($options['end_date']))
{
$date = new Zend_Date($options['end_date']);
$date->setTime('23:59:59');
$date->setTimezone('UTC');
$options['end_date'] = $date->toString('yyyy-MM-dd HH:mm:ss');
}
date_default_timezone_set('UTC');
$options['mainTerms'] = array();
if(!empty($options['terms']) && !is_array($options['terms']))
{
$options['mainTerms'] = explode(' ', $options['terms']);
}
if(!is_array($options['terms']))
{
$options['terms'] = array();
}
if($options['group_id'] || $options['client_id'])
{
$TwitterSearches = new Model_DbTable_TwitterSearches();
$options['terms'] = array_merge($TwitterSearches->getList($options),$options['terms']);
if(empty($options['terms']))
{
$options['terms'] = array();
}
}
return $options;
}
public static function prepareSelect($select, $options)
{
if($options['start_date'])
{
$select->where('t.datetime >= ?', $options['start_date']);
}
if($options['end_date'])
{
$select->where('t.datetime <= ?', $options['end_date']);
}
foreach($options['mainTerms'] as $mainTerm)
{
$select->where('t.content LIKE ?', "%$mainTerm%");
}
if($options['user_id'])
{
$select->where("t.user_id = ?", $options['user_id']);
}
if($options['terms'])
{
$select->where('MATCH (t.content) AGASINT(?)', $options['terms']);
}
if($options['rate'])
{
if($options['rate'] == 'NotRated')
{
$select->where('r.rate IS NULL');
}
else
{
$select->where('r.rate = ?', $options['rate']);
}
}
if($options['last_update'])
{
$select->where('t.created_at > ?', $options['last_update']);
}
if($options['max_datetime'])
{
$select->where('t.created_at < ?', $options['max_datetime']);
}
return $select;
}
}
The Model:
<?php
class Model_TwitterPost extends Zf_Model
{
private $_name = 'twitter';
protected $_properties = array(
'id',
'status_id',
'user_id',
'content'
);
protected $_User = null;
public function setUser(Zf_Model $User)
{
$this->_User = $User;
}
public function getUser()
{
return $this->_User;
}
public function getPermalink()
{
return 'http://twitter.com/' . $this->screen_name . '/' . $this->status_id;
}
public function hasTerm($term)
{
if(preg_match("/\b$term\b/i", $this->getContent()))
{
return true;
}
return false;
}
public function getEntityName()
{
return $this->_name;
}
public function getUserProfileLink()
{
return $this->getUser()->getProfileLink() . '/status/' . $this->getStatusId();
}
}
Abstract model (Generic Object):
<?php
abstract class Zf_Model
{
protected $_properties = array();
protected $_modified = array();
protected $_data = array();
protected $_new = true;
protected $_loaded = false;
public function __construct($id=false)
{
$id = (int)$id;
if(!empty($id))
{
$this->_data['id'] = (int)$id;
$this->setNew(false);
}
}
public function populate($data)
{
if(is_array($data) && count($data))
{
foreach($data as $k => $v)
{
if(in_array($k,$this->_properties))
{
$this->_data[$k] = $v;
}
}
}
$this->setLoaded(true);
}
public function setNew($new=true)
{
$this->_new = (bool)$new;
}
public function isNew()
{
return $this->_new;
}
public function setLoaded($loaded = true)
{
$this->_loaded = (bool)$loaded;
}
public function isLoaded()
{
return $this->_loaded;
}
public function __call($methodName, $args) {
if(method_exists($this, $methodName))
{
return $this->$methodName($args);
}
$property = $methodName;
if (preg_match('~^(set|get)(.*)$~', $methodName, $matches))
{
$filter = new Zend_Filter_Word_CamelCaseToUnderscore();
$property = strtolower($filter->filter($matches[2]));
if(in_array($property, $this->_properties))
{
if('set' == $matches[1])
{
$this->_data[$property] = $args[0];
if(true === $this->isLoaded())
{
$this->_modified[$property] = true;
}
return $this;
}
elseif('get' == $matches[1])
{
if(array_key_exists($property, $this->_data))
{
return $this->_data[$property];
}
throw new Exception("The property $property or $methodName() method was not setted for " . get_class($this));
}
}
}
throw new Exception("The property '$property' doesn't exists.");
}
public function __get($key)
{
if(isset($this->_data[$key]))
{
return $this->_data[$key];
}
return $this->$key;
}
public function __set($key,$value)
{
if(array_key_exists($key,$this->_properties))
{
$this->_data[$key] = $value;
return;
}
$this->$key = $value;
}
public function getId()
{
return (!$this->_data['id']) ? null : $this->_data['id'];
}
public function toArray()
{
// If it's a new object
if(true === $this->isNew())
{
return $this->_data;
}
// Else, if it's existing object
$data = array();
foreach($this->_modified as $k=>$v)
{
if($v)
{
$data[$k] = $this->_data[$k];
}
}
if(count($data))
{
return $data;
}
return false;
}
public function reload()
{
$this->_modified = array();
}
}

Categories