Prevent unbounded mysqli queries - php

I'm working for a company that uses this Mysqli php class to do mysql calls.
The problem is, the previous programmer wasn't great about preventing unbounded queries. So there are things dispersed throughout the code like the following:
$db -> where('id',$_POST['id']);
$db -> delete('table');
This code is supposed to only delete one record where id = $_POST['id']. However, if $_POST['id'] is empty, we've got problems. It then deletes the entire table. One solution to this problem would be to find all the places in the code where delete or update functions are called and then make sure that the where variable is actually set.
if(isset($_POST['id']) && $_POST['id']!=''){
$db -> where('id',$_POST['id']);
$db -> delete('table');
}
But, that would take a lot of work because I know there are about 200 instances in the code. I'm hoping there might be a way to alter the following 2 functions to prevent them from executing unbound queries in the first place. Any help is appreciated!!
/**
* Update query. Be sure to first call the "where" method.
*
* #param string $tableName The name of the database table to work with.
* #param array $tableData Array of data to update the desired row.
*
* #return boolean
*/
public function update($tableName, $tableData)
{
if ($this->isSubQuery)
return;
$this->_query = "UPDATE " . self::$_prefix . $tableName ." SET ";
$stmt = $this->_buildQuery (null, $tableData);
$status = $stmt->execute();
$this->reset();
$this->_stmtError = $stmt->error;
$this->count = $stmt->affected_rows;
return $status;
}
/**
* Delete query. Call the "where" method first.
*
* #param string $tableName The name of the database table to work with.
* #param integer $numRows The number of rows to delete.
*
* #return boolean Indicates success. 0 or 1.
*/
public function delete($tableName, $numRows = null)
{
if ($this->isSubQuery)
return;
$this->_query = "DELETE FROM " . self::$_prefix . $tableName;
$stmt = $this->_buildQuery($numRows);
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return ($stmt->affected_rows > 0);
}
public function where($whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
{
// forkaround for an old operation api
if (is_array($whereValue) && ($key = key($whereValue)) != "0") {
$operator = $key;
$whereValue = $whereValue[$key];
}
if (count($this->_where) == 0) {
$cond = '';
}
$this->_where[] = array($cond, $whereProp, $operator, $whereValue);
return $this;
}

You should catch the bad value when it's being passed to the where function, not later. That way it's easier to follow stack traces.
public function where($whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
{
if (is_null($whereValue) || trim($whereValue) == '') {
throw new Exception('Cannot pass null or empty string as a condition to MysqliDb::where')
}
// ...
}
You could check through the _where protected property array inside of the delete function too, but it's not good practice to silently fail a method by doing a simple return out of the function. If you insist, though:
public function delete($tableName, $numRows = null)
{
foreach ($this->_where as $w) {
if (is_null($w[3]) || trim($w[3]) == '') {
return;
// or alternatively throw new Exception('...')
}
}
// ...
}

What about returning if this (the where clause) is empty?
https://github.com/joshcam/PHP-MySQLi-Database-Class/blob/b3754d20bcebf07d65d552b2257696539a1cb144/MysqliDb.php#L1074
if (count($this->_where) == 0) {
return;
}

please update to a latest stable version. this issue is fixed there.
where('val', unset) wont count as no condition.

Related

TYPO3 8.7 Query Sorting with Flexform uid´s

I got a problem with many TYPO3 extensions with ordering query results by Uid´s which come from a flexform Plugin setting in the Backend. I try to create a query what gives me the result uid´s in the same order like the flexform is from the Plugin setting. Like i choose data.uid 5 7 and 3 and my query results give me those in this order.
For Example:
Siteinfo:
PHP 7.0
TYPO3 8.7
mariadb:10.1
Debian server
This Function is called from the Controller.
$partners = $this->partnerRepository->findByUids($this->settings['showMainSponsor']);
in $this->settings['showMainSponsor'] is the value ="3, 4 ,1".
These are the Uid´s from the selected area in the TYPO3 Plugin Settings.
The repository function "findByUids" looks like this.
public function findByUids($uids){
if(!isset($uids) || empty($uids)){
return NULL;
}
$uidListString = $uids;
if(!is_array($uids)){
$uidListString = explode(',', $uids);
}
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(FALSE);
//here i set the orderings
$orderings = $this->orderByField('uid', $uidListString);
$query->setOrderings($orderings);
$query->matching(
$query->logicalAnd(
$query->in('uid', $uidListString)
)
);
return $query->execute();
}
A function called "orderByField" is called here which sets all the orderings.
/**
* #param string $field
* #param array $values
*
* #return array
*/
protected function orderByField($field, $values) {
$orderings = array();
foreach ($values as $value) {
$orderings["$field={$value}"] = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING;
}
return $orderings;
}
These method of ordering the queryresult by the given uid list from the Flexform works in TYPO3 6.2 and 7.6. Now i tried to attach this extension to a TYPO3 8.6 project but this method doesnt work anymore. I tried to debug it and looked up in the query. There i found what broke this query. The query which doesnt work looks like this:
SELECT `tx_partner_domain_model_partner`.* FROM `tx_partner_domain_model_partner` `tx_partner_domain_model_partner` WHERE (`tx_partner_domain_model_partner`.`uid` IN (3, 4, 1)) AND (`tx_partner_domain_model_partner`.`sys_language_uid` IN (0, -1)) AND ((`tx_partner_domain_model_partner`.`deleted` = 0) AND (`tx_partner_domain_model_partner`.`t3ver_state` <= 0) AND (`tx_partner_domain_model_partner`.`pid` <> -1) AND (`tx_partner_domain_model_partner`.`hidden` = 0) AND (`tx_partner_domain_model_partner`.`starttime` <= 1506603780) AND ((`tx_partner_domain_model_partner`.`endtime` = 0) OR (`tx_partner_domain_model_partner`.`endtime` > 1506603780))) ORDER BY `tx_partner_domain_model_partner`.`uid=3` DESC, `tx_partner_domain_model_partner`.`uid=4` DESC, `tx_partner_domain_model_partner`.`uid=1` DESC
I tried this on my DBMS and it failed. The reason are the last 3 statements.
`tx_partner_domain_model_partner`.`uid=3` DESC, `tx_partner_domain_model_partner`.`uid=4` DESC, `tx_partner_domain_model_partner`.`uid=1` DESC
TYPO3 escaped the uid with `` like
`tx_partner_domain_model_partner`.`uid=4` DESC
if we do the call like this without these `` arround the uid=3 ..
`tx_partner_domain_model_partner`.uid=3 DESC, `tx_partner_domain_model_partner`.uid=4 DESC, `tx_brapartner_domain_model_partner`.uid=1 DESC
it works fine. Maybe there is a security reason why TYPO3 does this on his newest version but i dont find any other good solution for this basic case.
At the moment i got a foreach where i query every uid by his own by findByUid but this dont seem to me like a "best practice" way. Does anybody got a cleaner way for this case of getting data from the db? Or maybe this is a Bug ?
I Hope someone can help me.
best regards
Fanor
Without Overwrite:
Clear default Orderings:
$query->setOrderings(array());
Transform your QueryBuilder to the new Doctrine QB:
/** #var Typo3DbQueryParser $queryParser */
$queryParser = $this->objectManager->get(Typo3DbQueryParser::class);
/** #var QueryBuilder $doctrineQueryBuilder */
$doctrineQueryBuilder = $queryParser->convertQueryToDoctrineQueryBuilder($query);
Add the UIDs via concreteQb
$concreteQb = $doctrineQueryBuilder->getConcreteQueryBuilder();
foreach ($uidList as $uid) {
$concreteQb->addOrderBy("$key={$uid}", QueryInterface::ORDER_DESCENDING);
}
Get the mapped results:
/** #var DataMapper $dataMapper */
$dataMapper = $this->objectManager->get(DataMapper::class);
return $dataMapper->map(YourDataClass::class, $$doctrineQueryBuilder->execute()->fetchAll());
I think the problem is in \TYPO3\CMS\Core\Database\Query\QueryBuilder::orderBy where the fieldName gets quoted. You could overwrite this class and split the fieldName by = and build a quoted string and intval() the remaining string like:
public function orderBy(string $fieldName, string $order = null): QueryBuilder
{
if (strpos($fieldName, '=') !== false) {
list($field, $value) = GeneralUtility::trimExplode('=', $fieldName);
$field = $this->connection->quoteIdentifier($field);
$value = intval($value);
$fieldName = $field . $value;
}
else {
$fieldName = $this->connection->quoteIdentifier($fieldName);
}
$this->concreteQueryBuilder->orderBy($fieldName, $order);
return $this;
}
public function findByUidList($uidList)
{
$uids = GeneralUtility::intExplode(',', $uidList, true);
if ($uidList === '' || count($uids) === 0) {
return [];
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->getTableName());
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
$records = $queryBuilder
->select('*')
->from($this->getTableName())
->where($queryBuilder->expr()->in('uid', $uids))
->add('orderBy', 'FIELD('.$this->getTableName().'.uid,' . implode(',', $uids) . ')')
->execute()
->fetchAll();
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$dataMapper = $objectManager->get(DataMapper::class);
$result = $dataMapper->map($this->objectType, $records);
return $result;
}
/**
* Return the current table name
*
* #return string
*/
protected function getTableName()
{
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$dataMapper = $objectManager->get(DataMapper::class);
$tableName = $dataMapper->getDataMap($this->objectType)->getTableName();
return $tableName;
}
This works great with TYPO3 v10.4! :)

How I can close the connection between PHP & Mysql immediately after accessing

May wabsite has faced max_user_connections error
After contacting the website hosting provider they said I have to close the connection immediately after accessing the database using mysql_close()
My question is that how I can close the connection if my php file is :
*/
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../');
exit;
}
/**
* Indicates to the Q2A database layer that database connections are permitted fro this point forwards
* (before this point, some plugins may not have had a chance to override some database access functions).
*/
function qa_db_allow_connect()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_db_allow_connect;
$qa_db_allow_connect=true;
}
/**
* Connect to the Q2A database, select the right database, optionally install the $failhandler (and call it if necessary).
* Uses mysqli as of Q2A 1.7.
*/
function qa_db_connect($failhandler=null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_db_connection, $qa_db_fail_handler, $qa_db_allow_connect;
if (!$qa_db_allow_connect)
qa_fatal_error('It appears that a plugin is trying to access the database, but this is not allowed until Q2A initialization is complete.');
if (isset($failhandler))
$qa_db_fail_handler = $failhandler; // set this even if connection already opened
if ($qa_db_connection instanceof mysqli)
return;
// in mysqli we connect and select database in constructor
if (QA_PERSISTENT_CONN_DB)
$db = new mysqli('p:'.QA_FINAL_MYSQL_HOSTNAME, QA_FINAL_MYSQL_USERNAME, QA_FINAL_MYSQL_PASSWORD, QA_FINAL_MYSQL_DATABASE);
else
$db = new mysqli(QA_FINAL_MYSQL_HOSTNAME, QA_FINAL_MYSQL_USERNAME, QA_FINAL_MYSQL_PASSWORD, QA_FINAL_MYSQL_DATABASE);
// must use procedural `mysqli_connect_error` here prior to 5.2.9
$conn_error = mysqli_connect_error();
if ($conn_error)
qa_db_fail_error('connect', $db->connect_errno, $conn_error);
// From Q2A 1.5, we explicitly set the character encoding of the MySQL connection, instead of using lots of "SELECT BINARY col"-style queries.
// Testing showed that overhead is minimal, so this seems worth trading off against the benefit of more straightforward queries, especially
// for plugin developers.
if (!$db->set_charset('utf8'))
qa_db_fail_error('set_charset', $db->errno, $db->error);
qa_report_process_stage('db_connected');
$qa_db_connection=$db;
}
/**
* If a DB error occurs, call the installed fail handler (if any) otherwise report error and exit immediately.
*/
function qa_db_fail_error($type, $errno=null, $error=null, $query=null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_db_fail_handler;
#error_log('PHP Question2Answer MySQL '.$type.' error '.$errno.': '.$error.(isset($query) ? (' - Query: '.$query) : ''));
if (function_exists($qa_db_fail_handler))
$qa_db_fail_handler($type, $errno, $error, $query);
else {
echo '<hr><font color="red">Database '.htmlspecialchars($type.' error '.$errno).'<p>'.nl2br(htmlspecialchars($error."\n\n".$query));
qa_exit('error');
}
}
/**
* Return the current connection to the Q2A database, connecting if necessary and $connect is true.
*/
function qa_db_connection($connect=true)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_db_connection;
if ($connect && !($qa_db_connection instanceof mysqli)) {
qa_db_connect();
if (!($qa_db_connection instanceof mysqli))
qa_fatal_error('Failed to connect to database');
}
return $qa_db_connection;
}
/**
* Disconnect from the Q2A database.
*/
function qa_db_disconnect()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_db_connection;
if ($qa_db_connection instanceof mysqli) {
qa_report_process_stage('db_disconnect');
if (!QA_PERSISTENT_CONN_DB) {
if (!$qa_db_connection->close())
qa_fatal_error('Database disconnect failed');
}
$qa_db_connection=null;
}
}
/**
* Run the raw $query, call the global failure handler if necessary, otherwise return the result resource.
* If appropriate, also track the resources used by database queries, and the queries themselves, for performance debugging.
*/
function qa_db_query_raw($query)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (QA_DEBUG_PERFORMANCE) {
global $qa_usage;
// time the query
$oldtime = array_sum(explode(' ', microtime()));
$result = qa_db_query_execute($query);
$usedtime = array_sum(explode(' ', microtime())) - $oldtime;
// fetch counts
$gotrows = $gotcolumns = null;
if ($result instanceof mysqli_result) {
$gotrows = $result->num_rows;
$gotcolumns = $result->field_count;
}
$qa_usage->logDatabaseQuery($query, $usedtime, $gotrows, $gotcolumns);
}
else
$result = qa_db_query_execute($query);
// #error_log('Question2Answer MySQL query: '.$query);
if ($result === false) {
$db = qa_db_connection();
qa_db_fail_error('query', $db->errno, $db->error, $query);
}
return $result;
}
/**
* Lower-level function to execute a query, which automatically retries if there is a MySQL deadlock error.
*/
function qa_db_query_execute($query)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$db = qa_db_connection();
for ($attempt = 0; $attempt < 100; $attempt++) {
$result = $db->query($query);
if ($result === false && $db->errno == 1213)
usleep(10000); // deal with InnoDB deadlock errors by waiting 0.01s then retrying
else
break;
}
return $result;
}
/**
* Return $string escaped for use in queries to the Q2A database (to which a connection must have been made).
*/
function qa_db_escape_string($string)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$db = qa_db_connection();
return $db->real_escape_string($string);
}
/**
* Return $argument escaped for MySQL. Add quotes around it if $alwaysquote is true or it's not numeric.
* If $argument is an array, return a comma-separated list of escaped elements, with or without $arraybrackets.
*/
function qa_db_argument_to_mysql($argument, $alwaysquote, $arraybrackets=false)
{
if (is_array($argument)) {
$parts=array();
foreach ($argument as $subargument)
$parts[] = qa_db_argument_to_mysql($subargument, $alwaysquote, true);
if ($arraybrackets)
$result = '('.implode(',', $parts).')';
else
$result = implode(',', $parts);
}
elseif (isset($argument)) {
if ($alwaysquote || !is_numeric($argument))
$result = "'".qa_db_escape_string($argument)."'";
else
$result = qa_db_escape_string($argument);
}
else
$result = 'NULL';
return $result;
}
/**
* Return the full name (with prefix) of database table $rawname, usually if it used after a ^ symbol.
*/
function qa_db_add_table_prefix($rawname)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$prefix = QA_MYSQL_TABLE_PREFIX;
if (defined('QA_MYSQL_USERS_PREFIX')) {
switch (strtolower($rawname)) {
case 'users':
case 'userlogins':
case 'userprofile':
case 'userfields':
case 'messages':
case 'cookies':
case 'blobs':
case 'cache':
case 'userlogins_ibfk_1': // also special cases for constraint names
case 'userprofile_ibfk_1':
$prefix = QA_MYSQL_USERS_PREFIX;
break;
}
}
return $prefix.$rawname;
}
/**
* Callback function to add table prefixes, as used in qa_db_apply_sub().
*/
function qa_db_prefix_callback($matches)
{
return qa_db_add_table_prefix($matches[1]);
}
/**
* Substitute ^, $ and # symbols in $query. ^ symbols are replaced with the table prefix set in qa-config.php.
* $ and # symbols are replaced in order by the corresponding element in $arguments (if the element is an array,
* it is converted recursively into comma-separated list). Each element in $arguments is escaped.
* $ is replaced by the argument in quotes (even if it's a number), # only adds quotes if the argument is non-numeric.
* It's important to use $ when matching a textual column since MySQL won't use indexes to compare text against numbers.
*/
function qa_db_apply_sub($query, $arguments)
{
$query = preg_replace_callback('/\^([A-Za-z_0-9]+)/', 'qa_db_prefix_callback', $query);
if (!is_array($arguments))
return $query;
$countargs = count($arguments);
$offset = 0;
for ($argument = 0; $argument < $countargs; $argument++) {
$stringpos = strpos($query, '$', $offset);
$numberpos = strpos($query, '#', $offset);
if ($stringpos === false || ($numberpos !== false && $numberpos < $stringpos)) {
$alwaysquote = false;
$position = $numberpos;
}
else {
$alwaysquote = true;
$position = $stringpos;
}
if (!is_numeric($position))
qa_fatal_error('Insufficient parameters in query: '.$query);
$value = qa_db_argument_to_mysql($arguments[$argument], $alwaysquote);
$query = substr_replace($query, $value, $position, 1);
$offset = $position + strlen($value); // allows inserting strings which contain #/$ character
}
return $query;
}
/**
* Run $query after substituting ^, # and $ symbols, and return the result resource (or call fail handler).
*/
function qa_db_query_sub($query) // arguments for substitution retrieved using func_get_args()
{
$funcargs=func_get_args();
return qa_db_query_raw(qa_db_apply_sub($query, array_slice($funcargs, 1)));
}
/**
* Return the number of rows in $result. (Simple wrapper for mysqli_result::num_rows.)
*/
function qa_db_num_rows($result)
{
if ($result instanceof mysqli_result)
return $result->num_rows;
return 0;
}
/**
* Return the value of the auto-increment column for the last inserted row.
*/
function qa_db_last_insert_id()
{
$db = qa_db_connection();
return $db->insert_id;
}
/**
* Return the number of rows affected by the last query.
*/
function qa_db_affected_rows()
{
$db = qa_db_connection();
return $db->affected_rows;
}
/**
* For the previous INSERT ... ON DUPLICATE KEY UPDATE query, return whether an insert operation took place.
*/
function qa_db_insert_on_duplicate_inserted()
{
return (qa_db_affected_rows() == 1);
}
/**
* Return a random integer (as a string) for use in a BIGINT column.
* Actual limit is 18,446,744,073,709,551,615 - we aim for 18,446,743,999,999,999,999.
*/
function qa_db_random_bigint()
{
return sprintf('%d%06d%06d', mt_rand(1, 18446743), mt_rand(0, 999999), mt_rand(0, 999999));
}
/**
* Return an array of the names of all tables in the Q2A database, converted to lower case.
* No longer used by Q2A and shouldn't be needed.
*/
function qa_db_list_tables_lc()
{
return array_map('strtolower', qa_db_list_tables());
}
/**
* Return an array of the names of all tables in the Q2A database.
*/
function qa_db_list_tables()
{
return qa_db_read_all_values(qa_db_query_raw('SHOW TABLES'));
}
/*
The selectspec array can contain the elements below. See qa-db-selects.php for lots of examples.
I bet you have your code set to use persistent connections for MySQL. This can result in open but idle connections, maxing out whatever your (most likely shared) hosting provider allocates for you.
You have a QA_PERSISTENT_CONN_DB constant in your code that determines your connection mode. You will find it defined as true or false somewhere in a configuration file. (It's not defined in your code above). Ensure it is set to false and see if the problem is resolved. When persistent connections are off, the code above takes care of closing the connections. You don't need to add it in.
If you're interested in knowing more about persistent connections, this answer covers a lot of ground; and there's more in the manual:
Persistent connections are links that do not close when the execution of your script ends.
Next time, make sure you read through your code to see if the functionality is already there (in this case, it's inside function qa_db_disconnect()), and if it's simply a matter of configuration.

Why is this php function causing a sever 500 error?

I'm trying to implement these two functions in a separate file functions.php and call it in index.php
function is_field($column, $table, $requested) {
$is_field_query = "SELECT ".$column." FROM ".$table." WHERE ".$column."='".$requested."'";
$is_field_result = $mysqli->query($is_field_query);
$is_true = $is_field_result->num_rows;
$is_field_result->close();
return $is_true;
}
function get_content($column, $table, $requested) {
$get_content_query = "SELECT ".$column." FROM ".$table." WHERE ".$column."='".$requested."'";
$get_content_result = $mysqli->query($get_content_query);
$get_content_row = $get_content_result->fetch_array(MYSQLI_ASSOC);
$get_content_content = $get_content_row["content"];
$get_content_result->close();
return $content;
}
I have tried it over and over again and I have no idea why it wont work. The first one is returning 1 for valid or 0 for invalid. The second retrieves the content from a specific cell in the MySQL table. Any help would be much appreciated.
You're using $mysqli inside the function, but you never pass the MySQLi resource itself. Consider writing your function like this:
function is_field($mysqli, $column, $table, $requested) {
Or, create a class that takes a MySQLi resource and reference it with $this->mysqli inside your function.
Also, code like this may be another issue:
$is_field_result = $mysqli->query($is_field_query);
$is_true = $is_field_result->num_rows;
You're not checking whether $is_field_result is false; therefore, the next statement causes a fatal error, because a property can't be fetched from something that's not an object.
if (($is_field_result = $mysqli->query($is_field_query)) === false) {
die($mysqli->error);
}
$is_true = $is_field_result->num_rows;
It turns out the reason it was not working was I needed to add an extra field into the function to accept the passing of $mysqli from the connection.
function is_field($mysqli, $column, $table, $requested) {
$is_field_query = "SELECT * FROM $table WHERE $column='$requested'";
if (($is_field_result = $mysqli->query($is_field_query)) == false) {
die($mysqli->error);
}
$is_true = $is_field_result->num_rows;
$is_field_result->close();
return $is_true;
}
function get_content($mysqli, $column, $table, $requested) {
$get_content_query = "SELECT * FROM $table WHERE $column='$requested'";
if (($get_content_result = $mysqli->query($get_content_query)) == false) {
die($mysqli->error);
}
$get_content_row = $get_content_result->fetch_array(MYSQLI_ASSOC);
$get_content = $get_content_row["content"];
$get_content_result->close();
return $get_content;
}

PHP Turn recursive array function into a class

I have a simple recursive array function that looks like this:
function recursive_array($results) {
global $DBH;
if (count($results)) {
echo $res - > Fname;
foreach($results as $res) {
$STH = $DBH - > query("SELECT FID,FParentID,Fname FROM list WHERE FParentID = ".$res - > FID."");
$fquerycount = $STH - > rowCount();
$STH - > setFetchMode(PDO::FETCH_OBJ);
recursive_array($STH);
}
}
}
$FID = isset($_GET['FID']) ? $_GET[' FID'] : 0;
$STH = $DBH - > query("SELECT FID,FParentID,Fname FROM list WHERE FParentID ='0' ");
$STH - > setFetchMode(PDO::FETCH_OBJ);
recursive_array($STH);
I also have created a simple query class that looks like this:
class queryloop {
function __construct($args) {
global $DBH;
$table = $args['tbl'];
if (array_key_exists('orderby', $args)): $orderby = 'ORDER BY '.$args['orderby'];
else: $orderby = '';endif;
if (array_key_exists('groupby', $args)): $groupby = 'GROUP BY '.$args['groupby'];
else: $groupby = '';endif;
if (array_key_exists('start', $args)): unset($orderby);$start = $args['start'].' , ';
else: $start = '';endif;
if (array_key_exists('limit', $args)): $limit = 'LIMIT '.$start.' '.$args['limit'];
else: $limit = '';endif;
// UNSET the previously used array keys so they are not use again to create the query string
unset($args['tbl']);
unset($args['orderby']);
unset($args['groupby']);
unset($args['start']);
unset($args['limit']);
// Checks if args still an array after UNSET above. If not empty create the query string
if (!empty($args)): foreach($args as $k = > $v): $querystr. = 'AND '.$k.' = \''.$v.'\'';endforeach;
// If args array empty return empty query string
else: $querystr = '';endif;$STH = $DBH - > query("SELECT * FROM ".$table." WHERE key = '".KEY."' ".$querystr." ".$groupby." ".$orderby." ".$limit." ");
if ($STH): $STH - > setFetchMode(PDO::FETCH_OBJ);
while ($row = $STH - > fetch()): foreach($row as $key = > $val):
// check if value is numeric //
if (is_numeric($row - > $key)): $data[$row - > ID][$key] = $row - > $key;
// check if value is array //
elseif(is_array($row - > $key)): $data[$row - > ID][$key] = $row - > $key;
// check if value is not numeric or array convert to html entities //
else: $data[$row - > ID][$key] = htmlentities($row - > $key);endif;endforeach;endwhile;$this - > data = json_encode($data); // return json array if data
else: $this - > data = ''; // return 'null' if no data
endif;
}
}
$args = array('tbl' = > 'atable', 'limit' = > '5', 'start' = > '200', 'orderby' = > 'ID DESC');
$loop = new queryloop($args) // run the loop etc.
How do I turn my recursive array into something like the class queryloop so that I can "pull out" json endoded data I know that this (below) is totally wrong but what ever I do I cannot get a correctly formed json array or even anything to return form my attempted class below. Help would be much appreciate. Thanks in advance.
class recloop {
function __construct() {}
function recursive_array($results) {
global $DBH;
if (count($results)) {
foreach($results as $res) {
echo $res - > Name;
$STH = $DBH - > query("SELECT * FROM atable WHERE ParentID = ".$res - > ID."");
$fquerycount = $STH - > rowCount();
$STH - > setFetchMode(PDO::FETCH_OBJ);
recursive_array($STH);
}
}
}
function recursive_start() {
global $DBH;
$ID = isset($_GET['ID']) ? $_GET['ID'] : 0;
$STH = $DBH - > query("SELECT * FROM atable WHERE ParentID = '".$ID."' ");
$STH - > setFetchMode(PDO::FETCH_OBJ);
recursive_array($STH);
}
}
How do I turn my recursive array into something like the class queryloop so that I can "pull out" json endoded data I know that this (below) is totally wrong but what ever I do I cannot get a correctly formed json array or even anything to return form my attempted class below. Help would be much appreciate. Thanks in advance.
To answer your question, I would say it's not specific if you encapsulate your routines into objects or not that much, but that you take care that each object is there for a sole purpose. For example:
One object is fetching the data from the database.
One object/composite/array is the data-structure, representing the data.
One object or function is taking over the job to convert/encode the data into json.
Within your code I see that you right now are only running SQL-queries. The data fetched from the database server is not stored into a return variable at all, it get's directly consumed while being recursively processed. I assume you do this for debugging reasons.
So the actual question is, what do you want to do? You write that you want to encode an object into json output, which is perfectly possible with json_encodeDocs, however I think you refer to some specific data, like the entity (data) of the most parentId or something.
Following is some mock-up code based on your code for reading purposes (not tested, must not match your needs) that can provide all parent objects of that one specified by ID by using recursion. The recursion has been criticised because this can result in running a lot of queries - and additionally there is risk to create an endless loop which will result in a recursion stack overflow - your program crashes then.
To handle that alternatively, this is bound to the database design (which should be done before the design of the code, and I don't know your database design nor what you actually want to do, so I can't add assumptions for that). So the following code takes care of already queried objects only while still using recursion as the strategy to query your database.
For the actual data-structure I opted for an array of plain old PHP objects, keyed by the ID field from the database (which I assume that it exists per record):
/**
* HTTP Get Parameter (Input)
*/
class HTTPGetParameter {
private $name;
private $default;
public function __construct($name, $default = '') {
$this->name = (string) $name;
$this->default = (string) $default;
}
/**
* #return string
*/
public function getValue()
{
return isset($_GET[$name]) ? $_GET[$name] : $this->default;
}
/**
* #return int
*/
public function getValueInt()
{
return (int) $this->getValue();
}
/**
* #link http://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.tostring
*/
public function __toString()
{
return $this->getValue();
}
}
/**
* Data Provider
*/
class PDODataProvider
{
private $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* #return array
*/
public function findAllATableParents($id)
{
return $this->findAllOn('atable', 'ParentID', $id);
}
public function findAllBTableParents($id)
{
return $this->findAllOn('btable', 'ParentID', $id);
}
private function findAllOn($table, $field, $id)
{
$id = (int) $id;
$objects = array();
$sql = sprintf("SELECT * FROM %s WHERE %s = '%d'", $table, $field, $id);
$pdoStatement = $this->pdo->query($sql);
$pdoStatement->setFetchMode(PDO::FETCH_OBJ);
foreach($pdoStatement as $parent)
{
$parentId = $parent->ID;
# parents that had been queried are skipped
if (isset($objects[$parentId]))
continue;
$objects[$parentId] = $parent;
# add parent objects by recursion
$objects += $this->findAllParents($parentId);
}
return $objects;
}
}
/**
* main
*/
$data = new PDODataProvider($DBH);
$id = new HTTPGetParameter('ID', 0);
$objects = $data->findAllParents($id->getValueInt());
echo json_encode($objects);
I hope this example is helpful for you to answer your question.

