Passing parameters to a patch function in php - php

I've just begun developing code in php, and currently I'm developing a small API for a faculty at my university.
I'm using prepared statements to interact with a MySQL database, but in this function the parameters do not get passed to the function.
function patch_admin() {
global $db,$file, $settings;
$payload = get_json();
if($payload[password_crypt] != NULL)
{
metamessage(lang('please_use_password_function'));
return HTTP_BAD_REQUEST;
}
if($payload[admin_id] != NULL)
{
foreach($payload as $key => $value) {
if($key == "admin_id") { continue; }
$querystr="UPDATE " .table('admin'). " SET :key=:value WHERE admin_id=:admin_id";
$params = [ ':key' => $key, ':value' => $value, ':admin_id' => $payload[admin_id]];
$query = $db->prepare($querystr);
$query->execute($params);
}
}
return HTTP_BAD_REQUEST;
}
The query and the parameters look like this:
UPDATE or_admin
SET :key = :value
WHERE admin_id=:admin_id
Array
(
[:key] => fname
[:value] => Bernharddiener
[:admin_id] => 1626704194
)
Am I missing something obvious here?
When I replace the :key with the field names, I'm able to patch the database, but I want to get the column names from the JSON I send.
Best regards,
Til

You should check and set params the same way you do with the table name
Ex :
$querystr="UPDATE " .table('admin'). " SET `".mysql_real_escape_string($key)."` =:value WHERE admin_id=:admin_id";
$params = [ ':value' => $value, ':admin_id' => $payload[admin_id]];

Related

Is there a MySQL function to 'use existing value' during update?

