I feel like I'm really overly complicating this whole scenario. Hopefully somebody can help.
I have a form which submits data to two tables (items and uploads). The form data goes to items, and the attachment to uploads. Basically I'd like both tables to have a corresponding itemId column.
My two functions create() and uploadFile() both work. However, I'm not sure how to use the the lastInsertId value of $crud->create() in my variable named $itemId - see comments in my code.
Reduced versions of my functions are below, including comments.
class.crud.php
class crud {
private $db;
function __construct($DB_con) {
$this->db = $DB_con;
}
public function create($inv, $ip, $make){
$stmt = $this->db->prepare("INSERT INTO items (inv,ip,make) VALUES (:inv,:ip,:make");
$stmt->bindparam(":inv", $inv);
$stmt->bindparam(":ip", $ip);
$stmt->bindparam(":make", $make);
$stmt->execute();
return true;
}
public function uploadFile($itemId, $inv, $file, $file_type, $file_size) {
$stmt = $this->db->prepare("INSERT INTO uploads (itemId,inv,file,type,size) VALUES (:itemId,:inv,:file,:file_type,:file_size)");
$stmt->bindParam(":itemId", $itemId); // inserts 777
$stmt->bindParam(":inv", $inv);
$stmt->bindparam(":file", $file);
$stmt->bindparam(":file_type", $file_type);
$stmt->bindparam(":file_size", $file_size);
$stmt->execute();
return true;
}
}
add-data.php
if (isset($_POST['btn-save'])) {
$itemId = '777'; //this successfully inserts 777 into the uploads.itemId teble, but i'd like to insert the lastInsertId value of $crud->create()
$inv = $_POST['inv'];
$ip = $_POST['ip'];
$make = $_POST['make'];
$file = rand(1000, 100000) . "-" . $_FILES['file']['name'];
$file_loc = $_FILES['file']['tmp_name'];
$file_size = $_FILES['file']['size'];
$file_type = $_FILES['file']['type'];
$folder = "uploaded_files/";
if ($crud->create($inv, $ip, $make)) {
echo 'success';
} else {
echo 'error';;
}
if (move_uploaded_file($file_loc, $folder . $file)) {
$crud->uploadFile($itemId, $inv, $file, $file_type, $file_size);
}
}
<form method='post' enctype="multipart/form-data">
<input type='text' name='inv'>
<input type='text' name='ip'>
<input type='text' name='make'>
<input type='file' name='file'>
<button type="submit" name="btn-save"></button>
</form>
The structure of both my tables are as follows;
items (itemId is primary, unique and auto-increment)
+--------+---------+-----------------+-------+
| itemId | inv | ip | make |
+--------+---------+-----------------+-------+
| 1 | 1293876 | 123.123.123.123 | Dell |
+--------+---------+-----------------+-------+
| 2 | 4563456 | 234.234.234.234 | Dell |
+--------+---------+-----------------+-------+
| 3 | 7867657 | 345.345.345.345 | Apple |
+--------+---------+-----------------+-------+
items (upload_id is primary, unique and auto-increment)
+-----------+--------+-----+----------+------------+------+
| upload_id | itemId | inv | file | type | size |
+-----------+--------+-----+----------+------------+------+
| 56 | 777 | 123 | test.txt | text/plain | 266 |
+-----------+--------+-----+----------+------------+------+
| 57 | 777 | 123 | test.txt | text/plain | 266 |
+-----------+--------+-----+----------+------------+------+
| 58 | 777 | 123 | test.txt | text/plain | 266 |
+-----------+--------+-----+----------+------------+------+
Please forgive the messy code. I'm just trying to get the logic correct and then I can work on it.
Any advice is appreciated.
So with help from #Maximus2012 I was able to solve this.
I changed my create() function to return lastInsertId() in place of just true or false. Then, I assigned the value returned by the create() function to a variable rather than using a static value.
So my working code now looks like this;
public function create($inv, $ip, $make){
$stmt = $this->db->prepare("INSERT INTO items (inv,ip,make) VALUES (:inv,:ip,:make");
$stmt->bindparam(":inv", $inv);
$stmt->bindparam(":ip", $ip);
$stmt->bindparam(":make", $make);
$stmt->execute();
return $this->db->lastInsertId();
}
Then in my add-data.php page I simply changed one vcariable to the following;
$itemId = $crud->create($inv, $ip, $make);
Solved.
Related
I am trying to make a csv upload page for an application that Im building. It needs to be able to upload thousands of rows of data in seconds each row including a first name, last name, and phone number. The data is being uploaded to a vm that is running ubuntu server. When I run the script to upload the data it takes almost 2 minutes to upload 1500 rows. The script is using PDO, I have also made a test script in python to see if It was a php issue and the python script is just as slow. I have made csv upload scripts in the past that are exactly the same that would upload thousands of rows in seconds. We have narrowed the issue down to the script as we have tested it on other vms that are hosted closer to us and the issue still persist. Is there an obvious issue with the script or PDO that could be slowing it down? Below is the code for the script.
<?php
$servername =[Redacted];
$username = [Redacted];
$password = [Redacted];
try {
$conn = new PDO("mysql:host=$servername;dbname=test", $username, $password);
// set the PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected successfully";
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
echo print_r($_FILES);
$fileTmpPath = $_FILES['fileToUpload']['tmp_name'];
$fileName = $_FILES['fileToUpload']['name'];
$fileSize = $_FILES['fileToUpload']['size'];
$fileType = $_FILES['fileToUpload']['type'];
$CSVfp = fopen($fileTmpPath, "r");
$final_array = [];
while (($data = fgetcsv($CSVfp)) !== false) {
$recived = [];
foreach ($data as $i) {
array_push($recived, $i );
}
array_push($final_array, $recived);
}
echo print_r($final_array);
fclose($CSVfp);
$non_compliant_rows = [];
foreach ($final_array as $key => $row){
$fname = preg_replace('/[^A-Za-z0-9]/', "", $row[0]);
$lname = preg_replace('/[^A-Za-z0-9]/', "", $row[1]);
$mobileNumber = preg_replace( '/[^0-9]/i', '', $row[2]);
$sanatized_row = array($fname, $lname, $mobileNumber);
$recived[$key] = $sanatized_row;
if (strlen($mobileNumber) > 10 or strlen($mobileNumber) < 9){
array_push($non_compliant_rows, $final_array[$key]);
unset($final_array[$key]);
}
}
$final_array = array_values($final_array);
echo print_r($final_array);
foreach($final_array as $item){
try{
$stmt = $conn->prepare("INSERT INTO bulk_sms_list(fname, lname, pn, message, send) VALUES (?, ?, ?, 'EMPTY', 1) ;");
$stmt->execute($item);
}catch(PDOException $e){
echo $e;
}
}
echo "done";
The phone numbers column has a UNIQUE constraint to prevent us from having duplicate phone numbers. We have tried to use batch inserting but if one row doesn't comply with the constraints then all of the insertions fail.
below is the schema of the table:
+---------+------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| fname | text | NO | | NULL | |
| lname | text | NO | | NULL | |
| pn | text | NO | UNI | NULL | |
| message | text | YES | | NULL | |
| send | int | NO | | 1 | |
+---------+------+------+-----+---------+----------------
EDIT:
I have timed the clean up portion of the script at the request of #aynber. the time of the clean up was 0.24208784103394 Seconds. The time it took to do the sql portion is 108.2597219944 Seconds
The fastest solution should be to use LOAD DATA LOCAL INFILE. Since you answered in a comment that duplicate phone numbers should result in skipping a row, you can use the IGNORE option.
Load directly from the file, instead of processing it with PHP.
You can do some of your transformations in the LOAD DATA statement.
For example:
LOAD DATA INFILE ? IGNORE INTO TABLE bulk_sms_list
FIELDS TERMINATED BY ','
(#fname, #lname, #mobile)
SET fname = REGEXP_REPLACE(#fname, '[^A-Za-z0-9]', ''),
lname = REGEXP_REPLACE(#lname, '[^A-Za-z0-9]', ''),
pn = IF(LENGTH(#mobile) BETWEEN 9 AND 10, #mobile, NULL),
message = 'EMPTY',
send = 1;
Then follow the import with some cleanup to get rid of any rows with invalid phone numbers:
DELETE FROM bulk_sms_list WHERE pn IS NULL;
Read https://dev.mysql.com/doc/refman/8.0/en/load-data.html for more information.
i have json data like this :
{
"response": {
"count": 212,
"list": [
{
"code": "02007",
"name": "swept the room",
"rate": 750000,
"withValue": false
},
{
"code": "02005",
"name": "mop room",
"rate": 600000,
"withValue": false
},
{
"code": "02003",
"name": "buying food",
"rate": 175000,
"withValue": false
}
]
},
"metaData": {
"message": "OK",
"code": 200
}
}
and i have table schema like this :
mysql> desc master_service;
+----------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+----------------+
| id | int(25) | NO | PRI | NULL | auto_increment |
| code | varchar(10) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| rate | double | YES | | NULL | |
| withvalue | tinyint(1) | YES | | NULL | |
+----------------+-------------+------+-----+---------+----------------+
and my coding like this.
//using php pdo
include_once 'db_connect.php';
$data = json_decode($response, true);
$tempservice = array();
if(isset($data['response']) && isset($data['response']['list']))
{
//use foreach on ['response']['list'] index - here are teachers data stored
foreach($data['response']['list'] as $service)
$tempservice[$kesadaran['code']] = $service['name'];
}
foreach($tempservice as $key =>$value) {
$sql = "insert into master_service(code,name) Values ('$key','$value')";
$st = $dbh->prepare($sql);
$st->execute ($data);
}
it can only save the database in the form of codes and names. i want rate and withValue can be save on database
Just use a foreach loop and add the missing columns, no need for a restructured $tempservice array:
include_once 'db_connect.php';
$data = json_decode($response, true);
// Simplified if, checking for isset($data['response') is redundant
if (isset($data['response']['list']))
{
// Prepare the statement using placeholders
$sql = "INSERT INTO master_service (code,name,rate,withValue) VALUES (:code,:name,:rate,:withValue)";
$stmt = $dbh->prepare($sql);
foreach($data['response']['list'] as $row)
{
// Execute query with parameters
$stmt->execute($row);
}
}
Important: I replaced the variables in your query with placeholders. This way it is safe against SQL Injection. I also put the prepare outside of the loop so that it doesn't "reprepare" on every iteration.
You can try this:-
include_once 'db_connect.php';
$data = json_decode($response, true);
$tempservice = [];
if(isset($data['response']) && isset($data['response']['list'])){
//use foreach on ['response']['list'] index - here are teachers data stored
foreach($data['response']['list'] as $service){
$withValue = ($service['withValue']) ? 1 : 0;
$tempservice[$service['code']] = ['name'=>$service['name'], 'rate'=>$service['rate'], 'withvalue'=>$withValue];
}
}
foreach($tempservice as $key =>$value) {
$sql = "insert into master_service(code, name, rate, withvalue) Values ('$key', '".$value['name']."', '".$value['rate']."', '".$value['withvalue']."')";
$st = $dbh->prepare($sql);
$st->execute ();
}
Note: please fix any syntax error. Also you can skip creating tempservice array and can execute insert sql there
foreach($data['response']['list'] as $value) {
$sql = "insert into master_service(code,name,rate,withvalue) Values ('{$value['code']}','{$value['name']}', '{$value['rate']}', '{$value['withValue']}')";
$st = $dbh->prepare($sql);
$st->execute ($data);
}
sorry i forgot the true parameter on json_decode.. edited
When I try to use the fetch_assoc or fetch_array(MYSQLI_ASSOC), the program just crashes and doesn't display any error information(I do have changed error_reporting to E_ALL).But it works fine when using fetch_array(MYSQLI_NUM) or fetch_row().
The weird thing is I can run my program on Wamp. But I cannot on the Apache+PHP+MYSQL environment installed manually.
so is this a PHP configuration problem or a MYSQL problem ?
$studentId = $this->mydblink->real_escape_string($studentId);
$result = $this->mydblink->query("SELECT * FROM student WHERE id = '$studentId'");
if($result->num_rows <= 0){
$result->free();
return null;
}
else{
$returnValue = array();
while($row = $result->fetch_assoc()){
array_push($returnValue,$row);
}
$result->free();
return $returnValue;
}
fetch_array() makes associative array in which there is a key and value.
fetch_assoc() only has value
You can also use fetch_object () that creates objects of your table name.
| id | category | name |
----------------------------------
| 1 | 2, 5, 7, 28 | Will |
----------------------------------
| 2 | 2, 9, 15, 18 | Drew |
EXAMPLES:
$sql=mysqli_query($connect, "SELECT id, category, name FROM users");
$data=mysqli_fetch_array($sql);
// 0=>id, 1=>category, 2=>name
$data[1]; // is same like $data['category'];
$data['category'];
$data=mysqli_fetch_assoc($sql);
$data['name'];
$data=mysqli_fetch_object($sql);
$data->name;
$data->category;
$data->id;
I'm having some issues getting a tree menu to work from bottom-up.
I already have a script to work from top-down, which works fine.
This is a very simplified version of my table:
+-----+-----------+--------------------+
| uid | parent_id | page_address |
+-----+-----------+--------------------+
| 1 | 0 | index.php |
| 2 | 0 | login.php |
| 3 | 2 | dashboard.php |
| 4 | 3 | bookings.php |
| 5 | 3 | documents.php |
| 6 | 4 | changebookings.php |
| 7 | 4 | activities.php |
+-----+-----------+--------------------+
The page_address field is unique.
I can work out what page the user is currently on, for example changebookings.php
I would then like a menu to look like this:
login.php
dashboard.php
bookings.php
changebookings.php
activities.php
documents.php
However, the closest I've got so far is the following tree:
login.php
bookings.php
changebookings.php
As you can see, my script currently only returns the actual parent, and not a list of links currently in the parent.
For those interested, the script I use in total is at the bottom of this post.
Is there any easier way to get the bottom-up tree as required?
Many thanks
Phil
EDIT:
I've finally got the code to work, for future users who stumble upon this post, I have added the functionality below:
$dataRows = $databaseQuery->fetchAll(); // Get all the tree menu records
$dataRows = $result->fetchAll(PDO::FETCH_ASSOC);
foreach($dataRows as $row)
{
if($row['link_address']==substr($_SERVER['PHP_SELF'], 1, strlen($_SERVER['PHP_SELF'])-1))
{
$startingId = $row['parent_id'];
}
}
$menuTree = $this->constructChildTree($dataRows, $startingId);
private function constructChildTree(array $rows, $parentId, $nesting = 0)
{
$menu = array();
if(!in_array($nesting, $this->nestingData))
{
$this->nestingData[] = $nesting;
}
foreach($rows as $row)
{
if($row['parent_id']==$parentId && $parentId!=0)
{
$menu[] = $row['link_address'];
$newParentId = $this->getNextParent($rows, $row['parent_id']);
$parentChildren = $this->constructChildTree($rows, $newParentId, ($nesting+1));
if(count($parentChildren)>0)
{
foreach($parentChildren as $menuItem)
{
$menu[] = 'NESTING' . $nesting . '::' . $menuItem;
}
}
}
}
return $menu;
}
private function getNextParent($rows, $parentId)
{
foreach($rows as $row)
{
if($row['uid']==$parentId)
{
return $row['parent_id'];
}
}
}
Without reading your code you should be doing:
1) Get current page, look at parent ID.
2) Load all with that parent ID.
3) Get next Parent ID using current Parent ID as ID.
4) If new parent ID != 0, goto step 2 passing in the new Parent ID.
Sounds like you just need to edit your script to include ALL pages with the given ID as their parent ID.
<?PHP
$sql = "SELECT * FROM TABLE WHERE table parent_id=0";
$result = mysql_query($sql);
while($perant_menu = mysql_fetch_array($result))
{
echo display_child($perant_menu["uid"],$perant_menu["page_address"]);
}
// Recursive function
function display_child($parent_id,$name)
{
$sql= "SELECT * FROM table where parent_id = $parent_id";
$result = mysql_query($sql);
if(mysql_num_rows($result)>0)
{
while($menu = mysql_fetch_array($result))
{
echo display_child($menu["id"],$menu["page_address"]);
}
}
else
{
echo $name;
}
}
?>
I get the error PHP Fatal error: Call to a member function execute() on a non-object refering to the ....->execute() line whenever I call something like
$select_str = 'select id, stamp, lat, lng, spd from gps';
$select = $db->prepare($select_str);
$select->execute();
or
$insert = $db->prepare('insert into gps (id, lat, lng, spd) values (?, ?, ?, ?)');
$insert->execute(array($id, $lat, $lng, $spd));
From searching on the net I suspect that $select (or $insert) is somehow becoming a "dead object" which holds the data, but on which methods can't be called.
But I don't know how to prevent it and my PHP experience is very short (I'm coming from Perl land). Please help me to fix this, it is probably something minor.
And below is my complete script, I think (I hope) it is quite readable - it lets you create a table, insert a record, delete records, view them or drop the table again - all depending on the $_REQUEST['mode'] parameter:
<?php
#define('DBHOST', 'localhost');
#define('DBNAME', 'snake');
#define('DBUSER', 'snake');
#define('DBPASS', 'snake');
# lowercase mode and id parameters; replace commas by dots in lat, lng, spd
$mode = isset($_REQUEST['mode']) ? strtolower(trim($_REQUEST['mode'])) : '';
$id = isset($_REQUEST['id']) ? strtolower(trim($_REQUEST['id'])) : '';
$lat = isset($_REQUEST['lat']) ? strtr(trim($_REQUEST['lat']), ',', '.') : '';
$lng = isset($_REQUEST['lng']) ? strtr(trim($_REQUEST['lng']), ',', '.') : '';
$spd = isset($_REQUEST['spd']) ? strtr(trim($_REQUEST['spd']), ',', '.') : '';
# id must be 32 chars long hex number; lat, lng, spd must be decimal numbers
$id_ok = preg_match('/^[a-f0-9]{32}$/', $id);
$lat_ok = preg_match('/^[+-]?[0-9.]+$/', $lat);
$lng_ok = preg_match('/^[+-]?[0-9.]+$/', $lng);
$spd_ok = preg_match('/^\+?[0-9.]+$/', $spd);
# has the user selected a mode and provided valid input?
$create_ok = ($mode == 'create');
$insert_ok = ($mode == 'insert' && $id_ok && $lat_ok && $lng_ok && $spd_ok);
$delete_ok = ($mode == 'delete' && $id_ok);
$select_ok = ($mode == 'select');
$drop_ok = ($mode == 'drop');
# first call or invalid input: display web form and exit
if (!($create_ok || $insert_ok || $delete_ok || $select_ok || $drop_ok)) {
header('Content-Type: text/html; charset=utf-8');
print '<html>
<body>
<form method="post">
<p>Mode:<br />
<input type="radio" name="mode" value="create"><i>create table</i><br />
<input type="radio" name="mode" value="select" checked>select records (can specify id)<br />
<input type="radio" name="mode" value="insert">insert 1 record (must specify all)<br />
<input type="radio" name="mode" value="delete">delete records (must specify id)<br />
<input type="radio" name="mode" value="drop"><i>drop table</i><br />
</p>
<p>Id: <input type="text" name="id" size=32 maxlength=32 /> (32 hex chars)</p>
<p>Latitude: <input type="text" name="lat" size=10 /> (between -90 and 90)</p>
<p>Longitude: <input type="text" name="lng" size=10 /> (between -90 and 90)</p>
<p>Speed: <input type="text" name="spd" size=10 /> (not negative)</p>
<p><input type="submit" value="OK" /></p>
</form>
</body>
</html>
';
exit();
}
try {
# enable persistent connections and throw exception on any errors
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => true);
$db = new PDO('mysql:host=' . DBHOST . '; dbname=' . DBNAME, DBUSER, DBPASS, $options);
if ($create_ok) {
$db->exec('create table gps (
id char(32) not null check length(id)=32,
lat decimal(5,3) not null,
lgt decimal(5,3) not null,
spd decimal(5,3) unsigned not null,
stamp timestamp default now(),
index(id) )');
} else if ($insert_ok) {
$insert = $db->prepare('insert into gps (id, lat, lng, spd) values (?, ?, ?, ?)');
$insert->execute(array($id, $lat, $lng, $spd));
} else if ($delete_ok) {
} else if ($drop_ok) {
$db->exec('drop table gps');
header('Content-Type: text/plain');
print('Database dropped');
exit();
}
# display current table content in XML format
$select_str = 'select id, stamp, lat, lng, spd from gps';
# but filter by id if requested by user
if ($select_ok && $id_ok) {
$select = $db->prepare($select_str . ' where id = ?');
$select->execute(array($id));
} else {
$select = $db->prepare($select_str);
$select->execute();
}
header('Content-Type: text/xml; charset=utf-8');
print('<?xml version="1.0"?><gps>');
while ($row = $select->fetch(PDO::FETCH_ASSOC)) {
printf('<pos id="%s" stamp="%u" lat="%f" lng="%f" spd="%f" />',
$row['id'], $row['stamp'], $row['lat'], $row['lng'], $row['spd']);
}
print('</gps>');
} catch (Exception $e) {
header('Content-Type: text/plain');
print('Database problem: ' . $e->getMessage());
}
?>
And the MySQL user 'snake' has these permissions:
select * from mysql.user where User='snake';
+-----------+-------+------------------+-------------+-------------+-------------+-------------+-------------+-----------+-------------+---------------+--------------+-----------+------------+-----------------+------------+------------+--------------+------------+-----------------------+------------------+--------------+-----------------+------------------+------------------+----------------+---------------------+--------------------+------------------+----------+------------+-------------+--------------+---------------+-------------+-----------------+----------------------+
| Host | User | Password | Select_priv | Insert_priv | Update_priv | Delete_priv | Create_priv | Drop_priv | Reload_priv | Shutdown_priv | Process_priv | File_priv | Grant_priv | References_priv | Index_priv | Alter_priv | Show_db_priv | Super_priv | Create_tmp_table_priv | Lock_tables_priv | Execute_priv | Repl_slave_priv | Repl_client_priv | Create_view_priv | Show_view_priv | Create_routine_priv | Alter_routine_priv | Create_user_priv | ssl_type | ssl_cipher | x509_issuer | x509_subject | max_questions | max_updates | max_connections | max_user_connections |
+-----------+-------+------------------+-------------+-------------+-------------+-------------+-------------+-----------+-------------+---------------+--------------+-----------+------------+-----------------+------------+------------+--------------+------------+-----------------------+------------------+--------------+-----------------+------------------+------------------+----------------+---------------------+--------------------+------------------+----------+------------+-------------+--------------+---------------+-------------+-----------------+----------------------+
| localhost | snake | 684bce5059b3e0a8 | Y | Y | N | Y | Y | Y | N | N | N | N | N | N | Y | N | N | N | N | N | N | N | N | N | N | N | N | N | | | | | 0 | 0 | 0 | 0 |
+-----------+-------+------------------+-------------+-------------+-------------+-------------+-------------+-----------+-------------+---------------+--------------+-----------+------------+-----------------+------------+------------+--------------+------------+-----------------------+------------------+--------------+-----------------+------------------+------------------+----------------+---------------------+--------------------+------------------+----------+------------+-------------+--------------+---------------+-------------+-----------------+----------------------+
Thank you!
Alex
The key is the behaviour of PDO::prepare():
If the database server successfully prepares the statement, PDO::prepare() returns a PDOStatement object. If the database server cannot successfully prepare the statement, PDO::prepare() returns FALSE or emits PDOException (depending on error handling).
prepare() is somehow failing with your query.
By default, PDO is mute and does not throw any error messages. See this page to see how to make PDO show errors, and details about what goes wrong.