My POST form sends a value &messages=12,11
I get it using: $messages = $ep->remove($_POST["messages"]);
And my SQL string is:
$query = $db->prepare("DELETE FROM messages WHERE messageID IN ('".$messages."') AND accountID=:accountID");
$query->execute([':accountID' => $accountID]);
And the error appears....
<b>Fatal error</b>: Uncaught exception 'PDOException' with message 'SQLSTATE[22007]: Invalid datetime format: 1292 Truncated incorrect DOUBLE value: '12,11'' in /var/www/vhosts/xxx.xxx/xx/xxx/xx/xxxx/messages.php:17
This code deletes multiple messages from the database. But don't works for me. Any fix?
Remove the quotation marks:
$query = $db->prepare("DELETE FROM messages WHERE messageID IN (".$messages.") AND accountID=:accountID");
Otherwise the value you're sending is 12,11 (which isn't a number as per your database definition), as opposed to 12 and 11, which are both numbers.
Finally, this particular query structure is open to SQL injection. You may want to either sanitise the $messages variable (since it can only include numbers), or create a prepared statement.
For this example, sanitising could work as follows:
$messages = preg_replace('/[^0-9,]/', '', $messages);
//Removes all characters besides numbers and commas
You could also ensure that the list of numeric message IDs always matches the following regex pattern:
$\d+(?:,\d+)*$
That is, the parameter should always be some number, followed by an optional quantity of ,\d+ terms
if (!preg_match("/^\d+(?:,\d+)*$/", $messages)) {
// throw an exception, you are being injected
}
Just to add to the other answer, here is a way to properly prepare all the values
$array = explode(',', '1,2,3,4,5,6');
$keys = preg_replace('/(.+)/', ':v\1', array_keys($array));
print_r($keys);
$sql = "DELETE FROM messages WHERE messageID IN ( ".implode(',', $keys)." ) AND accountID=:accountID";
print_r($sql);
$params = array_combine($keys,$array);
$params['accountID'] = 'foo';
print_r($params);
//$stmt->execute($params);
Output
//print_r($keys);
Array
(
[0] => :v0
[1] => :v1
[2] => :v2
[3] => :v3
[4] => :v4
[5] => :v5
)
//print_r($sql);
DELETE FROM messages WHERE messageID IN ( :v0,:v1,:v2,:v3,:v4,:v5 ) AND accountID=:accountID
//print_r($params);
Array
(
[:v0] => 1
[:v1] => 2
[:v2] => 3
[:v3] => 4
[:v4] => 5
[:v5] => 6
[accountID] => foo
)
Sandbox
It was way too much for a comment.
Basically it takes the keys and using preg replace we can take the sequential number and add a string to it (placeholders have to start with a alpha). Technically the : in the array for execute is optional.
Then we can put that into the query, as it's all generated by PHP because the keys are made from explode. If you have keys from POST, don't use those instead use array_keys(array_values($array)) array values will make the array numerically indexed, then you use those keys.
Next using array combine we can merge those keys back into the original values and put that array into execute
In this case You can do it just with a regex, but I wanted to show something that was useful for more complex cases of IN.
Related
Why does the below code not assume an empty 'value' pair for the specified 'key'?
Take the following example:
$key1 = "An element";
$key2 = "Another, without a pair";
$key3 = "A third, with a pair";
$check=Array($key1=>21, $key2, $key3=>23);
If outputted using print_r, returns the following:
Array ( [An element] => 21 [0] => Another, without a pair [A third, with a pair] => 23 )
Rather than:
Array ( [An element] => 21 [Another, without a pair] => null [A third, with a pair] => 23 )
I want to have an array containing an unknown number of items, all of which may or may not have a key=>value pair. What are my options for ensuring that I get the second result?
Essentially, I want to pass a list of keys from my controller to a function, and for the function to identify them as key->value even if the value is null. Some of the keys might have values set, others might not.
It may be that the best solution lies in the foreach $key as $value {} code space, or that I can wrap $key1 in some form of parenthesis... I'm not sure!
Thanks
Add NULL as shown here.
$check=Array($key1=>21, $key2=>NULL, $key3=>23);
To Initialize the array:
$keys = array($key1, $key2, $key3);
$check = array_fill_keys($keys,NULL);
It depends in the type of the values.
Using NULL is a very common technique, But, if all values, are intended to be of the same type, maybe you would like to use an "empty" value for them, that is not NULL.
Example, if the values are integers, you may want to use 0 or -1 to indicate the value is not assigned. Or "" for strings.
In case that you are storing different types, then, you may like to use NULL as a non typed value.
So I've the following situation: A process is piping huge amounts of data into a PHP script I'm using to parse and then store some info in a DB.
The input data is a multiline string, but what really matters for me is to find particular key words and then say that the input data is an error of the type 1 to n.
I've an array like this:
$errors = [
1 => [
"error 4011",
"clp-no",
],
2 => [
"error 4012",
"clp-nf",
"0010x100"
],
];
The idea is to state what key is the error - the array keys are the error numbers. Currently I've this piece of code to take care of the situation:
$errorId = 25; // Default error for undetected / others
foreach ($errors as $ierrorId => $matches) {
foreach ($matches as $match) {
if (mb_stripos($raw, $match) !== false) {
$errorId = $ierrorId;
break 2;
}
}
}
This code works fine, however, it looks like there is a bottleneck when I look at resource usage when the processes dump information to it... (usually around 10 or 20 strings to be processed by running that 20 times.)
What is the recommended way to do what I'm trying to accomplish with the minimum resource usage?
The output of the code is the ID of the error. The ID of the error is one of the numeric keys of the $errors array.
This code basically groups possible messages that are in the reality the same error and then gives me a unique error ID.
Thank you.
Example of the $raw input this parses:
[0]: error 4011 processing request
.
No input data: clp-nf
.
This is an automated message from the PTD daemon
=> Error: 0010x111.
And some others, the bottom line is: The format can change and I can't rely on position and stuff, it must try to find one of the strings on the array and then return the array key. For instance the second message will output 2 because clp-nf can be found on the second position of the array.
I did a little benchmarking with different functions to find text strings.
mb_stripos (case insensitive)
mb_strpos (case sensitive)
mb_strpos and strtolower on both the string to be searched and the error strings
I also tried the nested array structure you posted above, and a flat error list with the keys being the error strings and the values being the error number. Here are the results I got from running 20,000 reps on a set of sample strings (all returned the same set of errors) with an array of six different error strings, including one error string with non-ASCII characters:
[nested_stripos] => 178.60633707047s
[nested_strpos] => 19.614015340805s
[nested_strpos_with_strtolower] => 25.815476417542s
[flat_stripos] => 177.30470108986s
[flat_strpos] => 18.139512062073s
[flat_strpos_with_strtolower] => 24.32790517807s
As you can see, using mb_stripos is very slow in comparison with mb_strpos. If you don't know what case the errors will be in, it is much quicker to convert everything to lowercase than to use mb_stripos. Using a flat list is marginally faster than the nested arrays over 20,000 reps but there is unlikely to be a noticeable difference unless your raw input is large.
I didn't test preg_match as it is fairly well known that regular expressions are slower than string matching.
If you start recording the error string that was matched (e.g. error 4012 or 0010x001), you can build up frequency tables and order your error strings by which occur the most frequently.
This was the fastest function:
# lines: an array of text strings, including several with non-ASCII characters
# $err_types: data structure holding the error messages to search for (see below)
function flat_strpos($err_types, $lines){
$list = array();
foreach ($lines as $l) {
$err = 25;
foreach ($err_types['flat'] as $e => $no) {
if (mb_strpos($l, $e) !== false) {
$err = $no;
break;
}
}
$list[] = "ERR $err; content: " . mb_substr($l, 0, 100);
}
return $list;
}
And for reference, the $err_type data structure:
$err_types = [
'flat' => [
'error 4011' => 1,
'clp-no' => 1,
'error 4012' => 2,
'clp-nf' => 2,
'0010x100' => 2,
'颜色' => 3
],
'nested' => [
1 => [
'error 4011',
'clp-no'
],
2 => [
'error 4012',
'clp-nf',
'0010x100'
],
3 => [
'颜色'
]
]
];
Facing a weird situation with arrays..
I am using LinkedIn API to get profile info which returns data in two formats..
If user has just one educational item
educations=>education=>school-name
educations=>education=>date
...
If more than one education item
educations=>education=>0=>school-name
educations=>education=>0=>date
...
educations=>education=>1=>school-name
educations=>education=>1=>date
...
Now I am trying to make it consistent and convert
educations=>education=>school-name
to
educations=>education=>0=>school-name
But getting error in code that i believe should work
if(empty($educations['education'][0]['school-name']))
{
$temp = array();
$temp['education'][0]=$educations['education'];
$educations = $temp;
}
This fails for "just one educational item", generates error on the first line for (isset,is_array and empty)
PHP Fatal error: Cannot use string offset as an array in ...
print_r returns
[educations] => Array
(
[education] => Array
(
[id] => 109142639
[school-name] => St. Fidelis College
[end-date] => Array
(
[year] => 2009
)
)
)
Usually you'd write the assignment like this:
$temp = array(
"education" => array($educations['education'])
);
To avoid any issues with indexes. This might also fix yours.
If you're unsure about the contents of $educations['education'][0]['school-name'] you can simply check each part:
if(isset($educations['education'], $educations['education'][0], $educations['education'][0]['school-name']))
This works because isset doesn't behave like a normal function. It takes multiple arguments in a lazy manner.
You want:
if(array_key_exists('school-name',$educations['education']))
{
$educations['education'] = array($educations['education']);
}
Today I experienced the same problem in my application. Fatal error: Cannot use string offset as an array in /home/servers/bf4c/bf4c.php on line 2447
line 2447
if (!isset($time_played[$player]["started"])) {
$time_played[$player]["started"] = $time;
}
$time_played was overwritten elsewhere and defined as a string. So make sure you do use unique variable names.
Here's a tip if you're running through a loop, and it breaks:
if( $myArray != "" ){
// Do your code here
echo $myArray['some_id'];
}
I'm having trouble with the executing the command mailq and turning it's output into values in a multidimensional array.
$results = array();
exec('/usr/bin/mailq', $mailQresult);
foreach ($mailQresult as $key => $mailqLine) {
$res = preg_match('/^([A-Z0-9]{1,20})\s+([1-9][0-9]*)\s+((Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+[A-Za-z]{3}\s+[0-9]{2}\s+[0-9]{2}\:[0-9]{2}\:[0-9]{2})\s*$/', $mailqLine, $matches);
if ($res) {
// Insert message ID and other data into the object.
// It will be competed afterwards.
$mailqArray = array(
'id' => $matches[1],
'size' => intval($matches[2]),
'date' => strftime($matches[3]),
'sender' => $matches[5],
'failed' => false,
'recipients' => $matches[4]
);
}
}
The output of mailq includes white space and returns and looks a bit like this:
ID SIZE DAYNAME MONTH DAY HOUR:MINUTES:SECONDS sender#address.com [new line here]
(Host or domain name not found. Name service error for name=address.com type=MX: Host not found, try again) [new line here]
recipient#address.com
so the the error message and the recipient end up in different keys.
I know that I could use shell_exec to get the output of mailq as one string but I have no idea then how to write the regular expression that will create a multidimensional array.
I might be approaching this wrong. Any advice is extremely welcome!
You could check the next item in the array to see if it contains a new messageid and other indicators. Probably a for loop instead of a foreach.
I have the following PHP code:
$testMessage = "TESTMESSAGE";
$db = new SQLite3('messages.sq3');
$db->exec('CREATE TABLE messages(id INTEGER PRIMARY KEY, message CHAR(255));');
$db->exec("INSERT INTO messages (message) VALUES ('$testMessage');");
$results = $db->query('SELECT * FROM messages ORDER BY id DESC LIMIT 5');
while ($row = $results->fetchArray()) {
print_r($row);
}
The resulting print_r:
Array ( [0] => 1 [id] => 1 [1] => TESTMESSAGE [message] => TESTMESSAGE )
Why is this data duplicated? Is this just the way the array is presented or are there really two copies of TESTMESSAGE string? Inspecting the sqlite file, I only see one actually stored there. I am trying to serialize the output via JSON and this duplication is carrying through to the serialization.
The default is to have the data with both numeric and string keys, merged in the same array.
You need to use $results->fetchArray(SQLITE3_NUM) or $results->fetchArray(SQLITE3_ASSOC) to get numeric and string keys respectively. The default is SQLITE3_BOTH, which I've always hated.
Both $row[1] and $row['message'] will give you the same data. This is because on technique uses the numerical index of the column and the other uses the name. They are both included in the column so that you can use either way to access them. It does not indicate any sort of duplication in the database itself.
Here you can see the documentation and how to tell PHP which version you want. By default it gives you both: http://php.net/manual/en/sqlite3result.fetcharray.php