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);
Related
NOTE: SOLUTION FOUND
I am trying to build a (my first!) PHP/JQuery/MySQL web app able to work with multidimensional data. In current state, almost everything works fine but one strange bug occurs when submitting data (see title) and I haven't found any explanation. Can anyone open my eyes?
When working my test form with 5 dimensional data (table names - one, two, three, four, five - all joined in chain):
if I submit completely new entry with all dimensions then all 5 INSERT INTO queries are generated correctly
but if I add to the existing entry (under 2nd dim) new 3rd dimension with corresponding child data (4th and 5th) - that means tables three, four and five - then foreign key field in table five (four_id) is omitted from the insertion query
all the rest options (two, three, four and five or new four and five) have no issues
There are 3 functions in php that are doing the work (first for main table, second for next 2 dimensions and third (recursive) for next n dimensions). As tables four and five in this example are "belonging" to the recursive one I am quite sure that the issue and key for solution should be there.
Each function is using both form data and existing data that is already submitted. Pk value of parent row is passed to the child in two possible ways:
After each INSERT INTO query a MySQL variable for new pk value is created: SET #last_id_tablename = LAST_INSERT_ID() to be used in child query when needed. If data submitted to parent and child (say four and five) is new for both tables then child table's query should be (and normally is)
INSERT INTO five (four_id, title) VALUES (#last_id_four, 'Some text')
If parent data is already existing and we add new related child row then the existing parent pk value (say 1) is passed to the child and query is
INSERT INTO five (four_id, title) VALUES (1, 'Some text')
So the issue is that when I have an entry with first 2 dimensions and I add 3 dimensions under existing 2nd (IOW I have parent row in one with its child row in two and under this I add new data starting from table three the generated queries are:
INSERT INTO three (two_id, title) values (1, 'Some text');
INSERT INTO four (three_id, title) values (#last_id_three, 'Some text')
INSERT INTO five (title) values ('Some text')
As you see, four_id and #last_id_four are missing in third line.
All other combinations including fully new data submmission for all dimensions are generating a correct query for five. Fully new data submission query list looks like this one (first table's last id is returned before the rest continues, passed to the next function and therefore it's in use already as a real number, let's say 10)
INSERT INTO one (title) values ('Some text');
INSERT INTO two (one_id, title) values (10, 'Some text');
SET #last_id_two = LAST_INSERT_ID();
INSERT INTO three (two_id, title) values (#last_id_two, 'Some text');
SET #last_id_three = LAST_INSERT_ID();
INSERT INTO four (three_id, title) values (#last_id_three, 'Some text')
SET #last_id_four = LAST_INSERT_ID();
INSERT INTO five (four_id, title) values (#last_id_four, 'Some text')
The only one explanation I thought about was that it's somehow related to the variable names in the recursive function and therefore I renamed all of vars but it didn't resolve the issue.
Below I show the full code of this recursive function
/*
Recursive function for inserting or editing nested data (since 4th until nth level)
$subTable - current table where we insert new or edit existing data
$subData - current table's data in form view
$existingSubJoin - current table data that is already in database (submitted earlier)
$existing... - corresponding variables for existing data
$parentTable - current table's parent table (where current tables FK is pointing)
$existingParentJoin - parent table data that already exists
$parentPkField, $parentPkValue - the names are self-explanatory
$parentPkValue can be a real number from existing row or #last_id_$parentTable
#last_id_$subTable - a MySQL variable that passes the last_insert_id() value from newly submitted parent row to the child row's FK
$subSingle - a new array of db field values for one row
$subSet - array for UPDATE statements (SET field = 'value', field2 = 'value2' etc)
$subFields - array of fields for INSERT INTO
$nextLastId = pk value or #last_id_$subTable to be passed as a last argument for next recursion
*/
public function buildQueryListChild($subTable, $subData, $existingSubJoin, $parentTable, $existingParentJoin, $parentPkField, $parentPkValue)
{
if (isset($subData))
{
foreach($subData as $sKey => $subRow)
{
$subSingle = array();
if (!isset($existingSubJoin['rows'][$sKey]))
{
$existingSubRow = $existingSubJoin['rows'][0];
}
else
{
$existingSubRow = $existingSubJoin['rows'][$sKey];
}
$subSet = array();
$subParentId = $parentTable . '_' . $parentPkField;
foreach ($subRow as $subField => $subValue)
{
if (isset($existingSubJoin['properties']['fields']))
{
foreach ($existingSubJoin['properties']['fields'] as $existingSubField)
{
if ($existingSubField['name'] == $subField)
{
if ($existingSubField['key'] == 'PRI')
{
$subRowPkField = $existingSubField['name'];
$subRowPkAlias = $existingSubField['alias'];
}
else
{
$subRowField = $existingSubField['name'];
$subRowAlias = $existingSubField['alias'];
$subRowType = $existingSubField['type'];
}
$sNumTypes = array('int', 'float', 'decimal', 'numeric', 'double', 'bit');
foreach ($sNumTypes as $sType)
{
$sNumber = strpos($existingSubField['type'], $sType) === true ? true : null;
}
$sString = $sNumber ? false : true;
}
}
}
if (empty($subRow[$subRowPkField]))
{
$newSub = true;
$updateSub = false;
}
else
{
$updateSub = true;
$newSub = false;
}
if (!is_array($subValue))
{
if ($subField != $subRowPkField && strpos($subRowType, 'timestamp') === false)
{
if ($subField == $subParentId)
{
$subSingle[$subParentId] = $parentPkValue;
}
else
{
if (!empty($subValue)) $subSingle[$subField] = $subValue;
}
if ($updateSub && $subField == $subRowField && $subSingle[$subField] != $existingSubRow['data'][$subRowAlias])
{
$uSubField = $subField;
$uSubValue = $subValue;
if (!$sNumber)
{
$uSubValue = "'$subValue'";
}
$subSet[$uSubField] = "$uSubField = $uSubValue";
}
}
}
}
if (!empty($subSet))
{
$subSets = implode(', ', $subSet);
$subRowPkValue = $subRow[$subRowPkField];
$current = "UPDATE $subTable SET $subSets WHERE $subRowPkField = $subRowPkValue;\n";
$sql .= $current;
}
if ($newSub)
{
$subRowPkValue = $subRow[$subRowPkField];
if (!empty($subSingle))
{
$subFields = implode(', ', array_keys($subSingle));
$subValues = "'" . implode("', '", array_values($subSingle)) . "'";
$subValues = str_replace("'$parentPkValue'", "$parentPkValue", $subValues);
$current = "INSERT INTO $subTable ($subFields) VALUES ($subValues);\n";
$sql .= $current;
$sql .= "SET #last_id_$subTable = LAST_INSERT_ID();\n";
}
}
foreach ($subRow as $sTable => $sData)
{
if (is_array($sData))
{
if (isset($existingSubJoin['rows'][$sKey]) && $sKey > 0)
{
$nextLastId = $sKey;
}
else
{
$nextLastId = "#last_id_$subTable";
}
$existingSData = $existingSubRow['joins']->$sTable;
$sql .= $this->buildQueryListChild($sTable, $sData, $existingSData, $subTable, $existingSubJoin, $subRowPkField, $nextLastId);
}
}
}
}
return $sql;
}
You see there a line
$current = "INSERT INTO $subTable ($subFields) VALUES ($subValues);\n";
where both $subFields and $subValues are imploded from corresponding submission array (array_keys and array_values) that is created in
if ($subField == $subParentId)
{
$subSingle[$subParentId] = $parentPkValue;
}
else
{
if (!empty($subValue)) $subSingle[$subField] = $subValue;
}
And as said, ($subFields) should always contain parenttable_id and ($subValues) its existing value or #last_id_parenttable
Sorry for this amount of information and thanks in advance for help!
SOLUTION FOUND - see ANSWER
There were also other issues that occurred in my code but, like changes I made in parent function, this is outside of this issue's scope. I hope all my explanations are clear :)
The (main?) cause was that the parent pk field name was not always passed to child. I discovered this when I got idea that the way how the fk value was set was not the best one and that this should be done at very beginning, before creating a new array. So the first thing I did was that I moved it right before iterating the fields
$subParentId = $parentTable . '_' . $parentPkField;
$subRow[$subParentId] = $parentPkValue;
foreach ($subRow as $subField => $subValue)
{ ... }
But it wasn't enough. Yes, I got the needed value but without $parentPkField so I got a nonexisting field name (parenttablename_). Therefore it was clear what I should really look for.
And I got it. Some time ago I built among others a "blank row" feature to be used in some cases when there is no real db row. In this case I just forgot to use it where needed :D
Therefore I had to make some corrections also to the parent function and pass an additional $blank array from there to the child. And of course corresponding changes to the current function. This way the existence of $parentPkField was ensured.
The diffs in this function are here (old commented, new below or otherwise explained)
/*
public function buildQueryListChild($subTable, $subData, $existingSubJoin, $parentTable, $existingParentJoin, $parentPkField, $parentPkValue)
*/
public function buildQueryListChild($subTable, $subData, $existingSubJoin, $blank, $parentTable, $existingParentJoin, $parentPkField, $parentPkValue)
{
.....
if (!isset($existingSubJoin['rows'][$sKey]))
{
// $existingSubRow = $existingSubJoin['rows'][0];
$existingSubRow = $blank['rows'][0];
}
......
// Added
$subRow[$subParentId] = $parentPkValue;
......
foreach ($subRow as $subField => $subValue)
{
// Added
if (!$existingSubJoin) $existingSubJoin = $blank;
......
if ($subField != $subRowPkField && strpos($subRowType, 'timestamp') === false)
{
/*
if ($subField == $subParentId)
{
$subSingle[$subParentId] = $parentPkValue;
}
else
{
if (!empty($subValue)) $subSingle[$subField] = $subValue;
}
*/
if (!empty($subValue)) $subSingle[$subField] = $subValue;
.......
if ($newSub)
{
// Removed as unneeded
//$subRowPkValue = $subRow[$subRowPkField];
if (!empty($subSingle))
{
........
// if / else moved here, see below foreach
if (isset($existingSubJoin['rows'][$sKey]) && $sKey > 0)
{
$nextLastId = $sKey;
}
else
{
$nextLastId = "#last_id_$subTable";
}
foreach ($subRow as $sTable => $sData)
{
if (is_array($sData))
{
/*
if (isset($existingSubJoin['rows'][$sKey]) && $sKey > 0)
{
$nextLastId = $sKey;
}
else
{
$nextLastId = "#last_id_$subTable";
}
*/
// The commented lines above: moved them before foreach but actually not sure if it had any impact
//Added
$nextBlank = $blank['rows'][0]['joins']->$sTable;
/*
$sql .= $this->buildQueryListChild($sTable, $sData, $existingSData, $subTable, $existingSubJoin, $subRowPkField, $nextLastId);
*/
$sql .= $this->buildQueryListChild($sTable, $sData, $existingNextData, $nextBlank, $subTable, $existingSubJoin, $subRowPkField, $nextLastId);
I have a for loop, and will form two arrays in the loo
foreach ($data as $key => $value) {
........
........
$user_insert[] = [
'keyy' => $value,
'key' => $value,
....
...
...
];
$someArray1[] = [
/*'user_id' => $insert_id,*/
'key1' => $value1,
'keyy' => $value,
.......
........
];
}
the count of $user_insert[] array is 4, the count of $someArray1 is 15.
after this for loop, I need to insert $user_insert array data to the database and use that inserted_id to insert next array $someArray1
foreach($user_insert as $insert_user){
$unique_user_insert = array_unique($insert_user);
//dd($unique_user_insert);
$insert_id = DB::table('users')->insertGetId($unique_user_insert);
foreach ($someArray1 as $someArray) {
$someArray['user_id'] = $insert_id;
DB::table('table_name')->insert($someArray);
}
}
So the problem here is the data in the second loop is inserting 60 times(4 * 15). I need to insert only 15 rows.
The data($someArray1) is coming from the first for loop, but I need to add a user_id to that array which I get after the insert operation in second for loop.
So how can i insert only 15 rows.
I'm going to assume that you are able to access your $someArray1 using the $insert_id value to find the appropriate user data.
foreach($user_insert as $insert_user){
$unique_user_insert = array_unique($insert_user);
$insert_id = DB::table('users')->insertGetId($unique_user_insert);
// Get the user information you need as $someArray1 should be user_id=>data
$userData = $someArray[$insert_id];
$userData['user_id'] = $insert_id;
// No need for an inner loop, just access the necessary properties of the loop you created earlier.
DB::table('table_name')->insert($userData);
}
Your tags indicate that you are using Laravel 5 too. If you are using the eloquent ORM, some of the insertion and ID retrieval can be cleaned up by creating Models for your DB tables.
Actually, each time you process a line from $user_insert, you loop over $someArray1 instead of fetching just the line you need.
The thing is to understand the line you need. As much as I can understand your piece of code, I would say the easiest (most readable) way of doing it is by using a for loop, not a foreach one :
for( $i = 0, $iMax = count( $user_insert ); $i < $iMax; ++$i ){
$insert_user = $user_insert[$i];
// Put your `$user_insert` insert code here
$someArray1[$i]['user_id'] = $insert_id;
DB::table('table_name')->insert( $someArray[$i] ); // Note the [$i] here
}
You also may do that with foreach by requesting indices :
foreach( $user_insert as $i => $insert_user ){
$unique_user_insert = array_unique($insert_user);
//dd($unique_user_insert);
$insert_id = DB::table('users')->insertGetId($unique_user_insert);
// Now use $i requested above :
$someArray1[$i]['user_id'] = $insert_id;
DB::table( 'table_name' )->insert( $someArray1[$i] );
}
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;
}
how to format sql server rows using php that look like this:
id company value monthyear
1 companyone 30 january2012
2 companytwo 20 february2012
3 companyone 10 february2012
into this:
monthyear: ['january2012', 'february2012']
and this:
company: 'companyone', value: [30, 10]
company: 'companytwo', value: [0, 20]
each instance of a month from the db is combined into one instance.
company one, which has two rows, is combined into one instance where each value is lined up in order of the month. company two, which only has one instance, has it's value defined as 0 where it has no instance in a month.
the farthest i've gotten is are two two dimensional array with array_merge_recursive and some conditional statements but then my head goes into knots.
SELECT
company,
GROUP_CONCAT(value SEPARATOR ',') AS value,
GROUP_CONCAT(monthyear SEPARATOR ',') AS monthyear
FROM
yourTable
GROUP BY
company
Some Reference for GROUP_CONCAT.
PHP solution:
Select the to be grouped attribute sorted (company). Loop over them and open a new group every time you encounter a different value for company. As long as the current row has the same row as the previous, add value and monthyear to the current company.
You could do this even without sorting:
while($row = mysql_fetch_assoc($resource))
{
$values[$row["country"]][] = $row["value"];
$monthyear[$row["country"]][] = $row["monthyear"];
}
Some output example
foreach ($values as $country => $valuesOneCountry)
{
// each country
var_dump($country);
foreach ($valuesOneCountry as $i => $value)
{
// value, monthyear for each original row
var_dump($value, $monthyear[$country][$i]);
}
}
Elegant way with OOP:
class Tuple
{
public $country, $values, $monthyears;
public function __construct($country, $values = array(), $monthyears = array())
{
$this->country = $country;
$this->values = $value;
$this->monthyears = $monthyears;
}
}
$tuples = array();
while($row = mysql_fetch_assoc($resource))
{
if (!isset($tuples[$row["country"]]))
$tuples[$row["country"]] = new Tuple($row["country"]);
// save reference for easy access
$tuple = $tuples[$row["country"]];
// or some method like $tuple->addValue($row["value"]);
$tuple->values[] = $row["value"];
$tuple->monthyears[] = $row["monthyear"];
}
var_dump($tuples);
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 =)