How does this PHP know which array key/values to use? - php

Below is part of a PHP database class someone else wrote, I have removed about 80% of it's code, all the un-related code to my question has been removed and just the amount remains that allows me to test this class without actually hitting a real database.
This class has a couple methods that let you set a key and value it then turns it into a mysql UPDATE and INSERT sql query using an array. I am trying to figure out how to use this code 100% so I can use this feature of it for UPDATE and INSERTS in my own application.
Basicly from what I gather you do something like this...
// assign some key/values to insert into DB
$db->assign('name', 'dfgd');
$db->assign('age', 87);
$db->assign('sex', 'female');
$db->assign('user_id', 4556);
// Do the insert
$db->insert('testing2');
Now where I am confused is I can keep on running code like this over and over on the page and it always will use the correct set of key/value array sets. Above you can see I used the assign() method 4 times and then call the insert() method which creates this
INSERT INTO test (name, age, sex, user_id) VALUES (jason davis, 26, male, 5345)
Now if I run another set like this on the same page...
// assign some key/values to insert into DB
$db->assign('name', 'dfgd');
$db->assign('age', 87);
$db->assign('sex', 'female');
$db->assign('user_id', 4556);
// Do the insert
$db->insert('testing2');
It then creates this...
INSERT INTO testing2 (name, age, sex, user_id) VALUES (dfgd, 87, female, 4556)
So how does it not combine the 2 sets of 4, so instead of inserting 8 record on the second insert, it completey replaces the first set of 4 values with the new set. This is great and what I want but I do not understand how it is happening? Also can this be improved anyway?
Below is a full class and my demo code, it can be ran without needing to connect to mysql for this demo, it will print to screen the SQL that it builds.
Also where would the public function reset() in the code below need to be used at, or would it not be needed?
<?php
class DB{
public $fields;
public function assign($field, $value){
$this->fields[$field] = ($value)==""?("'".$value."'"):$value;
}
public function assign_str($field, $value){
$this->fields[$field] = "'".addslashes($value)."'";
}
public function reset(){
$this->fields = array();
}
public function insert($table){
$f = "";
$v = "";
reset($this->fields);
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field;
$v.= ($v!=""?", ":"").$value;
}
$sql = "INSERT INTO ".$table." (".$f.") VALUES (".$v.")";
//print SQL to screen for testing
echo $sql;
//$this->query($sql);
return $this->insert_id();
}
public function update($table, $where){
$f = "";
reset($this->fields);
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field." = ".$value;
}
$sql = "UPDATE ".$table." SET ".$f." ".$where;
echo $sql;
//$this->query($sql);
}
public function query($_query){
$this->query = $_query;
$this->result = #mysql_query($_query, $this->link_id) or die( $_query."<p>".mysql_error($this->link_id) );
return $this->result;
}
public function insert_id(){
return #mysql_insert_id($this->link_id);
}
}
// start new DB object
$db = new DB;
// assign some key/values to insert into DB
$db->assign('name', 'jason davis');
$db->assign('age', 26);
$db->assign('sex', 'male');
$db->assign('user_id', 5345);
// Do the insert
$db->insert('test');
echo '<hr />';
// assign some key/values to insert into DB
$db->assign('name', 'dfgd');
$db->assign('age', 87);
$db->assign('sex', 'female');
$db->assign('user_id', 4556);
// Do the insert
$db->insert('testing2');
echo '<hr />';
// assign some key/values to UPDATE the DB
$db->assign('name', 'jason davis');
$db->assign('age', 26);
$db->assign('sex', 'male');
$db->assign('user_id', 5345);
// DO the DB UPDATE
$db->update('blogs', 'WHERE user_id = 23');
?>

Key in associative arrays are unique; assigning a new value erases the old.

If you still open for another database abstaction library, I want to suggest you to use AdoDB. It's can connect to multiple database, so you code will stay the same if you decide to switch database later. It have build in feature to sanitize data before insert/update.
For your code above, when you use AdoDB, you will write it like this:
$adodb =& ADONewConnection($dsn);
$data['name'] = 'dfgd';
$data['age'] = 87;
$data['sex'] = 'female';
$data['user_id'] = 4556;
// Do the insert
$result = $adodb->AutoExecute($table_name, $data, 'INSERT');
//If update, must have one of the key, such as id column
$result = $adodb->AutoExecute($table_name, $data, 'UPDATE', "id=$id");
You can read the documentation from the site, or inside zip file that you can download. I always use this library in all my project, even I prefer it more that build in CodeIgniter database library.

