i have a function (below) which is used in my mysql abstraction class and converts table names in fields like "tableName.fieldName" and replaces them with the specified variables (it's useful for joins). the array of fields is very mixed, and so i need it to support recursion so that it can change the table names in an array(array(array("tableName.fieldName"))) but also for the standard array("tableName.fieldName","tableName.field2Name",...)
however, after debugging, i see that the variables $i and $fields_arr are maintaining the same values, even though i pass on a new value for $fields_arr and $i is set at the begining of the loop. how can i make this work so that $i and $fields_arr take the new values i pass for them?
/**
* #param mixed $fields_arr standard array ("table.field1","table.field2",...)
* #param mixed $tables_and_vars eg. ("table1" => "tableVar1", "table2" => "tableVar2", ...)
* replaces all tablenames with desired tablevarnames
*/
private function tablesToTableVars($fields_arr, $tables_and_vars) {
// loop through every string
$numFields = count($fields_arr);
for($i = 0; $i < $numFields; $i++) {
$field = $fields_arr[$i];
if(is_numeric($field)) continue; // don't replace numbers
if(is_array($field)) {
$fields_arr[$i] = $this->tablesToTableVars($field, $tables_and_vars); // *** RECURSION ***
} else {
$tableNameLen = strpos($field, "."); // pos of period in string
if(strpos($field, ".") === false) continue; // don't replace fields that dont have tablenames
$searchTableName = substr($field, 0, $tableNameLen); // take 'table' from 'table.field'
// see if field contains a table
foreach($tables_and_vars as $tableName => $tableVar) {
if($searchTableName === $tableName) { // if it is the table name we're looking for
$fields_arr[$i] = $tableVar . substr($field, $tableNameLen); // change it to the variable name
break;
}
}
}
}
return $fields_arr;
}
can't use integer indexes for an associative array =)
Related
I have an mobile app that request a key in my server, The key structure contains 7 characters as follows:
# + [0-9] + [0-9] + [0-9] + [A-Z] + [A-Z] + [0-9]
#876EU8, #668KI2 .......
Whereas the key initially has seven characters, in this case three numbers, two letters and one number, doing the math this gives a maximum of 676,000 keys.
To gerate this keys I'm using this code in PHP:
function generateRandomString($length = 2) {
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
$randomKeyNumber = rand(100,999);
$randomKeyLetter = generateRandomString();
$randomKeyLast = rand(0,9);
$randomKey = "#".$randomKeyNumber.$randomKeyLetter.$randomKeyLast;//Returns a key like #876TG9
The next code check if the key exists inside the database, If exists he random another key, if not he insert the key in database and return this key to my app
This code works perfectly, but assuming the system has already generated a total of 650,000 keys, in the case of this code it would always generate the same keys, and the likelihood of it generate a key that does not exist yet is very small.
How can I solve this problem and avoid future problems? (There is no problem in creating the keys in an orderly manner, eg 000AA0, 000AA1, 000AA2, 000AA3 .... 999ZZ9)
What you can do is make a PDO::query() to issue a SELECT COUNT(*) or simply a SELECT * statement with all the keys you already have added, and then use PDOStatement::fetchColumn() to retrieve the number of rows that will be returned (i.e. in this case, all of them)
This is a manual example
<?php
$sql = "SELECT COUNT(*) FROM Keys";
if ($res = $conn->query($sql)) {
/* Check the number of rows that match the SELECT statement */
if ($res->fetchColumn() > 0) {
/* Issue the real SELECT statement and work with the results */
$sql = "SELECT name FROM fruit WHERE calories > 100";
foreach ($conn->query($sql) as $row) {
print "Name: " . $row['NAME'] . "\n";
}
}
/* No rows matched -- do something else */
else {
print "No rows matched the query.";
}
}
$res = null;
$conn = null;
?>
This is the code you need for your case:
<?php
$sql = "SELECT * From Keys";
if ($res = $conn->query($sql)) {
/* Check the number of rows that match the SELECT statement */
if ($res->fetchColumn() > 0) {
/* and then you get the id of the last one on the list, and to that one you add 1 */
$last_id = $conn->lastInsertId();
$new_id = $last_id + 1;
/* then you insert that in some place inside the key itself, that way you don't need to worry than two keys can be equal */
}
else {
/* No rows matched, just create a key and add to the database here */
}
}
¿>
Alternatively you can make a query SELECT statement combined with the countRows in PDO, it doesn't work all the times in the portable apps and/or databases, but like we don't know more about your app we can't know if this is gonna work.
PS. Don't use rand(). Use mt_rand() instead. It is more efficient with the resources of the server ;)
I know how to get a mysql-row and convert it to json:
$row = mysqli_fetch_assoc(mysqli_query($db, "SELECT * FROM table WHERE id=1"));
echo json_encode($row); // it's an ajax-call
but:
the db-row has different types like int, float, string.
by converting it using json_encode() all results are strings.
Is there a better way to correct the types than this:
$row['floatvalue1'] = 0+$row['floatvalue1'];
$row['floatvalue2'] = 0+$row['floatvalue2'];
$row['intvalue1'] = 0+$row['intvalue1'];
I would like to loop through the keys and add 0 because:
first coding rule: DRY - dont repeat yourself
but i can't because:
row has also other types than numbers (string, date)
there are many columns
design is in dev, so columns-names often changes
Thanks in advance and excuse my bad english :-)
EDIT (to answer the comment-question from Casimir et Hippolyte):
I call this php-code using ajax to get dynamically sql-values. in my javascript-code i use the results like this:
result['intvalue1'] += 100;
lets say the json-result of intval1 is 50, the calculated result is:
"50100", not 150
The code below is just a proof of concept. It needs encapsulation in a function/method and some polishing before using it in production (f.e. call mysqli_fetch_field() in a loop and store the objects it returns before processing any row, not once for every row).
It uses the function mysqli_fetch_field() to get information about each column of the result set and converts to numbers those columns that have numeric types. The values of MYSQLI_TYPE_* constants can be found in the documentation page of Mysqli predefined constants.
// Get the data
$result = mysqli_query($db, "SELECT * FROM table WHERE id=1");
$row = mysqli_fetch_assoc($result);
// Fix the types
$fixed = array();
foreach ($row as $key => $value) {
$info = mysqli_fetch_field($result);
if (in_array($info->type, array(
MYSQLI_TYPE_TINY, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_INT24,
MYSQLI_TYPE_LONG, MYSQLI_TYPE_LONGLONG,
MYSQLI_TYPE_DECIMAL,
MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE
))) {
$fixed[$key] = 0 + $value;
} else {
$fixed[$key] = $value;
}
}
// Compare the results
echo('all strings: '.json_encode($row)."\n");
echo('fixed types: '.json_encode($fixed)."\n");
something like
$row['floatvalue1'] = reset( sscanf ( $row['floatvalue1'] , "%f" ));
$row['floatvalue2'] = reset( sscanf ( $row['floatvalue2'] , "%f" ));
$row['intvalue1'] = reset( sscanf ( $row['intvalue1'] , "%d" ));
json_encode($row);
If you're simply trying to make sure that your values are operable with respect to their type, you need to first cast their type correctly.
Unless you need them server-side, I would just pass-on the json directly to the front-end and do the work there.
In Javascript, you could make an attempt at casting the numbers like so:
function tryNumber(string){
return !isNaN( parseInt(string) ) ? parseInt(string) : string;
}
function tryDate(string){
return !isNaN( new Date(string).getTime() ) ? new Date(string) : string;
}
tryNumber('foo'); // "hello"
tryNumber('24'); // 24
tryDate('bar'); // "bar"
tryDate('December 17, 1995'); // "Sun Dec 17 1995 00:00:00 GMT+0000 (GMT)"
These two lines attempt to cast the values as a Date/Number. If they can't be cast, they will remain String's.
A MySQLi OO version based on #axiac's answer, that produces a JSON array ($jsnAll) containing all records. In this code snippet, the method FixSQLType is called to fix a row. Note, it should be wrapped in a try{}catch{} block and "objMySQLi" has already been instantiated:
$lcAllRows = array();
// Make an SQL SELECT statement
$SQL = "SELECT * FROM $lcName WHERE $lcWhere";
// Run the query
$this->sqlResult = $this->objMySQLi->query($SQL);
// Fetch the result
while( $row = $this->sqlResult->fetch_assoc()){
$lcCount = count($lcAllRows) ;
// Call to fix, row
$fixedRow = $this->FixSQLType($row);
$lcAllRows[$lcCount]= $fixedRow;
}
$jsnAll = json_encode($lcAllRows);
The FixSQLType method. This is almost identical to #axiac's answer, except for the call to $this->sqlResult->fetch_field_direct($i). "fetch_field" seemed to get itself lost, using "fetch_field_direct" overcame that problem.
private function FixSQLType($pRow){
// FROM https://stackoverflow.com/a/28261996/7571029
// Fix the types
$fixed = array();
$i = 0;
foreach ($pRow as $key => $value) {
$info = $this->sqlResult->fetch_field_direct($i);
$i++;
if (in_array($info->type, array(
MYSQLI_TYPE_TINY, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_INT24,
MYSQLI_TYPE_LONG, MYSQLI_TYPE_LONGLONG,
MYSQLI_TYPE_DECIMAL,
MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE
))) {
$fixed[$key] = 0 + $value;
} else {
$fixed[$key] = $value;
}
}
return $fixed;
}
I am using flat file db, not mysql so I can't use $limit
I need to limit the number of records to 1, if more than 1 then echos something else:
$result = $db->getall(lmonth);
foreach($result as $item)
show_record($item);
}
Function getall()
/*!
* #function getall
* #abstract retrieves all records in the database, each record in an array
* element.
* #param orderby order the results. Set to the field name to order by
* (as a string). If left unset, sorting is not done and it is a lot faster.
* If prefixed by "!", results will be ordered in reverse order.
* If orderby is an array, the 1st element refers to the field to order by,
* and the 2nd, a function that will take two take two parameters A and B
* - two fields from two records - used to do the ordering. It is expected
* that the function would return -ve if A < B and +ve if A > B, or zero
* if A == B (to order in ascending order).
* #param includeindex if true, an extra field called 'FFDB_IFIELD' will
* be added to each record returned. It will contain an int that specifies
* the original position in the database (zero based) that the record is
* positioned. It might be useful when an orderby is used, and an future
* operation on a record is required, given it's index in the table.
* #result all database records as an array
*/
function getall($orderby = NULL, $includeindex = false)
{
if (!$this->isopen)
{
user_error("Database not open.", E_USER_ERROR);
return false;
}
// If there are no records, return
if ($this->records == 0)
return array();
if (!$this->lock_read())
return false;
// Read the index
$index = $this->read_index();
// Read each record and add it to an array
$rcount = 0;
foreach($index as $offset)
{
// Read the record
list($record, $rsize) = $this->read_record($this->data_fp, $offset);
// Add the index field if required
if ($includeindex)
$record[FFDB_IFIELD] = $rcount++;
// Add it to the result
$result[] = $record;
}
$this->unlock();
// Re-order as required
if ($orderby !== NULL)
return $this->order_by($result, $orderby);
else
return $result;
}
Function show_record()
function show_record($record){
$month = $record["lmonth"];
$status = $record["lstatus"];
$year = $record["lyear"];
}
if (($status == ON) && ($month >= $current_month) && ($year >= $current_year)){
echo "foo";
}
I tried to use break but it comes back 0(zero) records.
I tried using $i = 0...but it returned all or nothing
Any ideas?
Thanks
What about something like this?
function getall($orderby = null, $includeindex = false, $limit = null) {
...
if ($orderby !== null) {
$result = $this->order_by($result, $orderby);
}
if ($limit) {
return array_slice($result, 0, $limit);
}
return $result;
}
SOLUTION
Print_r did the trick, and I was using echo:
print_r (show_record($row));
Here is how the final code is working for me:
$result = $db->getall(lp_month,lp_year);
$i = 0;
foreach ($result as $row){
print_r (show_record($row));
if ($i >= 1)
break;
$i++;
}
Now looking forward to solve other minor problems, thanks
I am using flat file db, not mysql so I can't use $limit
Function getbyfieldsw()
/*!
* #function getbyfieldsw
* #abstract retrieves records in the database whose field matches the
* given regular expression.
* #param fieldname the field which to do matching on
* #param regex the regular expression to match a field on.
* Note: you should include the delimiters ("/php/i" for example).
* #param orderby order the results. Set to the field name to order by
* (as a string). If left unset, sorting is not done and it is a lot faster.
* If prefixed by "!", results will be ordered in reverse order.
* If orderby is an array, the 1st element refers to the field to order by,
* and the 2nd, a function that will take two take two parameters A and B
* - two fields from two records - used to do the ordering. It is expected
* that the function would return -ve if A < B and +ve if A > B, or zero
* if A == B (to order in ascending order).
* #param includeindex if true, an extra field called 'FFDB_IFIELD' will
* be added to each record returned. It will contain an int that specifies
* the original position in the database (zero based) that the record is
* positioned. It might be useful when an orderby is used, and an future
* operation on a record is required, given it's index in the table.
* #result matching records in an array, or false on failure
*/
function getbyfieldsw($fieldname, $orderby = NULL, $includeindex = false) {
if (!$this->isopen)
{
user_error("Database not open.", E_USER_ERROR);
return false;
}
// Check the field name
if (!$this->key_exists_array($fieldname, $this->fields))
{
user_error(
"Invalid field name for getbyfield: $fieldname",
E_USER_ERROR
);
return false;
}
// If there are no records, return
if ($this->records == 0)
return array();
if (!$this->lock_read())
return false;
// Read the index
$index = $this->read_index();
// Read each record and add it to an array
$rcount = 0;
foreach($index as $offset)
{
// Read the record
list($record, $rsize) = $this->read_record($this->data_fp, $offset);
// See if the record matches the regular expression
// Add the index field if required
if ($includeindex)
$record[FFDB_INDEX_RECORDS_OFFSET] = $rcount;
$result[] = $record;
++$rcount;
}
$this->unlock();
// Re-order as required
if ($orderby !== NULL)
return $this->order_by($result, $orderby);
else
return $result;
}
Function show_record()
function show_record($record){
$month = $record["lmonth"];
$status = $record["lstatus"];
$year = $record["lyear"];
}
if (($status == ON) && ($month >= $current_month) && ($year >= $current_year)){
echo "foo";
}
Call records - here is the problem - this works except for the break; - I need to explode the flat file to read each record individually, then add to the foreach():
$result = $db->getbyfieldsw(lp_month);
$i = 0;
foreach ($result as $item){
show_record($item);
if ($i >= 2)
break;
}
Since this is flat file, when calling the function getbyfieldsw() the file comes flat as a single file, no matter how many records on it, records = 1 or records = 100 is the same at this point.
Break; does not work because all records come as single record - therefore nothing to break.
What I want to do is split/explode the records, count them, and based on my if statement post:
if $i == 0 echo "content1";
if $i == 1 echo "content2";
if $i > 1 echo "content 3";
I got held here for the past 3 days...exhausted all solutions.
HELP glad appreciated
Thanks
Sorry for not helping your code directly, but you should consider switching to SQLITE instead of using your own database system. SQLITE is flat, open-source, efficient and easier to work with. PHP's sqlite module has everything you need. You should check it out!
http://www.sqlite.org/about.html
http://php.net/manual/en/book.sqlite.php
I use kohana and when you try to fetch data from database it returns class variables(like $user->firstname) as a database data. User table have a 12 columns and i fetch 8 columns but at this point some of columuns maybe empty(like $user->phone). How can i found empty column number ?(Proper way..)
Thanks A Lot
Generically, you could try something like:
/**
* Count number of empty data members in a row object.
*/
function countEmpty($row){
$fields = array_keys($row->as_array());
$cnt = 0;
foreach($fields as $f){
if (empty($row->$f)) $cnt++;
}
return $cnt;
}
i found solution. PHP have magic get_object_vars function:
$data = User_Model::factory()->read(
array('id' => $user_id),
'firstname, lastname, birthday, country, mobilephone, landphone, address'
);
$filled_data = 0;
foreach(get_object_vars($data) as $v)
{
if ($v != '') $filled_data++;
}
return round($filled_data / count(get_object_vars($data)) * 100);