Is there a way with MySQL to specify "use previous / inherit / no change / existing value"?
Rather than needing to pull the current data from the database and use it, or have a customized database function excluding editing those columns.
if(x > y) {
$role_id = 3;
} else {
$role_id = '#no-change'; // Is there a way to do this? (not proper SQL syntax)
}
$update_user = $this->db->update('users',
array(
'first_name' => filterName($post['first_name']),
'last_name' => filterName($post['last_name']),
'email' => filterEmail($post['email']),
'role_id' => $role_id,
), $user_id_to_edit, 'user_id');
In a case like this where the db function is using prepared statements (not shown) I can't use the column name as to reflect the current value.
Is there such a MySQL function / variable that will essentially "ignore" updating that column? (just leave the existing value)
UPDATE: Here's the Update function:
public function update($table, $data, $where_id, $column = 'user_id') {
// Check for $table or $data not set
if (( empty( $table ) || empty( $data )) || empty($data) ) {
return false;
}
// Initiate variable to append to
$placeholders ='';
// Parse data for column and placeholder names
foreach ($data as $key => $value) {
$placeholders .= sprintf('%s=:%s,', $key, $key);
}
// Trim excess commas
$placeholders = rtrim($placeholders, ',');
// Append where ID to $data
$data['where_id'] = $where_id;
// Prepary our query for binding
$stmt = $this->db->prepare("UPDATE {$table} SET {$placeholders} WHERE $column = :where_id");
// Execute the query
$stmt->execute($data);
// Check for successful insertion
if ( $stmt->rowCount() ) {
return true;
}
return false;
}
You could try this:
$data = array(
'first_name' => filterName($post['first_name']),
'last_name' => filterName($post['last_name']),
'email' => filterEmail($post['email']))
if(x > y) {
$data['role_id'] = 3;
}
$update_user = $this->db->update('users', $data, $user_id_to_edit, 'user_id');
That way, you can customize $data before hand if you like.
I should also mention, if you're concerned about redundancy, you can put your data sanitation inside a function. Something along the lines of:
function sanitize($data) {
if(x > y) {
$data['role_id'] = 3;
} else {
if(isset($data['role_id']) {
unset($data['role_id']);
}
}
return $data;
}
$data = array(
'first_name' => filterName($post['first_name']),
'last_name' => filterName($post['last_name']),
'email' => filterEmail($post['email']))
$update_user = $this->db->update('users', sanitize($data), $user_id_to_edit, 'user_id');
Edit: Something I should mention is that, if we're talking pure SQL, all you need to do is omit those columns from the query, so:
UPDATE table SET Col1=val1, Col2=val2, Col3=val3 WHERE id=val
But if for some reason you don't want to update Col3, just remove it from the query:
UPDATE table SET Col1=val1, Col2=val2 WHERE id=val
Since you have a function that just adds whatever you give it, you just need to sanitize the data ahead of time. That's probably the best way to do it.

Prepare data for inserting ST_GeomFromText in database (Codeigniter + MySql)

I'm having a problem inserting my data in database with Codeigniter. I have this testing seed function
$latitude = rand(45.500000 * 1000000, 46.400000 * 1000000) / 1000000;
$longitude = rand(13.600000 * 1000000, 15.500000 * 1000000) / 1000000;
$data = array(
'unique_id' => '12319334',
'latitude' => $latitude,
'longitude' => $longitude,
'coordinates' => "ST_GeomFromText('POINT($latitude $longitude)')",
);
$locationId = $this->Locations->insert($data);
And this is insert function in model
function insert($data, $tableName = "")
{
if ($tableName == "") {
$tableName = $this->table;
}
$this->db->insert($tableName, $data);
return $this->db->insert_id();
}
And this is a query that happens
INSERT
INTO
`locations`(
`unique_id`,
`latitude`,
`longitude`,
`coordinates`
)
VALUES(
'ZTE1NGY2YT',
45.990292,
14.948462,
'ST_GeomFromText(\'POINT(45.582315 14.821478)\')'
)
Error i get is Cannot get geometry object from data you send to the GEOMETRY field
After some testing in phpmyadmin i figure out that query for inserting this kind of data should look like this
INSERT
INTO
`locations`(
`unique_id`,
`latitude`,
`longitude`,
`coordinates`
)
VALUES(
'ZTE1NGY2YT',
45.990292,
14.948462,
ST_GeomFromText('POINT(45.582315 14.821478)')
)
So somehow i need to get rid off single quotes (') in 'ST_GeomFromText(\'POINT(45.582315 14.821478)\')' line
Anybody got idea how to properly prepare data (without executing direct query, since there is a lot more data to store) so it can be process properly?
If you need any additional information please let me know and i will provide.
Thank you!
Actually this did it
$this->db->set('coordinates', "ST_GeomFromText('POINT($latitude $longitude)')", false);
So i made a small hack on my model (i know its not pretty but it works for now) and data is inserted..
function insert($data, $tableName = "")
{
foreach ($data as $key => $value) {
if ($key == 'coordinates') {
$this->db->set('coordinates', $value, false);
unset($data['coordinates']);
}
}
if ($tableName == "") {
$tableName = $this->table;
}
$this->db->insert($tableName, $data);
return $this->db->insert_id();
}
Unfortunately, CodeIgniter's database escape logic assumes that the pattern is field_name => rendered data. This means that you can't use things like NOW() or MD5(), or ST_GeomFromText. You can see the logic here:
foreach ($data as $key => $val)
{
$fields[] = $this->escape_identifiers($key);
$values[] = $this->escape($val);
}
The best option I've managed is to extend the DB driver and have the model call a custom function. The other option is to drop back into raw SQL and manually escape the values.

Eloquent Query Dynamically based on json

I want to query dynamically based on payload(json) from database.
Example:
$data = [{"key":"age","relation":">","value":"15"},{"operator":"OR"},{"key":"age","relation":"<=","value":"20"}]
I want to do query based on that payload.
Right now what I'm doing is:
$query = User::all();
$payload = json_decode($data, true);
foreach($payload as $value){
if ($value['key'] == 'age') {
$query = $query->where('birthday', $value['relation'], Carbon::now()->subYears($value['age'])->format('Y-m-d');)
}
if($value['key'] == 'gender'{
$query = $query->where('gender', $value['relation'], $value['gender']);
}
}
The problem is yes it can work, but I don't think this is best approach. I don't get any solution to use the "operator" key. Operator usage is to change where to orWhere.
Any solution or tips to make it call dynamically like this?. I want my column at DB neat and simple. I can only think this way.
Thanks!
Encountering this problem, I would go with Local Query Scopes. In this approach You create a model method named scopeJson() or whatever you feel better with to handl all conditions inside. I tried to handle most conditions here not only single where and orWhere. I assumed that your payload contains only one builder at a time.
public function scopeJson($query, $json)
{
$wheres = [
'between' => ['whereBetween', 'not' => 'whereNotBetween'],
'null' => ['whereNull', 'not' => 'whereNotNull'],
'or' => ['orWhere', 'not' => 'orWhereNot'],
'in' => ['whereIn', 'not' => 'whereNotIn'],
'and' => ['where', 'not' => 'orWhereNot'],
'raw' => 'whereRaw'
];
$builder = json_decode($json);
if (count($builder) > 0) {
$query->where(
$builder[0]->key,
$builder[0]->relation,
$builder[0]->value
);
// notBetween, notNull, notOr, notIn, notAnd
if (stripos($builder[1]->operator, 'not') !== false) {
$whereCondition = $wheres[strtolower(substr($builder[1]->operator, 3))]['not'];
} else {
$whereCondition = $wheres[strtolower($builder[1]->operator)];
}
if (count($builder[2]) == 3) {
if ($whereCondition == 'whereRaw') {
$query->$whereCondition(implode(" ", $builder[2]));
} else {
// where, whereNot
$query->$whereCondition(
$builder[2]->key,
$builder[2]->relation,
$builder[2]->value
);
}
} elseif (count($builder[2]) == 2) {
// whereBetween, whereNotBetween, where, whereNot
$query->$whereCondition(
$builder[2]->key,
$builder[2]->value
);
} elseif (count($builder[2]) == 1) {
// whereNull, whereNotNull, whereRaw
$query->$whereCondition(
$builder[2]->key ?? $builder[2]->value // PHP 7.0 Null Coalescing Operator
);
}
}
return $query;
}
If this method is defined within your User's model then you can use it this way:
$users = User::json($data)->get();
PS: Although it should work, I didn't test it.
You can do raw query like this.
$data = '[{"key":"age","relation":">","value":"15"},{"operator":"OR"},{"key":"age","relation":"<=","value":"20"}]';
$query = "SELECT * FROM tablename WHERE";
$payload = json_decode($data, true);
foreach ($payload as $value) {
if (isset($value['operator'])) {
$query .= " " . $value['operator'];
} else {
if ($value['key'] == 'age') {
$query .= " 'birthday' " . $value['relation'] . " " . Carbon::now()->subYears($value['value'])->format('Y-m-d');
}
if ($value['key'] == 'gender') {
$query .= " 'gender' " . $value['relation'] . " " . $value['gender'];
}
}
}
This results in a query like this :
SELECT * FROM tablename WHERE 'birthday' > 2001-07-02 OR 'birthday' <= 1996-07-02
Of course, you might use printf() for formatting and making this cleaner in some other way but this will get you started hopefully.
You can use a variable function name to add your orWhere logic:
$query = User::all();
$payload = json_decode($data, true);
$function = 'where';
foreach($payload as $value){
if(isset($value['operator'])){
$function = $value['operator'] == 'OR' ? 'orWhere' : 'where';
} else {
if ($value['key'] == 'age') {
$query = $query->$function('birthday', $value['relation'], Carbon::now()->subYears($value['age'])->format('Y-m-d');)
} else {
$query = $query->$function($value['key'], $value['relation'], $value['value']);
}
}
}
As long as your json data doesn't match your database (ie. the json has age but the database has birthday) you will not be able to avoid having that if/else statement. That custom logic will have to remain.
Ultimately this idea is its own limiter because the stored queries will have to represent the current state of the database. This means that the maintenance cost of these queries will be significant the moment data is stored in a different way -- if you changed the column birthday to date_of birth all of your stored queries will break. Avoid this.
Instead this goal is better achieved by storing the queries on the Model using Query Scopes and Relationships. If you still need a dynamic list of requests you can store keywords that are associated with the queries and loop through them.

PHP how to use muliple parameters and update mySQL table with them

Im tying to update a table with the followning function in PHP. The problem is that the second parameter $work_place is not accepted and the update fails. This is my first time working with PHP and mySQL so my knowledge is a bit limited.
public function timestampOut($work_done, $work_place)
{
// clean the input to prevent for example javascript within the notes.
$work_done = strip_tags($work_done);
$work_place = strip_tags($work_place);
$userLastTimestampID = $this->getUserLastTimestampID();
$sql = "UPDATE timestamps SET timestamp_work_description = :work_done, timestamp_work_dropdown = :work_place, timestamp_out = now() WHERE timestamp_id = $userLastTimestampID[0] AND user_id = :user_id";
$query = $this->db->prepare($sql);
$query->execute(array(':work_done' => $work_done, ':user_id' => $_SESSION['user_id']));
$count = $query->rowCount();
if ($count == 1) {
return true;
} else {
$_SESSION["feedback_negative"][] = FEEDBACK_NOTE_CREATION_FAILED;
}
// default return
return false;
}
You just need to add work_place to the param array in your execute call, like so:
$query->execute(array(':work_done' => $work_done, ':work_place' => $work_place, ':user_id' => $_SESSION['user_id']));
Please read again how execute works. You would want to use it like this:
$query->execute(array(':work_done' => $work_done, ':work_place' => $work_place, ':user_id' => $_SESSION['user_id']));
Try to replace
$query->execute(array(':work_done' => $work_done, ':user_id' => $_SESSION['user_id']));
with
$query->execute(array(':work_done' => $work_done, ':user_id' => $_SESSION['user_id']), ':work_place' => $work_place);

Class returns integer instead of object while trying to get column data

Just getting into OOP, and changing from mysql queries to PDO. I am trying to create a class that will return the column names and meta data for a table. This is so I can output copy/paste data for all the tables I use. I have used such a tool, based on the mysql extension, for ages and it spits out variants such as complete SELECT/INSERT/UPDATE queries. Amongst other things I now want to add DECLARE listings for Stored Procedures - so getting meta data like type and length are essential. With about 150 tables across two schemas, such automation is essential.
With uncertainty about the reliability of getColumnMeta I hunted for code and found what looked good in a Sitepoint answer. I have attempted to wrap it in a class and mimic its original context but I am simply getting a number 1 when I try to echo or print_r the response. I have also had 'not an object' error messages while trying solutions.
This is the calling code
$db_host="localhost";
$db_username='root';
$db_pass='';
$db_name='mydatabase';
try{
$db= new PDO('mysql:host='.$db_host.';dbname='.$db_name,$db_username,$db_pass, array(PDO::ATTR_PERSISTENT=>false));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
}
catch(PDOException $e){echo "Error: ".$e->getMessage()."<br />"; die(); }
include 'ColMetaData.php'; //the file containing the class for getting a column listing for each table
$coldat= new supplyColumnMeta($db);
$tablemet=$coldat->getColumnMeta('groups'); // a manual insertion of a table name for testing
echo $tablemet;
And this is the class that sits in the include file
class supplyColumnMeta{
public function __construct($db){
$this->db=$db;
}
/**
* Automatically get column metadata
*/
public function getColumnMeta($table)
{$this->tableName=$table;
// Clear any previous column/field info
$this->_fields = array();
$this->_fieldMeta = array();
$this->_primaryKey = NULL;
// Automatically retrieve column information if column info not specified
if(count($this->_fields) == 0 || count($this->_fieldMeta) == 0)
{
// Fetch all columns and store in $this->fields
$columns = $this->db->query("SHOW COLUMNS FROM " . $this->tableName, PDO::FETCH_ASSOC);
foreach($columns as $key => $col)
{
// Insert into fields array
$colname = $col['Field'];
$this->_fields[$colname] = $col;
if($col['Key'] == "PRI" && empty($this->_primaryKey)) {
$this->_primaryKey = $colname;
}
// Set field types
$colType = $this->parseColumnType($col['Type']);
$this->_fieldMeta[$colname] = $colType;
}
}
return true;
}
protected function parseColumnType($colType)
{
$colInfo = array();
$colParts = explode(" ", $colType);
if($fparen = strpos($colParts[0], "("))
{
$colInfo['type'] = substr($colParts[0], 0, $fparen);
$colInfo['pdoType'] = '';
$colInfo['length'] = str_replace(")", "", substr($colParts[0], $fparen+1));
$colInfo['attributes'] = isset($colParts[1]) ? $colParts[1] : NULL;
}
else
{
$colInfo['type'] = $colParts[0];
}
// PDO Bind types
$pdoType = '';
foreach($this->_pdoBindTypes as $pKey => $pType)
{
if(strpos(' '.strtolower($colInfo['type']).' ', $pKey)) {
$colInfo['pdoType'] = $pType;
break;
} else {
$colInfo['pdoType'] = PDO::PARAM_STR;
}
}
return $colInfo;
}
/**
* Will attempt to bind columns with datatypes based on parts of the column type name
* Any part of the name below will be picked up and converted unless otherwise sepcified
* Example: 'VARCHAR' columns have 'CHAR' in them, so 'char' => PDO::PARAM_STR will convert
* all columns of that type to be bound as PDO::PARAM_STR
* If there is no specification for a column type, column will be bound as PDO::PARAM_STR
*/
protected $_pdoBindTypes = array(
'char' => PDO::PARAM_STR,
'int' => PDO::PARAM_INT,
'bool' => PDO::PARAM_BOOL,
'date' => PDO::PARAM_STR,
'time' => PDO::PARAM_INT,
'text' => PDO::PARAM_STR,
'blob' => PDO::PARAM_LOB,
'binary' => PDO::PARAM_LOB
);
}
Here's the problem:
public function getColumnMeta($table)
{
$this->tableName=$table;
// Clear any previous column/field info
$this->_fields = array();
$this->_fieldMeta = array();
$this->_primaryKey = NULL;
// Automatically retrieve column information if column info not specified
if(count($this->_fields) == 0 || count($this->_fieldMeta) == 0)
{
// Fetch all columns and store in $this->fields
$columns = $this->db->query("SHOW COLUMNS FROM " . $this->tableName, PDO::FETCH_ASSOC);
foreach($columns as $key => $col)
{
// Insert into fields array
$colname = $col['Field'];
$this->_fields[$colname] = $col;
if($col['Key'] == "PRI" && empty($this->_primaryKey)) {
$this->_primaryKey = $colname;
}
// Set field types
$colType = $this->parseColumnType($col['Type']);
$this->_fieldMeta[$colname] = $colType;
}
}
return true;//<<--- not returning an object/array!
}
Your getColumnMeta method returns a boolean, true. The string representation of this value is, of course, 1. If you want this method to return all of the meta-data, change the return statement to something like:
return array(
'fields' => $this->_fields,
'meta' => $this->_fieldMeta,
'primary' => $this->_primaryKey
);
There are some other issues with your code, too, but seeing as this is not codereview.stackexchange, I'm not going to go into too much details. I will say this, though: Please, try to follow the coding standards that most major players adhere to: these standards can be found here: PHP-FIG.
Oh, and if you want to show the meta-data, don't echo them, but rather var_dump or print_r them, seeing as you return an array or an object.
Or at least echo json_encode($instance->getColumnMeta($table)); to get a correct string representation of the returned value(s).

Categories