insert() and update() should (originally) set the $this->fields property back to an empty array upon execution, but you somehow (wrongly) deleted that code?
Update your code to this:
public function insert($table){
$f = "";
$v = "";
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field;
$v.= ($v!=""?", ":"").$value;
}
$sql = "INSERT INTO ".$table." (".$f.") VALUES (".$v.")";
$this->reset();
//print SQL to screen for testing
echo $sql;
//$this->query($sql);
return $this->insert_id();
}
public function update($table, $where){
$f = "";
foreach($this->fields as $field=>$value){
$f.= ($f!=""?", ":"").$field." = ".$value;
}
$sql = "UPDATE ".$table." SET ".$f." ".$where;
$this->reset();
echo $sql;
//$this->query($sql);
}

Ok, we have come to the conclusion that my previous answer was right:
Because you use the name keys, it replaces the old keys with the new keys.
$db->assign('user_id', "1");
basically does this:
$this->fields['user_id] = (1)==""?("'1'"):1;
And when you got to do it again, it replaces it
$this->fields['user_id'] = (2)==""?("'2'"):2;
Try doing an assign, and then only assign the user_id again, there rest of the data will stay the same.
To fix this problem, we would call the $this->reset() function after a query.
public function query($_query){
$this->query = $_query;
$this->result = #mysql_query($_query, $this->link_id) or die( $_query."<p>".mysql_error($this->link_id) );
$this->reset();
return $this->result;
}
or you could call it in the individual insert or update functions:
public function insert($table){
// .... stuff
$this->query($sql);
$this->reset();
return $this->insert_id();
}
The other possibility is that the original programmer didn't convey his intent to you well enough. He might expect you to call $db->reset() after every query.

Related

Updating database values each time php method is called

I have a php class
<?php
class Students
{
public $sesscode;
public $db_data;
function __construct($sesscode)
{
$this->sesscode = $sesscode;
$this->db_data = dbfetch(null, "SELECT * FROM students WHERE code = ?", [$this->sesscode]); //this simply returns data from db
$this->db_data = (empty($this->db_data)) ? $this->createProfile() : $this->db_data[0];
}
public function createProfile()
{
dbquery(null, "INSERT INTO students (code) VALUES (?)", [$this->sesscode]);
return dbfetch(null, "SELECT * FROM students WHERE code = ?", [$this->sesscode])[0];
}
public function updateAccount($row, $key, $val)
{
$data = json_decode($this->db_data[$row], true);
if (!$data) {
$data = [
$key => $val,
];
} else {
$data[$key] = trim($val);
}
dbquery(null, "UPDATE students SET account = ? WHERE code = ?", [json_encode($data), $this->sesscode]);
}
public function getData($row)
{
return json_decode($this->db_data[$row], true);
}
}
?>
Usage:
$student = new Student($sesscode); //sesscode is just some string
if (!empty($_POST)){
$student->updateAccount("account", "course", $_POST['course']);
//see class code/updateAccount method above for parameters. This means update dB table row "account", decode json and set course to post value of course
$student->updateAccount("account", "email", $_POST['email']);
$student->updateAccount("account", "phone", $_POST['phone']);
$student->updateAccount("account", "institution", $_POST['institution']);
}
My problem is, only the last line executes (and no, it isn't overwriting the data)
Moving the line that sets phone to take the place of institution will set phone and ignore those above it.
The line
$data = json_decode($this->db_data[$row], true);
resets the value of $data every time your updateAccount account function runs. It resets it to the value you fetched from the database when you created the object. Therefore this doesn't take account of any changes you've made in previous calls to updateAccount. So in fact it does overwrite your data, but probably just quite not in the way you imagined.
You need to either:
a) keep $data in-memory as a (private) property of the class rather than resetting it each time updateAccount runs,
or
b) re-fetch the existing data from the database each time updateAccount runs,
or
c) Store your data in relational format (with fields in separate columns) instead of as JSON within a single column - meaning you could update individual fields without interfering with the others,
and/or
d) provide a version of updateAccount where multiple values can be updated simultaneously - running separate UPDATE queries for each field is very inefficient.

Weird auto increment issue

