I'm writing a Perl script that is meant to deal with an API which returns metrics about a set of URLs that I pull from MySQL then post these metrics back into a different table. Currently this piece of code:
my $content = $response->content;
my $jsontext = json_to_perl($content);
my $adsql = 'INSERT INTO moz (url_id,page_authority,domain_authority,links,MozRank_URL,MozRank_Subdomain,external_equity_links) VALUES (?,?,?,?,?,?,?)';
my $adrs = $db->prepare( $adsql );
my $adsql2 = 'UPDATE url
SET moz_crawl_date = NOW()
where url_id = ?;';
my $adrs2 = $db->prepare( $adsql2 );
my $currentUrlId = 0;
foreach my $row (#$jsontext){
$adrs->execute($url_ids[$currentUrlId], $row->{'fmrp'}, $row->{'upa'}, $row->{'pda'}, $row->{'uid'}, $row->{'umrp'}, $row->{'ueid'});# || &die_clean("Couldn't execute\n$adsql\n".$db->errstr."\n" );
$adrs2->execute($url_ids[$currentUrlId]);
$currentUrlId++;
}
is throwing this error:
Not an ARRAY reference at ./moz2.pl line 124.
this is line 124:
foreach my $row (#$jsontext){
this whole chunk of code is in a while loop. I am actually able to iterate a couple times and fill my MySQL table before the script fails (technically the program works, but I don't want to just leave an error in it).
Anybody have any suggestions?
Perl gave you the correct answer
Not an ARRAY reference: #$jsontext
You are dereferencing $jsontext, which is the result of json_to_perl(string), to an array.
But json_to_perl() didn't return an arrayref.
json_to_perl seems to be from this API: http://search.cpan.org/~bkb/JSON-Parse-0.31/lib/JSON/Parse.pod#json_to_perl
which returns according to the doc either an arrayref or a hashref.
Apparently it did return a hashref in your case, so you have to add the logic to deal with the HASH case. Which seems to be a single row.
if (ref $jsontext eq 'HASH') {
# seems to be a single row
$adrs->execute($url_ids[$currentUrlId], $jsontext->{'fmrp'}, $jsontext->'upa'}, $jsontext->'pda'}, $jsontext->'uid'}, $jsontext->'umrp'}, $jsontext->'ueid'});# || &die_clean("Couldn't execute\n$adsql\n".$db->errstr."\n" );
$adrs2->execute($url_ids[$currentUrlId]);
$currentUrlId++;
} elsif (ref $jsontext eq 'ARRAY') {
foreach my $row (#$jsontext){
$adrs->execute($url_ids[$currentUrlId], $row->{'fmrp'}, $row->{'upa'}, $row->{'pda'}, $row->{'uid'}, $row->{'umrp'}, $row->{'ueid'});# || &die_clean("Couldn't execute\n$adsql\n".$db->errstr."\n" );
$adrs2->execute($url_ids[$currentUrlId]);
$currentUrlId++;
}
}
Related
I have the following Wordpress function that worked in PHP 7. Since converting to 8.1, it's not working.
function dropdown_handler() {
$output = drop_function();
//send back text to replace shortcode in post
return $output;
}
function drop_function() {
//get the csv file with amounts
if ($file_handle = fopen("wp-content/plugins/drop/amounts.csv", "r")) {
while (!feof($file_handle) ) {
$lines[] = fgetcsv($file_handle, 1024);
}
fclose($file_handle);
$lines = str_replace ("£","£",$lines);
}
else {
echo "Sorry, something went wrong";
}
In my error log I'm seeing "PHP Warning: Array to string conversion in" relating to the $lines = str_replace line but I think there's something wrong with the fopen statement.
Basically, the word Array is being stored in the $lines variable rather than the contents of the CSV file.
Any ideas please?
Your code was always broken, it's just broken in a slightly more obvious way than it used to be...
$lines[] = fgetcsv($file_handle, 1024);
fgetcsv, unless it fails, returns an array; you then add this array as a new item to another array, $lines. The result is an array of arrays, like this:
$lines = [
['line 1 first item', 'line 1 second item'],
['line 2 first item', 'line 2 second item'],
];
Later, you pass this whole array to str_replace; but str_replace only knows how to deal with a single dimension of array.
So this works:
$singleLine = ['line 1 first item', 'line 1 second item'];
var_dump(str_replace('item', 'ITEM', $singleLine));
But this doesn't:
var_dump(str_replace('item', 'ITEM', $lines));
Running that example on multiple versions of PHP reveals that under PHP 7.x, str_replace reacted by simply leaving the inner arrays untouched - in other words, it did nothing.
In PHP 8, it instead tries to turn each inner array into a string, issuing the warning and producing the word "Array" (which will then have any substitutions applied to it).
The fix for both PHP versions is to run the str_replace on each of the inner arrays, most simply by using array_map:
var_dump(
array_map(
fn($innerArray) => str_replace('item', 'ITEM', $innerArray),
$lines
)
);
Alternatively, you can just delete the str_replace line completely, since you were apparently happy enough when it wasn't actually doing anything.
I'm new to PowerShell and have a specific question about working with MySQL in PowerShell.
I got this function:
Function run-mySQLInsertQuery{
param(
$connection,
[string[]]$insertQuery
)
foreach ($command in $insertQuery){
$MySQLCommand = $connection.CreateCommand()
$MySQLCommand.CommandText = $command
$rowsInserted = $MySQLCommand.ExecuteNonQuery()
if ($rowsInserted) {
return $rowsInserted
} else {
return $false
}
}
}
With this version of the function i get the following Error:
Cause:
"The CommandText property has not been properly initialized."
Errorline:
$rowsInserted = $MySQLCommand.ExecuteNonQuery()
I searched for a solution and edited my function a bit to the following (for testing purpose):
Function run-mySQLInsertQuery{
param(
$connection,
[string[]]$insertQuery
)
$abcd = $insertQuery[1]
foreach ($command in $insertQuery){
$MySQLCommand = $connection.CreateCommand()
$MySQLCommand.CommandText = $abcd
$rowsInserted = $MySQLCommand.ExecuteNonQuery()
}
}
With this code, the function executes the query without a problem. My question now is, why? i cant really see a difference, because in $command should be the exact same query like it is in $abcd. Or am I getting something wrong?
EDIT:
As its asked it the comments, here is how i call the function:
[String[]]$statements = ""
foreach($key in $arrayStatus.Keys){
$item = $arrayStatus[$key]
$insertStatus = "INSERT INTO tx_tphbusinessofferings_domain_model_status (status_id, status) VALUES ('$key', '$item')"
$statements += $insertStatus
}
$Rows = run-mySQLInsertQuery -connection $mySQLconnection -insertQuery $statements
The problem is that you are initializing your array (the one you are passing in) with an empty string:
[String[]]$statements = ""
And then adding elements to it... so your first iteration of the passed array is an empty string, which won't work (it'll set the command text as empty, that's the error you are getting). It works on the second code because you are grabbing the second object of the array (which is your insert statement).
Initialize your array to empty and it should work:
[String[]]$statements = #()
Apart from that, your first script always returns on the first iteration, so it'll only work once (not for every insert you pass). Not sure what do you want to return if you are passing in more than one query, but that's up to your design decisions
I have an array
my #cols = ("accountid", "balance");
and a dataset
my $rowsref=$dbh->selectall_arrayref($_[0]);
foreach my $row (#$rowsref) {
print join(", ", map {defined $_ ? $_ : "(null)"} #$row), "\n";
}
which prints "1, 150".
I would like to get a JSON output like [{"accountid": 1, "balance": 150},{..}].
I have the JSON module loaded, but unsure how to merge #cols with each $row.
edit: added explanation of column name transaction in 2nd example
edit: Fixed for your requirement of a single JSON encoding of the whole resultset.
edit: forgot keys in cols mapping code in 2nd example.
edit: typo'd hashref everywhere to arrayref. :-
Firstly, use selectall_hashref instead, so that it already contains the key names.
Then use one of the JSON encoding modules to encode each row.
(making the same assumptions as your code....)
Using the list-of-hashrefs from selectall_hashref() as-is:
use JSON::XS;
use 5.10.0;
my $rowsref = $dbh->selectall_hashref($_[0]);
print JSON::XS::encode_json($rowsref),"\n";
Performing translation on colnames from selectall_hashref():
If the column names from the database aren't the same as your column names, then you'll need a mapping:
use JSON::XS;
use 5.10.0;
my $trans = { account => 'accountid', amount => 'balance' };
my $rowsref = $dbh->selectall_hashref($_[0]);
my $output = [];
for my $row (#$rowsref) {
push #$output, {
map {
my $colname = exists($trans->{$_}) ? $trans->{$_} : $_;
my $value = $row->{$_};
$colname => $value;
} keys %$row
});
}
print JSON::XS::encode_json($output),"\n";
For each $row above of the resultset, keys %$row gives back the column names in the row as returned from the database.
The map operation takes each of those column names and producues 2 scalar values; (1) $colname is either the original database column name or (if it's found in the $trans hashref) a 'translation' of the column name; (2) $value is the value returned by the database for this column name in this particular $row. $colname => $value returns both the $colname and $value from the map as a 'flattened' pair of scalar values. That means that the map operation returns a list of scalar values twice as long as the original list of column names returned by keys %$row.
Finally, the push #$output, { ... } creates an anonymous hash reference from that list of scalar values in key,value,key,value,... order and adds it to the end of the $output array reference.
Blind translation from selectall_arrayref()
If (for some reason) you have a pathological aversion to querying hashrefs from your database, I guess you could do:
use 5.10.0;
my #cols = ("accountid", "balance");
my $rowsref = $dbh->selectall_arrayref($_[0]);
my $output = [];
for my $row (#$rowsref) {
my %row = map { $cols[$_] => $row->[$_] } (0 .. $#cols);
push #$output, \%row;
}
print JSON::XS::encode_json($output),"\n";
That assumes there are only two columns coming back from the query, though.
"Defined or" operator:
By the way... assuming a late enough perl, you can replace this sort of thing:
defined $_ ? $_ : "(null)"
with this:
$_ // "(null)"
Your code editor (e.g: Vim) might not syntax highlight it correctly if it's not up to date with perl. (e.g: it might treat it as an m// construct).
Note that PostgreSQL can also generate JSON. If it is an option for you then Perl JSON module is redundant.
I have a foreach loop setup so that I can insert a huge array (about 200 keys). I admit that I do not fully understand how PDO works but I'm an idiot and reading tons of literature has only made me angry and more confused. In all of the examples I saw, the array was spelled out in the prepare statement. I cannot do that because I've "built" the array through 200 iterations of the foreach loop.
So how do I do an insert into without explicitly restating the array in the prepare statement? Thank you for helping me not kill myself with a spork.
$ar_header = array();
$ar_data = array();
for ($rowcount=1; $rowcount<=$tblrows; $rowcount++)
{
for ($cellcount=1; $cellcount<=$tblcells; $cellcount++)
{
if (isset($_POST["row"."$rowcount"."cell"."$cellcount"])){
// use this format as it matches with the database
$header = ($array1[$rowcount]).($array2[$cellcount]);
$data = $_POST ["row"."$rowcount"."cell"."$cellcount"];
//the array will add each new header and data repectively
$ar_header[] = $header;
$ar_data[] = $data;
$ar_god = array_combine($ar_header, $ar_data);
}
}
}
$qry = ("INSERT INTO invforms ('header') VALUES ('value')");
$prep = $conn->prepare($qry);
$prep->execute($ar_god);
I don't know why when I echo json_encode a query result set I get the number of the result row before each object. I just want to count the number of total rows returns and have them displayed only once in the beginning of the JSON string and then just the rows returns afterwards. I.e. using the following code:
//...active record query
$result = $this->db->get();
$data = array();
$count = 1;
foreach($result->result() as $row)
{
$data['count'] = $count;
$entry = array();
$entry['firstname'] = $row->first_name;
$entry['lastname'] = $row->last_name;
$entry['jobtitle'] = $row->title;
$entry['dept'] = $row->dept_name;
$entry['deptid'] = $row->dept_no;
if($row->emp_no == null)
{
$entry['ismanager'] = 0;
}
else
{
$entry['ismanager'] = 1;
}
$data[] = $entry;
$count++;
}
return $data;
and then json_encode it in the controller, I get:
{"count":35,"0":{"firstname":"Georgi","lastname":"Facello","jobtitle":"Senior Engineer","dept":"Development","deptid":"d005","ismanager":0},"1":{"firstname":"Kirk","lastname":"Facello","jobtitle":"Senior Engineer","dept":"Development","deptid":"d005","ismanager":0},....rest of the query results
What I don't want is the "0" and "1" etc, before the row results. I already have the total count of the returned results so I don't need the individual row numbers.
If someone could kindly help me out I would appreciate it, thanks.
If you try to serialize an array as JSON, it would become something like this:
[elem1, elem2, elem3, ...]
But if that "array" have other fields then it will be serialized as an object:
{"field":value, "0":elem1, "1":elem2, "2":elem3, ...}
Since there's no way to serialize field using the array syntax, and json_encode can not simply discard it, then it uses the object syntax. As stated in the docs:
Note:
When encoding an array, if the keys are not a continuous numeric sequence starting from 0, all keys are encoded as strings, and specified explicitly for each key-value pair.
A possible workaround for this would be separating the count from the list of elements:
$data = array();
$list = array();
$data['list'] = list;
$count = 1;
foreach($result->result() as $row)
{
$data['count'] = $count;
$entry = array();
...
$list[] = $entry;
$count++;
}
That would serialize to something like:
{"count":35,"list":[{"firstname":"Georgi","lastname":"Facello","jobtitle":"Senior Engineer","dept":"Development","deptid":"d005","ismanager":0},{"firstname":"Kirk","lastname":"Facello","jobtitle":"Senior Engineer","dept":"Development","deptid":"d005","ismanager":0},....rest of the query results]}
It looks like you might be using JSON_FORCE_OBJECT on your json_encode which will always make your numerical index show up as a property. You should show your json_encode step in your question.
If you turn option off and go with a default encoding, you will still need to nest your numerically indexed array in its own property or the numerical indexes will show up as properties in order to make valid JSON. For perhaps do something like this when assigning your rows to the object:
$data['records'][] = $entry;