I'm fairly new to OOP in PHP. I'm trying to update the value of an item in an array that is generated by a loop in its parent class. In my code the class DatabaseObject creates an attributes array with all of the table field name and value pairs. The contents of the array are used to create a SQL query to write to the database. The issue that I'm trying to resolve is that I need to update the attributes['hashed_password'] item with an encrypted password from the child User class. I'm not quite sure how I would update that array item so that the hashed password is submitted to the database instead of what came through a form submission.
class DatabaseObject {
public function create(){
global $db;
static::$attributes = [];
foreach (static::$db_fields as $field) {
if(property_exists($this, $field)){
$attributes[$field] = $this->$field;
}
}
$sql = "INSERT INTO " . static::$table_name . " (";
$sql .= join(", ", array_keys($attributes));
$sql .= ") VALUES ('";
$sql .= join("', '", array_values($attributes));
$sql .= "')";
$result = mysqli_query($db, $sql)
}
}
class User extends DatabaseObject {
protected static $table_name="users";
protected static $db_fields = array("user_id", "username", "hashed_password", "email");
public $user_id;
public $username;
public $hashed_password;
public $email;
self::attributes['hashed_password'] = password_hash($this->attributes['hashed_password'], PASSWORD_BCRYPT);
}
Any help would be appreciated.
The solution that worked for me was just to override the create function in the User object and manually update the array with the hashed password, so the new User object became:
class User extends DatabaseObject {
public function create(){
global $db;
static::$attributes = [];
foreach (static::$db_fields as $field) {
if(property_exists($this, $field)){
$attributes[$field] = $this->$field;
}
}
$attributes['hashed_password'] = password_hash($attributes['hashed_password'], PASSWORD_BCRYPT);
$sql = "INSERT INTO " . static::$table_name . " (";
$sql .= join(", ", array_keys($attributes));
$sql .= ") VALUES ('";
$sql .= join("', '", array_values($attributes));
$sql .= "')";
$result = mysqli_query($db, $sql)
}
}
}
Not sure if that's the most efficient way to do it, but it worked.
Related
I have 2 tables in the database, artists (username and password) and artistcard (contains info about the artist with one of the columns being artist_id to link to the artists table).
Now I want the users to be able to update the information displayed in the card:
Currently I get firstname, lastname, genre and location from the form that they can submit:
<?php if (isset($_POST['submit'])) { // Form has been submitted.
$genre = trim($_POST['genre']);
$location = trim($_POST['location']);
$firstname = trim($_POST['firstname']);
$lastname = trim($_POST['lastname']);
$artistcard = new Artistcard();
$artistcard->first_name = $firstname;
$artistcard->last_name=$lastname;
$artistcard->genre = $genre;
$artistcard->location = $location;
$artistcard->artist_id = $_SESSION['artist_id'];
$artistcard->update();
}
Now I want the update function to go through the fields where artist_id in the artistcard table matches id in the artist table (or session id). For this I have in the Artistcard class:
public function update() {
global $database;
$attributes = $this->sanitized_attributes();
$attribute_pairs = array();
foreach($attributes as $key => $value) {
$attribute_pairs[]= "{$key}='{$value}'";
}
// - UPDATE table SET key='value', key='value' WHERE condition
$sql = "UPDATE ".self::$table_name." SET ";
$sql .= join(", ", $attribute_pairs);
$sql .= " WHERE artist_id=". $database->escape_value($this->artist_id);
$database->query($sql);
return ($database->affected_rows() == 1) ? true : false;
}
which returns Database Query Failed. I'm guessing the SQL is not correct? I have an almost identical CREATE method, which works fine:
public function create(){
global $database;
$attributes = $this->sanitized_attributes();
$sql = "INSERT INTO ".self::$table_name." (";
$sql .= join(",",array_keys($attributes));
$sql .= ") VALUES ('";
$sql .= join("', '",array_values($attributes));
$sql .= "')";
if ($database->query($sql)) {
$this->id = $database->insert_id();
return true;} else {return false;}
}
I have this in my functions.php file
function getUserOrders($userId){
global $conn;
$query = "SELECT * ";
$query .= "FROM orders ";
$query .= "WHERE userid=" . $userId . " ";
$odrset = mysqli_query($conn, $query);
while ($odr = mysqli_fetch_assoc($odrset)){
return $odr;
}
}
What I neeed to do in my orders.php file is display specific fields and their values from the returned $odr array as this snippet suggests
$userId = sql_prep($_SESSION['userid']) ;
getUserOrders($userId);
echo $odr['title'].$odr['orderid'].'<br>'
I am only able to do it in the functions.php file...
function getUserOrders($userId){
global $conn;
$query = "SELECT * ";
$query .= "FROM orders ";
$query .= "WHERE userid=" . $userId . " ";
$odrset = mysqli_query($conn, $query);
confirm_query($odrset);
while ($odr = mysqli_fetch_assoc($odrset)){
echo $odr['title'].$odr['orderid'].'<br>';
}
}
..and calling it in my orders.php file like so:
$userId = sql_prep($_SESSION['userid']) ;
getUserOrders();
which is not good since i need to recycle the function somewhere else and display different fields and their values. So I need to have $odr returned as an array in my order.php
Store it as an array and then return the array.
function getUserOrders($userId){
global $conn;
$query =
"SELECT *
FROM orders
WHERE userid= ?";
$odrset = mysqli_prepare($conn, $query);
mysqli_stmt_bind_param($odrset, 'i', $userId);
mysqli_stmt_execute($odrset);
while ($odr = mysqli_fetch_assoc($odrset)){
$return[] = $odr;
}
return $return;
}
I've updated your mysqli connection to use a parameterized query with prepared statement. You can read more about these here, http://php.net/manual/en/mysqli.quickstart.prepared-statements.php. This is the preferred approach than escaping.
Later usage...
$orders = getUserOrders($_SESSION['userid']);
foreach($orders as $order) {
echo $order['title'] . $order['orderid'];
}
You may not need the sql_prep function with this approach, I'm not sure what that did. Your questions code didn't pass the userid to the function so I don't think that was your exact usage.
mysqli_fetch_assoc only returns one record at a time so you need to store the results inside an array and return the array from the function:
// functions.php
function getUserOrders($userId){
global $conn;
$query = "SELECT * ";
$query .= "FROM orders ";
$query .= "WHERE userid=" . $userId . " ";
$odrset = mysqli_query($conn, $query);
$results = array();
while ($odr = mysqli_fetch_assoc($odrset)){
$results[] = $odr;
}
return $results;
}
// in your orders file
$userid = sql_prep($_SESSION['userid']);
$orders = getUserOrders($userid);
foreach ($order as $orders) {
echo $order['title']. $order['orderid'] . '<br>';
}
i'm just switching from mysql_* to PDO since i read that mysql_* will be removed in the future and now i have no idea to abstract my current class for insert,update,and delete operation to PDO, maybe some one can point me out how to translate it to PDO based?
this is my connection class for handling all connection and other related function (i already making this one PDO so there is no problem with this)
<?php
require_once(folder.ds."constants.php");
class MySQLDatabase {
private $dbh;
private $host = DB_SERVER;
private $dbname = DB_NAME;
private $stmt;
public $query_terakhir;
public $error_text;
function __construct(){
$this->open_connection();
}
public function open_connection(){
$dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try{
$this->dbh = new PDO($dsn,DB_USER,DB_PASS,$options);
}
catch(PDOException $e) {
date_default_timezone_set('Asia/Jakarta');
$dt = time();
$waktu = strftime("%Y-%m-%d %H:%M:%S", $dt);
$log = array_shift(debug_backtrace());
file_put_contents('PDOErrors.txt',$waktu. ": " .$e->getMessage(). ": " .$log['file']. ": line " .$log['line']. "\n", FILE_APPEND);
}
}
public function query($sql){
$this->stmt = $this->dbh->prepare($sql);
}
public function bind($param, $value, $type = null){
if (is_null($type)) {
switch (true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
}
}
$this->stmt->bindValue($param, $value, $type);
}
public function execute(){
return $this->stmt->execute();
}
public function fetchall(){
return $this->stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function fetch(){
return $this->stmt->fetch(PDO::FETCH_ASSOC);
}
public function rowCount(){
return $this->stmt->rowCount();
}
public function lastInsertId(){
return $this->dbh->lastInsertId();
}
public function beginTransaction(){
return $this->dbh->beginTransaction();
}
public function endTransaction(){
return $this->dbh->commit();
}
public function cancelTransaction(){
return $this->dbh->rollBack();
}
public function debugDumpParams(){
return $this->stmt->debugDumpParams();
}
}
$database = new MySQLDatabase();
?>
and here is one of my class that help me to do saving(create or update), delete and others, with this class i only need to change $nama_tabel for table name, $db_fields for my table fields and public $xxxxx that match with my table fields and create, update and delete function can work perfectly...
but with pdo i just can't figure out how to make it work for create, update and delete with the same method as above....
<?php
require_once('database.php');
class staff{
public static $nama_tabel="staff";
protected static $db_fields = array('id','name','job');
public $id;
public $name;
public $job;
private function has_attribute($attribute){
$object_var = $this->attributes();
return array_key_exists($attribute,$object_var);
}
protected function attributes(){
$attributes = array();
foreach(self::$db_fields as $field){
if(property_exists($this, $field)){
$attributes[$field] = $this->$field;
}
}
return $attributes;
}
protected function sanitized_attributes(){
global $database;
$clean_attributes = array();
foreach($this->attributes() as $key => $value){
$clean_attributes[$key] = $database->escape_value($value);
}
return $clean_attributes;
}
public function create(){
global $database;
$attributes = $this->sanitized_attributes();
$sql = "INSERT INTO " .self::$nama_tabel." (" ;
$sql .= join(", ", array_keys($attributes));
$sql .=")VALUES('";
$sql .= join("', '", array_values($attributes));
$sql .= "')";
if($database->query($sql)){
$this->id_kategori = $database->insert_id();
return true;
}else{
return false;
}
}
public function update(){
global $database;
$attributes = $this->sanitized_attributes();
$attribute_pairs = array();
foreach($attributes as $key => $value){
$attribute_pairs[] = "{$key}='{$value}'";
}
$sql ="UPDATE " .self::$nama_tabel." SET ";
$sql .= join(", ", $attribute_pairs);
$sql .=" WHERE id=" . $database->escape_value($this->id);
$database->query($sql);
return($database->affected_rows() == 1) ? true : false;
}
public function delete(){
global $database;
$sql = "DELETE FROM " .self::$nama_tabel;
$sql .= " WHERE id=". $database->escape_value($this->id);
$sql .= " LIMIT 1";
$database->query($sql);
if(!empty($this->gambar)){
$target = website .ds. $this->upload_dir .ds. $this->gambar;
unlink($target);
}
return($database->affected_rows() == 1) ? true : false;
}
}
?>
updated: this is my approach for create function after tweaking from update function from GolezTrol, but not inserting the value instead it inserting name=:name and content=:content and so on
Updated:its already fixed! here is the right one
public function create(){
global $database;
$attributes = $this->attributes();
$attribute_pairs = array();
foreach($attributes as $key => $value){
$attribute_pairs[] = ":{$key}";
}
$sql = "INSERT INTO " .self::$nama_tabel." (" ;
$sql .= join(", ", array_keys($attributes));
$sql .=")VALUES(";
$sql .= join(", ", $attribute_pairs);
$sql .= ")";
$database->query($sql);
foreach($attributes as $key => $value){
$database->bind(":$key", $value);
}
if($database->execute()){
$this->id = $database->lastInsertId();
return true;
}else{
return false;
}
}
2nd update : i experiencing some weird thing in while loop operation where i doing while and inside the while i check if this field id is equal with my other table id then i will show that id name field... and it show, but stoping my while loop, so i only get 1 row while loop (it supposed to show 40 row)
$database->query($sql_tampil);
$database->execute();
while($row = $database->fetch()){
$output = "<tr>";
if(!empty($row['id']))
$output .="<td><a data-toggle=\"tooltip\" data-placement=\"top\"
title=\"Tekan untuk mengubah informasi kegiatan ini\"
href=\"ubah_cuprimer.php?cu={$row['id']}\"
>{$row['id']}</a></td>";
else
$output .="<td>-</td>";
if(!empty($row['name'])){
$y = "";
$x = $row['name'];
if(strlen($x)<=40)
$y = $x;
else
$y=substr($x,0,40) . '...';
$output .="<td><a data-toggle=\"tooltip\" data-placement=\"top\"
title=\"{$row['name']}\"
href=\"ubah_cuprimer.php?cu={$row['id']}\"
> {$y} </td>";
}else
$output .="<td>-</td>";
$wilayah_cuprimer->id = $row['wilayah'];
$sel_kategori = $wilayah_cuprimer->get_subject_by_id();
if(!empty($sel_kategori))
$output .="<td><a href=\"#\" class=\"modal1\"
data-toggle=\"tooltip\" data-placement=\"top\"
title=\"Tekan untuk mengubah kategori artikel ini\"
name={$row['id']}>{$sel_kategori['name']}</a></td>";
else
$output .="<td><a href=\"#\" class=\"modal1\"
data-toggle=\"tooltip\" data-placement=\"top\"
title=\"Tekan untuk mengubah kategori artikel ini\"
name={$row['id']}>Tidak masuk wilayah</a></td>";
if(!empty($row['content'])){
$content = html_entity_decode($row['content']);
$content = strip_tags($content);
$z = "";
$v = $content;
if(strlen($v)<=40)
$z = $v;
else
$z=substr($v,0,40) . '...';
$output .="<td><a data-toggle=\"tooltip\" data-placement=\"top\"
title=\"{$content}\"
href=\"ubah_cuprimer.php?cu={$row['id']}\"
>{$z}</a> </td>";
}else
$output .="<td>-</td>";
if(!empty($row['tanggal']))
$output .="<td>{$row['tanggal']}</td>";
else
$output .="<td>-</td>";
if(!empty($row['id']))
$output .="<td><button class=\"btn btn-default modal2\"
name=\"{$row['id']}\"
data-toggle=\"tooltip\" data-placement=\"top\"
title=\"Tekan untuk menghapus layanan ini\" ><span
class=\"glyphicon glyphicon-trash\"></span></button></td>";
else
$output .="<td>-</td>";
$output .="</tr>";
echo $output;
}
And here is my $wilayah_cuprimer->get_subject_by_id(); function
public function get_subject_by_id(){
global $database;
$sql = "SELECT * ";
$sql .= "FROM ".self::$nama_tabel;
$sql .= " WHERE id = :id" ;
$sql .= " LIMIT 1";
$database->query($sql);
$database->bind(":id",$this->id);
$database->execute();
$array = $database->fetch();
return $array;
}
Good that you make the transition to PDO. It's better to do it now, then to find out some day that you can't upgrade PHP, because it would break your application.
As far as I can tell, $database->query($sql); only prepares a statement. That is the first step, but after that you need to execute it as well. You already have the $database->execute() method for that, but you don't call it in your insert, update and delete functions.
Apart from that issue, it would even be better if you used bind parameters for the updates too, and leave escaping strings up to the database.
It's hard to test your class in its entirety, but I hope this gives you some ideas. I added comments to describe the steps.
public function update(){
global $database;
// Don't need 'clean' attributes if you bind them as parameters.
// Any conversion you want to support is better added in $database->bind,
// but you don't need, for instance, to escape strings.
$attributes = $this->attributes();
// Place holders for the parameters. `:ParamName` marks the spot.
$attribute_pairs = array();
foreach($attributes as $key => $value){
$attribute_pairs[] = "{$key}=:{$key}";
}
$sql ="UPDATE " .self::$nama_tabel." SET " .
join(", ", $attribute_pairs) .
" WHERE id = :UPDATE_ID";
$database->query($sql);
// Bind the ID to update.
$database->bind(':UPDATE_ID', $this->id);
// Bind other attributes.
foreach($attributes as $key => $value){
$database->bind(":$key", $value);
}
// Execute the statement.
$database->execute();
// Return affected rows. Note that ->execute also returns false on failure.
return($database->affected_rows() == 1) ? true : false;
}
The code----
public function create(){
global $database;
//this code works perfectly to insert the new user into my database.
$sql = "INSERT INTO users (";
$sql .= "username, password, first_name, last_name";
$sql .= ") VALUES ('";
$sql .= $database->escape_value($this->username) ."', '";
$sql .= $database->escape_value($this->password) ."', '";
$sql .= $database->escape_value($this->first_name) ."', '";
$sql .= $database->escape_value($this->last_name) ."')";
if($database->query($sql)){
$this->id = $database->insert_id();
return true;
}else{
return false;
}
public function create()
{
global $database;
//this code is to be universal for other database types but it is not working.
$attributes = $this->attributes();
$sql = "INSERT INTO ".self::$table_name." (";
$sql .= join(", ", array_keys($attributes));
$sql .= ") VALUES ('";
$sql .= join("', '", array_values($attributes));
$sql .= "')";
if ($database->query($sql)) {
$this->id = $database->insert_id();
return true;
} else {
return false;
}
}
the problem - suppose to be generalizing the script so it can be used in any database structure. Its not working in my wamp.
I think there may be an issue with magic quote . Kindly use addslash php function for array_values($attributes)
This is not a programming error from your end the problem here is that MySQL is not interpreting this action as valid due to its SQL_MODE being in STRICT mode. You can either choose to edit your MySQL settings or add this code to your database class:
private function open_connection() {
$this->connection = mysqli_connect(DB_SERVER,DB_USER,DB_PASSWORD,DB_NAME);
if (!$this->connection) {
# code...
die("Connection to database failed" . mysqli_errno($this->connection));
}else{
/// this is the part you should take note of i changed the sql mode here using this code
mysqli_query($this->connection, "SET GLOBAL sql_mode = ''");
}
Your problem might be that you are using join("', '", array_values($attributes)) into the values you are trying to insert. But i guess, your column types aren't all strings, so you are creating something like :
INSERT INTO mytable(id,column1)
VALUES('id','mycolumn1')
Problem is probably that your id column's type is int, that might be why you have Incorrect integer value.
So you should have something like :
INSERT INTO mytable(id,column1)
VALUES(1,'mycolumn1')
Just guessing here, hope that helps.
I run the same issue. I wanted to post reply if anyone goes through the same problem. I think you were following lynda.com tutorial. As DCoder mentioned, id is auto increment value, and there is no way knowing what the value is, and moreover, join function made each property values-column values strings, in your case you get empty ""string. But id field is an int.
Your first create function, the one without join, worked because the id field is left out.
Now to fix the second create function after the line $attributes = $this->attributes(); add the following code
if($attributes["id"]==""){
array_shift($attributes);
}
after the line $attributes = $this->attributes();
Your attributes holds a list of properties; I am also making assumption that your code is the same as the tutorial I come across. So you check if the id is empty. If it is empty, you do an array_shift. This remove the first attribute, that is id, and in the process the new attributes array will have no id, and then when you apply the join it will be the same as your first create function. This will fix the issue. But I still would like to know how the instructor pass the tutorial without any issue. Great instructor and great tutorial for OOP--I am talking about the lynda.com tutorial.
You could elaborate your question more to get more response or the right answer. Since I run the same issue made it easier for me.
To let MySql generate sequence numbers for an AUTO_INCREMENT field you have some options:
explicitly assign NULL;
explicitly assign 0
public function create() {
global $database;
// USE '0' for your auto_increment not null filed if your filed need to sanitize.
$this->id = 0;
$attributes = $this->sanitized_attributes();
$sql = "INSERT INTO ".self::$table_name." (";
$sql .= join(", ", array_keys($attributes));
$sql .= ") VALUES ('";
$sql .= join("', '", array_values($attributes));
$sql .= "')";
if($database->query($sql)) {
$this->id = $database->insert_id();
return true;
} else {
return false;
}
}
This is how I figured to solve this problem
public function create(){
global $database;
$attributes = $this->sanitized_attributes();
$keys = array_keys($attributes);
$values = array_values($attributes);
$id = array_shift($values);
$id = (int) $id;
$sql = "INSERT INTO ".static::$table_name." (";
$sql .= join(", ", $keys);
$sql .= ") VALUES ($id,'";
$sql .= join("', '", $values);
$sql .= "')";
if($database->query($sql)) {
$this->id = $database->insert_id();
return true;
} else {
return false;
}
}
I'm learning how to code php and I'm working on the CRUD. So far, I've got the Create and Delete methods working, but for the life of me, I cannot get Update to work. Am I missing something completely obvious here? Thank you in advance.
In my User class I have the following (Only related methods included here):
protected static $table_name="users";
protected static $db_fields = array('id', 'f_name');
public $id;
public $f_name;
public static function find_by_id($id=0) {
global $db;
$result_array = self::find_by_sql("SELECT * FROM ".static::$table_name." WHERE id={$id} LIMIT 1");
return !empty($result_array) ? array_shift($result_array) : false;
}
protected function attributes() {
$attributes = array();
foreach(self::$db_fields as $field) {
if(property_exists($this, $field)) {
$attributes[$field] = $this->$field;
}
}
return $attributes;
}
protected function sanitized_attributes() {
global $db;
$clean_attributes = array();
foreach($this->attributes() as $key => $value){
$clean_attributes[$key] = $db->escape_value($value);
}
return $clean_attibutes;
}
public function update() {
global $db;
$attributes = $this->attributes();
$attribute_pairs = array();
foreach($attributes as $key => $value) {
$attibute_pairs[] = "{$key}='{$value}'";
}
$sql = "UPDATE ".self::$table_name." SET ";
$sql .= join(", ", $attribute_pairs);
$sql .= " WHERE id=". $db->escape_value($this->id);
$db->query($sql);
if ($db->affected_rows() == 1) {
echo "Yes!";
} else {
echo "No!";
}
}
In my html file, I simply have:
<?php
$user = User::find_by_id(1);
$user->f_name = "UpdatedUser";
$user->update();
?>
Well for one, your query is probably failing on account of this
$sql = "UPDATE ".static::$table_name." SET ";
being invalid. Try:
$sql = "UPDATE ".self::$table_name." SET ";