I am making a transaction table with an auto increment field of BIGINT(20).
When a new transaction is added, the insert Id is retrieved and formatted to be more readable:
public function add_transaction($paymethod, $cursus_id)
{
$this->load->model('Config_model');
$btw = $this->Config_model->get('transactions.btw');
$query = " INSERT INTO transacties(userid, paymethod, amount, btw_pc)
VALUES((SELECT userid FROM users WHERE lcase(username)=lcase('{$this->session->userdata('username')}')),
'{$paymethod}',
(SELECT prijs FROM p_cursus_uitvoering WHERE uitvoering_id = {$cursus_id}),
{$btw});";
$this->db->query($query);
$insertId = $this->db->insert_id();
$newCode = date('Ymd') . str_pad($insertId, 8, '0', STR_PAD_LEFT);
$this->db->where('transact_id', $insertId);
$this->db->update('transacties', ['transact_id' => $newCode]);
return $newCode;
}
The result is that the ID get updated from eg: 5 to 2015041800000005.
This is working perfectly, but as you can see, the newCode is returned in the function and used by another function where it's reinserted in another table.
Here is where the problem arises, the ID turns into: 201504192058506757.
Even when I echo the newCode, it still prints 201504192058506757.. even though it is inserted correctly once, but incorrectly the second time!
EDIT:
Here is the code snippet in which the function is called:
public function workshop(){
$this->load->model('Inschrijven_model');
$paymethod = $this->input->post("paymethod");
//eigenlijk uitvoering_id.....
$cursus_id = $this->input->post("cursus_id");
if($paymethod == null || $cursus_id == null){
redirect('cursus');
}
if($this->Inschrijven_model->cursus_has_room($cursus_id)){
if(!$this->Inschrijven_model->cursus_ingeschreven($cursus_id)){
$this->load->model('Cc_payment_model');
$this->load->model('Cursus_model');
$amount = $this->Cursus_model->get_price($cursus_id);
$orderId = $this->Cc_payment_model->add_transaction($paymethod, $cursus_id);
$this->Inschrijven_model->cursus_inschrijven($cursus_id,$orderId);
$function = explode('_',$paymethod);
$this->{$function[0]}($function[1], $amount, $orderId);
}else{
echo "Al ingeschreven";
}
}else{
echo "geen ruimte";
}
}
And here is the code snippet in which the return $newCode is being reinserted:
public function cursus_inschrijven($cursus_id,$transaction_id){
$query = " INSERT INTO p_cursus_in(uitvoering_id, userid, transact_id)
VALUES({$cursus_id},(SELECT userid FROM users WHERE lcase(username)=lcase('{$this->session->userdata('username')}')),{$transaction_id})";
$this->db->query($query);
}
It is reinserted so I can make a connection between someone's registration into a class and their payment for that class.
You have to convert the BIGINT to string before you get it into PHP
Here's how you can convert it with a SQL statement:
https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert
I don't know what framework you're using but you have to do a conversion if you're using BIGINT whose length is not supported by PHP directly. It seems that the framework will read the record after updated to check if the operation's done successfully, and because of the process of reading without the conversion, you got a wrong number.
You can also use PHP GMP module to handle BIGINT:
https://php.net/manual/en/book.gmp.php

Store fetched query into global array PHP