zend framework update from another table

I got model in Zend Framework what extends Zend_Db_Table where $this->_name = 'tableA'.
It is very nice how long I doing insert(), update(), or delete(). How I can realize updating main table based on value from another table.. ?
In raw SQL query it could looks like this:
UPDATE tableA SET fieldA = tableB.newValue
FROM tableB
WHERE tableA.someValue = tableB.someIndex // it will be complicate manipulation
AND tableA.index = .....
how I can build params for update() method:
parent::update( $data, $where );
There are no possible combinations of how to build params for the parent::update() method to get that final update query. The reason is because the Db Table update method just passes along your $data and $where variables to the Db Adapter's update method. The adapter's update method leaves no room for attaching additional information. You can't hack params at all
If you can't use table relationships with cascade update. Your best bet will be to extend the Db Adapter and create a new method to handle these types of updates. This should work.
/**
* Add this method to you custom adapter
* Direct copy of update method with integration of $from
* #see Zend_Db_Adapter_Abstract::update
**/
public function updateFrom($table, $from, array $bind, $where = '')
{
/**
* Build "col = ?" pairs for the statement,
* except for Zend_Db_Expr which is treated literally.
*/
$set = array();
$i = 0;
foreach ($bind as $col => $val) {
if ($val instanceof Zend_Db_Expr) {
$val = $val->__toString();
unset($bind[$col]);
} else {
if ($this->supportsParameters('positional')) {
$val = '?';
} else {
if ($this->supportsParameters('named')) {
unset($bind[$col]);
$bind[':col'.$i] = $val;
$val = ':col'.$i;
$i++;
} else {
/** #see Zend_Db_Adapter_Exception */
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception(get_class($this) ." doesn't support positional or named binding");
}
}
}
// Reason #1 you can't hack into $data array to pass reference to a table
$set[] = $this->quoteIdentifier($col, true) . ' = ' . $val;
}
$where = $this->_whereExpr($where);
/**
* Build the UPDATE statement
*/
$sql = "UPDATE "
. $this->quoteIdentifier($table, true)
. ' SET ' . implode(', ', $set)
. ' FROM ' . $this->quoteIdentifier($from, true) // My only edit
. (($where) ? " WHERE $where" : ''); // Reason #2 no room in where clause
/**
* Execute the statement and return the number of affected rows
*/
if ($this->supportsParameters('positional')) {
$stmt = $this->query($sql, array_values($bind));
} else {
$stmt = $this->query($sql, $bind);
}
$result = $stmt->rowCount();
return $result;
}
/** Add this to your extended Zend_Db_Table **/
public function update(array $data, $where)
{
$tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
$from = 'schema.name'; // Get from table name
return $this->_db->updateFrom($tableSpec, $from, $data, $where);
}
Note: I didn't test this out, but I am pretty confident it will work as expected. If you have any problems, just let me know. Because I copied the adapter's update method, I went ahead and added notes of the reasons why you can't hack those params.
EDIT
I almost forgot to mention. Every adapter is unique so you will have to check with your adapters update method. I just copied from Zend_Db_Abstract.
I think you need this: http://framework.zend.com/manual/en/zend.db.table.relationships.html

Categories