I have this mysql table
****** login_attempts **********
email (varchar 40)
attempts (tinyint 1)
ipaddress (CHAR 45)
I want to write some data to this table with this PHP code, which seems good.
$thecols = "(email,attempts,ipaddress)";
$parndata = array(':one'=>'1111',':two'=>2,':three'=>'3');
$bindings = "(:one,:two,:three)";
insertInto($db,'login_attempts',$thecols,$parndata,$bindings);
function insertInto($db,$table,$tablevals,$pars,$forbinds){
echo "INSERT INTO $table$tablevals VALUES $forbinds";
echo '<br>';
try{
$one = $db->prepare("INSERT INTO $table$tablevals VALUES $forbinds");
foreach ($pars as $key => $value) {
$one->bindParam($key,$value);
//echo $key.','.$value.'<br>';
}
if($one->execute()){ return true; }else{ return false; }
}catch(PDOException $ex){
throw $ex;
}catch(Exception $ex){
throw $ex;
}
}
but the above code writes the data like that
email [3]
attempts [3]
ipaddress [3]
why this happened ?
This is because PDOStatement::bindParam($param, &$variable) accepts $variable by reference. All parameters are binded to the same $value variable which has a value of 3 at the end of foreach loop.
Replace ->bindParam($key,$value); with ->bindValue($key,$value); to fix an issue.
Related
I'm trying to build a script where I need to read a txt file and execute some process with the lines on the file. For example, I need to check if the ID exists, if the information has updated, if yes, then update the current table, if no, then insert a new row on another temporary table to be manually checked later.
These files may contain more than 20,30 thousand lines.
When I just read the file and print some dummie content from the lines, it takes up to 40-50ms. However, when I need to connect to the database to do all those verifications, it stops before the end due to the timeout.
This is what I'm doing so far:
$handle = fopen($path, "r") or die("Couldn't get handle");
if ($handle) {
while (!feof($handle)) {
$buffer = fgets($handle, 4096);
$segment = explode('|', $buffer);
if ( strlen($segment[0]) > 6 ) {
$param = [':code' => intval($segment[0])];
$codeObj = Sql::exec("SELECT value FROM product WHERE code = :code", $param);
if ( !$codeObj ) {
$param = [
':code' => $segment[0],
':name' => $segment[1],
':value' => $segment[2],
];
Sql::exec("INSERT INTO product_tmp (code, name, value) VALUES (:code, :name, :value)", $param);
} else {
if ( $codeObj->value !== $segment[2] ) {
$param = [
':code' => $segment[0],
':value' => $segment[2],
];
Sql::exec("UPDATE product SET value = :value WHERE code = :code", $param);
}
}
}
}
fclose($handle);
}
And this is my Sql Class to connect with PDO and execute the query:
public static function exec($sql, $param = null) {
try {
$conn = new PDO('mysql:charset=utf8mb4;host= '....'); // I've just deleted the information to connect to the database (password, user, etc.)
$q = $conn->prepare($sql);
if ( isset($param) ) {
foreach ($param as $key => $value) {
$$key = $value;
$q->bindParam($key, $$key);
}
}
$q->execute();
$response = $q->fetchAll();
if ( count($response) ) return $response;
return false;
} catch(PDOException $e) {
return 'ERROR: ' . $e->getMessage();
}
}
As you can see, each query I do through Sql::exec(), is openning a new connection. I don't know if this may be the cause of such a delay on the process, because when I don't do any Sql query, the script run within ms.
Or what other part of the code may be causing this problem?
First of all, make your function like this,
to avoid multiple connects and also o get rid of useless code.
public static function getPDO() {
if (!static::$conn) {
static::$conn = new PDO('mysql:charset=utf8mb4;host= ....');
}
return static::$conn;
}
public static function exec($sql, $param = null) {
$q = static::getPDO()->prepare($sql);
$q->execute($param);
return $q;
}
then create unique index for the code field
then use a single INSERT ... ON DUPLICATE KEY UPDATE query instead of your thrree queries
you may also want to wrap your inserts in a transaction, it may speed up the inserts up to 70 times.
One of my class files has been spitting out the "There is no active transaction" warning when creating some records. It is the only one doing it and I can't figure out why because all my queries are executing successfully.
My php_errors log has given me zero insight to the issue, nor have any of the other related questions provided any solutions.
try {
App::$DB->beginTransaction();
$rQuery = App::$DB->prepare("INSERT INTO `Cervidae` (`".implode('`, `',array_keys($aSQL)) ."`) VALUES (".implode(",",$aPlaceholders).")");
$rQuery->execute(array_values($aSQL));
$this->_Cervid_ID = App::$DB->lastInsertId();
if (is_array($aData["Treatments"])) {
foreach ($aData["Treatments"] as $sTreatmentName => $sTreatmentData) {
$oTreatmentToCervid = new TreatmentToCervid(array("Treatment_ID" => $sTreatmentData["Treatment_ID"], "Cervid_ID" => $this->_Cervid_ID, "CreatedBy" => $aSQL["CreatedBy"], "UpdatedBy" => $aSQL["UpdatedBy"], "Dose" => $sTreatmentData["TreatmentDose"]), "NEW");
}
}
$oHerdSize = App::getFlat(self::get(array("COUNT(Cervidae.Cervid_ID) AS HerdSize"),array("Cervidae.Herd_ID = $nHerdID","Cervidae.IsActive = 1")));
$rQuery = App::$DB->prepare("UPDATE `Herds` SET `HerdSize` = ".$oHerdSize->HerdSize." WHERE `Herd_ID` = ".$nHerdID);
$rQuery->execute();
App::$DB->commit();
$this->__construct($this->_Cervid_ID, 'ID');
}
catch (Exception $e) {
App::$DB->rollBack();
throw new Exception($e->getMessage());
}
I have data (exact) from this HTTP POST:
rowno=1.00000000&date_line=2014-10-07&name=Dan%20Volunteer&affiliation=Enterprise&checkno=1701&amount=20025.00000000&total=20250.00000000¬es=&date_deposit=&rowno=2.00000000&date_line=2014-10-07&name=Harper%20Lee&affiliation=Enterprise%20B&checkno=1702&amount=225
then this code to process
<?php
file_get_contents("php://input");
$db = null;
if (isset($_SERVER['SERVER_SOFTWARE']) &&
strpos($_SERVER['SERVER_SOFTWARE'],'Google App Engine') !== false) {
// Connect from App Engine.
try{
$db = new pdo('mysql:unix_socket=/cloudsql/wonder:bread;dbname=loaf', 'root', '');
}catch(PDOException $ex){
die(json_encode(
array('outcome' => false, 'message' => 'Unable to connect.')
)
);
}
};
try {
if (array_key_exists('name', $_POST)) {
$stmt = $db->prepare('INSERT INTO entries (name, affiliation) VALUES (:name, :affiliation)');
$stmt->execute(array(':name' => htmlspecialchars($_POST['name']), ':affiliation' => htmlspecialchars($_POST['affiliation'])));
$affected_rows = $stmt->rowCount();
// Log $affected_rows.
}
} catch (PDOException $ex) {
// Log error.
}
$db = null;
?>
<?php
header("Content-type: application/vnd.fdf");
// read and store the data however you want
// reply with some FDF data
echo <<<RESPONSE
%FDF-1.2
1 0 obj
<< /FDF <<
/Status (Wham bam! File sent.)
>>
>>
endobj
trailer
<< /Root 1 0 R >>
%%EOF
RESPONSE;
?>
This http post has two records (row/recount count always varies), but only data from the last row is being inserted. Need all rows.
I'm going to stab at this one....I think what is happening is that you are just processing the return post as is, so the first rowno is being skipped over (rather the second rowno is overwriting the first). If you receive that post back as a string, you need to split it by preg_match() or explode() so that you can loop over it with your try.
Try this class on your string. This class will split the string into arrays based on rows. Then you need to take the resulting array $insert then process each array in your an sql loop...does that make sense?
class ProcessPost
{
public static function Split($value = '',$splitVal = 'rowno=')
{
if(!empty($value)) {
// Explode by row values
$rows = explode($splitVal,$value);
$rows = array_filter($rows);
if(is_array($rows) && !empty($rows)) {
foreach($rows as $_row => $querystring) {
parse_str($splitVal.$querystring,$_array[]);
}
foreach($_array as $row_key => $row_val) {
if(empty($row_val))
unset($_array[$row_key]);
}
return $_array;
}
}
}
}
$test = 'rowno=1.00000000&date_line=2014-10-07&name=Dan%20Volunteer&affiliation=Enterprise&checkno=1701&amount=20025.00000000&total=20250.00000000¬es=&date_deposit=&rowno=2.00000000&date_line=2014-10-07&name=Harper%20Lee&affiliation=Enterprise%20B&checkno=1702&amount=225';
$insert = ProcessPost::Split($test);
I have a foreach loop that iterates through an array.
In each instance, I organise the array into a query string and use MySQLi to add it to the database.
function storeProperties($data, $db) {
foreach ($data['property'] as $property) {
$query_string = "INSERT INTO table VALUES(..., ..., ...,)"
$db->query($query_string);
echo $db->error;
}
}
Is there a better way I should be doing this?
Obviously, this method uses n database queries one after another so this is memory intensive and time intensive.
Is there a better way to do this?
Should I be concatenating each query into a single string and running it all outside the for loop?
The following method is from my PDO workhorse, used for bulk insertions. It creates a single INSERT statement with multiple VALUES entries.
Use it as
$db->bulkinsert(
'tbl_my_tablename',
array('fieldname_1','fieldname_2', 'fieldname_3'),
array(
/* rec1 */ array('r1f1', 'r1f2', 'r1f3'),
/* rec2 */ array('r2f1', 'r2f2', 'r2f3'),
/* rec3 */ array('r3f1', 'r3f2', 'r3f3')
));
Please note that the method is an snip from a complex class definition, some methods used here are not contained in the code snippet, especially $this->connect() (connects to PDO),$this->begin() (starts transaction), $this->commit()and $this->rollback(), and the static Log class for Logging similar to Apache Commons ;-)
But I'm sure this is what you might need.
/**
* Performs fast bulk insertion
* Parameters:
* $tablename
* $datafields - non-assiciative array of fieldnames
* or propertynames if $data is an array of objects
* $data - array of either non-associative arrays (in the correct order)
* or array of objects with property names matching the $datafields array
*/
const MAX_BULK_DATA = 3000;
public function bulkinsert($tablename, $datafields, &$data) {
$result = 0;
try {
try {
$this->connect();
$datacount = count($data);
// loop until all data has been processed
$start = 0;
$lastbinds = 0;
$this->begin();
while ($start < $datacount) {
$ins = array();
$bindscount = min(self::MAX_BULK_DATA, $datacount - $start);
if ($bindscount != $lastbinds) {
// prepare the binds
$binds = substr(str_repeat(',?', count($datafields)), 1);
$binds = substr(str_repeat(",($binds)", $bindscount), 1);
$lastbinds = $bindscount;
}
for ($go = $start, $last = $start + $bindscount; $go < $last; $go++) {
if (is_object($data[$go])) {
try {
foreach($datafields as $propname) {
$rfl = new ReflectionProperty($data[$go], $propname);
$rfl->setAccessible(true);
$ins[] = $rfl->getValue($data[$go]);
}
}
catch(ReflectionException $e) {
throw new InvalidArgumentException('PDOCONNECT_ERR_SQL_UNKNOWN_PROPERTY', 0, $e);
}
}
else {
foreach($data[$go] as $value) {
$ins[] = $value;
}
}
}
$sql = sprintf('INSERT INTO %s (%s) VALUES %s', $tablename, join(',',$datafields), $binds);
Log::trace($sql);
$stmt = $this->pdo->prepare($sql);
$stmt->execute($ins);
$start = $last;
$result += $bindscount;
}
$this->commit();
}
catch(PDOException $e) {
// do something with the exception if necessary
throw $e;
}
}
catch(Exception $e) {
$this->rollback();
throw $e;
}
return $result;
}
}
I'm currently building my first PHP application. I want to read in bus times from a csv file and give back ti users the next bus from their residence to our university. It is my first try with PHP, so be gentle:
<?php
// configuration
require("../includes/config.php");
if (!empty($_SESSION["id"]))
{
//lookup database entries for house
$users = query("SELECT * FROM users WHERE id = ?", $_SESSION["id"]);
if ($users === false)
{
apologize("Sorry, there was an error");
}
//lookup database entries for house
$residences = query("SELECT * FROM residences WHERE id = ?", $users[0]["residence"]);
if ($residences === false)
{
apologize("Sorry, there was an error");
}
//if user specified a residence in his profile
if($residences[0]["id"] != 0)
{
$times = array();
//if there is no bus today, in this case sat and sun
if(date( "w", $timestamp) == 0 || date( "w", $timestamp) == 6)
{
$times[0] = "There is no bus today";
}
//load the busplan for his residence
else
{
//open file and load in array if time is higher than date("His");
$timesList = file_get_contents($users[0]["residence"] . ".csv");
$nextbuses = explode(',', $timesList);
$hoursMins = date("Gi");
$num = 0;
for($i = 0; $i < count($nextbuses); $i++)
{
if($hoursMins < $nextbuses[$i])
{
$times[$num] = $nextbuses[$i];
$num++;
}
}
}
render("shuttle_show.php", ["title" => "Next Shuttle from your residence.", "times" => $times]);
}
}
This uses the function query:
function query(/* $sql [, ... ] */)
{
// SQL statement
$sql = func_get_arg(0);
// parameters, if any
$parameters = array_slice(func_get_args(), 1);
// try to connect to database
static $handle;
if (!isset($handle))
{
try
{
// connect to database
$handle = new PDO("mysql:dbname=" . DATABASE . ";host=" . SERVER, USERNAME, PASSWORD);
// ensure that PDO::prepare returns false when passed invalid SQL
$handle->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
catch (Exception $e)
{
// trigger (big, orange) error
trigger_error($e->getMessage(), E_USER_ERROR);
exit;
}
}
// prepare SQL statement
$statement = $handle->prepare($sql);
if ($statement === false)
{
// trigger (big, orange) error
trigger_error($handle->errorInfo()[2], E_USER_ERROR);
exit;
}
// execute SQL statement
$results = $statement->execute($parameters);
// return result set's rows, if any
if ($results !== false)
{
return $statement->fetchAll(PDO::FETCH_ASSOC);
}
else
{
return false;
}
}
the other functions it uses are not relevant I guess. Now I cant seem to find why this keeps producing:
Notice: Undefined offset: 0 in /Applications/MAMP/htdocs/html/shuttle.php on line 16
Notice: Undefined offset: 0 in /Applications/MAMP/htdocs/html/shuttle.php on line 22
The relevant lines are
$residences = query("SELECT * FROM residences WHERE id = ?", $users[0]["residence"]);
and
if($residences[0]["id"] != 0)
Would appreciate some help! :)
Edit:
I transferred the same file to my linux system and without any changes I get a vardump. If I use the same vardump on MAMP on my Mac the array is empty. Now I get for:
var_dump($users);
array (size=1)
0 =>
array (size=5)
'id' => int 12
'username' => string 'frechdaxx' (length=9)
'mail' => string '*************#gmail.com' (length=23)
'hash' => string '$1$qr5axj4C$BET5zZGJza2DcHI8eD8fV0' (length=34)
'residence' => int 9573
Why is this a problem at all? the function query worked with the exact same syntax before when I accessed the user table and as we can see it gives back all other values correctly.
Why the difference between my environments? The array is empty on my MAMP server, but works on Linux. However, other code examples with the query function work perfectly on both environments
Why the big int of 9573? The value in the table is 2.
This is simply that $users[0]["residence"] doesn't exists (is not set). You should check it before attempting to use it. As you can see, it's only a notice, not a error. This means that in some case scenarios, the variable can be unset and PHP won't complain THAT much, but this is definitely not good as a general rule.
I would change it to:
$users = query("SELECT * FROM users WHERE id = ?", $_SESSION["id"]);
if (empty($users[0]["residence"]))
{
apologize("Sorry, there was an error");
}
Furthermore, that only "fixes" it. If you want to go to the root of the problem, it's in the query() function that is called in $users = query("SELECT * FROM users WHERE id = ?", $_SESSION["id"]);:
// execute SQL statement
$results = $statement->execute($parameters);
// return result set's rows, if any
if ($results !== false)
{
$fetched = $statement->fetchAll(PDO::FETCH_ASSOC);
// Check that ther was actually something fetched
if(!empty($fetched))
{
return $fetched;
}
}
/* You don't need to return false, null is the default returned value.
* If you *want* it, then delete the else. */
}
From the documentation, fetchall returns an empty array if there's nothing, so the function might return an empty array.
$users = query("SELECT * FROM users WHERE id = ?", $_SESSION["id"]);
if ($users === false) { }
You are strictly checking if query returned false, that only occurs when something was wrong with query or parameters. That does not check if any result was returned, and you are referring to the first returned record ($users[0]["residence"]) that is not present ($users is an empty array).
Since user id comes from $_SESSION["id"] it should be present and available in database, but there is possibility that somewhere in your code you are overwriting it with other id.