Twice now I've mistakenly used row() instead of row_array() when fetching a single row result. Usually, it goes unchecked without any warning messages. 15-30 minutes later I finally notice the issue; doh doh!
Does anyone have any good suggestions as to ways to prevent this mistake. The object format doesn't throw a PHP warning, but - of course - the code doesn't work when you expect an array from a model method.
I accidentally used:
if ($query->num_rows() > 0) {
return $query->row();
}
Which should instead be:
if ($query->num_rows() > 0) {
return $query->row_array();
}
I thought about overriding the row() database method and adding in a log_message() statement that would at least let me know (on debug level) that I'm using the object format, instead of the much more typical (for my code) format.
How do you avoid making data-type errors in your CodeIgniter model methods? Simple, declare your methods' return data type.
// returns null or an object
public function getFlatObject(int $id): ?object
{
return $this->db->get_where("your_table", ['id' => $id])->row();
}
// unconditionally returns an array
public function getFlatArray(int $id): array
{
return $this->db->get_where("your_table", ['id' => $id])->row_array();
}
If your return data does not match the declared type(s) (after :), then php is going to generate errors for you.
Let's take this opportunity to clear up any confusion...
If you call either row() or row_array() and your sql logic affords the possibility of more than one row of data -- then you are only going to get the first row of the result set.
If you call row() but there are no rows in the result set, you will have a return value of null.
If you call row_array() but there are no rows in the result set, you will receive an empty array as the return value.
Hence, row() may return an iterable or non-iterable data type; whereas row_array() can ALWAYS be safely passed directly into an iterator (like foreach() or array_map()) -- it just might not having anything to iterate.
row() returns object type, and row_array() returns array.
So it is better in parsing the data of row data in DB when using row_array.
The only way to "prevent" is pretty much learn over the documentation again so you can keep a common habit of using which function.
You can note yourself, if you are going to return multiple records, use the row_array(), for a singular record, use the row() function.
Remember, don't speed type your code, proofread your code step by step before continuing.
Overriding the original function is bad practice, using the row() function is useful when retrieving a singular record, so don't do this.
Related
I have a Laravel controller which takes a search parameter, looks through all the records in a certain time range, uses the laravel collection ->filter method, and then on the results of that filter method, returns the json ->json
$logs = RequestLog::orderBy('created_at', 'DESC')->whereBetween('created_at', [$start, $end])->get();
$logs = $logs->filter(function($log) { /* my own logic in here */ });
return response()->json($logs->toJson());
Now this seems to work just fine. It normally returns an array of the matching records, [{"id":1},{"id":2},{"id":3}]
But if the ->filter function leaves one result, let's say the 25th record in the array, the response now comes out like this: {25: {"id": 25}}. It ruins the normal array structure and returns a JSON object with a single key.
Is this expected? What's up with this? How do I deal with this?
Note: my filter function cannot be done via sql means, it has to be done in PHP.
[edit] it's actually returning an Object JSON anytime the filter results are anything other than the first items in the original Query results.
Normally after applying methods that modifies (especially removes an item from) a collection, the indexes may not come ordered. This is why you need ->values() on the collection.
So you'd have:
return response()->json($logs->values()->toJson());
values() Resets the keys on the underlying array.
I've set up a class to handle my MySQLi calls. The calls are working as they should, so no problems there.
I recently removed the result handling from my class's query method to two separate setter methods. There is now an error resulting from two consecutive fetch_all calls.
The previous (working) code with a single fetch_all call loads results into the two arrays res_rows and res_cols:
// Convert results into two sets of values
// and store in properties
// $res_rows=results by row (column names in element 0)
// Each row is an indexed array
// $res_cols=results by column (associative arrays)
// Key is the column name; Value is an array
$results=$res->fetch_all(MYSQLI_ASSOC);
// $colNames is an array of the column names
$colNames=array_keys($results[0]);
// Rows
foreach($results as $r) {
$this->res_rows[]=array_values($r);
}
array_unshift($this->res_rows,$colNames);
// Columns
$vals=array();
// Count the number columns in the query
$numCols=count($colNames);
// Iterate through the columns
for($i=0;$i<$numCols;$i++) {
$col=$colNames[i];
$storage=array();
foreach($results as $r) {
$storage[]=$r[$colNames[$i]];
}
$vals[]=$storage;
}
$this->res_cols=array_combine($colNames,$vals);
I've moved this code into two methods setRes_rows and setRes_cols and called them sequentially (see code). Each method pulls results from the result object using fetch all.
$this->setRes_rows($res);
$this->setRes_cols($res);
What happens is that the first call behaves as expected, and the second call returns an empty array.
If I reverse the calls (e.g. setRes_cols first), the same thing happens (first call works as expected; second call is empty). So I know the code is good. I even changed all the variable names in one of the methods with no effect.
I dumped all properties and methods on the result object between calls and it doesn't look like it changes. But for some reason that second fetch_all does not work.
The easy fix is for me to use a single fetch_all then call my methods. But I'm interested in knowing if there's anything weird I'm missing.
Thanks, everyone.
It looks like you need to reset the result pointer after a fetch_all.
I didn't find anything in the docs specifically for fetch_all - only about resetting after a fetch_assoc(), where we can find a reference to data_seek(): http://php.net/manual/en/mysqli-result.data-seek.php
So here's what you should do:
before the second fetch_all() do a
$res->data_seek(0); // where $res is your mysqli result Object
This basicly sets the result pointer to the first record.
What is the difference between these methods:
find()
findOrFail()
first()
firstOrFail()
get()
list()
toArray()
I've been using them and each one gives a different result and sometimes I need to add toArray() at the end of get() because my function is expecting an array. Won't the other methods produce arrays as well?
find($id) takes an id and returns a single model. If no matching model exist, it returns null.
findOrFail($id) takes an id and returns a single model. If no matching model exist, it throws an error1.
first() returns the first record found in the database. If no matching model exist, it returns null.
firstOrFail() returns the first record found in the database. If no matching model exist, it throws an error1.
get() returns a collection of models matching the query.
pluck($column) returns a collection of just the values in the given column. In previous versions of Laravel this method was called lists.
toArray() converts the model/collection into a simple PHP array.
Note: a collection is a beefed up array. It functions similarly to an array, but has a lot of added functionality, as you can see in the docs.
Unfortunately, PHP doesn't let you use a collection object everywhere you can use an array. For example, using a collection in a foreach loop is ok, put passing it to array_map is not. Similarly, if you type-hint an argument as array, PHP won't let you pass it a collection. Starting in PHP 7.1, there is the iterable typehint, which can be used to accept both arrays and collections.
If you ever want to get a plain array from a collection, call its all() method.
1 The error thrown by the findOrFail and firstOrFail methods is a ModelNotFoundException. If you don't catch this exception yourself, Laravel will respond with a 404, which is what you want most of the time.
Probably things changed but the findorFail method can take 2 arguments: $id and $columns mixed/array params respectively. Passing a second arg is not required. That said, this would work:
$post = Post::findOrFail([1,2], ['title', 'subtitle']);
If one of the $ids fails, the ModelNotFoundException with message 'No query results for model ... ' will be thrown.
I need to get data in PHP from PDO with some transformations of data during the fly. The best variants are FETCH_CLASS mode and FETCH_FUNC mode.
but the problem of FETCH_CLASS is that each property need to go throw __set() method to get transformation or at least to check if it needs transformation. And for 40K rows with 50 columns each it takes too much time.
FETCH_FUNC seems ideal for me cause i get all the columns of the row at once, can make some transforms BUT, the only problem is that i don't have column names cause column values come into function as parameters and the only way to get them is to call func_get_args().
So generally the question is -> How can i get row data at once in some method of the class, function, using some combination of PDO FETCH modes as an assoc array, obj or some like columnName->columnValue structure? Using foreach after getting result is to long - i want to get transformed data already after calling pdo's fetchAll() method
Seems to me like you're overthinking this and are trying to be too clever. This will do just fine:
$data = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$data[] = myTransformation($row);
}
In other words, just fetch with FETCH_ASSOC, then call your function on that result.
This question already has answers here:
How do I make sure that values from MySQL keep their type in PHP?
(5 answers)
Closed 1 year ago.
I'm using custom PHP wrapper for for mysqli. The class is designed to use prepared statements if bind parameters are passed to the a fetchResultSet function otherwise, as is my understanding, it saves a call to the database and uses the query function (perhaps I'm wrong saving a call and the rest of this question can be answered by simply using prepared statements even with no parameters to bind).
Once the query has been executed a fetchResult function passes back one of two objects ResultSetObjectResult or ResultSetObjectStmt both of witch implement a ResultSetObject interface. (The ResultSetObjectStmt object wraps an stmt object while the ResultSetObjectResult wraps a result object.)
Both classes return an associative array when a row is requested either though calling fetch_assoc on a result or calling bind_result on an stmt object (basically).
What I've noticed is that if a prepared statement is executed then the data returned are properly casted to integers, real numbers and strings depending on their types in the database; but when a result is returned and fetch_assoc is called on that result then all of the data are casted as strings. I realize this is mentioned in the documentation for fetch_assoc. I'm curious if there is another function or something I can do to have mysql properly cast the results.
[EDIT]
I should mention that this is only an issue because json_encode places data in quotes depending on their types.
[EDIT AGAIN]
I like to know if there is anyway to properly cast the data returned from a result without resorting to guess work using functions like is_numeric or by making an additional call to the database for the schema of the table. Using prepared statements exclusively when fetching data works, but I'd really love to save that additional prepare call to the database.
I don't know what wrapper your using but you could have a function like...
while ($row = $query->fetch_assoc()) {
$row = auto_cast($row);
}
function auto_cast($row) {
foreach ($row as $key => $value) {
if (ctype_digit($value)) {
$row[$key] = (int) $value;
}
elseif (is_numeric($value)) {
$row[$key] = (float) $value;
}
else {
$row[$key] = (string) $value;
}
}
return $row;
}
I will try to help here. When usign prepared statement, the PHP knows what is the column type and depending on that stores data in different types: string, integer, bool, etc. When using fetch_assoc it does not know this info. You could create a function that is getting the fields/columns MySQL data using mysqli_fetch_fields and use their type. Based on that you could typecast them. Hope that will help.
Fior information about different column types see here http://www.php.net/manual/en/mysqli.constants.php
MYSQLI_TYPE_LONG - Field is defined as INT an so on.