I'm trying to create a global variable which contains user specific data from my database. I've been looking over the internet but I couldn't find the right answer. I'm not quite sure how to approach this.
At this point of code, the user has been registered and logged in and his login data is saved inside $s_email and $s_password, which are session variables.
Here's some code (mysql.php) which contains the mysql class:
class mysql {
// Create database connection
private $db;
function __construct() {
$this->db = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME) or
die('Database fout.');
}
// Receive user data based on cookie, then fetch this data into an array
function getUserData($s_email, $s_password, $data){
$sql = "SELECT
`id`,
`firstname`,
`lastname`,
`city`,
`country`,
`gender`,
`bio`,
`active`,
`member_since`
FROM `users`
WHERE email = '$s_email'
AND password ='$s_password'";
// perform the query and store the result
$result = $this->db->query($sql);
// if the $result contains at least one row
if ($result->num_rows > 0) {
// output data of each row from $result
$data = array();
global $data;
while($row = $result->fetch_assoc()) {
$data[] = $row;
}
}
}
}
And this is my profile.php which will try to run the function (the echo $data[''] does not work, but that's the kind of approach I would like to take on this system):
<?php
// Receive user data and send it to mysql class
$mysql->getUserData($s_email, $s_password, $data);
echo $data['email'];
?>
Eventually I'm trying to create a simple access method to the array variables.
NOTE: I've only been scripting PHP and MySQL for 2 days, so any advice on my code is really appreciated.
Thank you,
Don't use globals.
In your function (don't use $data as an argument):
function getUserData($s_email, $s_password) {
// code
while($row = $result->fetch_assoc()) {
$data[] = $row;
}
return $data;
}
Then to use it (don't pass $data as an argument):
$data = $mysql->getUserData($s_email, $s_password);
Now you have rows so loop over them:
foreach($data as $row) {
echo $row['email'];
}
And you need to select email in the query.
I will first tell you how to do it and right after will explain why you shouldnt be doing that.
How to do it
To let the getUserData method to alter the value of the $data variable you are giving as argument you need to pass it by reference, like this:
function getUserData($s_email, $s_password, &$data) {
Why you should not be doing this in this situation
Keeping things brief. Because there are by far better approaches (like the one AbraCadaver is suggesting) and you dont really need to pass the $data by reference in the first place.

Insert Oracle CLOB in CodeIgniter

How do we use the CodeIgniter Framework to insert into a table that has a CLOB column?
Table is :
CREATE TABLE NOTIFICATIONS
{
ID NUMBER,
CONTENT CLOB
}
The PHP Code is :
$id = 1;
$content = /* An Incredibly Long & Complex String */
$query = "INSERT INTO notifications (id,content) values (?,?)";
$this->dbconnxn->query($query, array($id,$content));
But this doesn't seem to work.
Any ideas?
CodeIgniter doesn't support this, you need to do it manually. In order to not re-create a connection to the database, you can use the connection ID created by CI:
$this->db->conn_id
I have solved it creating in the model 2 functions 1) for insert and update the clob and 2) for read the clob:
1) In the model the function that I have created to set my Clob is updateClobImg64, it is called after the insert :
public function create_file($file){
$file->file_pk1=$this->getMaxPk1();
$data = array(
"file_pk1" => $file->file_pk1,
"file_name" => $file->file_name,
"file_type"=>$file->file_type,
"file_location"=>$file->file_location,
//"file_img64"=>$file->file_img64,
"file_created_date"=>$file->file_created_date,
"file_created_by" => $file->file_created_by,
"file_storage_type"=>$file->file_storage_type,
);
$this->db->insert("file_repository", $data);
if(isset($file->file_img64) && !empty($file->file_img64)) $this->updateClobImg64($file->file_pk1,$file->file_img64);
return $file->file_pk1;
}
public function updateClobImg64($file_pk1,$img64){
$sql='update "file_repository" set "file_img64"=EMPTY_CLOB() where "file_pk1"='.$file_pk1.' RETURNING "file_img64" INTO :clob';
$stid = oci_parse($this->db->conn_id, $sql);
$clob = oci_new_descriptor($this->db->conn_id, OCI_D_LOB);
oci_bind_by_name($stid, ":clob", $clob, -1, OCI_B_CLOB);
oci_execute($stid, OCI_NO_AUTO_COMMIT); // use OCI_DEFAULT for PHP <= 5.3.1
$clob->save($img64);
oci_commit($this->db->conn_id);
$clob->free();
OCIFreeStatement($stid);
}</span>
2) To read the clob is is need a function read that I have implemented this way:
function read_clob($field) {
return $field->read($field->size());
}
and Is called in the model select funtion:
public function get_all(){
$query = $this->db->get_where("file_repository", array());
if($query->num_rows() > 0){
$files=$query->result();
foreach($files as $key=>$row){
$files[$key]->file_img64=$this->read_clob($row->file_img64);
}
return $files;
}
}</span>
Hope this helps some one with this issue.

Is it possible to call a PHP function from an SQL query?

Say there is a special PHP function foo($number) that returns double the value of its input. Further, say there is a table that contains the column "number." Is there a way to have the PHP function and SQL query to run together so that I would get the following:
Number | Double
================
1 | 2
5 | 10
While in this simple example column Double can easily be implemented within the SQL statement, answers should cover the more general case of calling any PHP function, as there are many operations that are difficult to impossible in SQL but easy in PHP.
No, since the query results come straight from MySQL. You can apply the function to the result set after you execute your query, either by using a loop or by applying your function to the results using array_map() function.
Depending on what you're actually trying to achieve it might be possible to decouple the data source and the consumer a bit, enough to put another component between them.
Let's start with
<?php
$result = getData($pdo); // a)
doSomething($result); // b)
function getData($mysql) {
return mysql_query('SELECT x FROM soTest', $mysql);
}
function doSomething($result) {
while ( false!==($row=mysql_fetch_assoc($result)) ) {
echo ' ', join(', ', $row), "\n";
}
echo "----\n";
}
There's very little you can do to alter a mysql result resource. And doSomething() does nothing more than iterating over the result set. It does nothing that is special to a mysql result set, yet it allows nothing else but this exact resource type by using mysql_fetch_xyz().
Let's try this again using PDO (PDO_MYSQL).
$result = getData($pdo);
doSomething($result);
function getData($pdo) {
return $pdo->query('SELECT x FROM soTest');
}
function doSomething(PDOStatement $result) {
while ( $row=$result->fetch(PDO::FETCH_ASSOC) ) {
echo ' ', join(', ', $row), "\n";
}
echo "----\n";
}
That didn't change much. Some names but essentially this is the same. But PDOStatement implements the Traversable interface, so you can use it directly with foreach.
$result = getData($pdo);
doSomething($result);
function getData($pdo) {
return $pdo->query('SELECT x FROM soTest', PDO::FETCH_ASSOC);
}
function doSomething($traversable) {
foreach( $traversable as $row ) {
echo ' ', join(', ', $row), "\n";
}
echo "----\n";
}
That's a game changer... We can pass any traversable/iterator to doSomething() and it still does more or less the same thing as before.
Now we can put something "between" getData() and doSomething(). This something takes an inner iterator (like getData() provides in the form of an PDOStatement) and behaves itself like an iterator (so DoSomething() can use it) returning all elements of its inner iterator but modifying some elements.
I chose to extend FilterIterator for this example for no particular reason. You need php 5.3+ to run this demo since it uses an anonymous function:
<?php
$pdo = initDemo();
echo "#1: passing \$result\n";
$result = getData($pdo); // a)
doSomething($result); // b)
echo "#2: passing ModifyIterator\n";
$result = getData($pdo); // exact same as a)
$result = new ModifyIterator($result, null, function($e) {
$e['y'] = '/' . ($e['x'] * 2) .'/';
return $e;
});
doSomething($result); // exact same as b)
function getData($pdo) {
return $pdo->query('SELECT x FROM soTest', PDO::FETCH_ASSOC);
}
function doSomething($traversable) {
foreach($traversable as $row) {
echo ' ', join(', ', $row), "\n";
}
echo "----\n";
}
class ModifyIterator extends FilterIterator {
protected $fnAccept, $fnModify;
public function __construct($it, $fnAccept, $fnModify) {
// todo: test parameters!
$this->fnAccept = $fnAccept;
$this->fnModify = $fnModify;
if ( !($it instanceof Iterator) ) {
$it = new IteratorIterator($it);
}
parent::__construct($it);
}
public function accept() {
return $this->fnAccept ? $this->fnAccept(parent::current()) : true;
}
public function current() {
if ( $this->fnModify ) {
$fn = $this->fnModify;
return $fn(parent::current());
}
return parent::current();
}
}
function initDemo() {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'localonly', 'localonly');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('CREATE TEMPORARY TABLE soTest (x int auto_increment, primary key(x))');
$pdo->exec('INSERT INTO soTest VALUES (),(),(),(),()');
return $pdo;
}
prints
#1: passing $result
1
2
3
4
5
----
#2: passing ModifyIterator
1, /2/
2, /4/
3, /6/
4, /8/
5, /10/
----
The important part is that ModifyIterator forces very little particular behaviour on the inner iterator (e.g. you can still use an unbuffered query without the need to transfer all the data into the php process' memory at once) and that both getData() and doSomething() are left unchanged.
One way would be to fetch the results into objects:
class NumberDoubler
{
public $number;
public function __construct()
{
$this->number *= 2;
}
}
$pdo = new PDO('mysql:host=localhost;dbname=db_name', 'uname', 'pword');
$stmnt = $pdo->query('SELECT number FROM db_table');
$result = $stmnt->fetchAll(PDO::FETCH_CLASS, 'NumberDoubler');
print_r($result);
The result will be an array of objects with '$number' doubled. Of course, iteration will still be done "behind the scenes", and the manual warns, "Using this method to fetch large result sets will result in a heavy demand on system and possibly network resources."
See also PDOStatement::setFetchMode().
You don't need to use PHP. You can just execute a regular SQL statement as follows.
SELECT number, number * 2 FROM tablename;
//Your query would prob be like so.
$query = mysql_query("SELECT (number * 2) as double,number FROM table");
echo '<table>';
while($row = mysql_fetch_assoc($query))
{
echo sprintf('<tr><td>%d</td><td>%d</td></tr>',$row['number'],$row['double']);
}
echo '</table>';
I think I understand your question.
It sounds as though you want to pull a number from a database and double it through a php function.
I would first learn to write the php function... Check this out for a tutorial.
Once you have that down, pull your number from the database. Here is a tutorial on how to do that.
This is all about learning. Me posting code for you to just copy doesn't help you learn and is basically a waste of my time. Best of luck.
It depends on what the function is doing. Basic stuff like arithmetic can be done with SQL directly. Otherwise, you can loop over the result array and run the function in the particular field, e.g.:
$rows = array();
foreach(($row = mysql_fetch_assoc($result))) {
$row['double'] = func($row['number']);
rows[] = $row;
}
No, it's impossible to use php function() and sql together
However, you can get results from SQL database and apply whatever PHP function on it

Categories